How to handle app lifecycle with Flutter (on Android and iOS)?
There is a method called when the system put the app in the background or return the app to foreground named didChangeAppLifecycleState
.
Example with widgets:
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 new Text('Last notification: $_notification');
}
}
Also there are CONSTANTS to know the states that an application can be in, eg:
- inactive
- paused
- resumed
- suspending
The usage of these constants would be the value of the constant e.g:
const AppLifecycleState(state)
Flutter exact lifecycle equivalents to onResume / onPause on Android and viewWillAppear / viewDidDisappear on iOS
I've found a few solutions, each with its own pros and cons. The one that answers this question the best is FocusDetector.
Best Pick
FocusDetector (Edson Bueno)
FocusDetector handles all the cases covered in the original question. Instead of overrides (like initState() and dispose()), you supply callback functions to a wrapping widget called FocusDetector
. The two relevant callbacks are:
onFocusGained
=onResume
onFocusLost
=onPause
Cons
- Isn’t maintained by Flutter or Google (but it uses
VisibilityDetector
andWigetBindingObserver
under the hood) - The default
VisibilityDetectorController.updateInterval
is 500ms which means the events get fired somewhat late.
- Isn’t maintained by Flutter or Google (but it uses
Borrowing the style from one of my favorite posts:
Example Widget
class PageState extends State<Page> {
@override
void initState() {
super.initState();
log("onCreate / viewDidLoad / initState");
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
layoutComplete();
});
}
// Bonus one I've found helpful, once layout is finished
void layoutComplete() {
log("onActivityCreated / viewDidLoad / layoutComplete");
}
void viewWillAppear() {
log("onResume / viewWillAppear / onFocusGained");
}
void viewWillDisappear() {
log("onPause / viewWillDisappear / onFocusLost");
}
@override
void dispose() {
log("onDestroy / viewDidUnload / dispose");
super.dispose();
}
@override
Widget build(BuildContext context) {
return FocusDetector(
onFocusGained: viewWillAppear,
onFocusLost: viewWillDisappear,
child: Text('Rest of my widget'),
);
}
}
Other options
RouteObserver (Flutter)
didPush
=onResume
current screen is pushed ondidPopNext
=onResume
current screen is being navigated back todidPop
=onPause
dismissing current page / going backdidPushNext
=onPause
navigating forward to a new pageCons:
- Doesn’t cover use-case (2), backgrounding then foregrounding the app
WidgetsBindingObserver (Flutter)
AppLifecycleState.resumed
= The app is visible and responding to user inputAppLifecycleState.paused
= The app is not visible and not responding to user inputCons:
- Isn’t widget/route specific (for external nav (2))
- Doesn’t cover use-case (1), navigating between pages
VisibilityDetector (Google)
onVisibilityChanged
(visibility == 1) =onResume
onVisibilityChanged
(visibility == 0) =onPause
Cons:
- Doesn’t cover use-case (2), backgrounding then foregrounding the app
Flutter: How to track app lifecycle without a widget
Below is a code sample that can listen to AppLifecycleState
change events without directly involving a widget:
import 'package:flutter/material.dart';
class MyLibrary with WidgetsBindingObserver {
AppLifecycleState _state;
AppLifecycleState get state => _state;
MyLibrary() {
WidgetsBinding.instance.addObserver(this);
}
/// make sure the clients of this library invoke the dispose method
/// so that the observer can be unregistered
void dispose() {
WidgetsBinding.instance.removeObserver(this);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
this._state = state;
}
void someFunctionality() {
// your library feature
}
}
Now you can instantiate the library in a flutter widget, for instance, from which point it will start listening to any change in AppLifecycleState
.
Please note that, the above code doesn't take care of redundant bindings. For example, if the client of your library is meant to initialize the library more than once in different places, the didChangeAppLifecycleState()
method will be triggered multiple times per state change (depending on the number of instances of the library that were created). Also I'm unsure whether the solution proposed conforms to flutter best practices. Nevertheless it solves the problem and hope it helps!
Flutter - Handling Life Cycle
dispose() is used to execute code when the screen is disposed. Equal to onDestroy() of Android.
Example:
@override
void dispose() {
anyController?.dispose();
timer.cancel();
super.dispose();
}
Add this in your Code
Life cycle in flutter
createState()
:
When the Framework is instructed to build a StatefulWidget, it immediately callscreateState()
mounted
is true:
WhencreateState
creates your state class, abuildContext
is assigned to that state.buildContext
is, overly simplified, the place in the widget tree in which this widget is placed. Here's a longer explanation.
All widgets have abool this.mounted
property. It is turned true when thebuildContext
is assigned. It is an error to callsetState
when a widget is unmounted.initState()
:
This is the first method called when the widget is created (after the class constructor, of course.)initState
is called once and only once. It must callsuper.initState()
.didChangeDependencies()
:
This method is called immediately afterinitState
on the first time the widget is built.build()
:
This method is called often. It is required, and it must return a Widget.didUpdateWidget(Widget oldWidget)
:
If the parent widget changes and has to rebuild this widget (because it needs to give it different data), but it's being rebuilt with the sameruntimeType
, then this method is called.
This is because Flutter is re-using the state, which is long lived. In this case, you may want to initialize some data again, as you would ininitState
.setState()
:
This method is called often from the framework itself and from the developer. Its used to notify the framework that data has changeddeactivate()
:
Deactivate is called when State is removed from the tree, but it might be reinserted before the current frame change is finished. This method exists basically because State objects can be moved from one point in a tree to another.dispose()
:dispose()
is called when the State object is removed, which is permanent. This method is where you should unsubscribe and cancel all animations, streams, etc.mounted
is false:
The state object can never remount, and error will be thrown ifsetState
is called.
onResume() and onPause() for widgets on Flutter
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 State
s 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.
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,
),
],
),
);
}
}
Related Topics
Two Main Activities in Androidmanifest.Xml
How to Compile Ffmpeg-2.2.2 on Windows with Cygwin and Android Ndk R9C
Gridlayoutmanager - How to Auto Fit Columns
Get Meta Data of Image - Android
Fcm (Firebase Cloud Messaging) How to Send to All Phones
Trying to Fix Networkonmainthreadexception But Gives Toast Error
Value <Br of Type Java.Lang.String Cannot Be Converted to JSONobject on Android
Searchview in Centre and After Focus It Goes to App Bar in Android
How to Create Drawable from Resource
How to Download Xml File from Server and Save It in Sd Card
Android Studio: Exclude Resource File Under Resources Sourcesets
Finish an Activity After a Time Period
How to Discover Zeroconf (Bonjour) Services on Android? I'M Having Trouble with Jmdns
Change Order of Views in Linear Layout Android
What Is The Easiest Way to Use Svg Images in Android
CSS Media Queries on Nexus 7, Display Resolution Not Working in Code