Примусити навігатор Flutter перезавантажувати стан при появі


98

У мене є одна StatefulWidgetу Flutter with button, яка переходить до іншої StatefulWidgetза допомогою Navigator.push(). На другому віджеті я змінюю глобальний стан (деякі налаштування користувача). Коли я повертаюся з другого віджета на перший, використання Navigator.pop()першого віджета знаходиться у старому стані, але я хочу змусити його перезавантажитись. Будь-яка ідея, як це зробити? У мене є одна ідея, але це виглядає потворно:

  1. поп, щоб видалити другий віджет (поточний)
  2. знову клацніть, щоб видалити перший віджет (попередній)
  3. натиснути перший віджет (він повинен змусити перемалювати)

1
Ніякої відповіді, лише загальний коментар: у моєму випадку те, що привело мене сюди, що шукає це, було б вирішено просто наявністю методу синхронізації для shared_preferences, де я гарантовано повернусь до оновленого преф, який я написав лише хвилину тому на іншій сторінці . : \ Навіть використання .then (...) не завжди отримує мені оновлені дані, що очікують на запис.
ChrisH

Щойно повернув значення з нової сторінки на pop, вирішив мою проблему. Посилання на flutter.dev/docs/cookbook/navigation/returning-data
Джо М

Повинно бути побачити це: stackoverflow.com/a/64006691/10563627
Paresh Mangukiya

Відповіді:


78

Тут можна зробити кілька справ. Відповідь @ Mahi, хоча і є правильною, може бути трохи лаконічнішою і фактично використовувати push, а не showDialog, про що запитував ОП. Це приклад, який використовує 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 буде перебудовуватися. Це навіть працює у віджеті без збереження стану, як показано нижче (але також повинно працювати і у віджеті із встановленим станом).

Прикладом legacedWidget in flutter є тема програми, хоча вони визначають її у віджеті, замість того, щоб безпосередньо будувати, як я тут.

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 Насправді я спробував наведене вище рішення. Тоді я застосував зміни у. Then, але це не відобразиться. Думаю, зараз у мене є лише одна можливість використовувати провайдерів. У будь-якому разі, дякую вам за допомогу.
Аанал ​​Мехта,

19

Є 2 речі, передаючи дані від

  • 1-а сторінка - 2-а

    Використовуйте це на 1-й сторінці

    // 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");
    

    Використовуйте це на 1-й сторінці, це те саме, що було використано раніше, але з невеликими змінами.

    // 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?
енкубос

10

Easy Trick є використання Navigator.pushReplacement метод

Сторінка 1

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

Сторінка 2

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

Таким чином ви втрачаєте стек навігатора. Що можна сказати про вивільнення екрана кнопкою "Назад"?
encubos

5

Ця робота дійсно хороша, я отримав з цього документа зі сторінки флаттера : флаттер-документ

Я визначив метод управління навігацією з першої сторінки.

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

І нарешті, на другій сторінці, щоб щось повернути (я повернув bool, ви можете повернути все, що завгодно):

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

1
Ви навіть можете просто await Navigator....і пропустити значення результату в pop.
0llie

4

Помістіть це там, де ви натискаєте на другий екран (усередині асинхронної функції)

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

Покладіть це там, де ви вискакуєте

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

setStateНазивається всередині popзакриття , щоб оновити дані.


2
Де саме ви передаєте setStateаргумент? Ви в основному телефонуєте setStateвсередині закриття. Не передаючи це як аргумент.
Волкан Гювен

Це точна відповідь, яку я шукав. Я в основному хотів обробник завершення для Navigator.pop.
Девід Шопен

2

моє рішення пішло, додавши параметр функції на SecondPage, потім отримав функцію перезавантаження, яка виконується з FirstPage, а потім виконав функцію перед рядком Navigator.pop (context).

FirstPage

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

З повагою, Махі


Я не впевнений, що розумію, як зробити другу частину. По-перше, це просто просто Navigator.pop(context, true), так? Але як я можу отримати це trueзначення? Використовуючи BuildContext?
bartektartanus

Не працює для мене. Я використав результат push, назвав 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((){});такий спосіб, щоб уникнути оновлення утилізованих віджетів або віджетів, які вже не активні.
Махі

Помилки немає, але вона також не працює, як я передбачав;)
bartektartanus

У мене така ж проблема, як @bartektartanus. Якщо я додаю if! Змонтовано перед setState, мій стан ніколи не буде встановлено. Це здається простим, але я не зрозумів цього через кілька годин.
Свіфт

1

Потрібно для примусової перебудови одного з моїх віджетів без громадянства. Не хотів використовувати stateful. Придумали таке рішення:

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

Зверніть увагу, що context і enclosingWidgetContext можуть бути однаковими або різними контекстами. Якщо, наприклад, ви натискаєте зсередини StreamBuilder, вони будуть іншими.

Ми тут нічого не робимо з ModalRoute. Одного підписки досить, щоб змусити відновити.


1

Якщо ви використовуєте діалогове вікно попередження, ви можете використовувати майбутнє, яке завершується, коли діалогове вікно закрито. Після завершення майбутнього ви можете змусити віджет перезавантажити стан.

Перша сторінка

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

У діалоговому вікні попередження

Navigator.of(context).pop();

0

Сьогодні я зіткнувся з такою ж ситуацією, але мені вдалося її вирішити набагато простішим способом, я просто визначив глобальну змінну, яка була використана в першому класі з утворенням статусу, і коли я переходжу до другого віджета, що відповідає стану, я дозволяю їй оновлювати значення глобальної змінна, яка автоматично примушує оновити перший віджет. Ось приклад (я написав це поспіхом, тому я не поставив риштування або матеріальну програму, я просто хотів проілюструвати свою думку):

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буде викликаний знову, чого не трапиться в цьому коді. Мені чогось не вистачає?
Рікардо BRGWeb

0

Цей простий код працював для мене, щоб перейти до кореня та перезавантажити стан:

    ...
    onPressed: () {
         Navigator.of(context).pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
                },
    ...
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.