Flutter gezginini patlarken durumu yeniden yüklemeye zorla


96

Ben bir tane StatefulWidgetbeni başkasına gider düğme ile Flutter içinde StatefulWidgetkullanarak Navigator.push(). İkinci widget'ta genel durumu değiştiriyorum (bazı kullanıcı tercihleri). İkinci widget'tan birinciye döndüğümde Navigator.pop(), ilk widget'ı kullanmak eski durumda, ancak onu yeniden yüklemeye zorlamak istiyorum. Bunu nasıl yapacağına dair bir fikrin var mı? Bir fikrim var ama çirkin görünüyor:

  1. ikinci widget'ı (mevcut olanı) kaldırmak için pop
  2. ilk widget'ı (önceki) kaldırmak için tekrar açılır
  3. ilk widget'ı it (yeniden çizmeye zorlamalıdır)

1
Cevap yok, sadece genel bir yorum: Benim durumumda, beni buraya getiren şey, sadece bir dakika önce başka bir sayfada yazdığım güncellenmiş bir öneri geri alacağımı garanti eden shared_preferences için bir senkronizasyon yöntemine sahip olmakla çözülürdü. . : \ .Sonra (...) kullanmak bile bana her zaman bekleyen-yazılan güncel verileri getirmiyor.
ChrisH

Pop'taki yeni sayfadan bir değer döndürdüm, sorunumu çözdüm. Bakın flutter.dev/docs/cookbook/navigation/returning-data
Joe M

Yanıtlar:


77

Burada yapabileceğiniz birkaç şey var. @ Mahi'nin yanıtı doğruyken biraz daha özlü olabilir ve OP'nin sorduğu gibi showDialog yerine aslında itmeyi kullanabilir. Bu, Navigator.pushşunları kullanan bir örnektir :

import 'package:flutter/material.dart';

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Container(
      color: Colors.green,
      child: new Column(
        children: <Widget>[
          new RaisedButton(
            onPressed: () => Navigator.pop(context),
            child: new Text("back"),
          ),
        ],
      ),
    );
  }
}

class FirstPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new FirstPageState();
}

class FirstPageState extends State<FirstPage> {

  Color color = Colors.white;

  @override
  Widget build(BuildContext context) {
    return new Container(
      color: color,
      child: new Column(
        children: <Widget>[
          new RaisedButton(
              child: new Text("next"),
              onPressed: () {
                Navigator
                    .push(
                  context,
                  new MaterialPageRoute(builder: (context) => new SecondPage()),
                )
                    .then((value) {
                  setState(() {
                    color = color == Colors.white ? Colors.grey : Colors.white;
                  });
                });
              }),
        ],
      ),
    );
  }
}

void main() => runApp(
      new MaterialApp(
        builder: (context, child) => new SafeArea(child: child),
        home: new FirstPage(),
      ),
    );

Ancak, bunu yapmanın kullanım durumunuza iyi uyan başka bir yolu var. globalİlk sayfanızın yapısını etkileyen bir şey olarak kullanıyorsanız, bir InheritedWidget kullanabilirsiniz. genel kullanıcı tercihlerinizi tanımlamak için ve her değiştirildiğinde FirstPage yeniden oluşturulur. Bu, aşağıda gösterildiği gibi durum bilgisi olmayan bir pencere öğesinde bile çalışır (ancak durum bilgisi olan bir pencere öğesinde de çalışmalıdır).

Flutter'daki inheritedWidget'in bir örneği, uygulamanın Temasıdır, ancak benim burada olduğu gibi doğrudan inşa etmek yerine onu bir widget içinde tanımlıyorlar.

import 'package:flutter/material.dart';
import 'package:meta/meta.dart';

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Container(
      color: Colors.green,
      child: new Column(
        children: <Widget>[
          new RaisedButton(
            onPressed: () {
              ColorDefinition.of(context).toggleColor();
              Navigator.pop(context);
            },
            child: new Text("back"),
          ),
        ],
      ),
    );
  }
}

class ColorDefinition extends InheritedWidget {
  ColorDefinition({
    Key key,
    @required Widget child,
  }): super(key: key, child: child);

  Color color = Colors.white;

