บังคับให้ Flutter navigator โหลดสถานะใหม่เมื่อ popping


96

ฉันมีStatefulWidgetปุ่มหนึ่งในปุ่ม Flutter with ซึ่งนำทางฉันไปยังอีกStatefulWidgetปุ่มหนึ่งโดยใช้Navigator.push()ไฟล์. ในวิดเจ็ตที่สองฉันกำลังเปลี่ยนสถานะส่วนกลาง (ค่ากำหนดของผู้ใช้บางอย่าง) เมื่อฉันกลับจากวิดเจ็ตที่สองเป็นวิดเจ็ตแรกการใช้Navigator.pop()วิดเจ็ตแรกอยู่ในสถานะเก่า แต่ฉันต้องการบังคับให้โหลดซ้ำ มีความคิดอย่างไรที่จะทำสิ่งนี้? ฉันมีความคิดอย่างหนึ่ง แต่มันดูน่าเกลียด:

  1. ป๊อปเพื่อลบวิดเจ็ตที่สอง (อันปัจจุบัน)
  2. ป๊อปอีกครั้งเพื่อลบวิดเจ็ตแรก (อันก่อนหน้า)
  3. กดวิดเจ็ตแรก (ควรบังคับให้วาดใหม่)

1
ไม่มีคำตอบเป็นเพียงความคิดเห็นทั่วไป: ในกรณีของฉันสิ่งที่ทำให้ฉันมาที่นี่เพื่อค้นหาสิ่งนี้จะได้รับการแก้ไขเพียงแค่มีวิธีการซิงค์สำหรับ shared_preferences ที่ฉันรับประกันว่าจะได้รับการอัปเดตกลับมาก่อนหน้านี้ในหน้าอื่น . : \ แม้จะใช้. แล้ว (... ) ไม่ได้รับข้อมูลที่อัปเดตที่รอการเขียน
ChrisH

เพิ่งส่งคืนค่าจากหน้าใหม่ในป๊อปแก้ไขปัญหาของฉัน อ้างอิงflutter.dev/docs/cookbook/navigation/returning-data
Joe M

ต้องดูสิ่งนี้: stackoverflow.com/a/64006691/10563627
Paresh Mangukiya

คำตอบ:


77

มีสองสามสิ่งที่คุณสามารถทำได้ที่นี่ คำตอบของ @ Mahi ในขณะที่ถูกต้องอาจกระชับกว่าเล็กน้อยและใช้ push มากกว่า showDialog ตามที่ OP กำลังถาม นี่คือตัวอย่างที่ใช้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(),
      ),
    );

อย่างไรก็ตามมีอีกวิธีในการทำเช่นนี้ที่อาจเข้ากับกรณีการใช้งานของคุณได้ดี หากคุณใช้globalเป็นสิ่งที่มีผลต่อการสร้างหน้าแรกของคุณคุณสามารถใช้InheritedWidgetเพื่อกำหนดการตั้งค่าผู้ใช้ทั่วโลกของคุณและทุกครั้งที่มีการเปลี่ยนแปลง FirstPage ของคุณจะสร้างใหม่ สิ่งนี้ยังใช้งานได้ภายในวิดเจ็ตไร้สถานะดังที่แสดงด้านล่าง (แต่ควรทำงานในวิดเจ็ตที่มีสถานะเช่นกัน)

ตัวอย่างของ inheritWidget ในการกระพือปีกคือ Theme ของแอปแม้ว่าพวกเขาจะกำหนดมันภายในวิดเจ็ตแทนที่จะสร้างโดยตรงอย่างที่ฉันมีที่นี่

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

หากคุณใช้วิดเจ็ตที่สืบทอดมาคุณไม่ต้องกังวลกับการดูป๊อปของหน้าที่คุณผลักดันซึ่งจะใช้ได้กับกรณีการใช้งานพื้นฐาน แต่อาจพบปัญหาในสถานการณ์ที่ซับซ้อนมากขึ้น


ยอดเยี่ยมเคสแรกทำงานให้ฉันได้อย่างสมบูรณ์แบบ (กรณีที่สองเหมาะสำหรับสถานการณ์ที่ซับซ้อนมากขึ้น) การใช้ OnWillPopUp จากหน้าที่สองไม่ชัดเจนสำหรับฉัน และไม่ได้ผลเลย
cdsaenz

เฮ้ฉันจะทำอย่างไรถ้าฉันต้องการอัปเดตรายการของฉัน สมมติว่าหน้าแรกของฉันมีรายการและหน้าที่สองมีรายละเอียดของรายการ ฉันกำลังอัปเดตมูลค่าของรายการและฉันต้องการให้อัปเดตกลับที่รายการ จะบรรลุได้อย่างไร?
Aanal Mehta

