Flutter go_router parameters within ShellRoute not working - flutter

I am using go_router and need have setup a ShellRoute. It is working fine so far. Now one of the routes inside that ShellRoute should have a route with params. And I can not make it work..
This is my setup:
final rootNavigatorKey = GlobalKey<NavigatorState>();
class AppRouter {
static final _shellNavigatorKey = GlobalKey<NavigatorState>();
static final router = GoRouter(
initialLocation: IntroView.path,
debugLogDiagnostics: true,
navigatorKey: rootNavigatorKey,
routes: [
ShellRoute(
navigatorKey: _shellNavigatorKey,
pageBuilder: (context, state, child) {
return NoTransitionPage(
child: ScaffoldView(
initLocation: state.location,
child: child,
),
);
},
routes: [
GoRoute(
name: ProjectsView.name,
path: ProjectsView.path,
parentNavigatorKey: _shellNavigatorKey,
pageBuilder: (context, state) {
return const FadePageTransition(
page: ProjectsView(),
);
},
routes: [
GoRoute(
parentNavigatorKey: rootNavigatorKey,
path: 'projects/:projectTitle',
pageBuilder: (context, state) {
return FadePageTransition(
page: ProjectDetailScaffold(
project: Projects.values.byName(
state.params['projectTitle']!,
),
),
);
},
),
],
),
...
And I am trying to push to ProjectDetailScaffold like this:
rootNavigatorKey.currentContext!.push(
'/projects/wishlists',
);
But I get an error that no route exists for /projects/wishlists...
What am I missing here?
Let me know if you need any more info.

I figured it out by myself:
The problem was that I specified path of the subRoute wrong. I added it's parent path as well when I only need to add :projectTitle. Here is the working solution:
GoRoute(
parentNavigatorKey: rootNavigatorKey,
path: ':projectTitle',
pageBuilder: (context, state) {
return FadePageTransition(
page: ProjectDetailScaffold(
project: Projects.values.byName(
state.params['projectTitle']!,
),
),
);
},
),

Related

Flutter: Nested navigation inside a page using go_router

In my Flutter app, I use go_router to navigate between pages.
Here are the current pages in my app:
accounts_page
add_account_page
import_accounts_page
Now, I would like to implement nested navigation inside add_account_page, so I can add a new account using multiple steps, let's say:
account_info_step
account_type_step
account_detail_step
Here is what I tried:
final _navigatorKey = GlobalKey<NavigatorState>();
final _addAccountNavigatorKey = GlobalKey<NavigatorState>();
late final router = GoRouter(
navigatorKey: _navigatorKey,
initialLocation: "/accounts_page",
routes: [
ShellRoute(
navigatorKey: _addAccountNavigatorKey,
builder: (context, state, child) => AddAccountPage(child: child),
routes: [
GoRoute(
parentNavigatorKey: _addAccountNavigatorKey,
name: "account_info_step",
path: "/account_info_step",
builder: (context, state) => const AccountInfoStep(),
),
GoRoute(
parentNavigatorKey: _addAccountNavigatorKey,
name: "account_type_step",
path: "/account_type_step",
builder: (context, state) => const AccountTypeStep(),
),
GoRoute(
parentNavigatorKey: _addAccountNavigatorKey,
name: "account_detail_step",
path: "/account_detail_step",
builder: (context, state) => const AccountDetailStep(),
),
],
),
GoRoute(
name: "accounts_page",
path: "/accounts_page",
pageBuilder: (context, state) => const AccountsPage(),
),
GoRoute(
name: "import_accounts_page",
path: "/import_accounts_page",
pageBuilder: (context, state) => const ImportAccountsPage(),
),
],
);
And then I call context.pushNamed("account_info_step"), but nothing happens.
Is it possible then to use go_router to implement nested navigation inside add_account_page and if yes, how?
Thanks.
If your intention is to get to AccountInfoStep() full path leading to this builder should be added in your case:
context.push("/accounts_page/account_info_step")
I assume you are missing name for your top routes to represent proper nesting to use the context.pushNamed()
I think the way you are trying to create nested navigation is not right. Also you don't need to use ShellRoute, you can use shell route if you want to create something like a persistent bottom navigation bar and changing only the child while keeping the bottom nav bar at the bottom.
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:untitled/homepage.dart';
import 'package:untitled/test_page.dart';
final _navigatorKey = GlobalKey<NavigatorState>();
// final _addAccountNavigatorKey = GlobalKey<NavigatorState>();
class MyRouter{
static final router = GoRouter(
navigatorKey: _navigatorKey,
initialLocation: "/accounts_page",
routes: [
GoRoute(
name: "accounts_page",
path: "/accounts_page",
builder: (context, state) => const MyHomePage(),
routes: <RouteBase>[
GoRoute(
name: "account_info_step",
path: "account_info_step",
builder: (context, state) => const TestPage(),
),
GoRoute(
name: "account_type_step",
path: "account_type_step",
builder: (context, state) => const TestPage(),
),
GoRoute(
name: "account_detail_step",
path: "account_detail_step",
builder: (context, state) => const TestPage(),
),
]
),
],
);
}
I think this is what you are looking for. Also, note that you don't need to add a '/' in path for nested screens.
And to navigate to those nested screens, you can do something
like this:-
context.go('/accounts_page/account_info_step');

Go_Router Pass Object to new route

I want to pass object from the listview to the sub route. It seems no way to pass the object.
Is there any how to do it?
GoRouter routes(AuthBloc bloc) {
return GoRouter(
navigatorKey: _rootNavigatorKey,
routes: <RouteBase>[
GoRoute(
routes: <RouteBase>[
GoRoute(
path: loginURLPagePath,
builder: (BuildContext context, GoRouterState state) {
return const LoginPage();
},
),
GoRoute(
path: homeURLPagePath,
builder: (BuildContext context, GoRouterState state) =>
const HomePage(),
routes: <RouteBase>[
GoRoute(
path: feeURLPagePath,
name: 'a',
builder: (context, state) => FeePage(),
routes: [
/// Fee Details page
GoRoute(
name: 'b',
path: feeDetailsURLPagePath,
builder: (BuildContext context, GoRouterState state) =>
const FeeDetails(),
),
]),
],
),
],
path: welcomeURLPagePath,
builder: (BuildContext context, GoRouterState state) =>
const SplashPage(),
),
],
refreshListenable: GoRouterRefreshStream(bloc.stream),
debugLogDiagnostics: true,
initialLocation: welcomeURLPagePath,
},
);
}
The error says no initial match found for feeDetails!
Use extra parameter in context.goNamed()
Example:
Object:
class Sample {
String attributeA;
String attributeB;
bool boolValue;
Sample(
{required this.attributeA,
required this.attributeB,
required this.boolValue});}
Define GoRoute as
GoRoute(
path: '/sample',
name: 'sample',
builder: (context, state) {
Sample sample = state.extra as Sample; // -> casting is important
return GoToScreen(object: sample);
},
),
Call it as:
Sample sample = Sample(attributeA: "True",attributeB: "False",boolValue: false)
context.goNamed("sample",extra:sample );
Receive it as:
class GoToScreen extends StatelessWidget {
Sample? object;
GoToScreen({super.key, this.object});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(
object.toString(),
style: const TextStyle(fontSize: 24),
)),
);
}
}

How can i get Uri params in Flutter Mobile when open with Deeplink

I configured Navigator through GoRouter and I want to know how to receive a parameter through a deep link.
Instead of configuring arguments in Widget corresponding to the destination,
For iOS simulators, on the connected terminal,
xcrun simctl openurl booted "customScheme://myHost.name.com/login?p=0"
If I enter , I want to enter the login screen and use the parameter p=0 in Login Widget at the same time. Is there any other way?
I considered using uni_links, but I do not want to use it because it interferes with Flutter's basic Deeplinking activities.
(The reason is that if you run the command from the terminal as shown above, if you have uni_links, you will only move to the root and not reach the sub-destination.)
Below is the routerConfig of GoRouter.
final _router = GoRouter(
initialLocation: '/home',
navigatorKey: _rootNavigatorKey,
observers: [
GoRouterObserver(),
routeObserver
],
routes: [
GoRoute(
path: "/",
builder: (context, state) {
return const Root();
},
routes: [
ShellRoute(
navigatorKey: _shellNavigatorKey,
builder: (context, state, child) {
return ScaffoldWithNavBar(child: child);
},
routes: [
GoRoute(
path: 'home',
builder: (context, state) {
return const Home();
},
),
GoRoute(
path: 'discover',
builder: (context, state) {
return const Discover();
},
),
GoRoute(
path: 'shop',
builder: (context, state) {
return const Shop();
}),
],
),
GoRoute(
path: 'login',
parentNavigatorKey: _rootNavigatorKey,
builder: (context, state) {
return const Login();
},
routes: loginRouter),
],
)
],
);

How to get route history and queryParameters

My goRouter like:
GoRouter(initialLocation: '/', routes: [
GoRoute(
path: '/',
builder: (context, state) => HomeScreen(),
routes: [
GoRoute(
path: 'page1',
name: 'page1',
builder: (context, state) => Page1Screen(),
),
GoRoute(
path: 'page2',
name: 'page2',
builder: (context, state) => Page2Screen(),
),
GoRoute(
path: 'page3',
name: 'page3',
builder: (context, state) => Page3Screen(),
),
],
),
]);
: HomeScreen use go to page1.
: Page1Screen use push to page2
: Page2Screen use push to page3
: page3 wants use go to return page1
In go_router 5.1.10 version i can use this code get route history to return page1.
final matches = goRouter.routerDelegate.currentConfiguration.matches;
final route = matches.lastWhereOrNull((value) => value.fullpath.contains('page1'));
context.go(goRouter.namedLocation(kRouteHotel, params: route.encodedParams, queryParams: route.queryParams));
but 5.2.0 has remove encodedParams and queryParams.
How should I do to have the same function?

Line coverage Unit Test in flutter with GoRouter

I am beginner in testing in flutter,
How to implement a test in this class
This is the code
import 'package:go_router/go_router.dart';
import 'package:moxify_app/src/features/features.dart';
final router = GoRouter(
debugLogDiagnostics: true,
routes: [
GoRoute(
name: 'home',
path: '/home',
builder: (context, state) => const HomePage(),
),
GoRoute(
name: 'login',
path: '/',
builder: (context, state) => const LoginPage(),
),
],
);
I happen to came across this when doing unit tests. To cover that edge case, iterate through your routes and check if the route pagebuilder property is not null.
test(
'check if routes are valid',
() async {
for (var route in goRoutes) {
expect(route.name, isNotNull);
expect(route.path, isNotNull);
expect(route.pageBuilder, isNotNull);
}
},
);