  static ColorDefinition of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(ColorDefinition);
  }

  void toggleColor() {
    color = color == Colors.white ? Colors.grey : Colors.white;
    print("color set to $color");
  }

  @override
  bool updateShouldNotify(ColorDefinition oldWidget) =>
      color != oldWidget.color;
}

class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var color = ColorDefinition.of(context).color;

    return new Container(
      color: color,
      child: new Column(
        children: <Widget>[
          new RaisedButton(
              child: new Text("next"),
              onPressed: () {
                Navigator.push(
                  context,
                  new MaterialPageRoute(builder: (context) => new SecondPage()),
                );
              }),
        ],
      ),
    );
  }
}

void main() => runApp(
      new MaterialApp(
        builder: (context, child) => new SafeArea(
              child: new ColorDefinition(child: child),
            ),
        home: new FirstPage(),
      ),
    );

Devralınan pencere öğesini kullanırsanız, ittiğiniz sayfanın pop-up'ını izleme konusunda endişelenmenize gerek kalmaz, bu temel kullanım durumları için işe yarar, ancak daha karmaşık bir senaryoda sorun yaşamanıza neden olabilir.


Mükemmel, ilk vaka benim için mükemmel çalıştı (ikincisi daha karmaşık bir durum için mükemmel). İkinci sayfadan OnWillPopUp'ı kullanmak benim için net değildi; ve hiç çalışmadı bile.
cdsaenz

Hey, liste öğemi güncellemek istersem ne olur? İlk sayfamın liste öğelerini ve ikinci sayfamın öğe ayrıntılarını içerdiğini varsayalım. Öğenin değerini güncelliyorum ve liste öğelerinde tekrar güncellenmesini istiyorum. Bunu nasıl başarabilirim?
Aanal Mehta

@AanalMehta Size hızlı bir tavsiyede bulunabilirim - ya her iki sayfadan da kullanılan bir arka uç deposu (örn. Sqflite tablosu veya bellek içi listesi) ve ardından. setState'te daha önce değiştirdiğim artan bir sayaç kullandım - bu en temiz çözüm değil ama çalışıyor) ... Veya değişiklikleri .then işlevindeki orijinal sayfaya geri gönderebilir, listenizde değişiklikler yapabilir ve sonra yeniden oluşturun (ancak bir listede değişiklik yapmanın bir yenilemeyi tetiklemediğini unutmayın, bu nedenle bir kez daha artan sayacı kullanın).
rmtmckenzie

@AanalMehta Ancak bu yardımcı olmazsa, daha alakalı olabilecek başka yanıtlar aramanızı (listelerle ilgili birkaç şey gördüğüme oldukça eminim) veya yeni bir soru sormanızı öneririm.
rmtmckenzie

@rmtmckenzie Aslında yukarıdaki bir çözümü denedim. Değişiklikleri. Sonra uyguladım ama yansıtılmayacak. Sanırım artık sağlayıcıları kullanmak için tek bir seçeneğim var. Her neyse, yardımın için çok teşekkür ederim.
Aanal Mehta

18

Verileri aktaran 2 şey var

  • 1. Sayfadan 2. sayfaya

    Bunu 1. sayfada kullanın

    // sending "Foo" from 1st
    Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));
    

    Bunu 2. sayfada kullanın.

    class Page2 extends StatelessWidget {
      final String string;
    
      Page2(this.string); // receiving "Foo" in 2nd
    
      ...
    }
    

  • 2. sayfadan 1. sayfaya

    Bunu 2. sayfada kullanın

    // sending "Bar" from 2nd
    Navigator.pop(context, "Bar");
    

    Bunu 1. sayfada kullanın, daha önce kullanılanla aynıdır, ancak küçük değişikliklerle.

    // receiving "Bar" in 1st
    String received = await Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));
    

Navigator.popUntil yöntemini kullanarak C rotasından çıkarken A rotasını nasıl yeniden yükleyebilirim.
Vinoth Vino

1
@VinothVino Bunu yapmanın doğrudan bir yolu yok, bir çeşit geçici çözüm yapmanız gerekiyor.
CopsOnRoad

10

PushReplacement'ı kullanabilir ve yeni Rotayı belirtebilirsiniz.


Ama gezgin yığınını kaybediyorsunuz. Ya android geri düğmesini kullanarak ekranı açarsanız?
encubos

10

Kolay Trick kullanmaktır Navigator.pushReplacement yöntemi

