How to make go_router, Appbar, and Drawer work together in Flutter? - flutter

I'm using the go_router package in Flutter for app routing, but I'm running into issues when I use it alongside the default Flutter Appbar and Drawer widgets.
Typical "go" and "push" methods that I'm calling from clicks in the Drawer don't work as expected when pushing the back button.
The AppBar doesn't imply the leading back or menu behavior.
Is there something particular that needs to be done to get go_router to play nicely with the Flutter Navigator? Maybe I need to set some particular fields or a global key?
Here's what my setup looks like:
class MainApp extends ConsumerStatefulWidget {
const MainApp({Key? key}) : super(key: key);
#override
ConsumerState<MainApp> createState() => _MainAppState();
}
class _MainAppState extends ConsumerState<MainApp> {
late GoRouter router;
late Future<void> jwtInit;
#override
void initState() {
jwtInit = ref.read(jwtProvider.notifier).init();
router = GoRouter(
routes: [
GoRoute(
path: "/",
name: "home",
pageBuilder: (context, state) => MaterialPage<void>(
key: state.pageKey,
child: const HomeScreen(),
),
),
GoRoute(
path: "/settings",
name: "settings",
pageBuilder: (context, state) => MaterialPage<void>(
key: state.pageKey,
child: const SettingsScreen(),
),
),
GoRoute(
path: "/programs",
name: "programs",
pageBuilder: (context, state) => MaterialPage<void>(
key: state.pageKey,
child: const ProgramScreen(),
),
),
GoRoute(
path: "/programs/:programId",
name: "program",
pageBuilder: (context, state) => MaterialPage<void>(
key: state.pageKey,
child: ProgramDetailsScreen(
// programId: 39,
programId: int.parse(state.params["programId"]!),
),
),
),
GoRoute(
path: "/activity/:activityId",
name: "activity",
pageBuilder: (context, state) {
return MaterialPage<void>(
key: state.pageKey,
child: ActivityScreen(
id: int.parse(state.params["activityId"]!),
),
);
}),
GoRoute(
path: "/login",
name: "login",
pageBuilder: (context, state) => MaterialPage<void>(
key: state.pageKey,
child: const LoginScreen(),
),
),
],
errorPageBuilder: (context, state) => MaterialPage<void>(
key: state.pageKey,
child: const Scaffold(
body: Center(
child: Text("PAGE NOT FOUND!"),
),
),
),
// refreshListenable: api,
redirect: (context, state) {
final loggedIn = ref.read(jwtProvider.notifier).isLoggedIn;
final goingToLogin = state.location == '/login';
// the user is not logged in and not headed to /login, they need to login
if (!loggedIn && !goingToLogin) return '/login';
// the user is logged in and headed to /login, no need to login again
if (loggedIn && goingToLogin) return '/';
// no need to redirect - go to intended page
return null;
},
);
super.initState();
}
#override
Widget build(BuildContext context) {
//The reason for this FutureBuilder is to wait for the api key to
//load from storage before allowing the initial page to route. Otherwise
//the routing goes too fast and it looks logged out.
return FutureBuilder(
future: jwtInit,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
//Run the UI
return MaterialApp.router(
debugShowCheckedModeBanner: false,
title: 'MyApp',
theme: MyTheme.darkTheme(context),
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
);
} else {
return Container();
}
});
}
}
In my drawer, I'm calling the navigation like this:
onTap: () {
context.push("/settings");
}

Use ShellRouter with GoRouter to meet your requirement!
Example:
Router
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _shellNavigatorKey = GlobalKey<NavigatorState>();
final router = GoRouter(
initialLocation: '/',
navigatorKey: _rootNavigatorKey,
routes: [
ShellRoute(
navigatorKey: _shellNavigatorKey,
pageBuilder: (context, state, child) {
print(state.location);
return NoTransitionPage(
child: ScaffoldAppAndBottomBar(child: child));
},
routes: [
GoRoute(
parentNavigatorKey: _shellNavigatorKey,
path: '/home',
pageBuilder: (context, state) {
return NoTransitionPage(
child: Scaffold(
body: const Center(
child: Text("Home"),
),
),
);
},
),
GoRoute(
path: '/',
parentNavigatorKey: _shellNavigatorKey,
pageBuilder: (context, state) {
return const NoTransitionPage(
child: Scaffold(
body: Center(child: Text("Initial")),
),
);
},
),
],
),
],
);
ScaffoldAppAndBottomBar
class ScaffoldAppAndBottomBar extends StatelessWidget {
Widget child;
ScaffoldAppAndBottomBar({super.key, required this.child});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text(
"App Bar",
),
backgroundColor: Colors.amber,
),
body: SafeArea(child: child),
bottomNavigationBar: Container(
color: Colors.blue,
height: 56,
width: double.infinity,
child: const Center(child: Text("Bottom Navigation Bar")),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.red,
onPressed: () {
context.go('/home');
},
child: const Icon(Icons.home),
));
}
}
Output:
Initially
After pressing floating button
Refer detailed code and explaination of bottom NavigationBar using ShellRoute and GoRouter here

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'),
),

