Go Router (Flutter)- push the exact same route and refresh that route - flutter

With GoRouter, Is there a way to push the exact same route with a different parameter and make the page reload? I am thinking of something like Navigator.pushReplacement
I am using context.go('/my_page/?param={someID}') to push to MyPage (a stateful widget)
The initial push to this route works fine and I load up the page for the particular ID
I am trying to push this same route again (replace the route and reload with different ID) using context.go('/my_page/?param={differentID}'). My breakpoints are hitting the return statement in my GoRoute pageBuilder, and I also hit a breakpoint in MyPage.build. I see the new ID passed into this widget when breakpointing in the build.
But the Page does not rebuild visually (or break in initState - side note, init state is used to load up a couple cubits with the passed in ID - could the page being a stateful widget be the problem?)
Maintain state on the Material Page is false. Also, pushing different routes works just fine.
Is this a stateful widget issue (meaning relocate all of my cubit init calls)? or is there a different way to push the same route?
EDIT _____________
This question was specific to rebuilding the same route, but the greater problem I was working on was to infinitely drill into the same page over and over again, while maintaining the navigation stack.
I was using a state manager to hold a list of routes and trigger the navigation by using context.go() to replace the current route completely.
is there a way to dynamically nest routes with context.push() while maintaining the entire nav stack?

After some research, I have noticed that you can redirect to the same page by using pageKey: UniqueKey() in the definition of the GoRoute().
Example code from my project:
GoRoute(
path: RoutePaths.serviceDetail.value,
name: RouteNames.serviceDetail.value,
pageBuilder: (context, state) {
return CustomTransitionPage<void>(
key: UniqueKey(),
child: const ServiceDetailPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) =>
PageTransitions.subpageTransition(animation, child),
);
},
),

I think context.replace would accomplish what you are trying to do, since it should replace what is currently on the stack.

Related

Keep Navigator state data (provider/InheritedWidget) when pushing routes

In my flutter app I have a list of instances, when user clicks one I open the instance page and provide the InstanceData model to it using
Navigator.push(context, MaterialPageRoute(builder: (context) =>
ChangeNotifierProvider(
create: (_) => InstanceData(...),
child: Instance()
)
));
This does work, but when the Instance widget does Navigator.push, the provider data is lost because my provider is below the app's Navigator.
I googled for a solution and found that I can do:
Navigator.push(context, MaterialPageRoute(builder: (context) =>
ChangeNotifierProvider(
create: (_) => InstanceData(),
child: Navigator(
onGenerateRoute: (settings) {
return MaterialPageRoute(
builder: (context) => Instance()
);
},
)
)
));
Here's the dartpad to show it: https://dartpad.dev/?id=51fcbabb1ad401afb193558ccde3b5e1
Now, all (even deeply nested) widgets that are opened from Instance() will have access to the InstanceData. Also, I actually need multiple nesting. E.g. we click Instance and create Provider for it, then inside Instance we click Area and I want all widgets from Area() and below to have access to both InstanceData and AreaData, so I will need to create another Navigator to hold AreaData. Think of it as in this example:
https://medium.com/coding-with-flutter/flutter-case-study-multiple-navigators-with-bottomnavigationbar-90eb6caa6dbf
which shows that we keep separate history inside each Tab. Now, if each my Instance is in its own Tab, and Instance also has tabs panel with each tab being Area that also has to keep its own history - that's what I need.
However I am new to flutter and I am not sure if that's the correct approach. Creating own Navigator on each route push just so that my Inherited/Provider data is accessible? Looks like a hack to me.
E.g. what if I want to show two Instance() widgets side by side - I would wrap them with their own Navigator just to pass the data but isn't navigator supposed to track pages/screens instead? How will it behave with the Back button?
So can somehow confirm the solution and if not, them suggest a state management for flutter that will allow to create do something like
Navigator.push(context, MaterialPageRoute(builder: (context) =>
CoolProvider(
create: (_) => InstanceData(...),
child: Instance()
)
));
where CoolProvider does work for any Navigator.push() inside any child widget of the Instance(), even if that's some "CoolNavigator.push" instead. Maybe it's some kind of sub-routes? E.g. Navigator.pushSubRoute() so that the main route is still there?
Maybe it's not even about Navigator? E.g. in the side-by-side Instances having Navigator is almost meaningless - user can't navigate to both instances at once, but I do need each Instance() to be clickable and to go to nested Areas with the InstanceData() available to those widgets.
Update: In fact I tried it side by side here:
https://dartpad.dev/?id=3c299f3972ac18a1b70b841380eaffef
and it seems to be working fine, because without the Navigator the push just replaces the whole page, while with navigator it correctly replaces just the half-side widget.
So is it the way I suppose to do? Any drawbacks - need to use navigatorKey, etc?
P.S. There are of course alternatives like passing InstanceData to each child ctor but that's a lot of manual properties passing around... having the Provider is surely a better way.

Flutter: Changing Cards in Screen without rebuilding the whole page