Sayfa 1

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => Page2(),
  ),
);

Sayfa 2

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => Page1(),
  ),
);

Bu şekilde gezgin yığınını kaybedersiniz. Ekranı geri düğmesiyle açmaya ne dersiniz?
Encubos

5

Bu iş gerçekten iyi, flutter sayfasından şu dokümandan aldım: flutter doc

İlk sayfadan itibaren gezinmeyi kontrol etme yöntemini tanımladım.

_navigateAndDisplaySelection(BuildContext context) async {
    final result = await Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => AddDirectionPage()),
    );

    //below you can get your result and update the view with setState
    //changing the value if you want, i just wanted know if i have to  
    //update, and if is true, reload state

    if (result) {
      setState(() {});
    }
  }

Yani, ben bunu bir mürekkep hokkasından bir eylem yöntemi olarak adlandırıyorum, ancak bir düğmeden de çağrılabilir:

onTap: () {
   _navigateAndDisplaySelection(context);
},

Ve son olarak ikinci sayfada, bir şeyi döndürmek için (bir bool döndürdüm, ne istersen iade edebilirsin):

onTap: () {
  Navigator.pop(context, true);
}

1
Hatta await Navigator....pop'da bir sonuç değerini atlayabilirsiniz.
0llie

4

Bunu ikinci ekrana ittiğiniz yere koyun (zaman uyumsuz bir işlevin içinde)

Function f;
f= await Navigator.pushNamed(context, 'ScreenName');
f();

Bunu patladığın yere koy

Navigator.pop(context, () {
 setState(() {});
});

setStateİçeride denir popverileri güncellemek için kapatma.


2
setStateTartışma olarak tam olarak nereden geçiyorsunuz? Temelde setStatebir kapanış içinde çağırıyorsunuz . Bunu bir argüman olarak aktarmamak.
Volkan Güven

Tam aradığım cevap buydu. Temelde bir tamamlama işleyicisi istedim Navigator.pop.
David Chopin

2

benim çözümüm SecondPage'e bir fonksiyon parametresi ekleyerek gitti, ardından FirstPage'den yapılan yeniden yükleme fonksiyonunu aldı, ardından fonksiyonu Navigator.pop (bağlam) satırından önce çalıştırdı.

İlk sayfa

refresh() {
setState(() {
//all the reload processes
});
}

sonra bir sonraki sayfaya geçerken ...

Navigator.push(context, new MaterialPageRoute(builder: (context) => new SecondPage(refresh)),);

İkinci sayfa

final Function refresh;
SecondPage(this.refresh); //constructor

sonra gezgin açılır satırından önce,

widget.refresh(); // just refresh() if its statelesswidget
Navigator.pop(context);

Bir önceki sayfadan yeniden yüklenmesi gereken her şey, poptan sonra güncellenmelidir.


1

dynamic resultBağlamı açtığınızda a'yı geri verebilir ve ardından setState((){})değer olduğu zaman çağırabilirsiniz , trueaksi takdirde durumu olduğu gibi bırakın.

Referans olması için bazı kod parçacıkları yapıştırdım.

handleClear() async {
    try {
      var delete = await deleteLoanWarning(
        context,
        'Clear Notifications?',
        'Are you sure you want to clear notifications. This action cannot be undone',
      );
      if (delete.toString() == 'true') {
        //call setState here to rebuild your state.

      }
    } catch (error) {
      print('error clearing notifications' + error.toString());
             }
  }



Future<bool> deleteLoanWarning(BuildContext context, String title, String msg) async {

  return await showDialog<bool>(
        context: context,
        child: new AlertDialog(
          title: new Text(
            title,
            style: new TextStyle(fontWeight: fontWeight, color: CustomColors.continueButton),
            textAlign: TextAlign.center,
          ),
          content: new Text(
            msg,
            textAlign: TextAlign.justify,
          ),
          actions: <Widget>[
            new Container(
              decoration: boxDecoration(),
              child: new MaterialButton(
                child: new Text('NO',),
                onPressed: () {
                  Navigator.of(context).pop(false);
                },
              ),
            ),
            new Container(
              decoration: boxDecoration(),
              child: new MaterialButton(
                child: new Text('YES', ),
                onPressed: () {
                  Navigator.of(context).pop(true);
                },
              ),
            ),
          ],
        ),
      ) ??
      false;
}

