82

In Flutter, is there a way to update widgets when the user leaves the app and come right back to it? My app is time based, and it would be helpful to update the time as soon as it can.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Josh
  • 2,232
  • 3
  • 15
  • 33

10 Answers10

138

You can listen to lifecycle events by doing this for example :

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';

class LifecycleEventHandler extends WidgetsBindingObserver {
  final AsyncCallback resumeCallBack;
  final AsyncCallback suspendingCallBack;

  LifecycleEventHandler({
    this.resumeCallBack,
    this.suspendingCallBack,
  });

  @override
  Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
    switch (state) {
      case AppLifecycleState.resumed:
        if (resumeCallBack != null) {
          await resumeCallBack();
        }
        break;
      case AppLifecycleState.inactive:
      case AppLifecycleState.paused:
      case AppLifecycleState.detached:
        if (suspendingCallBack != null) {
          await suspendingCallBack();
        }
        break;
    }
  }
}



class AppWidgetState extends State<AppWidget> {
  void initState() {
    super.initState();

    WidgetsBinding.instance.addObserver(
      LifecycleEventHandler(resumeCallBack: () async => setState(() {
        // do something
      }))
    );
  }
  ...
}
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    I'm just wondering, can you add a WidgetsBindingObserver to a StatelessWidget? Thanks. – Josh Apr 17 '18 at 06:10
  • Sure, but what do you want to do when such a lifecycle event happens? – Günter Zöchbauer Apr 17 '18 at 06:15
  • What that occurs, I would like to call `dispatch` for my `StoreConnector`, a handful of widgets below – Josh Apr 17 '18 at 06:18
  • 1
    You can put above code everywhere. It doesn't have to be inside a widget. That was just an example. – Günter Zöchbauer Apr 17 '18 at 06:33
  • Can you provide me a complete example because your code seems to be broken – CodeGeek Mar 25 '19 at 14:15
  • @CodeGeek I think yeah it's kinda broken when you try and call the suspend and resume methods. – Kaki Master Of Time Mar 26 '19 at 17:06
  • Yeah, so what can be done? Also, we cannot use this method when we change page and want something to be done when we come back to the same page. – CodeGeek Mar 27 '19 at 04:48
  • 2
    Where is FutureVoidCallback defined? I can't find it in the flutter package source and google is not helping either? – Chris Crowe Apr 01 '19 at 22:47
  • 2
    @ChrisCrowe you are right. You need to declare it yourself. `typedef FutureVoidCallback = Future Function();` – Günter Zöchbauer Apr 02 '19 at 03:50
  • @GünterZöchbauer Do I need to add observer to every widget or is it enough to have it at the top level, I was looking for something similar to Appdelegate (iOS) – anoop4real Jun 01 '20 at 15:54
  • @anoop4real top-level is enough. – Günter Zöchbauer Jun 01 '20 at 16:10
  • @GünterZöchbauer Lets say if I have a multiple paged app, does every page separately need to have an observer? – anoop4real Jun 06 '20 at 06:59
  • No, only in the App widget. @anoop4real – Günter Zöchbauer Jun 06 '20 at 11:59
  • @GünterZöchbauer Is it possible to find app kill state? (app kill/app close) – BIS Tech Nov 25 '20 at 11:44
  • I'm not fully up-to-date on this topic currently, but I guess not. The recent changes regarding persisting of UI state (scroll position and similar) seems to be to pass it to the Android-side of the app on every change so that in case the app is closed Android can persist the state without reaching out to Flutter. When the app is killed only sync code can be executed, and calling out to Flutter only works async. Simon Lightfoot gave a speech recently at the Flutter-Vikings conference about that topic and this is how I understood it. – Günter Zöchbauer Nov 25 '20 at 12:32
  • just be aware, resume is called every time you call `showDialog(...)` as well – Pierre Apr 12 '22 at 18:42
  • Great answer, but this doesn't remove the observer in the `dispose()` method It is good practice to do it with `WidgetsBinding.instance?.removeObserver(this);` – Jack' Jun 01 '22 at 07:28
  • 1
    @Jack' it's definitely good practice. In this case I omitted it because it should stay registered until the app exits so removing doesn't make much difference in this case, but definitely good to always clean up. – Günter Zöchbauer Jun 01 '22 at 08:31
30

Using system Channel:

import 'package:flutter/services.dart';

SystemChannels.lifecycle.setMessageHandler((msg){
  debugPrint('SystemChannels> $msg');
  if(msg==AppLifecycleState.resumed.toString())setState((){});
});

