The callback which is called everytime Navigator.push to next page - flutter

I have two Pages moving each others like this.
MyHomePage -> ResPage -> MyHomePage -> ResPage
I want to exec the function when every time the ResPage appears.
code is this
Navigator.push(context,MaterialPageRoute(
builder: (context) => ResPage()));
Navigator.push(context,MaterialPageRoute(
builder: (context) => MyHomePage())
resPage is Stateful Widget.
class ResPage extends StatefulWidget {
ResPage({Key key}) : super(key: key);
#override
_ResPageState createState() => _ResPageState();
}
class _ResPageState extends State<ResPage> {
void initState(){ // it called just once.
}
#override
Widget build(BuildContext context) {
return Stack( // it called many times.
children: <Widget>[
background,
Scaffold(
backgroundColor: Colors.transparent,
body: resStack
),
],
);
}
initState() is called one time, and build is called many times.
Is there any call back when the page appears??

Yes it is!
You can add a post frame callback(a call after the widget was built)
like this:
Widget build(BuildContext context) {
WidgetsBinding.addPostFrameCallback(functionToCall);
}

Related

How can I execute a FutureBuilder future on an Autorouter tabs more than once?

I am currently trying to execute a FutureBuilder future function in an Autorouter - the library (https://pub.dev/packages/auto_route#tab-navigation) - and it works perfectly. However, as I am using a FutureBuilder in the tabs, the future is only executed once - the first time I access the tab - and isn't re-executed again when I leave the tab and come back to it. I would like to be able to execute the future function every time I access the tab since the future is reading data from the database.
I have tried the following:
making the widget stateful and executing setState function to force a rebuild
using the overridden function didChangeDependencies
override the deactivate function of the widget
None of the above seem to work.
And after going through the documentation of the Autoroute library, I haven't come across any explanation on how to force a rebuild of the current tab.
I welcome any suggestions.
Thank you
NB: I'm using Flutter to make a mobile application, the solution doesn't necessarily have to work on a web application.
Tab View
class MyTabView extends StatelessWidget {
MyTabView({Key? key}) : super(key: key);
final tabRoutes = [
TabRoute1(),
TabRoute2(),
];
#override
Widget build(BuildContext context) {
return AutoTabsScaffold(
routes: tabRoutes,
bottomNavigationBuilder: (_, tabRouter) {
return BottomNavigationBar(
currentIndex: tabRouter.activeIndex,
onTap: tabRouter.setActiveIndex,
items: [
BottomNavigationBarItem(
icon: BaseIcon(
svgFileName: 'calendar.svg',
),
label: LocaleKeys.careProfessionalLabelProfile.tr(),
),
BottomNavigationBarItem(
icon: BaseIcon(
svgFileName: 'wallet.svg',
),
label: LocaleKeys.careProfessionalLabelChat.tr(),
),
],
);
},
);
}
}
Tab with child that contains FutureBuilder
class TabRoute2 extends StatefulWidget {
const TabRoute2({Key? key}) : super(key: key);
#override
State<TabRoute2> createState() => _TabRoute2State();
}
class _TabRoute2State extends State<TabRoute2> {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
// ---- END SPACER
Expanded(
child: ShowFutureData(),
),
],
);
}
}
ShowFutureData
class ShowFutureData extends StatefulWidget {
const ShowFutureData({
super.key,
});
#override
State<ShowFutureData> createState() =>
_ShowFutureDataState();
}
class _ShowFutureDataState extends State<ShowFutureData> {
late FutureDataObjectProvider futureObjectProvider;
#override
initState() {
super.initState();
futureObjectProvider = context.read<FutureDataObjectProvider>();
}
#override
Widget build(BuildContext context) {
retrieved = futureObjectProvider.retrieveAllData();
return FutureBuilder(
future: retrieved, // only executed when the tab is first accessed
initialData: const [],
builder: (context, snapshot) {
// do something with the data
},
);
}
}
You can reassign the future to recall the future.
FutureBuilder(
future: myFuture,
Then reassign it again
myFuture = getData();

Flutter pushing two page to navigator sequentially

I want to push two pages to the flutter navigator one after another, so that going back from 2nd page redirects me to the first page. The code for this action will look somewhat like below -
Navigator.of(context).pushNamed(FirstPage.PATH);
Navigator.of(context).pushNamed(SecondPage.PATH);
The above code works fine. But my confusion is, will it work always as the pushNamed function is asynchronous as it returns a future value. So it could happen that the second page got pushed to navigator before the first page.
The ideal implementation seems to me to wait for the first call to pushNamed return its value and then call the second one. But strangely the following two solutions don't work. The first page did get pushed but it doesn't push the second page.
Solution 1(Not working):
Navigator.of(context).pushNamed(
FirstPage.PATH.then((_) =>
Navigator.of(context).pushNamed(SecondPage.PATH));
Solution 2(Not working):
await Navigator.of(context).pushNamed(FirstPage.PATH);
Navigator.of(context).pushNamed(SecondPage.PATH);
Can anyone please clarify what I'm thinking wrong? Any help will be much appreciated. Thanks in Advance!
As an option you can pass a callback to pageA, add animation listener and call this callback when animation is finished.
this is full example:
import 'package:flutter/material.dart';
main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Home(),
routes: {
'pageA': (context) => PageA(),
'pageB': (context) => PageB(),
},
);
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: _onPressed,
child: Text('press me'),
),
),
);
}
void _onPressed() {
Navigator.of(context).pushNamed(PageA.routeName, arguments: _pushNextPage);
}
void _pushNextPage() {
Navigator.of(context).pushNamed(PageB.routeName);
}
}
class PageA extends StatefulWidget {
static const routeName = 'pageA';
#override
_PageAState createState() => _PageAState();
}
class _PageAState extends State<PageA> {
#override
void initState() {
super.initState();
WidgetsBinding.instance?.addPostFrameCallback((_) {
ModalRoute.of(context)?.animation?.addStatusListener(_statusListener);
});
}
void _statusListener(AnimationStatus status) {
if (status == AnimationStatus.completed) {
final route = ModalRoute.of(context);
route?.animation?.removeStatusListener(_statusListener);
final callback = route?.settings.arguments as VoidCallback;
callback.call();
}
}
#override
Widget build(BuildContext context) => Scaffold(body: Center(child: Text('PAGE A')));
}
class PageB extends StatelessWidget {
static const routeName = 'pageB';
#override
Widget build(BuildContext context) => Scaffold(body: Center(child: Text('PAGE B')));
}
Your solutions do not work, because the Future returned by pushNamed is only completed when the page is removed from the navigation stack again.
So in your examples, the second page is pushed, once the first page has been closed.
I don't think it can happen, that the second page will be pushed before the first page in this example:
Navigator.of(context).pushNamed(FirstPage.PATH);
Navigator.of(context).pushNamed(SecondPage.PATH);
This should be a valid solution for what you want to achieve.