I am pretty new to Flutter / Dart and right now, I have a problem. I have a homepage in my App and it displays an AppBar, a BottomNavigationBar and a body. The body is a simple card with content in it. The problem is: I need to be able to display three different cards, depending on the status I get from a server.
So there is the ActiveCard, the InactiveCard and the ErrorCard. The ErrorCard has a Button to go back to the InactiveCard and the ActiveCard as well as the InactiveCard should be wrapped in an Inkwell or similar to start a ride for example.
Now I am wondering: I am not sure how to implement all this. Can I just create three Stateless Widgets for each Card and depending on the server status just "replace" or "change" the body in my homepage? And where can I "control" the current status? Like I said, the Cards need to be clickable and "erase" themselves to make space for the following card (e.g: InactiveTrip gets clicked, now there should be the Card for ActiveTrip, because I started my trip and the user should see that my trip is currently active)
I cant provide code really, because so far I have not really something with will be even close to the result I expect.
Maybe you have some tips and packages I can use.
You can convert the statelessWidget to statefulWidget ans use setState() method.This is
recommended for small application but if your apps have more complexity then you have learn the state Management like provider,bloc pattern or Mvvm architechture.
yes, you can use statelessWidget for this purpose.
and you can use bloc pattern
to manage the page state.
I will use stateless for page and stream for the card content:
//Card model is your data modal
StreamController<Card> _streamController = StreamController();
Card _currentCard = /* default inactivate */;
StreamBuilder<bool>(
stream: _streamController.stream,
initialData: _currentCard,
builder: (BuildContext context, AsyncSnapshot<Card> snapshot) {
//return Container(/*content of your card*/);
},
),
//Everytime you need to change the card content, you sink the new value
_streamController.sink.add(_currentCard = /*new card data modal*/);

Flutter detect when Navigator is ready

Working on a Flutter app, I am handling deeplinks with a SDK for which I am given a listener that I must handle as soon as the app starts, therefore in the main().
Once a deeplink is received, I must navigate to the proper screen, based on the parameters passed along the deeplink data.
Since I receive the deeplink in the main function, I am detached from the Context of the app, therefore to access the NavigatorState I used a navigation singleton with a GlobalKey passed to my CupertinoApp's navigatorKey. I later use this key to retrieve the NavigatorState and call push. (instead of Navigator.of(context)....
However, if opening the app from a deeplink, it is very likely that the navigatorKey does not contain anything (yet).
How can I detect/wait until the Navigator is ready ?
As of right now my approach is to add a WidgetsBinding.instance.addPostFrameCallback in my App's initState, that resolves a promise to indicate when the GlobalKey pointing to the NavigatorState can be used... I'm sure there is a beter way to achieve this.
For this i make the initialRoute as a Loading screen and execute all my logic inside it , that way i can have different initialRoutes and i don't have to worry about not having MediaQuery, or Navigator, or even ThemeData.
However the question specifies a way to know if the navigator is available, for this i would use the builder function of MaterialApp/WidgetsApp/CupertinoApp, it's used to override the navigator altought i use it to add pading and a global background color and even override the navigator widget with a custom one,
CupertinoApp(
...
builder: (context, child){
//The navigator is ready
Future.delayed(Duration.zero,
() => print(navigatorKey.currentContext)); //We use a delayed since the child needs to be returned first. It's kinda like a hack 7u7
return child;
}

Flutter animations package - how to await Navigator.push() to do something when user navigates back

First of all, I'm using the animations package from Flutter and my question is only about using this package:
Before using that package, I pushed the second view to the Navigator with the await keyword, so when the user goes back from this second view, the code after this gets called:
await Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailView()),
);
loadData();
Now after integrating this animations library, I don't call the Navigator.push() myself, but only define the target widget and the navigation is done by the library.
Whereas till now, the code for any list item was just the ListTile widget, now it looks like that:
return OpenContainer(
openBuilder: (BuildContext _, VoidCallback closeContainer) {
return DetailView();
},
closedBuilder: (BuildContext _, VoidCallback openContainer) {
return _buildListTile(openContainer);
},
);
In the _buildListTile method the ListTile is wrapped with an InkWell which takes this VoidCallback openContainer for the onTap parameter.
What I can't find out is how to wait for the user clicking back on the second and thus coming back to the first view. I need to (re)load the data as shown in the first code snippet. Has anyone done that and can tell me?
I tried to play around with the openBuilder and closedBuilder, but unfortunately without success...
Thank to this GitHub issue two days ago the user 'The-Redhat' push on animations package master branch the change that enable OpenContainer onClose event
To use it before package official version release simply replace animations package in your pubspec.yaml with
animations:
git:
url: git://github.com/flutter/packages.git
path: packages/animations
At this point you can simply pass your custom function to OpenContainer widget that will be executed every time it will be closed. In you case you can now update data when user come back to "parent widget".
UPDATE
From 2 June 2020 this feature is available on animations v1.1.0
Add OpenContainer.onClosed, which is called with a returned value when the container was popped and has returned to the closed state.

Calling Navigator.of anywhere without context?

I've been setting up a tabbed navigation app based on this wonderful tutorial (https://medium.com/coding-with-flutter/flutter-case-study-multiple-navigators-with-bottomnavigationbar-90eb6caa6dbf).
Now I would like to display a modal overlay login route that covers the whole screen. My login controller checks if the user is logged in and I would like to fire an event on which the modal login route appears. The problem I have is now, that I don't have a context object where I receive the signal to display the login route:
Navigator.of(context).pushReplacementNamed('/');
How can I solve this or is this the wrong approach?
My User controller is a singleton object that gets initiated at app start. It checks then the user data model and if that is not set, it wants to invoke the login screen / route.
Thanks for any pointer in the right direction.
Martin
Define a navigator key that accesses from everywhere in-app (e.g in main class global space ) and pass it to root MaterialApp navigator key property in the build method
final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
then :
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
//...
);
}
then you can access context everywhere with doing like this:
navigatorKey.currentContext
example of navigating:
Navigator.of(navigatorKey.currentContext).pushReplacement(
MaterialPageRoute(builder: (_) => SecondScreen()));
You could use a globalKey to access the context of the widget u like (you have to pass it a key in the constructor) , and leave it in the global space, or static in any class....
although not the most elegant approach, should work
you can use this package to skip the required context.
https://pub.dev/packages/one_context
// go to second page using named route
OneContext().pushNamed('/second');
// go to second page using MaterialPageRoute
OneContext().push(MaterialPageRoute(builder: (_) => SecondPage()));