Flutter: How to use GoRouter with Bloc to route from Splash Screen to Login Screen

So I switched to Go router recently in my app since it is very easy to implement. But I am having trouble moving from Splash Screen to Login Screen. I have logic in my Splash Screen where I check if the user is logged in or not. Based on the user's auth, the screen either goes to Login Screen or the Home page.
This is Splash Screen.
class SplashScreen extends StatefulWidget {
static const routeName = "/SplashScreen";
const SplashScreen({Key? key}) : super(key: key);
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen>
with SingleTickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return BlocConsumer<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
if (kDebugMode) {
print('Listener: $state');
}
Future.delayed(const Duration(seconds: 3), () {
if (state.authStatus == AuthStatus.unAuthenticated) {
GoRouter.of(context).go('/login');
Navigator.pushNamed(context, SignUpScreen.routeName);
} else if (state.authStatus == AuthStatus.authenticated) {
//Navigator.popUntil(context, (route) => route.isFirst);
Navigator.pushReplacementNamed(context, HomePage.routeName);
}
});
},
builder: (context, Object? state) {
if (kDebugMode) {
print('object: $state');
}
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
"Welcome to Musajjal",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
const SizedBox(
height: 20,
),
Image.asset(
'assets/musajjalBlue.png',
width: 300,
height: 300,
),
const SizedBox(
height: 20,
),
const Text(
"Hifz ul Quran Records",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
),
const SizedBox(
height: 20,
),
const CircularProgressIndicator(
color: Colors.blueGrey,
),
],
),
),
);
},
);
}
}
Next, this. is my Go Router function
GoRouter _router(AuthenticationBloc bloc) {
return GoRouter(
routes: <GoRoute>[
GoRoute(
path: '/',
builder: (context, state) => const SplashScreen(),
routes: <GoRoute>[
GoRoute(path: 'login', builder: (context, state) => LoginScreen()),
GoRoute(
path: 'signUp', builder: (context, state) => SignUpScreen()),
GoRoute(path: 'homePage', builder: (context, state) => HomePage())
],
redirect: (BuildContext context, GoRouterState state) {
final isLoggedIn =
bloc.state.authStatus == AuthStatus.authenticated;
final isLoggingIn = state.location == '/login';
print(isLoggedIn);
if (!isLoggedIn && !isLoggingIn) return '/login';
if (isLoggedIn && isLoggingIn) return '/homePage';
return null;
},
),
],
);
}
The issue is the app gets stuck on Splash Screen and it does not move forward to login screen. Please help.
Try changing the logic in the redirect to
if (!isLoggedIn && !isLoggingIn) return '/login';
if (isLoggedIn) return '/homePage';
also, consider having the login logic as OR instead of AND --optional
if (!isLoggedIn || !isLoggingIn) return '/login';
Try the follwing code:
main.dart
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<AuthenticationBloc>(
create: (context) => AuthenticationBloc), 👈 Specify the BlocProvider here
),
],
child: MaterialApp(
theme: customTheme(context),
debugShowCheckedModeBanner: false,
routerConfig: router,
}
router.dart
final GoRouter router = GoRouter(routes: [
GoRoute(
path: "/",
builder: (context, state) {
return BlocBuilder<AuthCubit, AuthState>(
buildWhen: (oldState, newState) {
return oldState is AuthInitialState;
},
builder: (context, state) {
if (state is AuthLoading) {
// return const SplashScreen(); // alternative way
context.goNamed(SplashScreen.routeName); 👈 Display your splash screen here and you can provide delay while changing state in your bloc
}
else if (state is AuthenticationUnauthenticated) {
context.goNamed(LoginPage.routeName);
} else if (state is Authenticated) {
context.goNamed(HomePage.routeName);
} else {
return const Scaffold();
}
},
);
}),

How to use pushNamed with Dismissible Widget and see background transparent first page

I want to use pushNamed from page 1 to page 2 and use Dismissible Widget to close page 2.
when pull down I got a black screen when close it.
I want to see page 1 when I pull down close page 2
I use this example
class FirstScreen extends StatelessWidget {
const FirstScreen({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('First Screen'),
),
body: Center(
child: ElevatedButton(
// Within the `FirstScreen` widget
onPressed: () {
// Navigate to the second screen using a named route.
Navigator.pushNamed(context, '/second');
},
child: const Text('Launch screen'),
),
),
);
}
}
class SecondScreen extends StatelessWidget {
const SecondScreen({super.key});
#override
Widget build(BuildContext context) {
return Dismissible(
key: UniqueKey(),
movementDuration: const Duration(milliseconds: 0),
resizeDuration: const Duration(milliseconds: 1),
onDismissed: (d) => Navigator.of(context).pop(),
direction: DismissDirection.down,
child: Scaffold(
appBar: AppBar(
title: const Text('Second Screen'),
),
body: Center(
child: ElevatedButton(
// Within the SecondScreen widget
onPressed: () {
// Navigate back to the first screen by popping the current route
// off the stack.
Navigator.pop(context);
},
child: const Text('Go back!'),
),
),
),
);
}
}
if I use this one it works!! but I don't want use it
Navigator.of(context).push(
PageRouteBuilder(
opaque: false,
pageBuilder: (context, animation, secondaryAnimation) => const SecondScreen(),
));
void main() {
runApp(
MaterialApp(
title: 'Named Routes Demo',
initialRoute: '/',
routes: {
'/': (context) => const FirstScreen(),
'/second': (context) => const SecondScreen(), }, ),
);
}
Use MaterialApp.onGenerateRoute instead of MaterialApp.routes and use the PageRouteBuilder that works already.
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Named Routes Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
onGenerateRoute: (settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (context) => const FirstScreen());
case '/second':
return PageRouteBuilder(
opaque: false,
pageBuilder: (context, animation, secondaryAnimation) =>
const SecondScreen(),
);
}
},
);
}
}

