How to preserve state of Component of angular dart using RouterHook? - dart

I am trying to preserve the state of a component upon changing route in Angular Dart. I stumbled upon the RouterHook abstract class which has an abstract function called canReuse.
Does implementing the RouterHook class preserve the state of the component and scroll position upon change of route?
I want to call an API once the component is added to fetch data. Yet, if a navigation occurs forward and back to that component, the app shouldn't call the API again (state preserved). It would be good to have a discussion about the life cycle of AngularDart apps.
#Component(
selector: 'blog-listing',
templateUrl: 'blog_listing_component.html',
styleUrls: [
'package:angular_components/css/mdc_web/card/mdc-card.scss.css',
'blog_listing_component.css',
],
providers: [
ClassProvider(BlogService),
],
directives: [
BlogDetailsComponent,
coreDirectives,
routerDirectives,
FixedMaterialTabStripComponent,
MaterialButtonComponent,
MaterialIconComponent,
MaterialSpinnerComponent,
],
)
class BlogListingComponent implements OnInit ,RouterHook{
List<String> categories = ["Category 1","Category 2", "Category 3"];
String currentCategory;
int currentTabIndex;
int skip;
int limit;
int blogCount;
List<Blog> currentBlogsPerCategory;
Blog currentBlog;
final Router _router;
final BlogService _blogService;
BlogListingComponent(this._blogService, this._router);
BaseState getBlogsState;
bool get isLoading => getBlogsState is LoadingState;
bool get isError => getBlogsState is ErrorState;
#override
void ngOnInit() async {
currentCategory = categories[0];
_blogService.getBlogsPerCategory(currentCategory);
BlogService.blogsBloc.listen((state) {
this.getBlogsState = state;
if (state is GotBlogsByCategoryState) {
currentBlogsPerCategory = state.blogs;
currentBlogsPerCategory = state.blogs;
}else if (state is GotMoreBlogsByCategoryState) {
currentBlogsPerCategory.clear();
currentBlogsPerCategory.addAll(state.blogs);
}
});
}
#override
void ngOnDestroy() async {
_blogService.dispose();
}
void onBeforeTabChange(TabChangeEvent event) {
skip = 0;
limit = 9;
currentBlogsPerCategory.clear();
currentTabIndex = event.newIndex;
currentCategory = categories[event.newIndex];
currentBlogsPerCategory = null;
_blogService.getBlogsByCategory(categories[event.newIndex], skip, limit);
}
void onNextPagePressed(int page) {
skip = (page-1) * 9;
limit = skip + 9;
_blogService.getMoreBlogsByCategory(currentCategory, skip, limit);
}
void onBlogDetailsPressed(Blog blog) {
BlogService.currentBlog = blog;
goToBlogDetails();
}
Future<NavigationResult> goToBlogDetails(){
_router.navigate(RoutePaths.blogDetails.toUrl());
}
#override
Future<bool> canActivate(Object componentInstance, RouterState oldState, RouterState newState) {
// TODO: implement canActivate
throw UnimplementedError();
}
#override
Future<bool> canDeactivate(Object componentInstance, RouterState oldState, RouterState newState) {
// TODO: implement canDeactivate
throw UnimplementedError();
}
#override
Future<bool> canNavigate() {
// TODO: implement canNavigate
throw UnimplementedError();
}
#override
Future<bool> canReuse(Object componentInstance, RouterState oldState, RouterState newState) async{
return true;
}
#override
Future<NavigationParams> navigationParams(String path, NavigationParams params) {
// TODO: implement navigationParams
throw UnimplementedError();
}
#override
Future<String> navigationPath(String path, NavigationParams params) {
// TODO: implement navigationPath
throw UnimplementedError();
}
}

RouterHook should no be implemented by the component route and should be injected to be found by the Router.
class MyHook implements RouterHook {}
#GenerateInjector([
routerProviders,
Provider(RouterHook, useClass: MyHook)
])
const providers = ng.providers$Injector;
runApp(MyAppComponent, providers);
However, for your use case, it's simpler to use the CanReuse mixin.
class BlogListingComponent with CanReuse implements OnInit {}
or
class BlogListingComponent implements OnInit, CanReuse {
#override
Future<bool> canReuse(RouterState oldState, RouterState newState) async {
return true;
}
}

Related

'Future<bool> Function()' can't be assigned to the parameter type 'Future<bool>?'

