19

I'm new to Dart/Flutter and would like to build a simple app where a LinearProgressBar gets updated every second.

Without getting too much into the actual code, I have the following setup working.

  • A function that calculates the progress, based on passed time.
  • A LinearProgressBar showing the progress.
  • A periodic Timer recalculating the progress and updating the progress bar every second.
  • I debugprint 'tick' every time, the recalculation is done.

Everything is working as expected with one exception. The 'tick' keeps getting printed when I move the app in the background on my Android device.

On native Android, I would cancel my periodic Timer when the 'onPause' event is triggered.

Is there something similar in Flutter? All I could find was 'initState' and 'dispose'. Dispose, however, does not get called when moving the app to background.

I don't want the timer to keep ticking in the background.

On my research, I found this Stack Overflow question onresume-and-onpause-for-widgets-on-flutter. It's answer suggests using TickerProviderStateMixin.

I used it the following way.

class _BarItemState extends State<BarItem> with SingleTickerProviderStateMixin {
    Ticker ticker;
    num progress = 1.0;

    @override
    void initState() {
       super.initState();
       ticker = createTicker((duration) => setState(() {
          debugPrint('tick');
          progress = duration.inSeconds / 30;
       }))
      ..start();
    }

    // other stuff omitted

}

It is working, but I'm still not satisfied.

The reason is, that the ticker callback is getting now called every few milliseconds instead of once a second. This seems to me like a waste of resources (I don't need a smooth animation), ... am I overcomplicating things?

Even if it seems that I don't need it for my use case, I still would like to know:

How to handle the onPause/onResume events on my own?

forgemo
  • 4,634
  • 5
  • 22
  • 25

3 Answers3

19

You can override the didChangeAppLifecycleState of the WidgetBindingObserver interface to receive notifications for app lifecycle changes.

There's sample code in this page

rupinderjeet
  • 2,984
  • 30
  • 54
amir
  • 449
  • 3
  • 5
13

You can use lifecycle channel from SystemChannels.

Example:

SystemChannels.lifecycle.setMessageHandler((msg){
  debugPrint('SystemChannels> $msg');
});

Output:

I/flutter ( 3672): SystemChannels> AppLifecycleState.paused
I/flutter ( 3672): SystemChannels> AppLifecycleState.resumed
German Saprykin
  • 6,631
  • 2
  • 29
  • 26
  • I felt that amirs solution is more idomatic. Thanks for the answer, though! – forgemo Nov 13 '17 at 19:36
  • 1
    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:40
0

From Flutter 3.13 onwards we can follow the new approach AppLifecycleListener instead of WidgetBindingObserver, It has some extra features compared to WidgetBindingObserver

Dart Pad example

Create a stateful class and declare App lifecycle listener property

  late final AppLifecycleListener _listener;

Create an AppLifecycleListener class instance in initState, and pass the required callbacks in it as shown below

 @override
  void initState() {
    lifeCycleListener = AppLifecycleListener(
      onStateChange: _onLifeCycleChanged
    );
    super.initState();
  }

Dispose the listener

  @override
  void dispose() {
    lifeCycleListener.dispose();
    super.dispose();
  }

Implement your logic _onLifeCycleChanged based on the lifecycle

  void _onLifeCycleChanged(AppLifecycleState state) {
    switch(state){
      case AppLifecycleState.detached:
        // TODO: Handle this case.
      case AppLifecycleState.resumed:
        // TODO: Handle this case.
      case AppLifecycleState.inactive:
        // TODO: Handle this case.
      case AppLifecycleState.hidden:
        // TODO: Handle this case.
      case AppLifecycleState.paused:
        // TODO: Handle this case.
    }
  }

In WidgetsBindingObserver when overriding the didChangeAppLifecycleState method, we can only listen for the actual state change, for example when your app got into the resumed state. In AppLifecycleListener we can listen to the transitions between the states, for example, whether your app got into the resumed state from the inactive as shown below.

    lifeCycleListener = AppLifecycleListener(
      onStateChange: _onLifeCycleChanged,
      onDetach: _onDetach,
      onPause: _onPause
      ....
    );


   /// on Detach this will be called
  _onDetach() => print('on Detach');
  _onPause() => print('on Pause');
  ....

One more feature is onExitRequested callback, we can alert the user before closing the application, I think this will be used in desktop based applications

  AppLifecycleListener(
      onExitRequested: _onExit
    );

  Future<AppExitResponse> _onExit() async {
    final response = await showDialog<AppExitResponse>(
      context: context,
      barrierDismissible: false,
      builder: (context) => AlertDialog(
        title: const Text('Are you sure you want to close app?'),
        content: const Text('All unsaved date will be lost.'),
        actions: [
          TextButton(
            child: const Text('Cancel'),
            onPressed: () {
              Navigator.of(context).pop(AppExitResponse.cancel);
            },
          ),
          TextButton(
            child: const Text('Exist the App'),
            onPressed: () {
              Navigator.of(context).pop(AppExitResponse.exit);
            },
          ),
        ],
      ),
    );

    return response ?? AppExitResponse.exit;
  }
krishnaji
  • 1,605
  • 4
  • 14
  • 25