Flutter: How to combine PageRoute Animation with Navigator.of(context).pushNamed

I want to use animated page route on my flutter project. Now all of my routes are Named Route and I don't wanna change them. Is there any way I can use Page Route Animation with named route? Like: If I am going from PageOne() to PageTwo() using Navigator.of(context).pushNamed(PageTwo.routename), I don't wanna see default transition, May be I want to use scale animation or fade animation. Is there any way to do that?
onTap: () {
Navigator.of(context).pushNamed(
ProductsSearch.routeName,
arguments: ScreenArguments(null, null, null, null, null, null, true, false),
);
},
As your Question I have solved this demo answer try once it will work .
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: HomePage(),
onGenerateRoute: (RouteSettings settings) {
final SecondHome args = settings.arguments;
switch (settings.name) {
case '/':
return SlideRightRoute(widget:HomePage());
break;
case '/second':
return
SlideRightRoute(widget:SecondHome(args.data1,args.data2,args.boolvalue));
break;
}
},
),
);
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: new Center(
child: RaisedButton(
onPressed: () {
Navigator.of(context).pushNamed(
'/second',
arguments:SecondHome("data1","data2",true),
);
},
child: Text('Second Home'),
),
),
);
}
}
class SecondHome extends StatelessWidget {
String data1;
String data2;
bool boolvalue;
SecondHome(this.data1,this.data2,this.boolvalue);
#override
Widget build(BuildContext context) {
print("Secoendhomedata${data1}");
return Scaffold(
appBar: AppBar(
title: Text('Second Home'),
),
body: new Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go Back'),
),
),
);
}
}
class SlideRightRoute extends PageRouteBuilder {
final Widget widget;
SlideRightRoute({this.widget})
: super(
pageBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return widget;
},
transitionsBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return new SlideTransition(
position: new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: child,
);
},
);
}