Перезагрузить навигатор Flutter при появлении

96

У меня есть один StatefulWidgetв флаттера с помощью кнопки, которая управляет , меня к другому , StatefulWidgetиспользуя Navigator.push(). Во втором виджете я меняю глобальное состояние (некоторые пользовательские настройки). Когда я возвращаюсь от второго виджета к первому, Navigator.pop()первый виджет находится в старом состоянии, но я хочу принудительно перезагрузить его. есть идеи как это сделать? У меня есть одна идея, но выглядит она некрасиво:

  1. pop, чтобы удалить второй виджет (текущий)
  2. нажмите еще раз, чтобы удалить первый виджет (предыдущий)
  3. нажмите первый виджет (он должен принудительно перерисовать)
бартектартанус
источник
1
Нет ответа, просто общий комментарий: в моем случае то, что привело меня сюда в поисках, было бы решено с помощью простого метода синхронизации для shared_preferences, где я гарантированно верну обновленный pref, который я написал всего минуту назад на другой странице . : \ Даже использование .then (...) не всегда дает мне обновленные данные, ожидающие записи.
ChrisH
Просто вернул значение с новой страницы на поп, решил мою проблему. Обратитесь к flutter.dev/docs/cookbook/navigation/returning-data
Джо М.
Обязательно посмотрите это: stackoverflow.com/a/64006691/10563627
Пареш Мангукия

Ответы:

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 будет перестраиваться. Это работает даже в виджете без состояния, как показано ниже (но должно работать и с виджетом с сохранением состояния).

Примером унаследованного виджета во флаттере является тема приложения, хотя они определяют ее в виджете, а не напрямую, как я здесь.

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

Если вы используете унаследованный виджет, вам не нужно беспокоиться о том, чтобы следить за появлением всплывающей страницы, которую вы нажали, что будет работать для базовых сценариев использования, но может привести к проблемам в более сложном сценарии.

rmtmckenzie
источник
Отлично, первый случай у меня сработал отлично (второй идеально подходит для более сложной ситуации). Мне было непонятно, как использовать OnWillPopUp со второй страницы; и даже не сработало.
cdsaenz
Эй, а что, если я хочу обновить свой элемент списка. Скажем, моя 1-я страница содержит элементы списка, а вторая страница содержит детали элемента. Я обновляю значение элемента и хочу, чтобы оно снова обновлялось в элементах списка. Как этого добиться?
Aanal Mehta 05
@AanalMehta Я могу дать вам быструю рекомендацию - либо иметь внутреннее хранилище (например, таблицу sqflite или список в памяти), которое используется на обеих страницах, либо в .then вы можете заставить свой виджет как-то обновиться (лично я использовали увеличивающийся счетчик, который я меняю в setState раньше - это не самое чистое решение, но оно работает) ... Или вы можете передать изменения обратно на исходную страницу в функции .then, внести изменения в свой список и затем перестройте (но обратите внимание, что внесение изменений в список не вызывает обновления, поэтому еще раз используйте увеличивающийся счетчик).
rmtmckenzie 08
@AanalMehta Но если это не поможет, я бы порекомендовал вам поискать другие ответы, которые могут быть более актуальными (я почти уверен, что видел несколько о списках и т. Д.), Или задать новый вопрос.
rmtmckenzie 08
@rmtmckenzie На самом деле я пробовал одно из вышеуказанных решений. Затем я применил изменения, но они не отразились. Думаю, теперь у меня есть только одна возможность использовать провайдеров. В любом случае, большое спасибо за вашу помощь.
Aanal Mehta
18

Есть 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")));
    
CopsOnRoad
источник
Как я могу перезагрузить маршрут A при выходе из маршрута C с помощью метода Navigator.popUntil.
Vinoth Vino,
1
@VinothVino Прямого способа сделать это пока нет, вам нужно найти какое-то обходное решение.
CopsOnRoad,
10

Вы можете использовать pushReplacement и указать новый маршрут

Собхан Ячак
источник
Но вы теряете стек навигатора. Что делать, если вы открываете экран с помощью кнопки возврата Android?
encubos
10

Easy Trick является использование 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);
},

И, наконец, на второй странице, чтобы что-то вернуть (я вернул 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).

Первая страница

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

Все, что нужно перезагрузить с предыдущей страницы, должно быть обновлено после всплывающего окна.

Rodalyn Camba
источник
1

Вы можете передать обратно a, 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((){});этот способ, чтобы избежать обновления удаленных виджетов или виджетов, которые больше не активны.
Mahi
Никакой ошибки, но и она не работает, как я предсказывал;)
bartektartanus
У меня та же проблема, что и у @bartektartanus. Если я добавлю if! Монтируется перед setState, мое состояние никогда не будет установлено. Это кажется простым, но я не понял этого через несколько часов.
Swift
1

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

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

Обратите внимание, что context и enclosingWidgetContext могут быть одинаковыми или разными контекстами. Если, например, вы нажимаете изнутри StreamBuilder, они будут другими.

Здесь мы ничего не делаем с ModalRoute. Одного действия по подписке достаточно для принудительного восстановления.

CKK
источник
1

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

Первая страница

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повторного вызова, чего не происходит в этом коде. Я что-то упускаю?
Ricardo BRGWeb
0

Этот простой код помог мне перейти в корень и перезагрузить состояние:

    ...
    onPressed: () {
         Navigator.of(context).pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
                },
    ...
Хуанма Менендес
источник