AppBar navigation for nested pages - flutter

Normally you would have a Scaffold with its own AppBar on every page, but why is that needed with go_router? Why can't you just have a single Scaffold with an AppBar and let that handle navigation.
Going to the SecondScreen in this example won't update the AppBar and show the back button.
Why is this not possible?
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() => runApp(const MyApp());
final GoRouter _router = GoRouter(
routes: <RouteBase>[
ShellRoute(
builder: (context, state, child) {
return Scaffold(
body: child,
appBar: AppBar(
title: const Text('Test'),
),
);
},
routes: [
GoRoute(
path: '/',
pageBuilder: ((context, state) => const NoTransitionPage(
child: HomeScreen(),
)),
routes: [
GoRoute(
path: 'second',
pageBuilder: ((context, state) => const NoTransitionPage(
child: SecondScreen(),
)),
),
],
),
],
),
],
);
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
#override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: () => context.go('/second'),
child: const Text('Go to the Second screen'),
),
);
}
}
class SecondScreen extends StatelessWidget {
const SecondScreen({super.key});
#override
Widget build(BuildContext context) {
return const Center(
child: Text('Second screen'),
);
}
}

Edit: SecondScreen in this example won't update the AppBar and show the back button :
Because you are using the same appBar for all the child compnents. The main purpose of the ShellRoute is have same appBar. For it to have backButton it cannot have same appBar. The appBar should change.Which violates the usage of ShellRoute
For you to have backbutton you should navigate from ShellRoute to GoRoute or GoRoute to ShellRoute , Withtin the ShellRoute back button is not possible as it creates a shell around its children. So having backbutton inside the ShellRoute would defeat the purpose of having a same appBar throughout.
There are two corrections
change second to /second
GoRoute(
path: '/second', 👈 Change to this
pageBuilder: ((context, state) => const NoTransitionPage(
child: SecondScreen(),
)),
),
For appBar to be having backbutton use context.push('/second') instead of context.go('/second')
- context.go('/second') // Used to replace a page
- context.push('/second') // Used to push a page to navigation stack.
child: ElevatedButton(
onPressed: () => context.push('/second'), // 👈 change it to push
child: const Text('Go to the Second screen'),
),
Refer this examples:
How to change the app bar title depending on the selected GoRouter route?
How to Use Shell Route with GoRoute in same hierarchy Routes

Related

Back button not showing when using ShellRouter with GoRouter in flutter