Where should I initialize the blocs in flutter_bloc

I am using Flutter_bloc package to work with bloc pattern in flutter, but i am wondering if it is a good practice to use a MultiBlocProvider inside main function and add all of my blocs in there like this:
void main()async{
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(Mafqood());
}
class Mafqood extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers : [
BlocProvider<AuthBloc>(
create: (context) => AuthBloc(AuthInitialState(), AuthRepository()),
),
BlocProvider<LoginBloc>(
create: (context) => LoginBloc(LoginInitialState(), AuthRepository()),
),
BlocProvider<ProfileBloc>(
create: (context) => ProfileBloc(ProfileInitialState(), AuthRepository()),
),
],
child: MaterialApp(
or it is better to add the bloc just where I need it? and why?
Thanks in advance.
You should use MultiBlocProvider inside the main function as you did. This is the best practice. And this is the goal of the providers.
Edit:
Now I realized that there is another answer here.
The main usage of MultiBlocProvider is using the bloc object in different places inside your application before you had to define which bloc depends on another bloc.
If you have an app that each screen use its own bloc, then you don't have a need for MultiBlocProvideras you can creat the bloc in the build function of the screen
class ParentScreen extends StatelessWidget {
const ParentScreen ({Key? key, this.data}) : super(key: key);
final data;
#override
Widget build(BuildContext context) {
return BlocProvider<MyBloc>(
create: (_) => MyBloc()),
child: MyScreenBody());
}
}
Class MyScreenBody extends StatefulWidget {
const MyScreenBody({Key? key}) : super(key: key);
#override
_MyScreenBodyState createState() => _MyScreenBodyState();
}
class _MyScreenBodyState extends State<MyScreenBody> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocBuilder<MyBloc, MyState>(
builder: (context, state) {
return //... your code
}
)
);
}

Flutter: Refresh parent widget on Navigation.pop

