Flutter - Dispose background service when exiting app - flutter

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

Related

Code execution stopped at await in async function in flutter while implementing Text to Speech

I am trying the text to speech feature in a flutter, but it is not working.
I tried many code examples from the internet and googled but did not find why not working in my project.
To keep it simple I was trying in one simple file but still, it is not working.
On click of a button, the first print statement is executing but after that not get anything.
Below is the code
import 'package:flutter/material.dart';
import 'package:flutter_tts/flutter_tts.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late FlutterTts flutterTts;
#override
initState() {
super.initState();
initTts();
}
initTts() {
flutterTts = FlutterTts();
}
#override
void dispose() {
super.dispose();
flutterTts.stop();
}
Future _speak() async {
print("1");
await flutterTts.speak("Hello");
print("2");
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter TTS'),
),
body: InkWell(
onTap: _speak,
child: Text("Click"),
)
),
);
}
}
The console output is
Performing hot reload...
Syncing files to device Android SDK built for x86...
Reloaded 1 of 568 libraries in 544ms.
I/flutter (21300): 1

onDestroy or dispose in flutter

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

Can addPostFrameCallback get called after the widget is disposed?

From the docs of addPostFrameCallback, I see that
Post-frame callbacks cannot be unregistered. They are called exactly
once.
and so I wonder if addPostFrameCallback can be called after the widget is disposed since it cannot be unregistered?
I think the _postFrameCallback's callback will be called after the widget is disposed.
View the Flutter source code:
firstly, Post-frame callbacks cannot be unregistered. They are called exactly once. event registered after widget disposed, but still can't unregister.
secondly, when a new frame comes down, Flutter framework just check callback whether null, and then call the callback directly.
void handleBeginFrame(Duration? rawTimeStamp) {
final List<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.from(_postFrameCallbacks);
_postFrameCallbacks.clear();
for (final FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
}
void _invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace? callbackStack ]) {
assert(callback != null);
callback(timeStamp);
}
So, the callback will be called.
But, you can't update the UI (setState) in the callback because widgets have already been disposed.
example:
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Test(),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
bool isEnabled = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:Listener(
onPointerDown: (event) {
setState(() {
isEnabled = true;
});
},
onPointerUp: (event) {
setState(() {
isEnabled = false;
});
},
child: Container(
height: 200,
width: 300,
color: Colors.red,
child: isEnabled ? Test1() : Text("null"),
),
),
),
);
}
}
class Test1 extends StatefulWidget {
#override
_Test1State createState() => _Test1State();
}
class _Test1State extends State<Test1> {
#override
Widget build(BuildContext context) {
return Text("hahah");
}
_onFrameStart(Duration duration) {
print("in _onFrameStart");
}
#override
void dispose() {
SchedulerBinding.instance.addPostFrameCallback(_onFrameStart);
super.dispose();
}
}

When AppLifeCycleState.detached gets called?

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

flutter restart app or redirect to first widget

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