I have a simple structure
A ShellRoute with Home as a initial page a sub route with /post/:id I navigator from the homepage to the post page using push but the backbutton is not showing on the app bar.
Also it's worth noting if on the Post widget the context.canPop() returns true but in the didUpdateWidget Methods of the Shell WidgetGoRouter.of(context).canPop() returns false so my guess is that for some reason the context of the shell is not the same as the one of the page but my NavigatorState keys are the ones of the same _shellNavigator. Yet if I hot reload on the Post Widget the canPop method start returning true but the back button does not appear
I tried setting the two pages Home and Post at the same level with no luck (see comments). I read the doc and another answer on SO and I think I follow everything done. I might be missing something obvious.
go_router: ^6.0.9
flutter: 3.3.0
final GlobalKey<NavigatorState> _rootNavigator = GlobalKey(debugLabel: 'root');
final GlobalKey<NavigatorState> _shellNavigator =
GlobalKey(debugLabel: 'shell');
class Shell extends StatefulWidget {
const Shell({super.key, required this.child});
final Widget child;
#override
State<Shell> createState() => _ShellState();
}
class _ShellState extends State<Shell> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: const Text('Title'),
),
body: widget.child,
);
}
}
class App extends StatelessWidget {
App({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp.router(
routerDelegate: _router.routerDelegate,
routeInformationParser: _router.routeInformationParser,
routeInformationProvider: _router.routeInformationProvider,
);
}
final GoRouter _router =
GoRouter(navigatorKey: _rootNavigator, initialLocation: '/', routes: [
ShellRoute(
navigatorKey: _shellNavigator,
builder: (context, state, child) =>
Shell(key: state.pageKey, child: child),
routes: [
GoRoute(
parentNavigatorKey: _shellNavigator,
path: '/',
builder: (context, state) => Home(
key: state.pageKey,
),
routes: [
GoRoute(
parentNavigatorKey: _shellNavigator,
path: 'post/:id',
builder: (context, state) => Post(
key: state.pageKey,
),
)
]),
// GoRoute(
// parentNavigatorKey: _shellNavigator,
// path: '/post/:id',
// builder: (context, state) => Post(
// key: state.pageKey,
// ),
// )
])
]);
}
class Home extends StatelessWidget {
const Home({super.key});
#override
Widget build(BuildContext context) {
var data = [
{'id': 'Route a'},
{'id': 'Route b'},
];
return GridView.count(
crossAxisSpacing: 5,
mainAxisSpacing: 5,
crossAxisCount: 2,
children: data
.map((e) => Center(
child: InkWell(
onTap: () {
GoRouter.of(context).push("/post/${e['id']}");
// context.push("/post/${e['id']}");
},
child: Text(e['id']!,
style: const TextStyle(
color: Colors.black,
)),
),
))
.toList(),
);
}
}

Having Flutter gorouter redirect property doesn't let navigation work

Having Flutter gorouter redirect property at top-level doesn't let navigation to go to/push any other page. It redirects to initialLocation upon pressing routing button instead of intended page(ItemOne()).
Log:
[GoRouter] going to /one
[GoRouter] redirecting to RouteMatchList(/)
Gorouter Code:
void main() => runApp(const NavApp());
const isAuth = true;
class NavApp extends StatelessWidget {
const NavApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: GoRouter(
debugLogDiagnostics: true,
initialLocation: '/',
redirect: (context, state) => isAuth ? '/' : '/one',
routes: [
GoRoute(
path: '/',
builder: (context, state) => const NavHome(),
),
GoRoute(
path: '/one',
builder: (context, state) => const ItemOne(),
),
],
),
);
}
}
HomePage Code:
class NavHome extends StatelessWidget {
const NavHome({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Nav Home'),
),
body: Center(
child: IconButton(
onPressed: () => context.push('/one'),
icon: const Text('Push One'),
),
),
);
}
}
Page we route to using button:
class ItemOne extends StatelessWidget {
const ItemOne({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Item 1'),
),
body: const Text('This is page for Item One'),
);
}
}
Please use it like this
Center(
child: IconButton(
onPressed: () => context.go('/one'),
icon: const Text('Push One'),
),

alert dialog pops up even when different route is called

I have a simple flutter app with two screens. On the first screen, i have an alert dialog that pops up every time a user visits the screen.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() => runApp(const MyApp());
/// The route configuration.
final GoRouter _router = GoRouter(
routes: <RouteBase>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) {
return const HomeScreen();
},
routes: <RouteBase>[
GoRoute(
path: 'details',
builder: (BuildContext context, GoRouterState state) {
return const DetailsScreen();
},
),
],
),
],
);
/// The main app.
class MyApp extends StatelessWidget {
/// Constructs a [MyApp]
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp.router(
routeInformationProvider: _router.routeInformationProvider,
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate);
}
}
/// The home screen
class HomeScreen extends StatefulWidget {
/// Constructs a [HomeScreen]
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
void initState() {
Timer(const Duration(seconds : 1), (() {
showDialog(
context: context,
builder: (context) {
return someDialogy();
});
print('i have been called forth');
}));
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home Screen')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => context.go('/details'),
child: const Text('Go to the Details screen'),
),
],
),
),
);
}
}
/// The details screen
class DetailsScreen extends StatelessWidget {
/// Constructs a [DetailsScreen]
const DetailsScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Details Screen')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <ElevatedButton>[
ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('Go back to the Home screen'),
),
],
),
),
);
}
}
Widget someDialogy () {
return AlertDialog(
content: Center(
child: Text('data'),
),
);
}
When i try to navigate to my second screen using a hyperlink on web say http://localhost/secondscreen, the popup from my first screen shows up.
My guess is that in constructing the route stack, flutter calls the initstate in my first page which does show my popup. What is the best way to go around this while maintaining the popup that shows when my first page is called?
it would be helpful to show some mode code on this, like the somedialogy() method
anyways I suspect the issue might be with your routing solution,
try
onPressed: () {
// Navigate to the second route when the button is pressed
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
},
I think its all about Timer
try this:
Future.delayed(Duration(seconds: 1), () {
showDialog(
context: context,
builder: (context) {
return someDialogy();
});});
read this for more about Timer Understanding Flutter’s Timer class and Timer.periodic
and this 2 Types of Flutter Delay Widgets

