Subscribe to application transition changes - flutter

I need my app to react whenever the user:
switches to another app
returns back to the app
tries to close the app
How can I subscribe to these changes?

You can use the onGenerateRoute property on the MaterialApp to listen and execute your methods each time a named route is made ( when you navigate over pages in your pages) :
MaterialApp(
onGenerateRoute: (settings) {
print("navigated to new route")
},
//...
You can use WillPopScope widget to listen for the navigator's pop() method which goes back to the previous screen if it exists in the stack route, and close the app when it's the only one left in the stack route.
WillPopScope(
onWillPop: () async {
print("route popped");
return Future.value(true);
}, child: WillPopScope(
onWillPop: () async {
print("route popped");
return Future.value(true); // will allow popping, if the value is false, it will not pop.
},
child: Scaffold(//...)
if you're willing to use a custom personalized Navigator in your app, then you can set from it the onGeneratedRoute and also the onPopPage which let you listen to pop() over the sub-tree it takes:
Navigator(
onPopPage: /*...*/,
onGenerateRoute: /*...*/,
),

Another option you can do, is to use life cycle methods to execute a piece of code whenever a widget screen is rendered ( initState() will run), and when you pop() it, it will be disposed ( dispose() will execute ), but it requires using StatefulWidget:
class Example extends StatefulWidget {
const Example({super.key});
#override
State<Example> createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container();
}
}

1- when switch to another app as well as return to the app You can use
WidgetsBindingObserver in your widgets and listen to AppLifecycleState.
2- in order to handle resume callback
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;
}
}
3- to perform some function on app close try to use willpopscope

Related

How can I refresh the page when the user comes back to the app (from background)?

Right now the only way my app can refresh (it's a news app so it needs to constantly refresh) is two ways: 1) Refresh by scrolling up or 2) Restart the app after killing it from background.
I want to make it so that when a user just comes back to the app (say I'm using my app, then I go to WeChat to send a text, then I come back), the app is refreshed.
This is the refresh scroll code.
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
new GlobalKey<RefreshIndicatorState>();
Then it calls this function:
Future<void> _refresh() async {
print("Refreshed");
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => MyHomePage(),
),
).then((value) => null);
}
What should I do to achieve what I need?
Is there a way to check if someone has "returned" to my app? Then I can just call the function.
You need to subscribe to the application life cycle. In Flutter it's not build in the same was it is natively. However, there is a good article I stumbled across a last year, trying to accomplish the same thing:
https://medium.com/pharos-production/flutter-app-lifecycle-4b0ab4a4211a
The gist of the implementation looks like this:
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();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
print('state = $state');
}
#override
Widget build(BuildContext context) {
return YourWidget();
}
}
A better solution is to give the child widget of your FutureBuilder or StreamBulder a random unique key. This forces the underlying widget to redraw (and trigger the future or stream) when you come back from the background.
key: UniqueKey(),
Example:
return FutureBuilder<PriceData>(
future: DbService.getPriceData(),
builder: (context, priceData) {
if (priceData.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
return AnimatedAppear(
key: UniqueKey(), <---- here!

How to reload the page whenever the page is on screen - flutter

Is there any callbacks available in flutter for every time the page is visible on screen? in ios there are some delegate methods like viewWillAppear, viewDidAppear, viewDidload.
I would like to call a API call whenever the particular page is on-screen.
Note: I am not asking the app states like foreground, backround, pause, resume.
Thank You!
Specifically to your question:
Use initState but note that you cannot use async call in initState because it calls before initializing the widget as the name means. If you want to do something after UI is created didChangeDependencies is great. But never use build() without using FutureBuilder or StreamBuilder
Simple example to demostrate:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(MaterialApp(home: ExampleScreen()));
}
class ExampleScreen extends StatefulWidget {
ExampleScreen({Key key}) : super(key: key);
#override
_ExampleScreenState createState() => _ExampleScreenState();
}
class _ExampleScreenState extends State<ExampleScreen> {
List data = [];
bool isLoading = true;
void fetchData() async {
final res = await http.get("https://jsonplaceholder.typicode.com/users");
data = json.decode(res.body);
setState(() => isLoading = false);
}
// this method invokes only when new route push to navigator
#override
void initState() {
super.initState();
fetchData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: isLoading
? CircularProgressIndicator()
: Text(data?.toString() ?? ""),
),
);
}
}
Some lifecycle method of StatefulWidget's State class:
initState():
Describes the part of the user interface represented by this widget.
The framework calls this method in a number of different situations:
After calling initState.
After calling didUpdateWidget.
After receiving a call to setState.
After a dependency of this State object changes (e.g., an InheritedWidget referenced by the previous build changes).
After calling deactivate and then reinserting the State object into the tree at another location.
The framework replaces the subtree below this widget with the widget
returned by this method, either by updating the existing subtree or by
removing the subtree and inflating a new subtree, depending on whether
the widget returned by this method can update the root of the existing
subtree, as determined by calling Widget.canUpdate.
Read more
didChangeDependencies():
Called when a dependency of this State object changes.
For example, if the previous call to build referenced an
InheritedWidget that later changed, the framework would call this
method to notify this object about the change.
This method is also called immediately after initState. It is safe to
call BuildContext.dependOnInheritedWidgetOfExactType from this method.
Read more
build() (Stateless Widget)
Describes the part of the user interface represented by this widget.
The framework calls this method when this widget is inserted into the
tree in a given BuildContext and when the dependencies of this widget
change (e.g., an InheritedWidget referenced by this widget changes).
Read more
didUpdateWidget(Widget oldWidget):
Called whenever the widget configuration changes.
If the parent widget rebuilds and request that this location in the
tree update to display a new widget with the same runtimeType and
Widget.key, the framework will update the widget property of this
State object to refer to the new widget and then call this method with
the previous widget as an argument.
Read more
Some widgets are stateless and some are stateful. If it's a stateless widget, then only values can change but UI changes won't render.
Same way for the stateful widget, it will change for both as value as well as UI.
Now, will look into methods.
initState(): This is the first method called when the widget is created but after constructor call.
#override
void initState() {
// TODO: implement initState
super.initState();
}
didChangeDependecies() - Called when a dependency of this State object changes.Gets called immediately after initState method.
#override
void didChangeDependencies() {
super.didChangeDependencies();
}
didUpdateWidget() - It gets called whenever widget configurations gets changed. Framework always calls build after didUpdateWidget
#override
void didUpdateWidget (
covariant Scaffold oldWidget
)
setState() - Whenever internal state of State object wants to change, need to call it inside setState method.
setState(() {});
dispose() - Called when this object is removed from the tree permanently.
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
You don't need StatefulWidget for calling the api everytime the screen is shown.
In the following example code, press the floating action button to navigate to api calling screen, go back using back arrow, press the floating action button again to navigate to api page.
Everytime you visit this page api will be called automatically.
import 'dart:async';
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => ApiCaller())),
),
);
}
}
class ApiCaller extends StatelessWidget {
static int counter = 0;
Future<String> apiCallLogic() async {
print("Api Called ${++counter} time(s)");
await Future.delayed(Duration(seconds: 2));
return Future.value("Hello World");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Api Call Count: $counter'),
),
body: FutureBuilder(
future: apiCallLogic(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) return const CircularProgressIndicator();
if (snapshot.hasData)
return Text('${snapshot.data}');
else
return const Text('Some error happened');
},
),
);
}
}
This is the simple code with zero boiler-plate.
The simplest way is to use need_resume
1.Add this to your package's pubspec.yaml file:
dependencies:
need_resume: ^1.0.4
2.create your state class for the stateful widget using type ResumableState instead of State
class HomeScreen extends StatefulWidget {
#override
HomeScreenState createState() => HomeScreenState();
}
class HomeScreenState extends ResumableState<HomeScreen> {
#override
void onReady() {
// Implement your code inside here
print('HomeScreen is ready!');
}
#override
void onResume() {
// Implement your code inside here
print('HomeScreen is resumed!');
}
#override
void onPause() {
// Implement your code inside here
print('HomeScreen is paused!');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Go to Another Screen'),
onPressed: () {
print("hi");
},
),
),
);
}
}
If you want to make an API call, then you must be (or really should be) using a StatefulWidget.
Walk through it, let's say your stateful widget receives some id that it needs to make an API call.
Every time your widget receives a new id (including the first time) then you need to make a new API call with that id.
So use didUpdateWidget to check to see if the id changed and, if it did (like it does when the widget appears because the old id will be null) then make a new API call (set the appropriate loading and error states, too!)
class MyWidget extends StatefulWidget {
Suggestions({Key key, this.someId}) : super(key: key);
String someId
#override
State<StatefulWidget> createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
dynamic data;
Error err;
bool loading;
#override
Widget build(BuildContext context) {
if(loading) return Loader();
if(err) return SomeErrorMessage(err);
return SomeOtherStateLessWidget(data);
}
#override
void didUpdateWidget(covariant MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// id changed in the widget, I need to make a new API call
if(oldWidget.id != widget.id) update();
}
update() async {
// set loading and reset error
setState(() => {
loading = true,
err = null
});
try {
// make the call
someData = await apiCall(widget.id);
// set the state
setState(() => data = someData)
} catch(e) {
// oops an error happened
setState(() => err = e)
}
// now we're not loading anymore
setState(() => loading = false);
}
}
I'm brand new to Flutter (literally, just started playing with it this weekend), but it essentially duplicates React paradigms, if that helps you at all.
Personal preference, I vastly prefer this method rather than use FutureBuilder (right now, like I said, I'm brand new). The logic is just easier to reason about (for me).

Navigate while Widget state build is being executed

I'm building a simple Flutter app. Its launch screen determines if the user if logged in or not, and depending on that redirects to the login or main/home screen afterwards.
My Launch screen is a StatefulWidget, its state is shown below. It uses a ViewModel class that extends ChangeNotifier (its code is irrelevant, so I didn't include it).
class _LaunchPageState extends State<LaunchPage> {
LaunchViewModel _viewModel = LaunchViewModel();
#override
void initState() {
super.initState();
_viewModel.checkSessionStatus();
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<LaunchViewModel>(
builder: (_) => _viewModel,
child: Scaffold(
body: Consumer<LaunchViewModel>(
builder: (context, viewModel, _) {
if (viewModel.state is LaunchInitial) {
return CircularProgressIndicator();
}
if (viewModel.state is LaunchLoginPage) {
Navigator.pushNamed(context, "login");
}
if (viewModel.state is LaunchMainPage) {
Navigator.pushNamed(context, "main");
}
return Container();
},
),
),
);
}
}
The ViewModel emits one of 3 states:
LaunchInitial: Default state.
LaunchLoginPage: Indicates that the Login page should be displayed.
LaunchMainPage: Indicates that the Main page should be displayed.
The LaunchInitial state is handled fine, and a progress bar is displayed on the screen. But the other 2 states cause the app to crash. The following error is thrown:
This Overlay widget cannot be marked as needing to build because the framework is already in the process of building widgets
It seems that trying to redirect to another screen while the Consumer's build method is being executed is causing this issue. What's the correct way to do this?
Thanks!
You can't directly call Navigator within widget tree. If you have event-state builder, so better change the widget tree you are rendering:
builder: (context, viewModel, _) {
if (viewModel.state is LaunchInitial) {
return CircularProgressIndicator();
}
if (viewModel.state is LaunchLoginPage) {
return LoginPage();
}
if (viewModel.state is LaunchMainPage) {
return MainPage();
}
return Container();
},
You have to return Widget with each child inside build method.
Alternatively, you can do this with Navigation:
#override
void didChangeDependencies() {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (viewModel.state is LaunchLoginPage) {
Navigator.pushNamed(context, "login");
}
if (viewModel.state is LaunchMainPage) {
Navigator.pushNamed(context, "main");
}
});
super.didChangeDependencies();
}
addPostFrameCallback method will be called right after the build method completed and you can navigate inside.
Be sure your provider don't have lifecycle issue.

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

