Flutter use WidgetsBindingObserver only when on top of the stack - flutter

I have a StatefulWidget and at the beginning of the State method's build I do
WidgetsBinding.instance
.addObserver(LifecycleEventHandler(resumeCallBack: () async {
if (mounted) {
refreshState();
}
}));
This is the code of the LifecycleEventHandler
class LifecycleEventHandler extends WidgetsBindingObserver {
final AsyncCallback resumeCallBack;
LifecycleEventHandler({
this.resumeCallBack,
});
#override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
switch (state) {
case AppLifecycleState.resumed:
if (resumeCallBack != null) {
await resumeCallBack();
}
break;
case AppLifecycleState.inactive:
break;
case AppLifecycleState.paused:
break;
case AppLifecycleState.detached:
break;
}
}
}
the resumeCallBack is being called every time the app is resumed, even if the page is not visible to the user (if I navigate to another page without removing this from the stack).
Is there a way to run this callback only if the page is visible to the user? I don't want to dispose the state/page but just avoid unnecessary state updates

Related

Call local_auth in flutter resume state will request authentication continuously

I'm using the local_auth package to lock app services after login if the user closes the app
what I want is to call the authentication function in the resume state of AppLifecycleState (when the user opens the app again)
but this prompt authentication infinitely
class _AppState extends State<App> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) async {
switch (state) {
case AppLifecycleState.inactive:
break;
case AppLifecycleState.paused:
break;
case AppLifecycleState.resumed:
{
await _auth.authenticate(
useErrorDialogs: true,
stickyAuth: true,
);
}
break;
case AppLifecycleState.detached:
break;
}
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
This will call authentication forever
how to solve this?

Multiple WidgetsBindingObserver widgets performances

I would simply like to know if there is any performances issue by using in multiple widgets the WidgetsBindingObserver and more specifically, the call to :
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.resumed:
break;
case AppLifecycleState.inactive:
break;
case AppLifecycleState.paused:
break;
case AppLifecycleState.detached:
break;
}
}
Is it the same listener for the whole or is it memory consuming for multiple listeners added to multiple places ?

Flutter interstitial ad with AppOpen ads

Currently Im using native_admob_flutter for my flutter app. I also included AppOpenAds and Interstital ad within the app.
I want to display the AppOpen ad when user open the app/ resume to the app, here is how I do it in main.dart:
#override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
switch (state) {
case AppLifecycleState.resumed:
loadAndDisplayAppOpenAd();
break;
case AppLifecycleState.inactive:
break;
case AppLifecycleState.paused:
break;
case AppLifecycleState.detached:
break;
}
}
However I found that displaying Interstial ad will also change the app state -> inactive -> paused -> resume, which trigger AppOpen ad.
This really create bad user experinence. Is there a way to overcome this? Thank you!
You may need a variable to make sure that app has been paused by user action, and not by interstitial.
static bool pausedByInterstitial = false;
...
class AppLifecycleReactor extends WidgetsBindingObserver {
final AppOpenAdManager appOpenAdManager;
AppLifecycleReactor({required this.appOpenAdManager});
static bool pausedByInterstitial = false;
#override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
if (state == AppLifecycleState.resumed && !pausedByInterstitial) {
appOpenAdManager.showAdIfAvailable();
}
}
}
...
showInter() {
AppLifecycleReactor.pausedByInterstitial = true;
InterstitialListener(
...
onAdHiddenCallback: (ad) {
AppLifecycleReactor.pausedByInterstitial = false;
},
...
);
}

I need some functions call when app reopen

hai I need to call some function when app will reopen from sleep
after user can put app in minimize then when the reopen the app that function have to call in flutter
You need to listen to lifecycle events like this
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(() {
// here the app has resumed
}))
);
}
...
}

Is there a way to repaint the screen or navigate to a different screen on AppLifecycleState.paused in Flutter

I have a Flutter app that contains sensitive medial information and business wants this information to be hidden when the app is put into the background, including the Recent Apps screen.
I have added WidgetsBindingObserver and am listening to the events correctly. Resumed state correctly fires and sends user back to login page; however, the paused event doesn't do anything on receipt of the state. For reference I have tried pushing a new screen onto the stack as well as popping all screens until reaching the login but neither works.
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.paused:
Navigator.of(context).push(new PageRouteBuilder(
pageBuilder: (_, __, ___) => new Splash(
inBackground: true,
),
));
break;
case AppLifecycleState.resumed:
Navigator.of(context).pushNamed('/login');
break;
default:
break;
}
}
I expect that when the paused event is received to be able to change the screen to protect this sensitive info. Any ideas are welcome!
EDIT: Most recent code.
import 'package:boxview_mobile_flutter/screens/splash/index.dart';
import 'package:boxview_mobile_flutter/services/shared_prefs.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
class PushNotifications extends StatefulWidget {
#override
_PushNotificationsState createState() => _PushNotificationsState();
}
class _PushNotificationsState extends State<PushNotifications> with WidgetsBindingObserver {
bool loggedOut = false;
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
#override
Widget build(BuildContext context) {
return Container(child: Splash(loggedOut: this.loggedOut));
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.paused:
print("Paused");
break;
case AppLifecycleState.inactive:
print("inactive");
setState(() {
loggedOut = true;
});
break;
case AppLifecycleState.suspending:
print("suspending");
break;
case AppLifecycleState.resumed:
setState(() {
loggedOut = false;
});
print("resumed");
break;
}
}
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_firebaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
print("onMessage: $message");
},
onLaunch: (Map<String, dynamic> message) async {
print("onLaunch: $message");
},
onResume: (Map<String, dynamic> message) async {
print("onResume: $message");
},
);
_firebaseMessaging.requestNotificationPermissions(const IosNotificationSettings(sound: true, badge: true, alert: true));
_firebaseMessaging.onIosSettingsRegistered.listen((IosNotificationSettings settings) {
print("Settings registered: $settings");
});
_firebaseMessaging.getToken().then((String token) {
assert(token != null);
SharedPreferencesHelper.setFirebaseToken(token);
});
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}
Splash is just a splash page and the boolean loggedOut param just says don't forward to login page.
dont forget to add with WidgetsBindingObserver to your state class
class YourClass extends StatefulWidget { ....
class _YourClassState extends State<BottomNavigator> 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) {
switch (state) {
case AppLifecycleState.paused:
print("Paused");
setState(() {
_notification = state;
});
break;
case AppLifecycleState.inactive:
setState(() {
_notification = state;
});
print("inactive");
break;
case AppLifecycleState.suspending:
setState(() {
_notification = state;
});
print("suspending");
break;
case AppLifecycleState.resumed:
setState(() {
_notification = state;
});
print("resumed");
break;
}
}
and change your app state with _notification
for example
#override
Widget build(BuildContext context) {
return _notification == AppLifecycleState.inactive
? Scaffold(
body: Text("inactive"),
)
: YourRealWidget()
you can look more infromation for WidgetsBindingObserver from here