GoRouter | Extra is cleared on hot reload - flutter

Let's say I have a route defined as the following:
GoRoute(
path: Screen.classDetail.path,
builder: (context, state) => AdminClassScreen(
classItem: state.extra as Class,
),
),
and I navigate to it using
router.push(Screen.classDetail.path, extra: extra);
When I hot reload on Web, the application goes into a bad state because the object I am passing in as extra is not preserved. Is there a way around this?

Related

do I need just one didChangeAppLifecycleState in Main for app with multi routes

Can I safely use just one didChangeAppLifecycleState, set up in Main, when I have 2, or more, routes, and all State changes seem to notify the didChange.. in Main?
My app has 2 routes with Main calling /Manage and /Manage calls /Game. I noticed that if I set up didChangeAppLifecycleState in Main and one of the routes, when I change State in that route, the didChange..() function is activated in both Main and the route. Can I safely assume this is how it is meant to work, and use only the didChangeAppLifecycleState in Main?
Does not make sense to me, but there must be a reason
You can set OrientationBuilder code on runApp like this.
runApp(
OrientationBuilder(
builder: (context, orientation) {
return Phoenix(
child: ProviderScope(
observers: [RiverpodLogger()],
child: MyApp(environment, orientation),
),
);
}
),
);

How to get previous location of route using Go Router?

What im trying to do is for example if router came from login to verification screen do something but when prev location from sign up and not login dont do something.
So i want to get the prev location to do some logic to it, im aware that i can pass params to pages then do the logic from it for example passing a bool and do the logic if true or false but i want a better or right way, if you know want i mean. Thank you.
And i encounter this method from go router, what it does? is it for logging purpose only, im trying to add route name or location in the parameter but it throws an error. What param do i need to input in here, i dont know where to find Route thank you.
context.didPush(Route<dynamic> route, Route<dynamic>? previousRoute);
You should pass the route you are coming from to the route you are pushing to.
This way you can know where you came from and apply the specific logic.
I found a solution by creating a main route then put the other in the subroute of the router then i will call
context.location
in the page from GoRouter pagckage then it will return the path of the location
GoRoute(
name: AppRoute.login,
path: '/login',
builder: (context, state) => LoginView(credentials: state.extra as Credentials?),
routes: [
GoRoute(
name: AppRoute.signUp,
path: 'signup',
pageBuilder: (BuildContext context, GoRouterState state) => CupertinoPage<void>(
key: state.pageKey,
child: const SignupView(),
),
),
GoRoute(
name: AppRoute.verifyNumber,
path: 'verifyNumber',
pageBuilder: (BuildContext context, GoRouterState state) => CupertinoPage<void>(
key: state.pageKey,
child: VerifyNumberView(user: state.extra as User),
),
),
]
),
its important that you put your sub route because if not it will only return its own route not including the base route
result with subroute : /login/forgot_password
result without subroute: /forgot_password
base on the result i can do the logic

NavigationRail with go_router

I'm developing a web/desktop application that has a fairly standard UI layout involving a NavigationRail on the left and a content pane taking up the remainder of the screen.
I've reciently added go_router so I can properly support URLs in web browsers, however in doing so I have lost the ability to have any form of transition/animation when moving between pages as calling context.go() causes a hard cut to the next page.
Theres also the issue that go_router routes have to return the full page to be rendered meaning I need to include the navigation rail on every page rather than each page being just the content relevant to that page. I believe this is also the main reason all animations are broken, because clicking a link effectively destroys the current navigation rail and builds a new one for the new page
I couldn't see anything in go_router but is there any form of builder API available that can output and refresh a single section of the page? I'm thinking of something like bloc's BlocBuilder which listens for state changes and rebuilds just the widget its responsible for when a change occures.
Alternatively, is there a way to update the current URL without rebuilding the whole page?
Or is go_router just not capable of what I'm after, and if so, are there any alternatives that can do this?
The overall effecti I'm after is similar to the material site https://m3.material.io/develop
Clicking around the various buttons feels like you are navigating around within an app rather than clicking on links and loading new pages
Thanks for your help
I solved this with a Shell Route. I got the idea when going through the go_router 5 migration guide. I saw that they had a navigatorBuilder property in which they were wrapping every route in a Scaffold with AppBar. And it said to migrate:
Before migration:
final GoRouter router = GoRouter(
routes: <GoRoute> [
GoRoute(
path: '/',
builder: (_, __) => const Text('/'),
),
GoRoute(
path: '/a',
builder: (_, __) => const Text('/a'),
)
],
navigatorBuilder: (BuildContext context, GoRouterState state, Widget child) {
return Scaffold(
appBar: AppBar(title: Text(state.location)),
body: child,
);
}
);
After Migration:
final GoRouter router = GoRouter(
routes: <GoRoute> [
ShellRoute(
builder: (_, GoRouterState state, child) {
return Scaffold(
body: child,
appBar: AppBar(title: Text(state.location)),
);
},
routes: [
GoRoute(
path: 'a',
builder: (_, __) => const Text('a'),
),
GoRoute(
path: 'b',
builder: (_, __) => const Text('b'),
)
],
),
],
);
Now every route in the ShellRoute.routes property will be wrapped with that Scaffold and AppBar, it will not rebuild upon navigation, so will behave as a persistent AppBar, NavigationBar, or whatever should. Now it shows the page transition animations when you do context.go('some_route') (for example from a NavigationBar in the ShellRoute). You also don't run into the issue Felix ZY was having, accessing GoRouter from the context.