I have a parent widget "BookmarkedShows" and child widget "ListOfShows". From child widget, when user taps on list item, it opens details page. When the user removes the show from bookmark from details page, on pressing back button, the show is not removed from the listing page. ie the parent is not refreshed. I'm using BlocBuilder.
There are some options mentioned in other question to add .then() to Navigator.push() method. However Navigator.push() happens in children component. How would I force refresh parent BlocBuilder during Navigation.pop()?
Parent "BookmarkedShows":
class BookmarkedShows extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => BookmarkShowsBloc()..add(LoadBookmarkedShows()),
child: BlocBuilder<BookmarkShowsBloc, BookmarkedShowsState>(
builder: (BuildContext context, BookmarkedShowsState state) {
return ShowList("Bookmarked shows", state.shows)
}),
);
}
}
Child "ListOfShows":
class ListOfShows extends StatelessWidget {
final String listName;
final List<Show> shows;
const ListOfShows(this.listName, this.shows);
#override
Widget build(BuildContext context) {
return Wrap(children: shows.map((show) => showItem(show, context)).toList());
}
InkWell showItem(Show show, BuildContext context) {
return InkWell(
onTap: () async {
await Navigator.of(context).push(MaterialPageRoute(
builder: (context) => showDetails(show)));
},
child: Container(
CachedNetworkImage(
imageUrl: show.portraitPoster
),
));
}
}
The question stated is a bit unclear, but I'm going to answer it the best I can.
If you want your widget to be able to update you need to make it Stateful.
Make your BookmarkedShows Widget Stateful:
class BookmarkedShows extends StatefulWidget {
BookmarkedShows ({Key key}) : super(key: key); //Can also work without this line
#override
StatefulBookmarkedShows createState() => StatefulBookmarkedShows();
}
class StatefulBookmarkedShows extends State<BookmarkedShows> {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => BookmarkShowsBloc()..add(LoadBookmarkedShows()),
child: BlocBuilder<BookmarkShowsBloc, BookmarkedShowsState>(
builder: (BuildContext context, BookmarkedShowsState state) {
return ShowList("Bookmarked shows", state.shows)
}),
);
}
}
On returning back to parent you could implement something like in this Flutter docs example which might help to update the parent when navigating back. The async method awaits a response back from the child(Navigator).
When returning back to the Stateful parent you can call this like in the above mentioned async method:
LoadBookmarkedShows();
setState(() { });
I hope it works. Goodluck.

Flutter Push vs. State-related views

There are two ways to change what a user sees on display: I can push to another page or I can change the state of my stateful widget and rebuild it. Can you tell me, which way is best practice? (And if it depends - what I guess - on what?)
Pushing:
class Pushing extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
onPressed: () => Navigator.push(context, new MaterialPageRoute(builder: (context) => new SecondPage())),)
),
);
}
}
Using States
class UsingStates extends StatefulWidget {
#override
State createState() => new _UsingStatesState();
}
class _UsingStatesState extends State<UsingStates> {
bool isPageTwo;
#override
void initState() {
isPageTwo = false;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: isPageTwo ? Center(child: Text('Page two')) : Center(child: RaisedButton(onPressed: () {
setState(() {
isPageTwo = true;
});
})),
);
}
}
Of course the answer is: It depends.
When to use Navigator:
Routes pushed with the navigator are in a way equivalent to...
Activity and Fragment in Android
A route in Angular or React
A html file of a classic web page
You would use the Navigator to switch between logically separate parts of your app. Think of a StackOverflow app that comes with different pages, e.g. "Question List", "Question Detail", "Ask Question" form and "User Profile".
Navigator takes care of back navigation (hardware back button on android phones + back arrow in AppBar)
Note that a route does not have to overlay the full screen. showDialog also uses Navigator.push() internally (that's why you use Navigator.pop() to dismiss a dialog.
Similar to startActivityForResult on Android, a route can also return a result when pop is called. Think of a dialog that lets you pick a date.
When to use State:
Use State when the screens are a logical unit, e.g.:
When you load a list of items from a server, you would have 4 different states:
Loading
"An error occured..."
Placeholder displayed when the list is empty
The ListView
A form with multiple steps
A screen with multiple tabs (in this case the navigations is handled by the tab bar)
A "Please wait" overlay that blocks the screen while a POST request is sent to the server
After all Navigator is also a StatefulWidget that keeps track of the route history. Stateful widgets are a fundamental building block of Flutter. When your app is really complex and Navigator does not fit your needs, you can always create your own StatefulWidget for full control.
It always helps to look at Flutter's source code (CTRL + B in Android Studio).
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new Page1(title: 'Flutter Demo Home Page'),
);
}
}
class Page1 extends StatefulWidget {
Page1({Key key, this.title}) : super(key: key);
final String title;
#override
Page1State createState() => new Page1State();
}
class Page1State extends State<Page1> {
StreamController<int> streamController = new StreamController<int>();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new RaisedButton(child: new Text("This is Page 1, Press here to go to page 2"),onPressed:()=>streamController.add(2) ,),
),
);
}
#override
void initState() {
streamController.stream.listen((intValue){
print("Page 1 stream : "+intValue.toString());
if(intValue==2){
Navigator.of(context).push(new MaterialPageRoute(builder: (context)=>Page2(title: "Page 2",)));
}
});
super.initState();
}
#override
void dispose() {
streamController.close();
super.dispose();
}
}
class Page2 extends StatefulWidget {
Page2({Key key, this.title}) : super(key: key);
final String title;
#override
Page2State createState() => new Page2State();
}
class Page2State extends State<Page2> {
StreamController<int> streamController = new StreamController<int>();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new RaisedButton(child: new Text("This is Page 2, Press here to go to page 2"),onPressed:()=> streamController.add(1),),
),
);
}
#override
void initState() {
streamController.stream.listen((intValue){
print("Page 2 stream : "+intValue.toString());
if(intValue==1){
Navigator.of(context).push(new MaterialPageRoute(builder: (context)=>Page1(title: "Page 1",)));
}
});
super.initState();
}
#override
void dispose() {
streamController.close();
super.dispose();
}
}
Sorry for bad formatting. You can run this code without any dependencies. Hope it helped