How to go back and refresh the previous page in Flutter?

I have a home page which when clicked takes me to another page through navigates, do some operations in then press the back button which takes me back to the home page. but the problem is the home page doesn't get refreshed.
Is there a way to reload the page when i press the back button and refreshes the home page?
You can trigger the API call when you navigate back to the first page like this pseudo-code
class PageOne extends StatefulWidget {
#override
_PageOneState createState() => new _PageOneState();
}
class _PageOneState extends State<PageOne> {
_getRequests()async{
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new RaisedButton(onPressed: ()=>
Navigator.of(context).push(new MaterialPageRoute(builder: (_)=>new PageTwo()),)
.then((val)=>val?_getRequests():null),
),
));
}
}
class PageTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
//somewhere
Navigator.pop(context,true);
}
}
Or you can just use a stream if the API is frequently updated, the new data will be automatically updated inside your ListView
For example with firebase we can do this
stream: FirebaseDatabase.instance.reference().child(
"profiles").onValue
And anytime you change something in the database (from edit profile page for example), it will reflect on your profile page. In this case, this is only possible because I am using onValue which will keep listening for any changes and do the update on your behalf.
(In your 1st page): Use this code to navigate to the 2nd page.
Navigator.pushNamed(context, '/page2').then((_) {
// This block runs when you have returned back to the 1st Page from 2nd.
setState(() {
// Call setState to refresh the page.
});
});
(In your 2nd page): Use this code to return back to the 1st page.
Navigator.pop(context);
use result when you navigate back from nextScreen as follow :
Navigator.of(context).pop('result');
or if you are using Getx
Get.back(result: 'hello');
and to reload previous page use this function :
void _navigateAndRefresh(BuildContext context) async {
final result = await Get.to(()=>NextScreen());//or use default navigation
if(result != null){
model.getEMR(''); // call your own function here to refresh screen
}
}
call this function instead of direct navigation to nextScreen
The solution which I found is simply navigating to the previous page:
In getx:
return WillPopScope(
onWillPop: () {
Get.off(() => const PreviousPage());
return Future.value(true);
},
child: YourChildWidget(),
or if you want to use simple navigation then:
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) =>PreviousPage() ,));
Simply i use this:
onPressed: () {
Navigator.pop(context,
MaterialPageRoute(builder: (context) => SecondPage()));
},
this to close current page:
Navigator.pop
to navigate previous page:
MaterialPageRoute(builder: (context) => SecondPage())
In FirtsPage, me adding this for refresh on startUpPage:
#override
void initState() {
//refresh the page here
super.initState();
}
For a more fine-grained, page-agnostic solution I came up with this Android Single LiveEvent mimicked behaviour.
I create such field inside Provider class, like:
SingleLiveEvent<int> currentYearConsumable = SingleLiveEvent<int>();
It has a public setter to set value. Public consume lets you read value only once if present (request UI refresh). Call consume where you need (like in build method).
You don't need Provider for it, you can use another solution to pass it.
Implementation:
/// Useful for page to page communication
/// Mimics Android SingleLiveEvent behaviour
/// https://stackoverflow.com/questions/51781176/is-singleliveevent-actually-part-of-the-android-architecture-components-library
class SingleLiveEvent<T> {
late T _value;
bool _consumed = true;
set(T val) {
_value = val;
_consumed = false;
}
T? consume() {
if (_consumed) {
return null;
} else {
_consumed = true;
return _value;
}
}
}
await the navigation and then call the api function.
await Navigator.of(context).pop();
await api call
You can do this with a simple callBack that is invoked when you pop the route. In the below code sample, it is called when you pop the route.
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
_someFunction()async{
Navigator.of(context).push(MaterialPageRoute(builder: (_)=> PageTwo(
onClose():(){
// Call setState here to rebuild this widget
// or some function to refresh data on this page.
}
)));
}
#override
Widget build(BuildContext context) {
return SomeWidget();
}
...
} // end of widget
class PageTwo extends StatelessWidget {
final VoidCallback? onClose;
PageTwo({Key? key, this.onClose}) : super(key: key);
#override
Widget build(BuildContext context) {
return SomeWidget(
onEvent():{
Navigate.of(context).pop();
onClose(); // call this wherever you are popping the route
);
}
}