`

UpaJah
  • 6,954
  • 4
  • 24
  • 30
  • 5
    This may be obvious to others, but it took me a while to figure out I needed to move this to a location after the Flutter runApp call. – edhubbell Jul 11 '18 at 13:39
  • do we put this code into initState()? The systemChannels will act like a listener to listener to the change of lifecycle? – GPH Oct 08 '18 at 08:57
  • What is the type of the msg variable? Why is it not of type AppLifecycleState? – Scorb Jul 29 '20 at 15:30
  • 1
    @edhubbell from the [docs](https://api.flutter.dev/flutter/widgets/WidgetsBinding/instance.html) `If you need the binding to be constructed before calling runApp, you can ensure a Widget binding has been constructed by calling the WidgetsFlutterBinding.ensureInitialized() function.` – mx1up Nov 13 '20 at 16:57
  • 1
    This is a good solution, unfortunately if you set the handler for these channels to your own implementations, you will no longer receive notifications for `WidgetsBindingObserver`, as they rely on these message handlers to work. For instance, `didChangeAppLifecycleState` will no longer be called. – mrcendre Jan 21 '22 at 09:43
25
import 'package:flutter/material.dart';

abstract class LifecycleWatcherState<T extends StatefulWidget> extends State<T>
    with WidgetsBindingObserver {
  @override
  Widget build(BuildContext context) {
    return null;
  }

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        onResumed();
        break;
      case AppLifecycleState.inactive:
        onPaused();
        break;
      case AppLifecycleState.paused:
        onInactive();
        break;
      case AppLifecycleState.detached:
        onDetached();
        break;
    }
  }

  void onResumed();
  void onPaused();
  void onInactive();
  void onDetached();
}

Example

class ExampleStatefulWidget extends StatefulWidget {
  @override
  _ExampleStatefulWidgetState createState() => _ExampleStatefulWidgetState();
}

class _ExampleStatefulWidgetState
    extends LifecycleWatcherState<ExampleStatefulWidget> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }

  @override
  void onDetached() {

  }

  @override
  void onInactive() {

  }

  @override
  void onPaused() {

  }

  @override
  void onResumed() {

  }
}
Karzan Kamal
  • 1,074
  • 10
  • 9
  • 1
    This is exactly the way to do. Still one question: return null; in Widget build() is no more allowed. How to solve this? – cwhisperer Jun 21 '22 at 13:32
  • 2
    @cwhisperer you can use SizedBox() instate of null or remove the "Widget build(BuildContext context)" I didn't test it but maybe it will not make a problem. – Karzan Kamal Jun 22 '22 at 15:28
  • This seems to roughly be the approach the lifecycle package takes: https://pub.dev/packages/lifecycle – holocronweaver Aug 24 '22 at 03:18
18

Simple way:

import 'package:flutter/services.dart';

handleAppLifecycleState() {
    AppLifecycleState _lastLifecyleState;
    SystemChannels.lifecycle.setMessageHandler((msg) {
      
     print('SystemChannels> $msg');

        switch (msg) {
          case "AppLifecycleState.paused":
            _lastLifecyleState = AppLifecycleState.paused;
            break;
          case "AppLifecycleState.inactive":
            _lastLifecyleState = AppLifecycleState.inactive;
            break;
          case "AppLifecycleState.resumed":
            _lastLifecyleState = AppLifecycleState.resumed;
            break;
          case "AppLifecycleState.suspending":
            _lastLifecyleState = AppLifecycleState.suspending;
            break;
          default:
        }
    });
  }

just add handleAppLifecycleState() in your init()

OR

class AppLifecycleReactor extends StatefulWidget {
      const AppLifecycleReactor({ Key key }) : super(key: key);
    
      @override
      _AppLifecycleReactorState createState() => _AppLifecycleReactorState();
    }
    
    class _AppLifecycleReactorState extends State<AppLifecycleReactor> with WidgetsBindingObserver {
      @override
      void initState() {
        super.initState();
        WidgetsBinding.instance.addObserver(this);
      }
    
      @override
      void dispose() {
        WidgetsBinding.instance.removeObserver(this);
        super.dispose();
      }
    
      AppLifecycleState _notification;
    
      @override
      void didChangeAppLifecycleState(AppLifecycleState state) {
        setState(() { _notification = state; });
      }
    
      @override
      Widget build(BuildContext context) {
        return Text('Last notification: $_notification');
      }
    }

For more details you refer documentation

Venkat.R
  • 7,420
  • 5
  • 42
  • 63
Rahul Mahadik
  • 11,668
  • 6
  • 41
  • 54
18

For deeply testing, I think the results are worth for read. If you are curious about which method you should use, just read the below: (Tested on Android)

There are three methods for LifeCycle solution.

  1. WidgetsBindingObserver
  2. SystemChannels.lifecycle
  3. flutter-android-lifecycle-plugin

The main difference between WidgetsBindingObserver and SystemChannels.lifecycle is that WidgetsBindingObserver have more capables If you have a bunch of widgets that need to listen LifeCycle. SystemChannels is more low layer, and used by WidgetsBindingObserver.

After several testing, If you use SystemChannels after runApp, and home widget mixin with WidgetsBindingObserver, home widget would be failed, because SystemChannels.lifecycle.setMessageHandler override the home's method.

So If you want to use a global, single method, go for SystemChannels.lifecycle, others for WidgetsBindingObserver.

And what about the third method? This is only for Android, and If you must bind your method before runApp, this is the only way to go.

Tokenyet
  • 4,063
  • 2
  • 27
  • 43
6

Here’s an example of how to observe the lifecycle status of the containing activity (Flutter for Android developers):

import 'package:flutter/widgets.dart';

class LifecycleWatcher extends StatefulWidget {
  @override
  _LifecycleWatcherState createState() => _LifecycleWatcherState();
}

class _LifecycleWatcherState extends State<LifecycleWatcher> with WidgetsBindingObserver {
  AppLifecycleState _lastLifecycleState;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    setState(() {
      _lastLifecycleState = state;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (_lastLifecycleState == null)
      return Text('This widget has not observed any lifecycle changes.', textDirection: TextDirection.ltr);

    return Text('The most recent lifecycle state this widget observed was: $_lastLifecycleState.',
        textDirection: TextDirection.ltr);
  }
}

void main() {
  runApp(Center(child: LifecycleWatcher()));
}
Igor Bokov
  • 99
  • 1
  • 9
6

Solutions implemented for detecting onResume event using "WidgetsBindingObserver" OR "SystemChannels.lifecycle" works only when App is gone in background completely like during lock screen event or during switching to another app. It will not work if user navigate between screens of app. If you want to detect onResume event even when switching between different screens of same app then use visibility_detector library from here : https://pub.dev/packages/visibility_detector

  @override
Widget build(BuildContext context) {
  return VisibilityDetector(
    key: Key('my-widget-key'),
    onVisibilityChanged: (visibilityInfo) {
      num visiblePercentage = visibilityInfo.visibleFraction * 100;
      debugPrint(
          'Widget ${visibilityInfo.key} is ${visiblePercentage}% visible');
      if(visiblePercentage == 100){
                debugPrint("Resumed !");
              }
    },
    child: someOtherWidget,
  );
}
user3930098
  • 395
  • 7
  • 14
1

If you want to execute onResume method but only in one page you can add this in your page:

var lifecycleEventHandler;

@override
  void initState() {
    super.initState();

    ///To listen onResume method
    lifecycleEventHandler = LifecycleEventHandler(
        resumeCallBack: () async {
          //do something
        }
    );
    WidgetsBinding.instance.addObserver(lifecycleEventHandler);
  }

@override
  void dispose() {
    if(lifecycleEventHandler != null)
      WidgetsBinding.instance.removeObserver(lifecycleEventHandler);

    super.dispose();
  }

and having LifecycleEventHandler class as the first answer of this post:

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';

class LifecycleEventHandler extends WidgetsBindingObserver {
  final AsyncCallback resumeCallBack;
  final AsyncCallback suspendingCallBack;

  LifecycleEventHandler({
    this.resumeCallBack,
    this.suspendingCallBack,
  });

  @override
  Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
    switch (state) {
      case AppLifecycleState.resumed:
        if (resumeCallBack != null) {
          await resumeCallBack();
        }
        break;
      case AppLifecycleState.inactive:
      case AppLifecycleState.paused:
      case AppLifecycleState.detached:
        if (suspendingCallBack != null) {
          await suspendingCallBack();
        }
        break;
    }
  }
}
Álvaro Agüero
  • 4,494
  • 1
  • 42
  • 39
0

If you want a reliable onOpen handler, you should call it both from initState and as in WidgetsBindingObserver docs.

Tested with:

  • The first start of the app.
  • Tap any system button (Back, Home, Recent apps) to close the app, then open the app again.

Code:

class MyWidgetState extends State<MyWidget> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    onOpen();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) onOpen();
  }

  void onOpen() {
    debugPrint('-------- OPEN --------');
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}
Denis Ryzhkov
  • 2,321
  • 19
  • 12
0

I have modified the answer by @Gunter to use with Riverpod. Any widget that watches the lifecycle provider will be rebuilt on state change.

lifecycle_state_provider.dart

class LifecycleStateNotifier extends Notifier<bool> {
  @override
  bool build() {
    final lifecycleEventHandler = LifecycleEventHandler._(ref);
    WidgetsBinding.instance.addObserver(lifecycleEventHandler);
    return true;
  }

  void updateState(val) {
    state = val;
  }
}

final lifecycleProvider = NotifierProvider<LifecycleStateNotifier, bool>(() {
  
  return LifecycleStateNotifier();
});

class LifecycleEventHandler extends WidgetsBindingObserver {
  Ref ref;
  LifecycleEventHandler._(this.ref);

  @override
  Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
    switch (state) {
      case AppLifecycleState.resumed:
         ref.read(lifecycleProvider.notifier).updateState(false);
        break;
      case AppLifecycleState.inactive:
      case AppLifecycleState.paused:
      case AppLifecycleState.detached:
         ref.read(lifecycleProvider.notifier).updateState(true);
        break;
    }
  }
}

main.dart

void main() {
  runApp(
    const ProviderScope(child: MainApp()),
  );
}

class MainApp extends ConsumerWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    bool inBackground = ref.watch(lifecycleProvider);
    print('inBackground: $inBackground - build called');
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Text('Hello World!'),
        ),
      ),
    );
  }
}

Github

If you don't interested of updating widgets, but rather just want to know in which state (background/foreground) app is now, check this answer.

Yuriy N.
  • 4,936
  • 2
  • 38
  • 31