Saygılarımızla, Mahi


İkinci kısmı nasıl yapacağımı anladığımdan emin değilim. Birincisi sadece basit Navigator.pop(context, true), değil mi? Ama bu truedeğeri nasıl elde edebilirim ? Kullanıyor BuildContextmusunuz?
bartektartanus

Benim için çalışmıyor. SetState denilen push sonucunu kullandım ve aldımsetState() called after dispose(): This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. ....
bartektartanus

hmm, bu ilginç, bu şekilde if(!mounted) return;her çağrıdan önce kullandınız setState((){});mı, artık aktif olmayan elden çıkarılmış widget'ları veya widget'ları güncellemekten kaçınabilirsiniz.
Mahi

Hata yok ama tahmin ettiğim gibi çalışmıyor;)
bartektartanus

@Bartektartanus ile aynı sorunu yaşıyorum. SetState'ten önce if! Bağlandı eklersem, durumum asla ayarlanmayacaktır. Basit görünüyor ama birkaç saat sonra çözemedim.
Hızlı

1

Vatansız aletlerimden birini yeniden inşa etmeye zorlamam gerekiyordu. Durum bilgisini kullanmak istemedi. Bu çözümle geldi:

await Navigator.of(context).pushNamed(...);
ModalRoute.of(enclosingWidgetContext);

Bağlam ve çevreleyenWidgetContext'in aynı veya farklı bağlamlar olabileceğini unutmayın. Örneğin, StreamBuilder'ın içinden push yaparsanız, bunlar farklı olacaktır.

Burada ModalRoute ile hiçbir şey yapmıyoruz. Yalnızca abone olma eylemi, yeniden oluşturmayı zorlamak için yeterlidir.


1

Bir uyarı iletişim kutusu kullanıyorsanız, iletişim kutusu kapatıldığında tamamlanan bir Gelecek kullanabilirsiniz. Geleceğin tamamlanmasından sonra, widget'ı durumu yeniden yüklemeye zorlayabilirsiniz.

İlk sayfa

onPressed: () async {
    await showDialog(
       context: context,
       builder: (BuildContext context) {
            return AlertDialog(
                 ....
            );
       }
    );
    setState(() {});
}

Uyarı iletişim kutusunda

Navigator.of(context).pop();

0

Bugün aynı durumla karşılaştım ama çok daha kolay bir şekilde çözmeyi başardım, sadece birinci durum bilgili sınıfta kullanılan global bir değişken tanımladım ve ikinci bir durum bilgili parçacığa gittiğimde küreselin değerini güncellemesine izin verdim. otomatik olarak ilk parçacığı güncellenmeye zorlayan değişken. İşte bir örnek (aceleyle yazdım, bu yüzden bir iskele veya malzeme uygulaması koymadım, sadece amacımı açıklamak istedim):

import 'package:flutter/material.dart';
int count = 0 ;

class FirstPage extends StatefulWidget {
FirstPage({Key key}) : super(key: key);

@override
_FirstPageState createState() => _FirstPageState();
}

class _FirstPageState extends State<FirstPage> {
@override
Widget build(BuildContext context) {
return InkWell(
onTap(){
Navigator.of(context).push(MaterialPageRoute(builder: (context) =>
                  new SecondPage());
},
child: Text('First', style : TextStyle(fontSize: count == 0 ? 20.0 : 12.0)),
),

}


class SecondPage extends StatefulWidget {
SecondPage({Key key}) : super(key: key);

@override
_SecondPageState createState() => _SecondPageState();
}

class _SecondPageState extends State<SecondPage> {
@override
Widget build(BuildContext context) {
return IconButton(
         icon: new Icon(Icons.add),
         color: Colors.amber,
         iconSize: 15.0,
         onPressed: (){
         count++ ;
         },
       ),
     }

4
Değişkeni değiştirmek, widget'ı otomatik olarak yeniden oluşturmaz. Sonra Navigator.pop()daha önce devlet tutacak Navigator.push()kadar setStatetekrar denir, bu kodda ne olmaz. Bir şey mi kaçırıyorum?
Ricardo BRGWeb

0

Bu basit kod, köke gidip durumu yeniden yüklemem için çalıştı:

    ...
    onPressed: () {
         Navigator.of(context).pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
                },
    ...
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.