Paksa navigator Flutter untuk memuat ulang status saat muncul

97

Saya punya satu StatefulWidgetdi Flutter dengan tombol, yang menavigasi saya ke yang lain StatefulWidgetmenggunakan Navigator.push(). Pada widget kedua saya mengubah status global (beberapa preferensi pengguna). Ketika saya kembali dari widget kedua ke widget pertama, menggunakan Navigator.pop()widget pertama dalam keadaan lama, tetapi saya ingin memaksanya memuat ulang. Ada ide bagaimana caranya mengerjakan ini? Saya punya satu ide tapi kelihatannya jelek:

  1. pop untuk menghapus widget kedua (yang sekarang)
  2. pop lagi untuk menghapus widget pertama (yang sebelumnya)
  3. dorong widget pertama (itu harus memaksa menggambar ulang)
bartektartanus
sumber
1
Tidak ada jawaban, hanya komentar umum: dalam kasus saya, apa yang membawa saya ke sini mencari ini akan diselesaikan hanya dengan memiliki metode sinkronisasi untuk shared_preferences di mana saya dijamin akan mendapatkan kembali pref yang diperbarui yang saya tulis beberapa saat yang lalu di halaman lain . : \ Bahkan menggunakan .then (...) tidak selalu memberi saya data terbaru yang tertunda-tulis.
ChrisH
Baru saja mengembalikan nilai dari halaman baru di pop, memecahkan masalah saya. Lihat flutter.dev/docs/cookbook/navigation/returning-data
Joe M
Harus melihat ini: stackoverflow.com/a/64006691/10563627
Paresh Mangukiya

Jawaban:

77

Ada beberapa hal yang dapat Anda lakukan di sini. Jawaban @ Mahi sementara benar bisa jadi sedikit lebih ringkas dan benar-benar menggunakan push daripada showDialog seperti yang ditanyakan OP. Ini adalah contoh yang menggunakan Navigator.push:

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(),
      ),
    );

Namun, ada cara lain untuk melakukan ini yang mungkin sesuai dengan kasus penggunaan Anda. Jika Anda menggunakan globalsebagai sesuatu yang mempengaruhi pembuatan halaman pertama Anda, Anda dapat menggunakan InheritedWidget untuk menentukan preferensi pengguna global Anda, dan setiap kali mereka diubah FirstPage Anda akan dibangun kembali. Ini bahkan berfungsi dalam widget stateless seperti yang ditunjukkan di bawah ini (tetapi harus berfungsi dalam widget stateful juga).

Contoh inheritedWidget dalam flutter adalah Tema aplikasi, meskipun mereka mendefinisikannya dalam widget alih-alih membuatnya langsung dibangun seperti yang saya miliki di sini.

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(),
      ),
    );

Jika Anda menggunakan widget yang diwariskan, Anda tidak perlu khawatir tentang melihat sembulan halaman yang Anda dorong, yang akan berfungsi untuk kasus penggunaan dasar tetapi mungkin berakhir dengan masalah dalam skenario yang lebih kompleks.