go_router package is not supporting hot reload on chrome in Flutter

Can you please help me with a solution to a problem that I am facing using go_router package in my flutter application.
Sample code:
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
final _router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
name: 'homepage',
builder: (context, state) => const MyHomePage(),
routes: [
GoRoute(
path: 'shoppingcart',
name: 'cartpage',
pageBuilder: (context, state) => MaterialPage(
key: state.pageKey,
fullscreenDialog: true,
child: const ShoppingCartScreen(),
),
)
],
),
],
);
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
routerConfig: _router,
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({
super.key,
});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('App Bar'),
actions: [
IconButton(
icon: const Icon(Icons.shopping_cart),
onPressed: () => context.pushNamed('cartpage'),
),
],
),
body: Container(
color: Colors.green[200],
child: const Center(child: Text("Home Page")),
));
}
}
class ShoppingCartScreen extends StatelessWidget {
const ShoppingCartScreen({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('shopping cart page'),
),
body: Container(
color: Colors.purple[200],
child: const Center(child: Text("Shopping Cart Screen")),
),
);
}
}
Steps to point out issue:
-Step 1: Run below flutter app code on chrome browser.
-Step 2: Click on Shopping Cart icon to go to another screen.
-Step 3: click hot reload
by clicking hot reload go_router is not persisting "Shopping Cart Screen", instead it is showing home screen.
What is the solution if I want to stay on same screen after hot reload?
I am using go_router 5.2.0 in my flutter project but it is not supporting hot reload with the code mentioned above.

GoRouter - Can I push 2 pages at once?

I'm using go_router and I am about to do this in a callback of one of my buttons:
EvelatedButton(
onPressed: () {
GoRouter.of(context)
..push('/page-1')
..push('/page-2');
},
)
This is to push 2 pages in the history at once. After the user click on this button, he ends up on the page page-2 and when he pops the page, there is page-1.
Is it acceptable to do that or is there any reason not to do it?
What would be those reasons and what should I do instead?
I don't think I've seen anything like that in go_router's examples.
For more context, here is a code snippet (or checkout https://github.com/ValentinVignal/flutter_app_stable/tree/go-router/push-twice-at-once):
When the button is pressed, I want to display the dialog page with the page-1 in the background.
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
final router = GoRouter(
initialLocation: '/page-0',
routes: [
GoRoute(
path: '/page-0',
builder: (_, __) => const Page0Screen(),
),
GoRoute(
path: '/page-1',
builder: (_, __) => const Page1Screen(),
),
GoRoute(
path: '/dialog',
pageBuilder: (context, state) => DialogPage(
key: state.pageKey,
child: const DialogScreen(),
),
),
],
);
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: router,
);
}
}
class Page0Screen extends StatelessWidget {
const Page0Screen({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page 0')),
body: Center(
child: ElevatedButton(
onPressed: () {
GoRouter.of(context)
..push('/page-1')
..push('/dialog');
},
child: const Text('Push'),
),
),
);
}
}
class Page1Screen extends StatelessWidget {
const Page1Screen({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page 1')),
body: const Center(
child: Text('Page 1'),
),
);
}
}
class DialogScreen extends StatelessWidget {
const DialogScreen({super.key});
#override
Widget build(BuildContext context) {
return const AlertDialog(
title: Text('Dialog'),
);
}
}
class DialogPage extends Page {
const DialogPage({
required this.child,
super.key,
});
final Widget child;
#override
Route createRoute(BuildContext context) {
return DialogRoute(
settings: this,
context: context,
builder: (context) {
return child;
},
);
}
}
Assuming your goal is to display a dialog you can use the showDialog function in flutter.
Below is a sample
showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Basic dialog title'),
content: const Text('A dialog is a type of modal window that\n'
'appears in front of app content to\n'
'provide critical information, or prompt\n'
'for a decision to be made.'),
actions: <Widget>[
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Disable'),
onPressed: () {
GoRouter.of(context).pop();
},
),
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Enable'),
onPressed: () {
GoRouter.of(context).pop();
},
),
],
);
},
);
go_router doesn't support pushing two routes at the same time. And it is not a good practice to push 2 pages at the same time.
What can you do instead?
You can transition from page1 to page2
Go to dialog page in the init method of the page2 using context.go('/dialog');
On exiting dialog page you can use context.pop() which will land you in page1