skip to Main Content

I have 3 widgets A,B,C
from A I push B and set a .then to print a debug message
if I return to A (from B) the .then fires and the message is printed
but if from B I push a replacement C and then return to A, the .then does not fire
so all to rebuild a widget every time the user returns to it

Context: In my quiz app, I have a home screen with a button that should only show up if the user is on a quiz (if they started a quiz and not finish, as the data is saved on device), a resume button, but not only that resume button should display only if the user is on a quiz, but it should display some quiz stats like what disciplines and all many question and so on…

So I need to rebuild that home screen every time the user returns to it

What I have tried:

Riverpod: Even though I don’t like that solution in this scenario because set the state of a riverpod means I have to set another entire set of data at once, and not only that data is extense (a list of question, what current question the user is, what was the answers the user gave and so on) but I also already save the quiz peace by peace, one integer here when the user goes to next question, on integer there when the user select an answer… But even trying to use riverpod, when returning to the home screen the homescreen is still with outdated info, as the riverpod only updates visible widgets.

navigatorObserver: I tried to use observers didPop didPush to check if the current route is at the top, and if so call a function on that, but I cound’t even pass the check part.

2

Answers


  1. Chosen as BEST ANSWER

    I used a RouteObserver with a mixin that implements RouteAware to accomplish my goal.

    How to do: First we need to instantiate a observer, in my case I used a global variable.

    final routeObserver = RouteObserver<PageRoute>();
    

    Then I passed it to the navigatorObservers in the MaterialApp.

      Widget build(BuildContext context) {
        return MaterialApp(
          (...)
          home: const MyHomePage(),
          navigatorObservers: [routeObserver],
        );
      }
    

    With this I created a mixin that extends State (so I can use context) that implements RouteAware (so I can use it's functionalities)

    mixin RouteAwareMixin<T extends StatefulWidget> on State<T>
        implements RouteAware {(...)}
    

    Then I overriden the RouteAware functions to call the functions onActivate and onDeactivate (more on that in the next step)

      @override
      void didChangeDependencies() {
        routeObserver.subscribe(this, ModalRoute.of(context)! as PageRoute);
        super.didChangeDependencies();
      }
    
      @override
      void dispose() {
        routeObserver.unsubscribe(this);
        super.dispose();
      }
    
      @override
      void didPopNext() {
        onActivate();
      }
    
      @override
      void didPush() {
        onActivate();
      }
    
      @override
      void didPop() {
        onDeactivate();
      }
    
      @override
      void didPushNext() {
        onDeactivate();
      }
    

    On that mixin I created two functions that will be overriden later by the classes that uses this mixin, one function for when activating the widget (widget gets or returs to the top of the stack), and one for when the widget gets deactivated (get poped out of the stack of some route get pushed on top of it)

      void onActivate() {
        print('MixinActivate: $this');
      }
    
      onDeactivate() {
        print('MixinDeactivate: $this');
      }
    

    With all setup I just need to use this mixin on State classes to get it's functionalities.

    class _MyHomePageState extends State<MyHomePage> with RouteAwareMixin {
      int _counter = 0;
    
      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
    
      @override
      void onActivate() {
        _incrementCounter();
        super.onActivate();
      }
    
      (...)}
    

    I will leave a entire demo app showing a demo code, just create a new app and replace the main.dart contents with the ones below.

    import 'package:flutter/material.dart';
    
    void main() => runApp(const MyApp());
    
    final routeObserver = RouteObserver<PageRoute>();
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
      static const String _title = 'Flutter RouteAwareMixin';
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: _title,
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const MyHomePage(),
          navigatorObservers: [routeObserver],
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({super.key});
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> with RouteAwareMixin {
      int _counter = 0;
    
      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
    
      @override
      void onActivate() {
        _incrementCounter();
        super.onActivate();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Flutter AwareMixin'),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text('$_counter'),
                SizedBox(
                  height: 16,
                ),
                ElevatedButton(
                    onPressed: () => Navigator.of(context).push(MaterialPageRoute(
                          builder: (context) => SecondScreen(),
                        )),
                    child: Text('OpenSecondScreen'))
              ],
            ),
          ),
        );
      }
    }
    
    class SecondScreen extends StatefulWidget {
      const SecondScreen({super.key});
    
      @override
      State<SecondScreen> createState() {
        return _SecondScreenState();
      }
    }
    
    class _SecondScreenState extends State<SecondScreen> with RouteAwareMixin {
      int _counter = 0;
    
      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
    
      @override
      void onActivate() {
        _incrementCounter();
        _incrementCounter();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('SecondScreen')),
          body: Center(
            child: Column(children: [
              Text('SecondScreenCounter: $_counter'),
              SizedBox(
                height: 16,
              ),
              ElevatedButton(
                  onPressed: () =>
                      Navigator.of(context).pushReplacement(MaterialPageRoute(
                        builder: (context) => ThirdScreen(),
                      )),
                  child: Text('OpenThirdScreen'))
            ]),
          ),
        );
      }
    }
    
    class ThirdScreen extends StatelessWidget {
      const ThirdScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('ThirdScreen')),
        );
      }
    }
    
    mixin RouteAwareMixin<T extends StatefulWidget> on State<T>
        implements RouteAware {
      @override
      void didChangeDependencies() {
        routeObserver.subscribe(this, ModalRoute.of(context)! as PageRoute);
        super.didChangeDependencies();
      }
    
      @override
      void dispose() {
        routeObserver.unsubscribe(this);
        super.dispose();
      }
    
      @override
      void didPopNext() {
        onActivate();
      }
    
      @override
      void didPush() {
        onActivate();
      }
    
      @override
      void didPop() {
        onDeactivate();
      }
    
      @override
      void didPushNext() {
        onDeactivate();
      }
    
      void onActivate() {
        print('MixinActivate: $this');
      }
    
      onDeactivate() {
        print('MixinDeactivate: $this');
      }
    }
    

  2. Use on WillPopScope (this will handle the backButton function) in the QuizScreen and do what you want to do.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search