rmtmckenzie.dll
sumber
Luar biasa, kasus pertama bekerja dengan sempurna untuk saya (yang kedua sempurna untuk situasi yang lebih kompleks). Menggunakan OnWillPopUp dari halaman kedua tidak jelas bagi saya; dan bahkan tidak bekerja sama sekali.
cdsaenz
Hai, bagaimana jika saya ingin memperbarui item daftar saya. Misalkan halaman pertama saya berisi item daftar dan halaman kedua berisi detail item. Saya memperbarui nilai item dan saya ingin itu diperbarui kembali pada item daftar. Bagaimana cara mencapainya?
Aanal Mehta
@AanalMehta Saya dapat memberi Anda rekomendasi cepat - baik memiliki penyimpanan backend (yaitu tabel sqflite atau daftar dalam memori) yang digunakan dari kedua halaman, dan di. Kemudian Anda dapat memaksa widget Anda untuk menyegarkan entah bagaimana (saya pribadi telah menggunakan penghitung inkrementasi yang saya ubah di setState sebelumnya - ini bukan solusi terbersih tetapi berfungsi) ... Atau, Anda dapat meneruskan perubahan kembali ke halaman asli dalam fungsi. lalu, membuat modifikasi pada daftar Anda, dan lalu bangun kembali (tetapi perhatikan bahwa membuat perubahan pada daftar tidak memicu penyegaran, jadi sekali lagi gunakan penghitung inkrementasi).
rmtmckenzie
@AanalMehta Tetapi jika itu tidak membantu, saya sarankan Anda mencari beberapa jawaban lain yang mungkin lebih relevan (saya cukup yakin saya telah melihat beberapa tentang daftar dll), atau ajukan pertanyaan baru.
rmtmckenzie
@rmtckenzie Sebenarnya saya mencoba solusi di atas. Saya menerapkan perubahan tersebut kemudian tetapi tidak akan tercermin. Saya kira sekarang saya hanya memiliki satu opsi untuk menggunakan penyedia. Bagaimanapun, terima kasih banyak atas bantuan Anda.
Aanal Mehta
19

Ada 2 hal, meneruskan data dari

  • Halaman ke-1 sampai ke-2

    Gunakan ini di halaman pertama

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

    Gunakan ini di halaman ke-2.

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

  • Halaman ke-2 sampai ke-1

    Gunakan ini di halaman ke-2

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

    Gunakan ini di halaman pertama, ini sama dengan yang digunakan sebelumnya tetapi dengan sedikit modifikasi.

    // receiving "Bar" in 1st
    String received = await Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));
    
CopsOnRoad
sumber
Bagaimana cara memuat ulang rute A ketika muncul dari rute C menggunakan metode Navigator.popUntil.
Vinoth Vino
1
@VinothVino Belum ada cara langsung untuk melakukan itu, Anda perlu melakukan beberapa solusi.
CopsOnRoad
10

Anda dapat menggunakan pushReplacement dan menentukan Route baru

Sobhan Jachuck
sumber
Tapi Anda kehilangan tumpukan navigator. Bagaimana jika Anda memunculkan layar menggunakan tombol kembali android?
encubos
10

The Trik Mudah adalah dengan menggunakan Navigator.pushReplacement metode

Halaman 1

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

Halaman 2

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => Page1(),
  ),
);
Tuhan secara resmi
sumber
Dengan cara ini Anda kehilangan tumpukan navigator. Bagaimana dengan memunculkan layar dengan tombol kembali?
encubos
5

Ini bekerja dengan sangat baik, saya dapatkan dari dokumen ini dari halaman flutter : flutter doc

Saya mendefinisikan metode untuk mengontrol navigasi dari halaman pertama.

_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(() {});
    }
  }

Jadi, saya menyebutnya dalam metode tindakan dari wadah tinta, tetapi dapat dipanggil juga dari tombol:

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

Dan akhirnya di halaman kedua, untuk mengembalikan sesuatu (saya mengembalikan bool, Anda dapat mengembalikan apa pun yang Anda inginkan):

onTap: () {
  Navigator.pop(context, true);
}
Pedro Molina
sumber
1
Anda bahkan dapat await Navigator....menghilangkan nilai hasil dalam pop.
0llie
4

Letakkan ini di tempat Anda mendorong ke layar kedua (di dalam fungsi async)

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

Taruh ini di tempat Anda muncul

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

Itu setStatedipanggil di dalam popclosure untuk memperbarui data.

Mathew Varghese
sumber
2
Di mana tepatnya Anda lulus setStatesebagai argumen? Anda pada dasarnya menelepon setStatedi dalam closure. Tidak menyampaikannya sebagai argumen.
Volkan Güven
Ini adalah jawaban yang tepat yang saya cari. Saya pada dasarnya menginginkan penangan penyelesaian untuk Navigator.pop.
David Chopin
2