Unable to access BLoC in go_router v5.0 redirect method, BLoC is not injected in the context

I'm using flutter_bloc and go_router.
This is an implementation of multiple-page-form, for this demo it's just 2 pages. I used ShellRoute to provide MultiFormBloc to 2 routes, so that both routes will have access to the same MultiFormBloc instance.
In the redirect method in step 2 route, I wanted to conditionally redirect user back to step 1 based on if user has completed step 1 or not (since that there's a possibility that user can request for step 2 page directly without finishing step 1, e.g. entering /step2 in the browser's search bar)
However, it seems like MultiFormBloc is not injected in the BuildContext provided in the redirect method.
How can I access bloc injected deep in the tree (not top level in main.dart) in the redirect method? Or is there a better way to do this conditional logic?
...
ShellRoute(
builder: (context, state, child) {
// Use [ShellRoute] for the sole purpose of providing [MultiFormBloc] to
// 2 routes. So that the 2 routes have access to the same bloc instance.
return BlocProvider(
create: (context) => MultiFormBloc(),
child: child,
);
},
routes: [
GoRoute(
name: 'step1',
path: 'step1',
builder: (context, state) => Step1(),
routes: [
GoRoute(
name: 'step2',
path: 'step2',
builder: (context, state) => Step2(),
redirect: (context, state) {
// If user haven't completed form 1, redirect user to form 1 page.
// Error accessing [MultiFormBloc], bloc not injected in context.
if (context.read<MultiFormBloc>().state.step1Value == null) {
return '/step1';
}
return null;
},
),
],
),
],
),
...
If you want to pass the same bloc to independent rotes you have to pass it’s value instead. You first create your bloc final myBloc = MyBloc(). Then, pass its value to each route.
// When you want to navigate to Step1()
BlocProvider.value(
value: myBloc,
child: Step1(),
)
// When you want to navigate to Step2()
BlocProvider.value(
value: myBloc,
child: Step2(),
)
Now, both Step1() and Step2() routes share the same bloc instance and have access to the same state.

Binding object and widget together in Flutter using Provider package

Imagine in the TodoScreen of the application, I have a TodoObjectList (list of todos obtained from some API) and I want to show them inside a list. So I created a list of TodoWidgets (StatelessWidget), each one having its own TodoObject as its property. Now I want TodoWidgets to be bound with its TodoObject, so I used the Provider package. The code is something like below (inside TodoScreen):
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ChangeNotifierProvider<TodoObject>(
builder: (_) => TodoObjectList[index],
child: Consumer<TodoObject>(
builder: (context, TodoObject, child) => TodoWidget(todo: TodoObject)
),
);
},
childCount: TodoObjectList.length,
),
)
This code works fine for the first time. But when I go back and navigate to TodoScreen for the second time (I won’t call the API again, TodoObjectList is already cached), Provider throws an error:
“A TodoObject was used after being disposed.”
I Know why this error is being thrown, but my question is how should I bind TodoWidget with TodoObject using provider, without facing this error when I have the API data stored somewhere.
You should use ChangeNotifierProvider.value instead. This link would probably help you if you had any follow up questions.