Flutter : cancel Timer if AppLifecycleState == resumed - flutter

i want try implementation AppLifecycleState using flutter.
I have Model like this :
(string) id
(string) name
(bool) fingerprintStatus = false
(bool) tokenExpiry = false
I want detect If the user exit From the App , If the user exit more than X Second i want change status tokenExpiry == true . So for handle my case i detect user Activity Using AppLifecycleState and Timer
But the problem is i don't know logic to do that. In my mind If user exit/close app , running timer for X second then update tokenExpiry . If user comeback again but the Timer not finished, Cancel and Reset Timer then do nothing.
How can i do this ?
My Expected
AppLifecycleState _appLifecycleState;
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
print(state.toString());
if(_appLifecycleState == AppLifecycleState.inactive || _appLifecycleState ==
AppLifecycleState.paused){
=> Running The Timer For 10 Second
}
else{
=> Cancel The Timer and Do Nothing
}
super.didChangeAppLifecycleState(state);
}

I know this is coming late but the best approach will be to save a Timestamp into SharedPreferences and then when the app AppLifecycleState resumes you retrieve that Timestamp and compare it with the current time AppLifecycleState Resumes.

Related

What should build() return after it realizes a need for a state change?

I think I'm missing something about the Riverpod philosophy.
I'm keeping all my game state in a ChangeNotifier, including the .stage which can be "start", "playing", "winning", etc:
final ChangeNotifierProvider<GameState> gameStateProvider =
ChangeNotifierProvider<GameState>((ref) => GameState(stage="start"));
My build() functions start by accessing this state:
build(context, ref) {
final GameState gameState = ref.watch(gameStateProvider);
And build themselves depending upon the state of gameState.
But somewhere I have to detect that the game has progressed to the point that I should update state, e.g. gameState.stage = 'winning'. I tried this:
build(context, ref) {
final GameState gameState = ref.watch(gameStateProvider);
if (gameState.stage == "playing" && gameState.someConditionsAreMet) {
gameState.stage = "winning"; // .stage setter triggers notifyListeners()
}
But I am not allowed to call notifyListeners in build() because it tries to mark the widget _needsBuild during the build phase.
So I wrap the call in a Timer.run() to make it asynchronous, and the widget happily rebuilds during the next frame:
build(context, ref) {
final GameState gameState = ref.watch(gameStateProvider);
if (gameState.stage == "playing" && gameState.someConditionsAreMet) {
Timer.run(() => gameState.stage = "winning"); // OK: will flag dirty only after build
// now what?!
...but now I don't know what to return from build() when that if gets triggered:
I can't return what I would normally return when gameState.stage == "winning", because it still thinks the stage is "playing" (the Timer.run() callback hasn't executed yet.)
I can't return what I would normally return when gameState.stage == "playing", because the conditions that made me want to trigger the change of stage make that impossible (e.g. I print the strongest enemy's HP, but there are now zero enemies.)
I can't return SizedBox.shrink() as a hack because that would cause a single frame to render blank, causing a flash on the screen before the .stage = "winning" widget can render.
I considered just doing the update to gameState.stage inside gameState itself, as soon as the right conditions were triggered. But then stateless widgets don't have a way of detecting that state has just changed to do one-time events, like pushing a welcome modal on top of the main screen the first time .stage changes from "start" to "playing".
So I'm clearly doing something wrong architecturally. How do experienced Riverpod users do it?
You can not Call Notify Listener In the Build Method, You can Only Listen or Watch for State Change int the Builder, Notify Listener Should only be called From the Change Notifier Class as this is the Purpose of that class

How to detect when app is minized in flutter

Is there a way to detect when the app has been minimized? Simply using WidgetsBindingObserver with the paused event doesn't work as it's indistinguishable from when the user turns off the screen / phone locks. Note, I need this to work for both android and ios.
A bit of context of what I'm doing. In the application, I'm running a timer. I want to stop this timer if the user minimizes the app (e.g. uses its phone for something else). If the user, however, turns off the screen/locks it, I want the timer to continue.
I suggest to take a look at this package: is_lock_screen
As the description suggest
Useful for determining whether app entered background due to locking screen or leaving app.
I would try with this:
#override
void didChangeAppLifecycleState(AppLifecycleState state) async {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.inactive) {
final isLock = await isLockScreen();
if(!isLock){
print('app inactive MINIMIZED!');
}
print('app inactive in lock screen!');
} else if (state == AppLifecycleState.resumed) {
print('app resumed');
}
}

What is best way to call a function if there no response from showDialog after x seconds in Futter

I'm kinda new to flutter. I searched but couldn't find a suitable answer to this... What would be the best way to call a function if the user doesn't respond to a showDialog alert after x seconds? If the user presses a button, then I don't want the function to execute.
You can start a Timer for x seconds as soon as you display your dialog & then execute your function. If the user clicks your button, you can stop your timer.
Timer _timer;
bool userResponded = false;
You will need a StatefulWidget & wherever you show your dialog, you need to start the timer.
showDialog(...); // Your showDialog method
// You have to update userResponded to true if user clicks on your dialog or whatever
// It should look something like this: setState(() => userResponded = true);
_timer = Timer(const Duration(seconds: 10), () { // Start your timer for x seconds
if (!userResponded) { // If user didn't respond
// execute your function
}
});
Also, you need to override the onDispose method & stop the timer:
#override
void onDispose() {
_timer?.cancel();
super.onDispose();
}
For this, you would use a Timer widget.
Timer example:
Timer _timer = Timer(
const Duration(milliseconds: 500),
() {
// Call some function after delay of 500ms
},
);
To cancel the timer, use _timer.cancel();
So, most likely in the initState method you'll want to set timer object, then when the user presses the button, you can cancel this timer which means the callback in it won't run after a delay you've specified.

Flutter : Strange behavior of Shared Preferences

I have a problem with inconsistent shared preferences value. I will try to describe it as simple as possible.
I'm using Firebase Cloud Messaging for push notifications. When app is in background and notification came in, background handler bellow is invoked.
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final int counter = (prefs.getInt('badge') ?? 0) + 1;
prefs.setInt('badge', counter).then((bool success) {
print(counter);
});
}
My widget uses WidgetsBindingObserver to determine lifecycle state. When I enter the app, state of that widget is onResume and there I want to read that badge value from shared preferences like this.
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final int counter = (prefs.getInt('badge') ?? 0);
print(counter);
}
}
Scenario 1:
App opened, notification came in - set badge field to 1.
App in background, notification came in - background handler set badge field to 2.
App resumed, read that badge field, it's still 1.
Scenario 2:
App opened, notification came in - set badge field to 1.
App in background, notification came in - background handler set badge field to 2.
App in background, notification came in - background handler set badge field to 3.
App resumed, read that badge field, it's still 1.
Question: Any idea why field isn't updated?
SharedPreferences can be used on background events handlers. The problem is that the background handler run in a different isolate so, when you try to get a data, the shared preferences instance is empty. To avoid this you simply have to force a refresh:
SharedPreferences prefs= await SharedPreferences.getInstance();
await prefs.reload();
final int counter = (prefs.getInt('badge') ?? 0);
In the same mode, if the shared preferences can be modified in a background hadler, be sure you call this "reload" function in the main isolate when you try to read from theirs.
SharedPreferences or any other local storage won't work in the _firebaseMessagingBackgroundHandler.
You should capture it on getInitialMessage or onMessageOpenedApp.
https://firebase.flutter.dev/docs/messaging/notifications/
TL;DR:
getInitialMessage gets triggered when the application is opened from a terminated state. While onMessageOpenedApp gets triggered when the application is opened from background state.
FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage message) {
if (message != null) {
Navigator.of(context).pushNamed('/messages', arguments: message.data);
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
if (message != null) {
Navigator.of(context).pushNamed('/messages', arguments: message.data);
}
});

how to close all notifications when app is closed flutter?

Does anyone have idea? Whenever I close my app, the notification also will automatically close. I try to using "AppLifecycleState.detached" but still doesn't work. It's didn't call out the function of "notifications.cancelAll()". Does anyone got idea to fix this other than using AppLifecycleState? Below are my code:
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
print("State: "+state.toString());
if(state == AppLifecycleState.detached){
print("detached");
notifications.cancelAll();
}
}