How to execute code before app exit flutter
Asked Answered
S

8

64

I want to detect when a user quit my app and execute some code before but I don't know how to do this. I tried to use this package: https://pub.dev/packages/flutter_lifecycle_state but I have this error:

flutter/.pub-cache/hosted/pub.dartlang.org/flutter_lifecycle_state-1.0.0/lib/flutter_lifecycle_state.dart:80:30: Error: Getter not found: 'suspending'. case AppLifecycleState.suspending

If you have any solution for this problem or know another way to detect when a user quit my app it could be cool

Siphon answered 12/2, 2020 at 8:58 Comment(6)
Have you read attention points? ①The lifecycle method calls for the widgets on the Flutter side are all made by the host app side. The flutter terminal will no longer receive any messages after the application is abruptly closed. ②When the root page on the Flutter side closes properly, the State#dispose method is not raised, so our onDestroy method is not raised, so if you want to free the resource, you'll have to do it yourself.Gammadion
Ah yes ! Do you have any other solution to this problem ?Siphon
It depends what exactly you want to do on exit of app.Gammadion
execute a function to delete a document on firebaseSiphon
I want to delete firebase document when exit app also. did you resolve it?Fatima
How about overriding didRequestAppExit() of WidgetsBindingObserver?Merciless
U
31

You can not do exactly what you want to do right now, anyway, the best approach right now is to check when the application it’s running in background/inactive using the AppLifecycleState from the SDK (basically does what your library is trying to do)

The library that you are using it’s outdated, since a pull request from November 2019 the AppLifecycleState.suspending it’s called AppLifecycleState.detached.

You can take a look at the AppLifecycleState enum in the api.flutter.dev website

Here’s an example of how to observe the lifecycle status of the containing activity:

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()));
}

I think that deleting your data on the inactive cycle and then creating it again in the resumed one can work for you.

Ultrasound answered 12/2, 2020 at 10:26 Comment(3)
That code is because he can see how the states works and how he could use the event of the lifecycle to call his function, probably deleting his data on paused cycle and then creating it again in the resumed one. It’s also the SDK way of doing what the library that he is trying to uses do.Quadrivalent
This is the way that is closest to what he wants to do, I edited the post because he will see that’s not exactly what he is asking for but it’s the only aproach at the moment.Quadrivalent
_lastLifecycleState needs to be initialized (or be declared late?). Also, Android Studio says that the null-check is impossible.Alula
D
18

The audio_service plugin does something very similar. The strategy is to wrap your app in a custom widget that listens for when the app life cycle state changes, and then run different code based on the state. I'm not saying you should use this plugin but that you can adapt the code to fit your needs. Replace references to AudioService below with whatever code you need to run.

Here is the code from audio_service:

/// A widget that maintains a connection to [AudioService].
///
/// Insert this widget at the top of your `/` route's widget tree to maintain
/// the connection across all routes. e.g.
///
/// ```
/// return MaterialApp(
///   home: AudioServiceWidget(MainScreen()),
/// );
/// ```
///
/// Note that this widget will not work if it wraps around [MateriaApp] itself,
/// you must place it in the widget tree within your route.
class AudioServiceWidget extends StatefulWidget {
  final Widget child;

  AudioServiceWidget({@required this.child});

  @override
  _AudioServiceWidgetState createState() => _AudioServiceWidgetState();
}

