Flutter Widgets Binding Observer - flutter

In Flutter,I want to go back to the page I left the app from. But when I try to back, it always navigates to LoginPage. For example,I have 3 page.LoginPage,WorkoutPage,ProgressPage.Login page is my launcher. When I am on ProgressPage, I leave the app. But when I resume,it navigates Login Page.I used this code in login page.
Login Page
class ProgressTabState extends State with WidgetsBindingObserver{
AppLifecycleState state;
#override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
WidgetsBinding.instance.removeObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState appLifecycleState) {
// TODO: implement didChangeAppLifecycleState
super.didChangeAppLifecycleState(state);
state = appLifecycleState;
}
in login page I use this code to navigate to MainTab.
Navigator.push(
context,
MaterialPageRoute(
builder: (_) {
return MainTabs();
},
),
);
in Main tabs I have Tabbarviews with two page. Workoutpage and ProgressPage. This is Workout Page.
class WorkoutTabState extends State <WorkoutTab> {
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body:Text("Workout Page"),
);
}
Progress Page
class ProgressTabState extends State with WidgetsBindingObserver{
AppLifecycleState state;
#override
void initState() {
// TODO: implement initState
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
WidgetsBinding.instance.removeObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState appLifecycleState) {
// TODO: implement didChangeAppLifecycleState
super.didChangeAppLifecycleState(state);
state = appLifecycleState;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body:Text("Progress Page"));
}}

In the login page dont use Navigator.push instead use Navigator.of(context).pushNamedAndRemoveUntil
The difference is Navigator.push will put login page as the first page so it will alway fall back to login page, after successful login you dont want that, you need to remove the login page from the routes stack, by using Navigator.of(context).pushNamedAndRemoveUntil, now when navigating between main and progress use Navigator.push
That will make the main page as the first route to fallbck to
Please read about it heare
https://api.flutter.dev/flutter/widgets/NavigatorState/pushAndRemoveUntil.html

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

Disposing Camera preview in CupertinoTabBar when switched in Flutter

I am building a QR code scanner app with a couple of tabs wrapped up within CupertinoTabBar in a CupertinoTabScaffold. I have a CupertinoTabController to take care of the switching between the tabs. One of this tabs has a CameraPreview widget from the Camera plugin of Flutter along with a proper dispose mechanism. However, whenever the tab are switched, the Camera stream still persists, causing the phone to heat up and also causes janky UX. Now I read that the BottomNavigationBar from Material widgets does not persist in this way. Any idea on how to achieve the same behaviour with CupertinoTabBar?
You can use the StatefulWidget for each a page of the tabs and then try to listening AppLifecycleState. Disponse controller if state inactive/paused.
In my case it's working fine.
class Example extends StatefulWidget {
#override
ExampleState createState() => ExampleState();
}
//Implement WidgetsBindingObserver to listen Lifecycle State
class ExampleState extends State<Example> with WidgetsBindingObserver {
late CameraController _controller;
...
...
#override
void initState() {
super.initState();
// Add Listener (Lifecycle State)
WidgetsBinding.instance!.addObserver(this);
}
Future<void> _setupController() async {
//todo setup/init controller
}
//Implements this method to listen Lifecycle State
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_controller.dispose();
_setupCameraAndControllerFuture = _setupController();
}
if (state == AppLifecycleState.inactive) {
_controller.dispose();
} else if (state == AppLifecycleState.paused) {
_controller.dispose();
}
}
#override
void dispose() {
// Remove Listener (Lifecycle State)
WidgetsBinding.instance!.removeObserver(this);
// dispose controller
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
...
...
);
}
}

Call a function when user leaves current screen in Flutter [duplicate]

This question already has answers here:
Detect if the user leaves the current page in Flutter?
(6 answers)
Closed 1 year ago.
I want to call a function or get notified when a user leaves the current screen whether he/she navigates back i.e pop the screen or navigate forward in the app.
You need to implement a route observer.
First you need to define an observer :
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
then you need to add the observer to your material app :
MaterialApp(
navigatorObservers: [routeObserver], ...
Ok. Now when you create a screen, you can add RouteAware to your state. You will need to subscribe to the observer, and dispose it as well. Therefore you need a stateful widget. Let's say you have a screen called MyScreen, it would look like this :
class MyScreen extends StatefulWidget {
#override
_MyScreenState createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> with RouteAware{
#override
void didChangeDependencies() {
routeObserver.subscribe(this, ModalRoute.of(context));
super.didChangeDependencies();
}
#override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
} ...
Great! Now you have access to these extra events such as :
#override
void didPopNext() {
super.didPopNext();
}
#override
void didPush() {
super.didPush();
}
#override
void didPushNext() {
super.didPushNext();
}
#override
void didPop() {
super.didPop();
}
Let me know if something was unclear.
you can use dispose() lifecycle function
quote from API
dispose method
Called when this object is removed from the tree permanently.
for example i'm using it to unsubscribe a Timer when user leaves the screen
example:
#override
void dispose() {
super.dispose();
timer.cancel();
}

Share bloc across different routes

I'm trying to share same bloc across two routes.
But when I come back from second route the bloc get automatically disposed so in the first route I find myself with all the stream closed.
For example this is the first route (HomePage) where I instantiate the bloc, download a list from api and show it in the build method.
class HomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
GroupsBloc _groupBloc;
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies(){
super.didChangeDependencies();
_groupBloc = GroupsBloc();
_groupBloc.getAll();
}
#override
void dispose(){
_groupBloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
...
}
}
Then I navigate to a second screen where I can add an item to the list.
_onAddGroupPress(){
Navigator.of(context).push(new MaterialPageRoute(
builder: (BuildContext context) => BlocProvider<GroupsBloc>(bloc: _groupBloc, child: GroupPage()),
fullscreenDialog: true
),
);
}
In the second screen I retrieve the bloc and I use it to add an item, then I go back to Home Page.
class GroupPage extends StatefulWidget {
#override
_GroupPageState createState() => _GroupPageState();
}
class _GroupPageState extends State<GroupPage> {
FormBloc _formBloc; //another bloc
GroupsBloc _groupBloc;
#override
void initState(){
super.initState();
}
#override
void didChangeDependencies(){
super.didChangeDependencies();
_formBloc = FormBloc();
_groupBloc = BlocProvider.of<GroupsBloc>(context); //retrieve of the bloc
}
#override
void dispose() {
_formBloc?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
...
}
In the dispose method of the second screen I dispose only _formBloc bloc but _groupBloc gets disposed too, so when I come back in the first page I found myself with _groupBloc disposed and all it's stream closes.
I tought of passing the bloc to the second screen as a props but I don't know if it's the right way to do this.
It obviously depends on the scope of your Bloc, but there is nothing preventing you from sharing the same instance throughout your whole app.
ie. simply wrap your whole MaterialApp inside BlocProvider<GroupsBloc>(bloc: _groupBloc, child: MaterialApp( ... ))
if "groups" are not global to your app, you should probably just pass the bloc along to the second widget.

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