didChangeAppLifecycleState doesn't work as expected - flutter

I hope I understand how didChangeAppLifecycleState worked correctly.
I have page A and page B . When I click the back device button from page B ( Navigator.of(context).pop(); ), I expect didChangeAppLifecycleState in pageA will get called, but it doesn't.
PageA
class _ABCState extends State<ABCrList> 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) {
if (state == AppLifecycleState.resumed) {
setState(() {
print(...);
});
}else{
print(state.toString());
}
}
....
This is the initState in pageA. The function used to call backend service.
#override
void initState() {
super.initState();
_bloc.getList(context); // return list and populate to ListView
});
}

The way you're thinking it is Android's way where onResume works, but in Flutter, things don't happen this way.
Generally, this gets called when the system puts the app in the background or returns the app to the foreground.
There are mainly 4 states for it:
resumed: The application is visible and responding to user input.
inactive: The application is in an inactive state and is not receiving user input.
paused: The application is not currently visible to the user, not responding user input, and running in the background.
detached: The application is still hosted on a flutter engine but is detached from any host views.
Edit:
When you're navigating to PageB from PageA, use something like:
Navigator.pushNamed(context, "/pageB").then((flag) {
if (flag) {
// you're back from PageB, perform your function here
setState(() {}); // you may need to call this if you want to update UI
}
});
And from PageB, you'll can use
Navigator.pop(context, true);

Related

Flutter Trigger bloc event again