I'm trying to implement an event callback directly in the constructor, but for some reason it does not compile and I do not understand what's the issue with my code.
abstract class Base {
final Future<bool>? onMagic;
Base({
this.onMagic
});
Future<void> doSomething() async {
if(onMagic != null) {
// does not work... why?
// final useMagic = await onMagic!();
onMagic?.then((magic) {
if(magic) print("TODO make something magical");
});
}
}
}
class RealMagic extends Base {
RealMagic() : super(
// Error: The argument type 'Future<bool> Function()' can't be assigned to the parameter type 'Future<bool>?'.
onMagic: () async => await _magic();
);
Future<bool> _magic() async {
return true;
}
}
I inlined the error above. If that's not possible which alternatives do I have to handle the optional callback?
The problem is with the type of the onMagic. It's not a Future<bool>, it should be a Future<bool> Function()?
abstract class Base {
final Future<bool> Function()? onMagic;
Base({this.onMagic});
Future<void> doSomething() async {
onMagic?.call().then((magic) {
if (magic) print("TODO make something magical");
});
}
}
class RealMagic extends Base {
RealMagic()
: super(
onMagic: () async => await _magic(),
);
static Future<bool> _magic() async { // Made this static so it can be accessed in the constructor
return true;
}
}

How to break the loop for a stream in dart?

I known the listen can be abort by StreamSubscription. But for some reason, I can not call listen for the File.openRead(). How can I abort the read operation stream?
import 'dart:io';
import 'dart:async';
class Reader {
Stream<int> progess(File file) async* {
var sum = 0;
var fs = file.openRead();
await for (var d in fs) {
// consume d
sum += d.length;
yield sum;
}
}
void cancel() {
// How to abort the above loop without using StreamSubscription returned by listen().
}
}
void main() async {
var reader = Reader();
var file = File.new("a.txt");
reader.progess(file).listen((p) => print("$p"));
// How to cancel it without
Future.delayed(Duration(seconds: 1), () { reader.cancel()});
}
You cannot cancel the stream subscription without calling cancel on the stream subscription.
You might be able to interrupt the stream producer in some other way, using a "side channel" to ask it to stop producing values. That's not a stream cancel, more like a premature stream close.
Example:
class Reader {
bool _cancelled = false;
Stream<int> progess(File file) async* {
var sum = 0;
var fs = file.openRead();
await for (var d in fs) {
// consume d
sum += d.length;
if (_cancelled) return; // <---
yield sum;
}
}
void cancel() {
_cancelled = true;
}
}
Another option is to create a general stream wrapper which can interrupt the stream. Maybe something like
import"dart:async";
class CancelableStream<T> extends Stream<T> {
final Stream<T> _source;
final Set<_CancelableStreamSubscription<T>> _subscriptions = {};
CancelableStream(Stream<T> source) : _source = source;
#override
StreamSubscription<T> listen(
onData, {onError, onDone, cancelOnError}) {
var sub = _source.listen(onData,
onError: onError, onDone: onDone, cancelOnError: cancelOnError);
var canSub = _CancelableStreamSubscription<T>(sub, this, cancelOnError ?? false);
_subscriptions.add(canSub);
return canSub;
}
void cancelAll() {
while (_subscriptions.isNotEmpty) {
_subscriptions.first.cancel();
}
}
}
class _CancelableStreamSubscription<T> implements StreamSubscription<T> {
final bool _cancelOnError;
final StreamSubscription<T> _source;
final CancelableStream<T> _owner;
_CancelableStreamSubscription(
this._source, this._owner, this._cancelOnError);
#override
Future<void> cancel() {
_owner._subscriptions.remove(this);
return _source.cancel();
}
#override
void onData(f) => _source.onData(f);
#override
void onError(f) {
if (!_cancelOnError) {
_source.onError(f);
} else {
_source.onError((Object e, StackTrace s) {
_owner._subscriptions.remove(this);
if (f is void Function(Object, StackTrace)) {
f(e, s);
} else {
f?.call(e);
}
});
}
}
#override
bool get isPaused => _source.isPaused;
#override
void onDone(f) => _source.onDone(() {
_owner._subscriptions.remove(this);
f?.call();
});
#override
void pause([resumeFuture]) => _source.pause(resumeFuture);
#override
void resume() => _source.resume;
#override
Future<E> asFuture<E>([E? value]) => _source.asFuture(value);
}
You can then use it like:
void main() async {
Stream<int> foo() async* {
yield 1;
yield 2;
yield 3;
yield 4;
}
var s = CancelableStream<int>(foo());
await for (var x in s) {
print(x);
if (x == 2) s.cancelAll();
}
}

How to properly clean ownerWindow listener in popover ? /JavaFX