class _AudioServiceWidgetState extends State<AudioServiceWidget>
    with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    AudioService.connect();
  }

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

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        AudioService.connect();
        break;
      case AppLifecycleState.paused:
        AudioService.disconnect();
        break;
      default:
        break;
    }
  }

  @override
  Future<bool> didPopRoute() async {
    AudioService.disconnect();
    return false;
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

Note:

  • Exiting the app for most users usually just means hiding it. The app is still alive in the background until the system kills it to save resources.
Denis answered 2/12, 2020 at 2:37 Comment(0)
A
9

Flutter version 3.13 added AppLifecycleListener so now you can listen to show, pause , resume , restart etc.

_listener = AppLifecycleListener(
  onShow: () => _handleTransition('show'),
  onResume: () => _handleTransition('resume'),
  onHide: () => _handleTransition('hide'),
  onInactive: () => _handleTransition('inactive'),
  onPause: () => _handleTransition('pause'),
  onDetach: () => _handleTransition('detach'),
  onRestart: () => _handleTransition('restart'),
  // This fires for each state change. Callbacks above fire only for
  // specific state transitions.
  onStateChange: _handleStateChange,
);

For more info read the official AppLifecycleListener docs, which include full code examples.

Andes answered 28/8, 2023 at 11:20 Comment(0)
T
7

What about putting your main/home/top Scaffold widget inside a WillPopScope?

class MyGreatestApp extends StatefulWidget {
  @override
  _MyGreatestAppState createState() => _MyGreatestAppState();
}

class _MyGreatestAppState extends State<MyGreatestApp> {
  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      child: Scaffold(...),
      onWillPop: _doNastyStuffsBeforeExit,
    );
  }

  Future<bool> _doNastyStuffsBeforeExit() async{
    // Since this is an async method, anything you do here will not block UI thread
    // So you should inform user there is a work need to do before exit, I recommend SnackBar

    // Do your pre-exit works here...

    // also, you must decide whether the app should exit or not after the work above, by returning a future boolean value:

    return Future<bool>.value(true); // this will close the app,
    return Future<bool>.value(false); // and this will prevent the app from exiting (by tapping the back button on home route)
  }
}
Throat answered 5/10, 2020 at 6:47 Comment(1)
Note that WillPopScope has the effect of disabling the "swipe to go back" gesture on iOS which is probably not what is desired. This behaviour is also by design (see github.com/flutter/flutter/issues/14203). Instead, you can use WidgetsBindingObserver.didPopRoute as this is a bit more low level (See Suragch's answer for an example).Wandis
R
5

1-) Firstly use WidgetsBindingObserver in your stateful widget with keyword 'with'.

2-) Initialize WidgetsBinding with WidgetsBinding.instance.addObserver(this); in your initState

3-) Dispose WidgetsBinding with WidgetsBinding.instance.removeObserver(this); in your dispose.

4-) Lastly, use didChangeAppLifecycleState to check if user left the app. Example Below;

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
  @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:
        //Execute the code here when user come back the app.
        //In my case, I needed to show if user active or not,
        FirebaseMethods().updateLiveStatus(_authInstance.currentUser.uid, true);
        break;
      case AppLifecycleState.paused:
        //Execute the code the when user leave the app
        FirebaseMethods()
            .updateLiveStatus(_authInstance.currentUser.uid, false);
        break;
      default:
        break;
    }
  }

  @override
  Widget build(BuildContext context) {}
}
Retrusion answered 11/5, 2022 at 19:26 Comment(0)
R
2

I implemented something similar in a plugin I made on Android side, the idea is you need to make use of Flutters MethodChannel class, then from native side call the method you want to be executed from flutter side before the app is closed.

In my case I implemented an override to onDetachedFromActivity which is called right before the Flutter engine is detached. This method is found in the io.flutter.embedding.engine.plugins.activity.ActivityAware interface

Royo answered 28/4, 2022 at 10:55 Comment(0)
T
0

In my testing, WidgetsBinfing can't detect onDestory. I think Flutters MethodChannel is the best way to do specific function when App's onDestory. (Like Basel Abuhadrous answer)

Tupper answered 19/5, 2023 at 4:45 Comment(0)
A
0

You can see the example here, worked for my app. https://api.flutter.dev/flutter/widgets/AppLifecycleListener-class.html

use AppLifecycleListener and add onExitRequested to the listener

  late final AppLifecycleListener _listener;
  final List<String> _states = <String>[];
  late AppLifecycleState? _state;

  @override
  void initState() {
    super.initState();
    _state = SchedulerBinding.instance.lifecycleState;
    _listener = AppLifecycleListener(
      // This fires for each state change. Callbacks above fire only for
      // specific state transitions.
      onStateChange: _handleStateChange,
      onExitRequested: () => _handleExit(),
    );
    if (_state != null) {
      _states.add(_state!.name);
    }
  }

  Future<AppExitResponse> _handleExit() async {
    print('this app is about to close');
    await Future.delayed(const Duration(seconds: 3));
    print('this app has closed');
    return AppExitResponse.exit;
  // you can call AppExitResponse.cancel to cancel the app exit
  }

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

  void _handleStateChange(AppLifecycleState state) {
    log('AppLifecycleState: ${state.name}');
  }
Antionetteantioxidant answered 25/5 at 14:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.