@AanalMehta ฉันสามารถให้คำแนะนำอย่างรวดเร็ว - อาจมีที่เก็บแบ็กเอนด์ (เช่นตาราง sqflite หรือรายการในหน่วยความจำ) ที่ใช้จากทั้งสองหน้าและใน. จากนั้นคุณสามารถบังคับให้วิดเจ็ตของคุณรีเฟรชได้ (โดยส่วนตัวแล้ว ได้ใช้ตัวนับส่วนเพิ่มที่ฉันเปลี่ยนใน setState มาก่อน - ไม่ใช่วิธีแก้ปัญหาที่สะอาดที่สุด แต่ใช้ได้ผล) ... หรือคุณสามารถส่งการเปลี่ยนแปลงกลับไปที่หน้าเดิมในฟังก์ชัน. then ทำการแก้ไขรายการของคุณและ จากนั้นสร้างใหม่ (แต่โปรดทราบว่าการเปลี่ยนแปลงรายการไม่ได้ทำให้เกิดการรีเฟรชดังนั้นให้ใช้ตัวนับเพิ่มอีกครั้ง)
rmtmckenzie

@AanalMehta แต่ถ้ายังไม่ได้ผลฉันขอแนะนำให้คุณมองหาคำตอบอื่น ๆ ที่อาจเกี่ยวข้องมากกว่า (ฉันค่อนข้างแน่ใจว่าฉันเคยเห็นรายการเกี่ยวกับรายการ ฯลฯ ) หรือถามคำถามใหม่
rmtmckenzie

@rmtmckenzie จริงๆแล้วฉันลองวิธีแก้ปัญหาข้างต้นแล้ว ฉันใช้ chan ges ใน. แล้ว แต่มันจะไม่สะท้อน ฉันเดาว่าตอนนี้ฉันมีทางเลือกเดียวในการใช้ผู้ให้บริการ ยังไงก็ขอบคุณมากสำหรับความช่วยเหลือ
Aanal Mehta

18

มี 2 ​​อย่างคือส่งผ่านข้อมูลจาก

  • หน้าที่ 1 ถึง 2

    ใช้สิ่งนี้ในหน้าแรก

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

    ใช้สิ่งนี้ในหน้าที่ 2

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

  • หน้าที่ 2 ถึง 1

    ใช้สิ่งนี้ในหน้าที่ 2

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

    ใช้สิ่งนี้ในหน้าแรกเหมือนกันซึ่งใช้ก่อนหน้านี้ แต่มีการปรับเปลี่ยนเล็กน้อย

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

ฉันจะโหลดเส้นทาง A ซ้ำได้อย่างไรเมื่อเปิดจากเส้นทาง C โดยใช้เมธอด Navigator.popUntil
Vinoth Vino

1
@VinothVino ยังไม่มีวิธีการโดยตรงในการทำเช่นนั้นคุณต้องทำวิธีแก้ปัญหาบางอย่าง
CopsOnRoad

10

คุณสามารถใช้ pushReplacement และระบุเส้นทางใหม่


แต่คุณกำลังสูญเสียสแต็กเนวิเกเตอร์ จะเกิดอะไรขึ้นถ้าคุณเปิดหน้าจอโดยใช้ปุ่มย้อนกลับของ Android?
encubos

10

เคล็ดลับง่ายๆคือการใช้Navigator.pushReplacementวิธี

หน้า 1

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

หน้า 2

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

วิธีนี้คุณจะสูญเสียสแต็กเนวิเกเตอร์ แล้วการเปิดหน้าจอด้วยปุ่มย้อนกลับล่ะ?
encubos

5

งานนี้ดีจริงๆได้จากเอกสารนี้จากเพจflutter : flutter doc

ฉันกำหนดวิธีการควบคุมการนำทางจากหน้าแรก

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

ดังนั้นฉันเรียกมันในวิธีการดำเนินการจากหมึก แต่สามารถเรียกได้จากปุ่ม:

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

และในที่สุดในหน้าที่สองเพื่อส่งคืนบางสิ่ง (ฉันคืนบูลคุณสามารถส่งคืนสิ่งที่คุณต้องการ):

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

1
คุณสามารถawait Navigator....เว้นค่าผลลัพธ์ในป๊อปได้ด้วยซ้ำ
0llie

4

วางไว้ในตำแหน่งที่คุณกำลังผลักไปยังหน้าจอที่สอง (ภายในฟังก์ชัน async)

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

ใส่ที่ที่คุณกำลังโผล่

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

setStateเรียกว่าภายในpopปิดเพื่อปรับปรุงข้อมูล


2
คุณส่งsetStateข้อโต้แย้งไปที่ใดกันแน่? โดยทั่วไปคุณกำลังโทรsetStateเข้ามาในการปิด ไม่ผ่านเป็นข้อโต้แย้ง
Volkan Güven

นี่คือคำตอบที่แท้จริงที่ฉันกำลังมองหา โดยพื้นฐานแล้วฉันต้องการตัวจัดการที่สมบูรณ์สำหรับNavigator.pop.
David Chopin

2