On my first screen, I fill out the form and then click the next button it added SubmitDataEvent() to the bloc. Then, the BolcListner listing and when it comes to SuccessSate it navigate to the next screen.
on the second screen, when I click the back button it navigates to the previous screen. After that, when I change the user-input data on the form and again click the next button now SubmitDataEvent() is not added.
I preferred some resources related to this and I understand the problem is that the state is in SuccessSate and it doesn't change to InitialState. So in dispose() I used bloc.close();
#override
void dispose() {
bloc.close();
super.dispose();
}
But still, it's not working. Also, I try with this code
#override
void dispose() {
bloc.emit(InitialState);
bloc.close();
super.dispose();
}
still, it's not working.
I used this to navigate between screens:
Navigator.popAndPushNamed()
What I want to do is:
On the first screen, when clicking on the next button SubmitDataEvent() added to the bloc and it in SuccessState it navigate to the next screen. When I click the back button on the second page it navigates again to the first screen. Now when I click the next button on the first screen I want to run all bloc process again.
There are no dependencies with the first and second screens.
first screen code:
...
#override
void initState() {
super.initState();
bloc = injection<SubmitPersonalDetailsBloc>();
EasyLoading.addStatusCallback((status) {
print('EasyLoading Status $status');
if (status == EasyLoadingStatus.dismiss) {
_timer?.cancel();
}
});
}
#override
void dispose() {
_scrollController.dispose();
bloc.close();
super.dispose();
}
#override
Widget buildView(BuildContext context) {
return Scaffold(
body: BlocProvider<SubmitPersonalDetailsBloc>(
create: (_) => bloc,
child: BlocListener<SubmitPersonalDetailsBloc,
BaseState<PersonalDetailsState>>(
listener: (context, state) {
if (state is LoadingSubmitPersonalDetailsState) {
EasyLoading.show(status: 'Submitting Data');
}
if (state is SubmitPersonalDetailsSuccessState) {
setState(() {
submitPersonalDetailsResponseEntity =
state.submitPersonalDetailsResponseEntity;
});
if (submitPersonalDetailsResponseEntity!.responseCode == "00") {
EasyLoading.showSuccess('Done!');
//Navigate next screen
EasyLoading.dismiss();
}
} else if (state is SubmitPersonalDetailsFailedState) {
EasyLoading.showError(state.error); }
},
....
The problem is on Dependency Injection, Once it creates an instance the parameters don't change. So when navigating to the next screen have to reset that instance.
#override
void dispose() {
_scrollController.dispose();
bloc.close();
injection.resetLazySingleton<SubmitPersonalDetailsBloc>(); // here reset the instance
super.dispose();
}
You can try with this snippet
if (result.responseCode == APIResponse.RESPONSE_SUCCESS) {
yield SubmitPersonalDetailsSuccessState(
submitPersonalDetailsResponseEntity: r);
} else
yield SubmitPersonalDetailsFailedState(error: r.responseMsg);
}

"Bad state: Stream has already been listened to" occurs when I visit screen multiple times

I'm using flutter_bluetooth_serial library and in initState() function I'm using listen to call a function. It's working fine when the app initially starts but when I visit this screen for the second time on the app I get a red screen saying "Bad state: Stream has already been listened to".
I'm new to flutter so please provide the exact code that can help me resolve this issue.
#override
void initState() {
super.initState();
widget.connection.input.listen(_onDataReceived).onDone(() {
// Example: Detect which side closed the connection
// There should be `isDisconnecting` flag to show are we are (locally)
// in middle of disconnecting process, should be set before calling
// `dispose`, `finish` or `close`, which all causes to disconnect.
// If we except the disconnection, `onDone` should be fired as result.
// If we didn't except this (no flag set), it means closing by remote.
if (isDisconnecting) {
print('Disconnecting locally!');
} else {
print('Disconnected remotely!');
}
if (this.mounted) {
setState(() {});
}
});
}
Try to override dispose() method of the state and cancel subscription within it. To do that you need to save subscription in a variable:
StreamSubscription _subscription;
#override
void initState() {
super.initState();
_subscription = widget.connection.input.listen(_onDataReceived, onDone: () {
...
});
}
override
void dispose() {
_subscription.cancel();
super.dispose();
}
Edit
If you need to subscribe to the connection.input multiple times across the app - you can transform it to broacast stream and subscribe for it. It should help. Like this:
final broadcastInput = connection.input.asBroadcastStream();
But if you need to use connection only in this widget I would recommend you to keep it inside state (not widget) and close it on dispose. It would be better lifecycle control solution.
BluetoothConnection _connection;
#override
void initState() {
super.initState();
_initConnection();
}
Future<void> _initConnection() async {
_connection = await BluetoothConnection.toAddress(address);
/// Here you can subscribe for _connection.input
...
}
#override
void dispose() {
connection;
super.dispose();
}

Flutter | Stop music when minimised using audioplayers

In my app, I am playing music (local) in a loop, which plays continuously unless the user stops it. I am using audioplayers package.
Future playLoop(String filePath) async {
player.stop();
player = await cache.loop(filePath);
}
Currently, when app is minimised, the music is not getting stoped. The feature I want to implement is that when the app is minimised, it should stop playing music in the background.
Thanks in advance.
Solutions :
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.paused) {
//stop your audio player
}else{
print(state.toString());
}
}
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
There are mainly 4 states for it:
resumed: The application is visible and responding to user input.
inactive: The application is in an inactive state and is not receiving
user input.
paused: The application is not currently visible to the user, not
responding user input, and running in the background.
detached: The application is still hosted on a flutter engine but is
detached from any host views.
The solution above is correct, but some steps are needed before to get it
1 add WidgetsBindingObserver to your class
class AnyClass extends StatefulWidgets {
_AnyClassState createState() => _AnyClassState();
}
class _AnyClassState extends State<AnyClass> with
WidgetsBindingObserver {
Widget build(BuildContext context) {
return ...
}
}
2 Now it will work, we can added the methods inside class
class _AnyClassState extends State<AnyClass> with
WidgetsBindingObserver {
// ADD THIS AppLifecycleState VARIABLE
late AppLifecycleState appLifecycle;
// ADD THIS FUNCTION WITH A AppLifecycleState PARAMETER
didChangeAppLifecycleState(AppLifecycleState state) {
appLifecycle = state;
setStae(() {});
if(state == AppLifecycle.paused) {
// IF YOUT APP IS IN BACKGROUND...
// YOU CAN ADDED THE ACTION HERE
print('My app is in background');
}
}
// CREATE INITSTATE AND DISPOSE METHODS
initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
Widget build(BuildContext context) {
return ...
}
}
NOW IT WILL WORK FINE!

Flutter Capture event when app comes to Foreground from triggered Intent

I am trying to pull off some basic stuff here. Scenario: I am checking for GPS status on init() using isLocationServiceEnabled. If the GPS is off, I'm showing a popup that redirects to Location settings using AndroidIntent. If hit back without turning on the GPS, I want to capture the event when my app comes to foreground. I guessed it has to do with the lifecycle and tried like below, nothing gets print on the console
AppLifecycleState _notification;
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
_notification = state;
print('onResumed called 1');
print(_notification);
});
if( state == AppLifecycleState.resumed){
print('onResumed called 2');
}
}
Am I missing something here?
Did you extend class with WidgetsBindingObserver like so:
class _WhateverWidget extends State<WhateverWidget> with WidgetsBindingObserver
and then initialize an instance like so:
#override void initState() {
WidgetsBinding.instance.addObserver(this);
super.initState();
}
#override void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}

