androidiosdartlifecycleflutter

onResume() and onPause() for widgets on Flutter


Right now, a widget only has initeState() that gets triggered the very first time a widget is created, and dispose(), which gets triggered when the widget is destroyed. Is there a method to detect when a widget comes back to the foreground? and when a widget is about to go to the background because another widget just was foregrounded? It's the equivalent of onResume and onPause being triggered for Android, and viewWillAppear and viewWillDisappear for ios


Solution

  • The most common case where you'd want to do this is if you have an animation running and you don't want to consume resources in the background. In that case, you should extend your State with TickerProviderStateMixin and use your State as the vsync argument for the AnimationController. Flutter will take care of only calling the animation controller's listeners when your State is visible.

    If you want the States that live in your PageRoute to be disposed when the PageRoute is obscured by other content, you can pass a maintainState argument of false to your PageRoute constructor. If you do this, your State will reset itself (and its children) when it's hidden and will have to re-construct itself in initState using the properties passed in as constructor arguments to its widget. You can use a model or controller class, or PageStorage, to hold the user's progress information if you don't want a complete reset.

    Here is a sample app that demonstrates these concepts.

    screen 1 screen 2 screen 3

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(new MaterialApp(
        onGenerateRoute: (RouteSettings settings) {
          if (settings.name == '/') {
            return new MaterialPageRoute<Null>(
              settings: settings,
              builder: (_) => new MyApp(),
              maintainState: false,
            );
          }
          return null;
        }
      ));
    }
    
    class MyApp extends StatefulWidget {
      MyAppState createState() => new MyAppState();
    }
    
    class MyAppState extends State<MyApp> with TickerProviderStateMixin {
      AnimationController _controller;
    
      @override
      void initState() {
        print("initState was called");
        _controller = new AnimationController(vsync: this)
          ..repeat(min: 0.0, max: 1.0, period: const Duration(seconds: 1))
          ..addListener(() {
            print('animation value ${_controller.value}');
          });
        super.initState();
      }
    
      @override
      void dispose() {
        print("dispose was called");
        _controller.dispose();
        super.dispose();
      }
    
      int _counter = 0;
    
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
          appBar: new AppBar(
            title: new Text('home screen')
          ),
          body: new Center(
            child: new RaisedButton(
              onPressed: () {
                setState(() {
                  _counter++;
                });
              },
              child: new Text('Button pressed $_counter times'),
            ),
          ),
          floatingActionButton: new FloatingActionButton(
            child: new Icon(Icons.remove_red_eye),
            onPressed: () {
              Navigator.push(context, new MaterialPageRoute(
                builder: (BuildContext context) {
                  return new MySecondPage(counter: _counter);
                },
              ));
            },
          ),
        );
      }
    }
    
    class MySecondPage extends StatelessWidget {
      MySecondPage({ this.counter });
    
      final int counter;
    
      Widget build(BuildContext context) {
        return new Scaffold(
          appBar: new AppBar(
            title: new Text('Certificate of achievement'),
          ),
          body: new Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              new Icon(Icons.developer_mode, size: 200.0),
              new Text(
                'Congrats, you clicked $counter times.',
                style: Theme.of(context).textTheme.title,
                textAlign: TextAlign.center,
              ),
              new Text(
                'All your progress has now been lost.',
                style: Theme.of(context).textTheme.subhead,
                textAlign: TextAlign.center,
              ),
            ],
          ),
        );
      }
    }