วิธีแก้ปัญหาของฉันดำเนินการโดยการเพิ่มพารามิเตอร์ฟังก์ชันบน SecondPage จากนั้นรับฟังก์ชันการโหลดซ้ำซึ่งกำลังดำเนินการจาก FirstPage จากนั้นเรียกใช้ฟังก์ชันก่อนบรรทัด Navigator.pop (บริบท)

หน้าแรก

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

จากนั้นกดไปที่หน้าถัดไป ...

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

SecondPage

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

จากนั้นก่อนถึงบรรทัดการนำทางป๊อป

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

ทุกสิ่งที่ต้องโหลดใหม่จากหน้าที่แล้วควรได้รับการอัปเดตหลังจากป๊อปอัป


1

คุณสามารถส่งกลับdynamic resultเมื่อคุณเปิดบริบทแล้วเรียกsetState((){})เมื่อค่าเป็นtrueอย่างอื่นให้ออกจากสถานะตามที่เป็น

ฉันได้วางข้อมูลโค้ดบางส่วนเพื่อเป็นข้อมูลอ้างอิงของคุณ

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

ขอแสดงความนับถือ Mahi


ฉันไม่แน่ใจว่าฉันเข้าใจวิธีการทำส่วนที่สอง อันดับแรกเป็นเพียงแค่Navigator.pop(context, true)ใช่ไหม? แต่ฉันจะได้รับtrueค่านี้ได้อย่างไร? ใช้BuildContext?
bartektartanus

ไม่ทำงานสำหรับฉัน ฉันใช้ผลลัพธ์จากการผลักดันที่เรียกว่า setState และได้setState() 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

อืมสิ่งนี้น่าสนใจที่คุณใช้if(!mounted) return;ก่อนการโทรทุกsetState((){});ครั้งด้วยวิธีนี้คุณสามารถหลีกเลี่ยงการอัปเดตวิดเจ็ตหรือวิดเจ็ตที่ถูกทิ้งที่ไม่ได้ใช้งาน
Mahi

ไม่มีข้อผิดพลาด แต่ก็ไม่ทำงานตามที่ฉันคาดการณ์ไว้;)
bartektartanus

ฉันมีปัญหาเดียวกันกับ @bartektartanus ถ้าฉันเพิ่ม if! ติดตั้งก่อน setState สถานะของฉันจะไม่ถูกตั้งค่า ดูเหมือนง่าย แต่ฉันไม่คิดออกหลังจากผ่านไปสองสามชั่วโมง
Swift

1

จำเป็นต้องบังคับให้สร้างวิดเจ็ตไร้สัญชาติของฉันขึ้นมาใหม่ ไม่ต้องการใช้ stateful มาพร้อมกับโซลูชันนี้:

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

โปรดสังเกตว่าบริบทและการปิดล้อมWidgetContextอาจเป็นบริบทเดียวกันหรือต่างกัน ตัวอย่างเช่นหากคุณผลักดันจากภายใน StreamBuilder ก็จะแตกต่างออกไป

เราไม่ได้ทำอะไรที่นี่กับ ModalRoute การสมัครสมาชิกเพียงอย่างเดียวก็เพียงพอที่จะบังคับให้สร้างใหม่


1

หากคุณกำลังใช้กล่องโต้ตอบการแจ้งเตือนคุณสามารถใช้อนาคตที่เสร็จสมบูรณ์เมื่อกล่องโต้ตอบถูกปิด หลังจากเสร็จสิ้นในอนาคตคุณสามารถบังคับให้วิดเจ็ตโหลดสถานะใหม่ได้

หน้าแรก

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

ในกล่องโต้ตอบการแจ้งเตือน

Navigator.of(context).pop();

0

วันนี้ฉันเผชิญกับสถานการณ์เดียวกัน แต่ฉันสามารถแก้ไขได้ด้วยวิธีที่ง่ายกว่ามากฉันเพิ่งกำหนดตัวแปรโกลบอลที่ใช้ในคลาส stateful แรกและเมื่อฉันไปที่วิดเจ็ต stateful ตัวที่สองฉันปล่อยให้มันอัปเดตค่าของ global ตัวแปรซึ่งบังคับให้วิดเจ็ตแรกอัปเดตโดยอัตโนมัติ นี่คือตัวอย่าง (ฉันเขียนอย่างเร่งรีบดังนั้นฉันจึงไม่ได้ใส่โครงนั่งร้านหรือแอปวัสดุฉันแค่อยากจะอธิบายประเด็นของฉัน):

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
การเปลี่ยนตัวแปรจะไม่สร้างวิดเจ็ตใหม่โดยอัตโนมัติ หลังจากNavigator.pop()นั้นจะคงสถานะไว้ก่อนNavigator.push()จนกว่าsetStateจะถูกเรียกอีกครั้งสิ่งที่ไม่เกิดขึ้นในรหัสนี้ ฉันพลาดอะไรไปรึเปล่า?
Ricardo BRGWeb

0

รหัสง่ายๆนี้ใช้งานได้สำหรับฉันไปที่รูทและโหลดสถานะใหม่:

    ...
    onPressed: () {
         Navigator.of(context).pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
                },
    ...
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.