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.
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
}))
);
}
...
}
Using system Channel:
import 'package:flutter/services.dart';
SystemChannels.lifecycle.setMessageHandler((msg){
debugPrint('SystemChannels> $msg');
if(msg==AppLifecycleState.resumed.toString())setState((){});
});
`
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() {
}
}
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
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.
WidgetsBindingObserver
SystemChannels.lifecycle
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.
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()));
}
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,
);
}
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;
}
}
}
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();
}
}
Related
when i open my app or runs in the background the didChangeAppLifecycleState() is not called and the print statements are not executed.
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
void main(){
runApp(
MaterialApp(
home: Home(),
)
);
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> with WidgetsBindingObserver{
AppLifecycleState state;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
void didChangeAppLifeCycleState(AppLifecycleState appLifecycleState) {
state = appLifecycleState;
print(appLifecycleState);
print(":::::::");
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child:Text("hi")
),
),
);
}
}
}
the print statements in the didChangeAppLifeCycleState() is not executing.
This isn't the answer to the OP's problem, every time that I have had this problem I have just forgotten to call WidgetsBinding.instance.addObserver(this); in initState().
If didChangeAppLifecycleState isn't called for you, don't forget to add your implementing class as a listener.
See the OP's code in initState() and dispose() and make sure you do the same.
There was a typo (lowercase "c" in "Lifecycle"), it should be didChangeAppLifecycleState:
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
state = state;
print(state);
print(":::::::");
}
Update: Don't forget to add observer in initState() and remove observer in dispose()
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
}
// ...
#override
void dispose() {
WidgetsBinding.instance!.removeObserver(this);
super.dispose();
}
Hope it helps!
I had to combine codes from the question and the above 2 answers to get the final result. So let me add a simple sample that illustrates how to use AppLifeCycleState properly.
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> 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:
// --
print('Resumed');
break;
case AppLifecycleState.inactive:
// --
print('Inactive');
break;
case AppLifecycleState.paused:
// --
print('Paused');
break;
case AppLifecycleState.detached:
// --
print('Detached');
break;
}
}
#override
Widget build(BuildContext context) {
return Container();
}
}
In Flutter how would I call Navigator.push when the value of a stream changes? I have tried the code below but get an error.
StreamBuilder(
stream: bloc.streamValue,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData && snapshot.data == 1) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SomeNewScreen()),
);
}
return Text("");
});
You should not use StreamBuilder to handle navigation.
StreamBuilder is used to build the content of a screen and nothing else.
Instead, you will have to listen to the stream to trigger side-effects manually. This is done by using a StatefulWidget and overriding initState/dispose as such:
class Example extends StatefulWidget {
final Stream<int> stream;
const Example({Key key, this.stream}) : super(key: key);
#override
ExampleState createState() => ExampleState();
}
class ExampleState extends State<Example> {
StreamSubscription _streamSubscription;
#override
void initState() {
super.initState();
_listen();
}
#override
void didUpdateWidget(Example oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.stream != widget.stream) {
_streamSubscription.cancel();
_listen();
}
}
void _listen() {
_streamSubscription = widget.stream.listen((value) {
Navigator.pushNamed(context, '/someRoute/$value');
});
}
#override
void dispose() {
_streamSubscription.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container();
}
}
Note that if you're using an InheritedWidget to obtain your stream (typically BLoC), you will want to use didChangeDependencies instead of initState/didUpdateWidget.
This leads to:
class Example extends StatefulWidget {
#override
ExampleState createState() => ExampleState();
}
class ExampleState extends State<Example> {
StreamSubscription _streamSubscription;
Stream _previousStream;
void _listen(Stream<int> stream) {
_streamSubscription = stream.listen((value) {
Navigator.pushNamed(context, '/someRoute/$value');
});
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
final bloc = MyBloc.of(context);
if (bloc.stream != _previousStream) {
_streamSubscription?.cancel();
_previousStream = bloc.stream;
_listen(bloc.stream);
}
}
#override
void dispose() {
_streamSubscription.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container();
}
}
You can extend StreamBuilder with custom listener like this:
typedef StreamListener<T> = void Function(T value);
class StreamListenableBuilder<T> extends StreamBuilder<T> {
final StreamListener<T> listener;
const StreamListenableBuilder({
Key key,
T initialData,
Stream<T> stream,
#required this.listener,
#required AsyncWidgetBuilder<T> builder,
}) : super(key: key, initialData: initialData, stream: stream, builder: builder);
#override
AsyncSnapshot<T> afterData(AsyncSnapshot<T> current, T data) {
listener(data);
return super.afterData(current, data);
}
}
Then connect listener for navigation this way:
StreamListenableBuilder(
stream: bloc.streamValue,
listener: (value) {
if (value==1) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SomeNewScreen()),
);
}
},
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
return Container();
});
I am using a very simple code to check the working of suspending method in the Flutter lifecycle callbacks when I exit the app by pressing back button from the device. But it's not getting called.
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
WidgetsBinding binding = WidgetsBinding.instance;
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
print("state: $state");
}
#override
void initState() {
super.initState();
binding.addObserver(this);
}
#override
Widget build(BuildContext context) {
return Center(child: Text("Center"));
}
#override
void dispose() {
binding.removeObserver(this);
super.dispose();
}
}
You should try the didPopRoute() lifecycle hook for back button on Android. From the docs:
Called when the system tells the app to pop the current route. For example, on Android, this is called when the user presses the back button.
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
WidgetsBinding binding = WidgetsBinding.instance;
#override
Future<bool> didPopRoute() {
// Android user pressed back button
final bool preventAppFromClosing = true;
return preventAppFromClosing;
}
#override
void initState() {
super.initState();
binding.addObserver(this);
}
#override
void dispose() {
binding.removeObserver(this);
super.dispose();
}
}
Logging into our Flutter app opens to dashboard that has a Scaffold with a Drawer full of menu items.
I'd like to perform some A/B testing with having the Drawer open on page load or at least animating the Drawer being opened immediately on load.
I'm aware of Scaffold.of(context).openDrawer() but I'm not sure where to place this code so that it will run immediately after the build() method. I'm also not aware of any fields on either Drawer or Scaffold which would load with the Drawer open.
Thanks for your time and help.
You need to wait after the first frame is loaded.
_onLayoutDone(_) {
//your logic here
}
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_onLayoutDone);
super.initState();
}
I wrote a post about this, you can take a look if you want : https://medium.com/#diegoveloper/flutter-widget-size-and-position-b0a9ffed9407
Override initState.
#override
void initState() {
super.initState();
// use this
Timer.run(() => Scaffold.of(context).openDrawer());
}
Store a state variable to hide and show drawer - isDrawerBeingShown.
Based on the state variable toggle the state of drawer. It is set to false by default so it will be displayed for the first time.
void _showDrawer(BuildContext context) async it must be marked as async so that it runs after build method.
Create showDrawerUtility method to show drawer on demand when ever required.
Edit:
Use GlobalKey
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey();
class MainScreen extends StatefulWidget {
MainScreen({Key key }) : super(key: key);
#override
State<MainScreen> createState() => new MainScreenState();
}
class MainScreenState extends State<MainScreen> {
bool isDrawerBeingShown;
#override
void initState() {
super.initState();
isDrawerBeingShown = false;
_showDrawer(context);
}
void _showDrawer(BuildContext context) async {
if(!isDrawerBeingShown) {
_scaffoldKey.currentState.openDrawer();
setState(() => isDrawerBeingShown = true);
}
}
#override
Widget build(BuildContext context) { // build method goes here}
}
follow my code
import 'package:easy_debounce/easy_debounce.dart';
import 'package:flutter/material.dart';
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey();
class openDrawerOnLoadPage extends StatefulWidget {
openDrawerOnLoadPage({Key? key}) : super(key: key);
#override
_openDrawerOnLoadPageState createState() => _openDrawerOnLoadPageState();
}
class _openDrawerOnLoadPageState extends State<openDrawerOnLoadPage> {
late bool isDrawerBeingShown;
#override
void initState() {
super.initState();
isDrawerBeingShown = false;
_showDrawer(context);
}
void _showDrawer(BuildContext context) async {
if (!isDrawerBeingShown) {
EasyDebounce.debounce('openDrawer', Duration(milliseconds: 100),
() async {
_scaffoldKey.currentState!.openDrawer();
setState(() => isDrawerBeingShown = true);
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
);
}
}
I've been facing some problems related to the setState function while using Stateful Widgets that updates itself with the help of Timers. The code below show 2 main classes that replicate how I came to find this error. The Text Widget "Lorem" should be inserted within 10 seconds - and it is - but it's never shown. I tried to debug the array "Items" and it does contain the "lorem" Text Widget after 5 seconds, as it should. The "build" function runs but doesn't make any difference in the UI.
class textList extends StatefulWidget {
#override
State<StatefulWidget> createState() =>
new _textListState();
}
class _textListState extends State<textList>
with TickerProviderStateMixin {
List<Widget> items = new List();
Widget lorem = new textClass("Lorem");
Timer timer;
#override
void initState() {
super.initState();
items.add(new textClass("test"));
items.add(new textClass("test"));
timer = new Timer.periodic(new Duration(seconds: 5), (Timer timer) {
setState(() {
items.removeAt(0);
items.add(lorem);
});
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
#override
Widget build(BuildContext context) {
Iterable<Widget> content = ListTile.divideTiles(
context: context, tiles: items).toList();
return new Column(
children: content,
);
}
}
class textClass extends StatefulWidget {
textClass(this.word);
final String word;
#override
State<StatefulWidget> createState() =>
new _textClass(word);
}
class _textClass extends State<textClass>
with TickerProviderStateMixin {
_textClass(this.word);
String word;
Timer timer;
#override
void initState() {
super.initState();
timer = new Timer.periodic(new Duration(seconds: 2), (Timer timer) {
setState(() {
word += "t";
});
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
#override
Widget build(BuildContext context) {
return new Text(word);
}
}
This is not how I came to find this error but this is the simplest way to replicate it. The main idea is: The children texts should keep updating themselves (in this case, adding "t"s in the end) and, after 5 seconds, the last of them should be replaced for the Text Widget "Lorem", what does happen in the list but not in the UI.
Here's what's wrong:
A State should never have any constructor arguments. Use the widget property to get access to final properties of the associated StatefulWidget.
Flutter is reusing your _textClass instance because the class name and keys match. This is a problem since you only set widget.word in initState so you're not picking up the new word configuration information. You can fix this either by giving the StatefulWidget instances unique keys to disambiguate them and cause the old State to be disposed, or you can keep around the old State and implement didUpdateWidget. The latter approach is shown below.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new Scaffold(
appBar: new AppBar(title: new Text('Example App')),
body: new textList(),
),
));
}
class textList extends StatefulWidget {
#override
State<StatefulWidget> createState() =>
new _textListState();
}
class _textListState extends State<textList>
with TickerProviderStateMixin {
List<Widget> items = new List();
Widget lorem = new textClass("Lorem");
Timer timer;
#override
void initState() {
super.initState();
items.add(new textClass("test"));
items.add(new textClass("test"));
timer = new Timer.periodic(new Duration(seconds: 5), (Timer timer) {
setState(() {
items.removeAt(0);
items.add(lorem);
});
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
#override
Widget build(BuildContext context) {
Iterable<Widget> content = ListTile.divideTiles(
context: context, tiles: items).toList();
return new Column(
children: content,
);
}
}
class textClass extends StatefulWidget {
textClass(this.word);
final String word;
#override
State<StatefulWidget> createState() =>
new _textClass();
}
class _textClass extends State<textClass>
with TickerProviderStateMixin {
_textClass();
String word;
Timer timer;
#override
void didUpdateWidget(textClass oldWidget) {
if (oldWidget.word != widget.word) {
word = widget.word;
}
super.didUpdateWidget(oldWidget);
}
#override
void initState() {
super.initState();
word = widget.word;
timer = new Timer.periodic(new Duration(seconds: 2), (Timer timer) {
setState(() {
word += "t";
});
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
#override
Widget build(BuildContext context) {
return new Text(word);
}
}