I am developing a note app, and I want to save my note when I closed the app (when removing the app from recent apps).
I tried dispose() method but it did not work.
I tried :
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
print(state);
final isDetached = state == AppLifecycleState.detached;}
but it did not work too. it does not print detached status, just it prints AppLifecycleState.paused AppLifecycleState.inactive AppLifecycleState.resume
so what should I do here!!
Your code should look something like this if you're observing the app lifecycle state:
class AppLifecycleReactor extends StatefulWidget {
const AppLifecycleReactor({ Key? key }) : super(key: key);
#override
State<AppLifecycleReactor> 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();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
print(state);
}
#override
Widget build(BuildContext context) {
return Widgets;
}
}
to summarise:
with WidgetsBindingObserver after extends State
WidgetsBinding.instance!.addObserver(this); in initstate
WidgetsBinding.instance!.removeObserver(this); in dispose
For more info check: https://api.flutter.dev/flutter/widgets/WidgetsBindingObserver-class.html
dispose() function is only visible in StatefulWidget
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final AppRouter _appRouter = AppRouter();
#override
Widget build(BuildContext context) {
return BlocProvider<CounterCubit>(
create: (context) => CounterCubit(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
onGenerateRoute: _appRouter.onGenerateRoute,
),
);
}
#override
void dispose() {
super.dispose();
// dispose your code here
}
}
Please consider using Firebase and save for example after every edit, or after 500ms after an edit using rx.debounce from rxdart.
You cannot rely on the app really being able to execute dispose or even save something to a database when it is closed. Removing an app from the recent list ends the app very quickly (kills the app), that's why it works even for hung/crashed apps.
You can always use shared preferences in that case. Share preferences are used to save data in your local storage and the implementation is also very simple. and don't worry it won't make your app laggy or slow. It works very smoothly
https://pub.dev/packages/shared_preferences
do upvote if helpful
Related
I built an app that tracks the location of the user even if the app is closed. But I want it to be destroyed, when the user kills the app via application exit in Android. Currently I still get the notification that the app is used in the background even if I closed it via task manager of my real device.
This is the message popping up after killing the app in the taskmanager, which tells that the app is still running in the background.
I am using WillStartForegroundTask from the geofence_service package.
Here is my code:
class CityDetailsScreen extends StatefulWidget {
CityDetailsScreen();
#override
_CityDetailsScreenState createState() => _CityDetailsScreenState();
}
class _CityDetailsScreenState extends State<CityDetailsScreen> {
bool permissionGranted = false;
final geofenceService = GeofenceService.instance.setup(
interval: 5000,
accuracy: 100,
loiteringDelayMs: 60000,
statusChangeDelayMs: 10000,
useActivityRecognition: false,
allowMockLocations: true,
geofenceRadiusSortType: GeofenceRadiusSortType.DESC);
#override
Widget build(BuildContext context) {
return WillStartForegroundTask(
onWillStart: () {
// You can add a foreground task start condition.
return geofenceService.isRunningService;
},
androidNotificationOptions: AndroidNotificationOptions(
channelId: 'geofence_service_notification_channel',
channelName: 'Geofence Service Notification',
channelDescription:
'This notification appears when the geofence service is running in the
background.',
channelImportance: NotificationChannelImportance.HIGH,
priority: NotificationPriority.HIGH),
iosNotificationOptions: IOSNotificationOptions(),
notificationTitle: 'Aschaffenburger Geheimnisse läuft im Hintergrund',
notificationText: 'Klicke, um in die App zurückzukehren!',
child: AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light,
child: Scaffold(
body: Container()
)
)
);
}
#override
void dispose() {
print("DISPOSE GeofenceService");
GeofenceService.instance.stop();
super.dispose();
}
}
The service should be completely destroyed when I detach the app!
Any ideas?
Please review app lifecycle management via WidgetsBindingObserver. It has limits, but using the paused or inactive events should solve your problem.
This WidgetsBindingObserver issue is a good introduction into the possibilities and problems of it, especially that you cannot rely on the detached event to be fired. It would otherwise probably be your first choice instead of paused or inactive.
Here is example code to try the WidgetsBindingObserver functionality:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() {
return _MyHomePageState();
}
}
class _MyHomePageState extends State<MyHomePage> 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) {
print(state);
if (state in [AppLifecycleState.paused, AppLifecycleState.inactive]) {
// GeofenceService.instance.stop();
}
if (state == AppLifecycleState.resumed) {
// GeofenceService.instance.setup(...);
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
//do something here before pop
return true;
},
child: Scaffold(
body: Container(),
),
);
}
}
My goal is to track routes being presented and dismissed for analytics purposes like described on this article: https://medium.com/flutter-community/how-to-track-screen-transitions-in-flutter-with-routeobserver-733984a90dea
I am pushing a CupertinoPageRoute and passing a Widget to it that implements the RouteAware Mixin.
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
return CupertinoApp(
...
navigatorObservers: [routeObserver],
}
Pushing like so:
Navigator.of(context).push(
CupertinoPageRoute(builder: (context) => MyWidget()),
);
The Widget
class MyWidget extends StatefulWidget {
MyWidget({Key key}) : super(key: key);
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> with RouteAware {
#override
void didChangeDependencies() {
super.didChangeDependencies();
routeObserver.subscribe(this, ModalRoute.of(context));
}
#override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}
#override
void didPop() {
print('never called');
}
#override
void didPopNext() {
print('never called');
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
CupertinoNavigationBar(
middle: Text('42'),
),
],
),
);
}
}
The didPop() method is never called when popping a CupertinoPageRoute with the back button unless I push with rootNavigator = true which is not what I want.
How can I fix this?
EDIT: This behavior only happens when pushing routes from a CupertinoTabView, as it creates a new nested navigator.
Github Issue
If I understand correctly, the final solution shown in the Medium article does not extend every StatefulWidget with RouteAware. Instead, it extents its own implementation of a RouteObserver to get notified about every route change.
Using this approach, you should be able to insert your custom RouteObserver into both you main App as well as your CupertinoTabView to achieve what you need.
I've:
class _PageState extends State<Page> 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) {
super.didChangeAppLifecycleState(state);
print('state = $state');
}
#override
Widget build(BuildContext context) => Scaffold();
}
AppLifeCycleState class has got 4 callbacks, 3 of them
- active
- paused
- resumed
Seems to work but detached never worked in any case.
I read the documentation but couldn't understand it in practical scenario, can anyone share a relevant code, when and where does it get called?
As the doc says
detached → const AppLifecycleState The application is still hosted on
a flutter engine but is detached from any host views.
When the application is in this state, the engine is running without a
view. It can either be in the progress of attaching a view when engine
was first initializes, or after the view being destroyed due to a
Navigator pop.
You can reproduce above issue on HomeScreen only when your home widgets go in the background(Press back button of android device)
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
didChangeAppLifecycleState(AppLifecycleState state) {
if (AppLifecycleState.paused == state) {}
print("Status :" + state.toString());
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Book'),
),
body: Center(
child: Text('Home Screen'),
),
);
}
}
You can produce above mention thing on other screens and have a call at detached, you can do by closing application programmatically on any click event of your widget
Android:
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
iOS:
exit(0)
It's a bug. I've tests on both iOS and Android devices. You can produce detached state when you closed the app by pressing the back button or swiping on Android devices, it's not woking on iOS devices. Please follow this issue on this
When I read the doc in flutter, I have a question that should flutter root widget always be StatelessWidget?
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Code Sample for Navigator',
// MaterialApp contains our top-level Navigator
initialRoute: '/',
routes: {
'/': (BuildContext context) => HomePage(),
'/signup': (BuildContext context) => SignUpPage(),
},
);
}
}
Because I think there's sometime need init function to call, and maybe not want the code of that write in HomePage. For example: check token expire or not, and decide go to HomePage or LoginPage.
Then the best option: should I change the root Widget to StatefulWidget, and just include the logic above in its initState function ?
Making root widget a StatefulWidget is useful when listen AppLifecycleState
such as do resume job like resume WebSocket connection
code snippet
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
AppLifecycleState _lastLifecycleState;
#override
void initState() {
super.initState();
initPlatformState();
WidgetsBinding.instance.addObserver(this);
}
initPlatformState() async {
Screen.keepOn(true);
}
Future<void> resumeCallBack() {
if (sl<WebSocketService>().webSocketState == 'lost') {
sl<WebSocketService>().initWebSocket();
}
if (mounted) {
setState(() {});
}
print("resumeCallBack");
}
Future<void> suspendingCallBack() {
if (mounted) {
setState(() {});
}
print("suspendingCallBack");
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) async {
super.didChangeAppLifecycleState(state);
print("AppLifecycleState Current state = $state");
setState(() {
_lastLifecycleState = state;
});
switch (state) {
case AppLifecycleState.inactive:
case AppLifecycleState.paused:
case AppLifecycleState.detached:
/*case AppLifecycleState.suspending:
await suspendingCallBack();
break;*/
case AppLifecycleState.resumed:
await resumeCallBack();
break;
}
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Template',
debugShowCheckedModeBanner: false,
theme: new ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/',
routes: {
'/': (context) => LoginPage(),
Yes, you can use StatefulWidget as your root parent. But, you should only use it when it makes sense.
Like, If you are initialising and observing some animations, firebase messaging, services, applifecycle states etc. which might require sometimes.
Otherwise Stateless widget are better to use.
It can be StatefulWidget or StatelessWidget. But placing a StatefulWidget in the root will impact your app performance because a simple state change in the StatefulWidget will cause the entire widget tree to rebuild and reduce your app performance.
always try to place StatefulWidget or InheritedWidget deep inside the widget tree.
better solution for your scenario is to use Providers or InheritedWidgets and listen to the token changes rather than changing the root widget to Stateful
I'm developing an app with flutter.
All goes fine, but I need something.
I need check if user's token has expired when app comes to foreground.
I get the app state with
didChangeAppLifecycleState(AppLifecycleState state).
An example of my code is this (I can't write the real code because is in my job's computer, and now I'm in home):
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
#override
void initState() {
WidgetsBinding.instance.addObserver(this);
super.initState();
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
void didChangeAppLifecycleState(AppLifecycleState state) {
var isLogged = boolFunctionToCheckLogin();
if (state == AppLifecycleState.resume && isLogged) {
// THIS IS THE PLACE WHEN I TRY TO CODE THE MAGIC
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Tutorial Lifecycle'),
),
body: Center(),
);
}
But, inside of didChangeAppLifecycleState function:
I have tried to make a showDialog with a Navigator.push to login
I have tried to make directly a Navigator.push
I tried to restart the app
All of these have the same problem: context is null or similar.
If I do any of these options directly with button or whatever, works.
But when app comes to foreground, the system says me that context is null or Material
Anybody can help to know how to do it??
Thank you all!!
I've solve it.
Into the conditional mark with "magic", I've call "showDialog" inside of setState:
void didChangeAppLifecycleState(AppLifecycleState state) {
var isLogged = boolFunctionToCheckLogin();
if (state == AppLifecycleState.resume && isLogged) {
setState(() {
showDialog(
context: context,
builder: (BuildContext context) {
... blablabla ...
}
});
}
}