solusi saya pergi dengan menambahkan parameter fungsi pada SecondPage, kemudian menerima fungsi reload yang dilakukan dari FirstPage, kemudian menjalankan fungsi sebelum baris Navigator.pop (konteks).

Halaman pertama

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

lalu mendorong ke halaman berikutnya ...

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

SecondPage

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

lalu di depan garis pop navigator,

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

Segala sesuatu yang perlu dimuat ulang dari halaman sebelumnya harus diperbarui setelah pop.

rodalyn camba
sumber
1

Anda dapat mengirimkan kembali dynamic resultketika Anda memunculkan konteks dan kemudian memanggil setState((){})ketika nilainya truedinyatakan biarkan saja keadaan apa adanya.

Saya telah menempelkan beberapa potongan kode untuk referensi Anda.

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;
}

Salam, Mahi

Mahi
sumber
Saya tidak yakin saya mengerti bagaimana melakukan bagian kedua. Pertama cukup sederhana Navigator.pop(context, true), bukan? Tetapi bagaimana saya bisa mendapatkan truenilai ini ? Menggunakan BuildContext?
bartektartanus
Tidak bekerja untuk saya. Saya telah menggunakan hasil dari push, yang disebut setState dan mendapatsetState() 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, yang menarik ini kamu gunakan if(!mounted) return;sebelum setiap panggilan dengan setState((){});cara ini kamu bisa menghindari update widget yang dibuang atau widget yang sudah tidak aktif lagi.
Mahi
Tidak ada kesalahan tetapi juga tidak berfungsi seperti yang saya prediksi;)
bartektartanus
Saya memiliki masalah yang sama dengan @bartektartanus. Jika saya menambahkan if! Mount sebelum setState, status saya tidak akan pernah disetel. Tampaknya sederhana tetapi saya belum mengetahuinya setelah beberapa jam.
Swift
1

Diperlukan untuk memaksa membangun kembali salah satu widget tanpa kewarganegaraan saya. Tidak ingin menggunakan stateful. Datang dengan solusi ini:

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

Perhatikan bahwa konteks dan enklosurWidgetContext bisa jadi konteks yang sama atau berbeda. Jika, misalnya, Anda mendorong dari dalam StreamBuilder, hasilnya akan berbeda.

Kami tidak melakukan apa pun di sini dengan ModalRoute. Tindakan berlangganan saja sudah cukup untuk memaksa pembangunan kembali.

CKK
sumber
1

Jika Anda menggunakan dialog peringatan, Anda dapat menggunakan Future yang selesai saat dialog ditutup. Setelah selesai masa depan Anda dapat memaksa widget untuk memuat ulang negara.

Halaman pertama

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

Dalam dialog Peringatan

Navigator.of(context).pop();
Nimna Perera
sumber
0

Hari ini saya menghadapi situasi yang sama tetapi saya berhasil menyelesaikannya dengan cara yang jauh lebih mudah, saya hanya mendefinisikan variabel global yang digunakan di kelas stateful pertama, dan ketika saya menavigasi ke widget stateful kedua saya membiarkannya memperbarui nilai global variabel yang secara otomatis memaksa widget pertama untuk diperbarui. Berikut ini contohnya (Saya menulisnya dengan tergesa-gesa jadi saya tidak memasang perancah atau aplikasi material, saya hanya ingin mengilustrasikan maksud saya):

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++ ;
         },
       ),
     }
Mazin Ibrahim
sumber
4
Mengubah variabel tidak akan secara otomatis membuat ulang widget. Setelah Navigator.pop()itu akan mempertahankan keadaan sebelum Navigator.push()sampai setStatedipanggil lagi, apa yang tidak terjadi dalam kode ini. Apakah saya melewatkan sesuatu?
Ricardo BRGWeb
0

Kode sederhana ini bekerja bagi saya untuk pergi ke root dan memuat ulang status:

    ...
    onPressed: () {
         Navigator.of(context).pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
                },
    ...
Juanma Menendez
sumber