Android onResume() method equivalent in Flutter

I am working on a Flutter app and need to pop the screen. I tried initState() method but no luck. initState() gets called when I open a class for the first time.
Do we have an equivalent of Android onResume() method in Flutter?
Any ideas?
You can use the WidgetsBindingObserver and check the AppLifeCycleState like this example:
class YourWidgetState extends State<YourWidget> with WidgetsBindingObserver {
#override
void initState() {
WidgetsBinding.instance?.addObserver(this);
super.initState();
}
#override
void dispose() {
WidgetsBinding.instance?.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
//do your stuff
}
}
}
Take in mind that It will called every time you open the app or go the background and return to the app. (if your widget is active)
If you just want a listener when your Widget is loaded for first time, you can listen using addPostFrameCallback, like this example:
class YourWidgetState extends State<YourWidget> {
_onLayoutDone(_) {
//do your stuff
}
#override
void initState() {
WidgetsBinding.instance?.addPostFrameCallback(_onLayoutDone);
super.initState();
}
}
Info : https://docs.flutter.io/flutter/widgets/WidgetsBindingObserver-class.html
Update: Null safety compliance
If you go to another page, then is called when you comeback
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(),
),
).then((value) {
_refreshFirstPage();
});
You can accomplish this by registering a didChangeAppLifecycleState observer:
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(final AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
setState(() {
// ...your code goes here...
});
}
}
#override
Widget build(final BuildContext context) {
// ...your code goes here...
}
}
See WidgetsBindingObserver for more information.
Use focus_detector more information can see visibility_detector
Get notified every time your widget appears or disappears from the screen.
Similar to onResume()/onPause() on Android and viewDidAppear()/viewDidDisappear() on iOS.
Focus Detector fires callbacks for you whenever something happens to take or give your widget focus. Such an event might be, for instance, the user:
Navigating to/from another screen;
Turning the device’s screen on/off while your widget is visible;
Switching to/from another app while your widget is visible;
Scrolling your widget in/out the screen;
#override
Widget build(BuildContext context) =>
FocusDetector(
onFocusLost: () {
logger.i(
'Focus Lost.'
'\nTriggered when either [onVisibilityLost] or [onForegroundLost] '
'is called.'
'\nEquivalent to onPause() on Android or viewDidDisappear() on iOS.',
);
},
onFocusGained: () {
logger.i(
'Focus Gained.'
'\nTriggered when either [onVisibilityGained] or [onForegroundGained] '
'is called.'
'\nEquivalent to onResume() on Android or viewDidAppear() on iOS.',
);
},
onVisibilityLost: () {
logger.i(
'Visibility Lost.'
'\nIt means the widget is no longer visible within your app.',
);
},
onVisibilityGained: () {
logger.i(
'Visibility Gained.'
'\nIt means the widget is now visible within your app.',
);
},
onForegroundLost: () {
logger.i(
'Foreground Lost.'
'\nIt means, for example, that the user sent your app to the background by opening '
'another app or turned off the device\'s screen while your '
'widget was visible.',
);
},
onForegroundGained: () {
logger.i(
'Foreground Gained.'
'\nIt means, for example, that the user switched back to your app or turned the '
'device\'s screen back on while your widget was visible.',
);
},
child: Container(),
);