I've created a JavaFX application with JDK8, that contains a window and multiples objects.
I'm trying now to put useless used object available for GarbageCollector.(Testing with JVisualVM).
But I'm stuck with one problem: Clean a Popover that contains handler and listener on window element.
Original code of the popover:
public class CustomPopOver extends PopOver {
/**
* Constructor with the Content of the PopOver.
* #param content the Node.
*/
public CustomPopOver (Node content) {
super(content);
addHandler();
}
/**
* Empty Constructor.
*/
public CustomPopOver () {
super();
addHandler();
}
private void addHandler() {
this.ownerWindowProperty().addListener((observable, oldValue, newValue) -> {
if (newValue != null) {
EventHandler<WindowEvent> preExistingHandler = newValue.getOnCloseRequest();
newValue.setOnCloseRequest(event -> {
if (this.isShowing()) {
this.hide(Duration.millis(0));
}
if (preExistingHandler != null) {
preExistingHandler.handle(event);
}
});
}
});
}
}
I tried lots of thing to go to this, but it's not working properly:
public class CustomPopOver extends PopOver implements DisposableBean {
private MyListener listener = new MyListener();
public CustomPopOver (Node content) {
super(content);
addHandler();
}
/**
* Empty Constructor.
*/
public CustomPopOver () {
super();
addHandler();
}
private void addHandler() {
this.ownerWindowProperty().addListener(listener);
}
#Override
public void destroy() {
if (this.getOwnerWindow() != null){
this.getOwnerWindow()
.removeEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, listener.windowCloseEventHandler);
this.getOwnerWindow()
.removeEventHandler(WindowEvent.WINDOW_HIDING, listener.windowHidingEventHandler);
}
this.ownerWindowProperty().removeListener(listener);
listener = null;
}
/**
* ChangeListener that removes itself when needed.
*/
private class MyListener implements ChangeListener<Window> {
EventHandler<WindowEvent> windowCloseEventHandler;
EventHandler<WindowEvent> windowHidingEventHandler;
#Override
public void changed(ObservableValue<? extends Window> observable, Window oldValue, Window newValue) {
if (oldValue != null) {
oldValue.removeEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, windowCloseEventHandler);
oldValue.removeEventHandler(WindowEvent.WINDOW_HIDING, windowHidingEventHandler);
}
if (newValue != null) {
EventHandler<WindowEvent> preExistingHandler = newValue.getOnCloseRequest();
windowCloseEventHandler = new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
if (isShowing()) {
hide(Duration.millis(0));
ownerWindowProperty().removeListener(MyListener.this);
}
if (preExistingHandler != null) {
preExistingHandler.handle(event);
}
newValue.removeEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, this);
}
};
newValue.setOnCloseRequest(windowCloseEventHandler);
windowHidingEventHandler = new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
ownerWindowProperty().removeListener(MyListener.this);
newValue.removeEventHandler(WindowEvent.WINDOW_HIDING, this);
}
};
newValue.setOnHiding(windowHidingEventHandler);
}
}
}
}
and we call the destroy method to clean the popover from jvm cache.
Code to test class CustomPopOver:
public class PopOverViewer extends Application {
private BorderPane pane;
public PopOverViewer() {
pane = new BorderPane();
pane.setCenter(button());
}
private Node button() {
HBox hBox = new HBox();
List<CustomPopOver > lists = new ArrayList<>();
Button show = new Button("click");
show.setOnAction(event -> {
CustomPopOver popOver = new CustomPopOver ();
lists.add(popOver);
popOver.show(show);
});
Button clean = new Button("clean");
clean.setOnAction(event -> {
lists.forEach(CustomPopOver::destroy);
lists.clear();
});
hBox.getChildren().addAll(show, clean);
return hBox;
}
#Override
public void start(Stage primaryStage) {
PopOverViewer app = new PopOverViewer();
primaryStage.setScene(new Scene(app.getPane()));
primaryStage.show();
}
private Parent getPane() {
return pane;
}
}
I would like that the class CustomPopover is clean from GC.
Thanks #fabian, placing WeakEventHandler on Handler inside of a strong referenced Listener, helped to clean it.
Code that worked:
public class CustomPopOver extends PopOver implements DisposableBean {
private MyListener listener = new MyListener();
/**
* Constructor with the Content of the PopOver.
* #param content the Node.
*/
public CustomPopOver(Node content) {
super(content);
addHandler();
}
/**
* Empty Constructor.
*/
public CustomPopOver() {
super();
addHandler();
}
private void addHandler() {
this.ownerWindowProperty().addListener(listener);
}
#Override
public void destroy() {
this.ownerWindowProperty().removeListener(listener);
listener = null;
}
/**
* ChangeListener that removes itself when needed.
*/
private class MyListener implements ChangeListener<Window> {
#Override
public void changed(ObservableValue<? extends Window> observable, Window oldValue, Window newValue) {
if (newValue != null) {
EventHandler<WindowEvent> preExistingHandler = newValue.getOnCloseRequest();
EventHandler<WindowEvent> windowCloseEventHandler = new WeakEventHandler<>(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
if (isShowing()) {
hide(Duration.millis(0));
ownerWindowProperty().removeListener(CustomPopOver.MyListener.this);
}
if (preExistingHandler != null) {
preExistingHandler.handle(event);
}
newValue.removeEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, this);
}
});
newValue.setOnCloseRequest(windowCloseEventHandler);
EventHandler<WindowEvent> windowHidingEventHandler = new WeakEventHandler<>(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
ownerWindowProperty().removeListener(CustomPopOver.MyListener.this);
newValue.removeEventHandler(WindowEvent.WINDOW_HIDING, this);
}
});
newValue.setOnHiding(windowHidingEventHandler);
}
}
}
}

