Перезагрузить навигатор 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(),
      ),
    );

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


Отлично, первый случай у меня сработал отлично (второй идеально подходит для более сложной ситуации). Мне было непонятно, как использовать 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")));
    

Как я могу перезагрузить маршрут 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);

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


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. Одного действия по подписке достаточно для принудительного восстановления.


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('/'));
                },
    ...
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.