Is there an solution for java.lang.IllegalStateException: Reply already submitted

I want to use pusher sdk in Flutter from android native code because its library no yet completely supported in flutter but when i send first message it received it successfully the next message make app crush with Reply already submitted error her on this line result.success(txt);
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "demo.gawkat.com/info";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler((methodCall, result) -> {
final Map<String, Object> arguments = methodCall.arguments();
if (methodCall.method.equals("getMessage")) {
Pusher pusher = new Pusher("faa685e4bb3003eb825c");
pusher.connect();
Channel channel = pusher.subscribe("messages");
channel.bind("new_message", (channelName, eventName, data) -> runOnUiThread(() -> {
Gson gson = new Gson();
Message message = gson.fromJson(data, Message.class);
String txt = message.text;
result.success(txt);
}));
}
});
}
}
Flutter code:
Future<String> _getMessage() async {
String value;
try {
value = await platform.invokeMethod('getMessage');
} catch (e) {
print(e);
}
return value;
}
Error is
FATAL EXCEPTION: main
Process: com.example.flutter_app, PID: 6296
java.lang.IllegalStateException: Reply already submitted
at io.flutter.view.FlutterNativeView$PlatformMessageHandlerImpl$1.reply(FlutterNativeView.java:197)
at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler$1.success(MethodChannel.java:204)
at com.example.flutter_app.MainActivity.lambda$null$0(MainActivity.java:40)
at com.example.flutter_app.-$$Lambda$MainActivity$axbDTe2B0rhavWD22s4E8-fuCaQ.run(Unknown Source:4)
at android.os.Handler.handleCallback(Handler.java:789)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767
I think it is happening after Flutter upgrade > 1.5.4.hotfix.
Anyway, Yes there is a solution (Refer this github issue),
In your Activitybelow onCreate() add this class:
private static class MethodResultWrapper implements MethodChannel.Result {
private MethodChannel.Result methodResult;
private Handler handler;
MethodResultWrapper(MethodChannel.Result result) {
methodResult = result;
handler = new Handler(Looper.getMainLooper());
}
#Override
public void success(final Object result) {
handler.post(
new Runnable() {
#Override
public void run() {
methodResult.success(result);
}
});
}
#Override
public void error(
final String errorCode, final String errorMessage, final Object errorDetails) {
handler.post(
new Runnable() {
#Override
public void run() {
methodResult.error(errorCode, errorMessage, errorDetails);
}
});
}
#Override
public void notImplemented() {
handler.post(
new Runnable() {
#Override
public void run() {
methodResult.notImplemented();
}
});
}
}
Then, instead of using MethodChannel result to setMethodCallHandler argument callback add name as rawResult and then inside that callback, add this line:
MethodChannel.Result result = new MethodResultWrapper(rawResult);
As below:
//......
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
(call, rawResult) -> {
MethodChannel.Result result = new MethodResultWrapper(rawResult);
//.....
I use flags for this problem.
Just make sure that methods of same channels are called simultaneously.
The problem seem to appear then.
If two methods needs to be called simulatenously without any problem define both methods in 2 different channels
var resultMap = Map<String, MethodChannel.Result> = HashMap()
new MethodChannel(getFlutterView(), CHANNEL_1).setMethodCallHandler((methodCall, result) -> {
final Map<String, Object> arguments = methodCall.arguments();
if (methodCall.method.equals("method1")) {
// implement method 1
}
});
new MethodChannel(getFlutterView(), CHANNEL_2).setMethodCallHandler((methodCall, result) -> {
final Map<String, Object> arguments = methodCall.arguments();
if (methodCall.method.equals("method2")) {
resultMap = resultMap + mapOf(CHANNEL_2 to MethodResultWrapper(result) // use this later to return result
// implement method2
result.success(true) // or whatever value
}
});
This reduce the chance of "Reply already submitted" error.
Incase if you are using MethodResultWrapper as #Blasanka answer use flags before result.success
when method is invoked set flag to true
val methodCheckFlag: Boolean = true
then when result need to be returned
if(methodCheckFlag) {
methodCheckFlag = false;
methodWrapperResult?.success(true) // or what ever value to return
}
or use the saved MethodResultWrapper as
if(methodCheckFlag) {
methodCheckFlag = false;
resultMap[CHANNEL_2]?.success(true) // or what ever value to return
}

Flutter: Update Widgets On Resume?

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

Resources