I'm using Beamer and Flutter to build an app
I've built logic for the default landing page of the application like a landing screen or a marketing page, then, once someone is logged in, a bottom navigator appears, I want this bottom nav to change the output of the middle of the screen (the Scaffold.body) but not refresh the browser or reload the bottom navbar itself, but I want the url to change
The routes will look something like this
- localhost
|-- /
|-- /login
|-- /dashboard
|-- /
|-- /account
|-- /history
There is example code for a nested router on the beamer repo that does what I want, with only a partial page update with animation but I can't figure out how the code works, i've been reading it for a few hours and I've made no progress
Here is a minimal reproduction of where I am stuck
import 'package:community_material_icon/community_material_icon.dart';
import 'package:flutter/material.dart';
import 'package:url_strategy/url_strategy.dart';
import 'package:beamer/beamer.dart';
void main() {
setPathUrlStrategy();
runApp(const MyApp());
}
class DefaultPage extends StatelessWidget {
const DefaultPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('app_title home page')),
body: Column(
children: [
const Text("Welcome to the app"),
TextButton(
onPressed: () {
Beamer.of(context).beamToNamed('/login');
},
child: const Text("Go to login page"),
),
],
),
);
}
}
class LoginPage extends StatelessWidget {
const LoginPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('app_title login page')),
body: Column(
children: [
const Text("Press button to login"),
TextButton(
onPressed: () {
Beamer.of(context).beamToNamed('/dashboard');
},
child: const Text("Login"),
),
],
),
);
}
}
class DashboardPage extends StatefulWidget {
const DashboardPage({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => _DashboardPageState();
}
class _DashboardPageState extends State<DashboardPage> {
final _beamerKey = GlobalKey<BeamerState>();
var _currentIndex = 0;
void _onTapUpdateIndex(index) {
setState(() {
_currentIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('app_title dashboard')),
body: Beamer(
key: _beamerKey,
routerDelegate: BeamerDelegate(
transitionDelegate: const NoAnimationTransitionDelegate(),
initialPath: '/dashboard',
locationBuilder: RoutesLocationBuilder(
routes: {
'/': (context, state, data) => const Text('Home'),
'/account': (context, state, data) => const Text('Account'),
},
),
),
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: _currentIndex,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(CommunityMaterialIcons.account), label: 'Account'),
],
onTap: (index) {
var pages = ['/dashboard', '/dashboard/account'];
_onTapUpdateIndex(index);
context.beamToNamed(pages[index]);
},
),
);
}
}
// The default router delegate handles logins, splash screen landings and redirects to the dashboard
// Once at the dashboard I want a new router to check all /dashboard/:page requests and update only partial amounts of the page
final _routerDelegate = BeamerDelegate(
initialPath: '/',
locationBuilder: RoutesLocationBuilder(
routes: {
'/': (context, state, data) => const DefaultPage(),
'/login': (context, state, data) => const LoginPage(),
'/dashboard': (context, state, data) => const DashboardPage(),
},
),
);
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
title: 'app_title',
theme: ThemeData(),
routerDelegate: _routerDelegate,
routeInformationParser: BeamerParser(),
backButtonDispatcher: BeamerBackButtonDispatcher(delegate: _routerDelegate),
);
}
}
Currently the only way I've made any progress is by defining all of the routes in the main router and having no nested routing functionality then anytime someone clicks a new link the whole dashboard is reloaded
Related
I'm using go_router to build an application that
does "dummy" pushes of routes (a bit like Twitter). It is possible to start with a page /a ,push /b, then /c, ... and then push again /a.
When a page is pushed twice, an assert fails: assert(!keyReservation.contains(key)); which ensures a key is used only once in the Navigator.
Is there a way to be able to push twice the same page with go_router?
Here is a small code snippet:
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
final router = GoRouter(
initialLocation: '/a',
routes: [
GoRoute(
path: '/a',
builder: (_, __) => const MyWidget(path: '/a'),
),
GoRoute(
path: '/b',
builder: (_, __) => const MyWidget(path: '/b'),
),
],
);
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(
routeInformationProvider: router.routeInformationProvider,
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate,
);
}
}
class MyWidget extends StatelessWidget {
const MyWidget({
required this.path,
Key? key,
}) : super(key: key);
final String path;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(path),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: () {
context.push('/a');
},
child: const Text('/a'),
),
TextButton(
onPressed: () {
context.push('/b');
},
child: const Text('/b'),
),
],
)),
);
}
}
The issue can be triggered by clicking on the TextButton with "/a".
It was a bug in go_router (see this issue) which has been solved in the version 4.2.3.
So now you should be able to push the same page twice.
I have a problem. In my main.dart I have a IndexedStack with a bottomNavigationBar. The IndexedStack exists of 4 screens, and one of the screens contains a Navigator inside that screen. Now in the bottomNavigationBar, I have a custom bar like this:
I want the Navigator to switch screens when I click on that bar, but because that custom bottom bar is not part of the Navigator, I can't call:
Navigator.pushNamed(context, 'location_details');
Because that results in the following error:
FlutterError (Could not find a generator for route RouteSettings("location_details", null) in the _WidgetsAppState.
Make sure your root app widget has provided a way to generate
this route.
Generators for routes are searched for in the following order:
1. For the "/" route, the "home" property, if non-null, is used.
2. Otherwise, the "routes" table is used, if it has an entry for the route.
3. Otherwise, onGenerateRoute is called. It should return a non-null value for any valid route not handled by "home" and "routes".
4. Finally if all else fails onUnknownRoute is called.
Unfortunately, onUnknownRoute was not set.)
Here is the main.dart code:
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int currentIndex = 0;
#override
Widget build(BuildContext context) {
final screens = [
LocationStateManager(),
MySessionsPage(),
MapPage(),
SettingsPage()
];
return Scaffold(
appBar: AppBar(
title: Text(widget.title,
style: TextStyle(
color: PRIMARY,
fontSize: 32,
fontFamily: 'JotiOne',
fontWeight: FontWeight.bold)),
centerTitle: true,
backgroundColor: DARK_BACKGROUND_PRIMARY,
),
body: SafeArea(
child: IndexedStack(index: currentIndex, children: screens),
),
bottomNavigationBar: new Theme(
data:
Theme.of(context).copyWith(canvasColor: DARK_BACKGROUND_PRIMARY),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
(isVisible)
? BottomBarTraveling()
: SizedBox(
height: 0,
),
BottomNavigationBar(
type: BottomNavigationBarType.fixed,
iconSize: 30,
currentIndex: currentIndex,
onTap: (index) => setState(() => currentIndex = index),
showSelectedLabels: false,
showUnselectedLabels: false,
selectedItemColor: NAVBAR_SELECTED,
unselectedItemColor: NAVBAR_UNSELECTED,
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: ''),
BottomNavigationBarItem(icon: Icon(Icons.list), label: ''),
BottomNavigationBarItem(icon: Icon(Icons.map), label: ''),
BottomNavigationBarItem(icon: Icon(Icons.settings), label: '')
],
)
],
),
));
}
}
And here is the LocationStateManager code which contains the Navigator:
class LocationStateManager extends StatefulWidget {
#override
_LocationStateManagerState createState() => _LocationStateManagerState();
}
class _LocationStateManagerState extends State<LocationStateManager> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: null,
body: Stack(children: <Widget>[
Navigator(
onGenerateRoute: (settings) {
switch (settings.name) {
case 'location_list':
return MaterialPageRoute(
builder: (context) => LocationListPage(),
);
case 'location_details':
return MaterialPageRoute(
builder: (context) =>
KiteupLocationPage(),
);
case 'kiteup_status_page':
return MaterialPageRoute(
builder: (context) => KiteupStatusPage(),
);
default:
return MaterialPageRoute(
builder: (context) => LocationListPage(),
);
}
},
),
]));
}
}
How can I change the Navigator route to location_details, by clicking on the BottomBarTraveling() bar, that is placed a level above the Navigator.
PS: Inside the BottomBarTraveling() is the gesturedetector which has the
onTap: () { Navigator.pushNamed(context, 'location_details'); }
Navigator is default navigator of flutter, in this case you want to deal with custom navigator (or neste navigator), you must decrate your ow navigator and use it insteads of Navigator. The point is create navigator = GlobalKey<NavigatorState>() and passing to key props of Navigator.
Update: Of course any function of navigator like pop/push/pushName on your custom navigator need using navigator instead of Navigator.
Example: A simple app with button out of Navigator and 2 screen with differences color inside Navigator, push button and Navigator was changed.
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: MyHomePage()));
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static final navigator = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
Expanded(
child: Navigator(
key: navigator,
initialRoute: '/',
onGenerateRoute: (settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) => const Sc1());
case 'sc2':
default:
return MaterialPageRoute(builder: (_) => const Sc2());
}
},
),
),
ElevatedButton(
child: const Text('To Sc2'),
onPressed: () {
navigator.currentState?.pushNamed('sc2');
},
),
],
),
);
}
}
// screen 1 with green color
class Sc1 extends StatelessWidget {
const Sc1({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
child: const Center(child: Text('Screen 1')),
);
}
}
// screen 2 with blue color
class Sc2 extends StatelessWidget {
const Sc2({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
child: const Center(child: Text('Screen 2')),
);
}
}
Addtional: In case you want to calling navigator outside of MyHomePage, you can declare an static like this and calling it from child nodes
class MyHomePage extends StatefulWidget {
static MyHomePageState of(BuildContext context) =>
context.findAncestorStateOfType<MyHomePageState>()!;
...
}
call() {
MyHomePage.of(context).navigator.push(...);
}
My drawer is not navigating to other screens. I don't know why, I have gone through a lot of StackOverflow solutions.
I have created routes also but it is still not working.
Here is my code for the drawer.
class SideDrawer extends StatelessWidget{
const SideDrawer({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
decoration: BoxDecoration(
color: Colors.greenAccent,
),
child: Text('Welcome!'),
),
ListTile(
title: const Text('Home'),
onTap: () => Navigator.pushReplacementNamed(context, Routes.home)
),
ListTile(
title: const Text('Settings'),
onTap: () => Navigator.pushReplacementNamed(context, Routes.settings)
),
ListTile(
title: const Text('LogOut'),
onTap: () => Navigator.pushReplacementNamed(context, Routes.logout)
),
],
),
);
}
}
Here is the routes class.
import 'package:adaptive_layout/widgets/candidate_form.dart';
import 'package:adaptive_layout/widgets/logout.dart';
import 'package:adaptive_layout/widgets/settings.dart';
class Routes {
static const String home = CandidateForm.routeName;
static const String settings = Settings.routeName;
static const String logout = LogOut.routeName;
}
All the classes just have a text displayer in the centre.
But neither the drawer closes on its own after onTap nor does it take to that page.
This is my MaterialAppWidget i.e. the main.dart.
import 'package:adaptive_layout/pages/desktop_page.dart';
import 'package:adaptive_layout/pages/mobile_page.dart';
import 'package:adaptive_layout/pages/tablet_page.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
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(
title: 'Views',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
if(constraints.maxWidth < 600){
return const MobilePage();
}
else if(constraints.maxWidth < 768){
return const TabletPage();
}
else{
return const DesktopPage();
}
}
),
),
);
}
}
Please help. being trying it for 2 days now.
This is happening because you're not defining your routes in the MaterialApp widget. This is not necessary if you're using annonymous routes, but it is a must if you're using named routes.
You'll need to get rid of you're home: property on the MaterialApp and move your LayoutBuilder elsewhere.
Like this:
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(
title: 'Views',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/',
routes: {
// Here you define the names and the Widgets (Preferably ones with a Scaffold) that are your pages
'/': (context) => const HomePage(),
'/second': (context) => const SecondScreen(),
},
);
}
}
Then to navigate to them using named Routes:
() => Navigator.pushReplacementNamed(context, '/second')
If you want more info, there's a cookbock on the Flutter website that show you how to use it.
I'm working on a simple navigation on an app that follows Flutter Navigator 2.0 to have better support on routes. An advantage of Navigator 2.0 is that you have granular controls on screen navigations and have better support for web.
Here's a diagram of what I'm trying to achieve.
I'm able to navigate from the LoginScreen to the "main page" without issues. The main page has a Drawer that displays HomeScreen by default. My issue here is that I'm unsure on how to properly display HomeScreen and ProfileScreen from the Drawer using Navigator 2.0. Following this guide demonstrates that a screen is pushed to the Navigator stack and routes are tracked with a RouterDelegate. Doing so pushes a new screen to the stack.
The app seems to work fine, but I just removed the transition animation. You can notice that a new screen is drawn even before the Drawer can finish its closing animation. The entire Drawer widget is drawn again since the main page is pushed to the stack.
This guide on displaying screens on a Drawer only replaces the widget on the same screen. What I'm currently doing is I'm rebuilding the main page every time it navigates to HomeScreen and ProfileScreen
Here's how the main page looks like. The currentPage is updated to display the Widget for the HomeScreen and ProfileScreen.
late Widget currentPage;
#override
Widget build(BuildContext context) {
currentPage = HomeScreen();
return Scaffold(
appBar: AppBar(title: Text(title)),
body: currentPage,
drawer: Drawer(
child: ListView(
children: <Widget>[
// ...
ListTile(
title: Text('Home'),
onTap: () {
widget.navHome();
Navigator.pop(context);
},
),
ListTile(
title: Text('Profile'),
// ...
),
],
),
),
);
}
The Navigator on my RouterDelegate has this setup.
Navigator(
key: navigatorKey,
transitionDelegate: NoAnimationTransitionDelegate(),
pages: [
if (show404)
MaterialPage(
key: ValueKey('UnknownPage'),
child: UnknownScreen(),
)
else if (page == Pages.home)
MaterialPage(
key: ValueKey('HomePage'),
child: HomePage(
title: 'Home',
handleLogout: _logOut,
navHome: _navHome,
navProfile: _navProfile,
currentScreen: HomeScreen(username: username),
),
)
else if (page == Pages.profile)
MaterialPage(
key: ValueKey('ProfilePage'),
child: HomePage(
title: 'Profile',
handleLogout: _logOut,
navHome: _navHome,
navProfile: _navProfile,
currentScreen: ProfileScreen(),
),
)
else // username is null, no user logged in
MaterialPage(
key: ValueKey('LoginPage'),
child: LoginPage(
title: 'Login',
onTapped: _handleLogin,
),
),
]
)
The functions are passed as arguments to update the routes in the RouterDelegate.
enum Pages { login, home, profile }
class PageRouterDelegate extends RouterDelegate<PageRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<PageRoutePath> {
// ... Other lines omitted for simplicity
void _handleLogin(String username, String password) {
// TODO Auth
this.page = Pages.home;
notifyListeners();
}
void _navHome() {
this.page = Pages.home;
notifyListeners();
}
void _navProfile() {
this.page = Pages.profile;
notifyListeners();
}
void _logOut() {
this.page = Pages.login;
notifyListeners();
}
}
Any suggestions on what approach can be done to display different screens from a Drawer and have their routes tracked using Navigator 2.0?
Update:
I found a solution for this issue, but it'll need to use two Navigators. Assigning keys to the Navigators helps us manage both of them.
The second Navigator is on the Home page that has a Drawer. This enables us to navigate through different pages without rebuilding the entire screen. The caveat in this approach is that only the routes on mainNavigator are displayed on the address bar on web.
Let me know if you have other suggestions on how this can be approached.
I gave up on using Navigator 2.0 for now due to the amount of boiler plate code that I need to write. I took a step back and used Navigator 1.0 with named routes. The same principle can still be applied to Navigator 2.0
What I did now is instead of building a different Screen on the main Navigator, I still rebuild the same screen(NavigatorPage) to save resources and pass arguments for the screen that I'd like to navigate to.
final _mainNavigatorKey = GlobalKey<NavigatorState>();
...
MaterialApp(
title: 'Navigator Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
navigatorKey: _mainNavigatorKey,
routes: {
/// [title] updates the title on the main AppBar
/// [route] NavigatorPage Router depends on route defined on this parameter
/// [showDrawer] show/hide main AppBar drawer
Nemo.home: (context) => NavigatorPage(
title: 'Home',
route: Nemo.home,
navigatorKey: _mainNavigatorKey,
showDrawer: true,
),
Nemo.post: (context) => NavigatorPage(
title: 'Post',
route: Nemo.post,
navigatorKey: _mainNavigatorKey,
showDrawer: true),
Nemo.profile: (context) => NavigatorPage(
title: 'Profile',
route: Nemo.profile,
navigatorKey: _mainNavigatorKey,
showDrawer: true),
Nemo.settings: (context) => NavigatorPage(
title: 'Settings',
route: Nemo.settings,
navigatorKey: _mainNavigatorKey,
showDrawer: true),
},
);
The NavigatorPage contains the main AppBar where we can update the title and display/hide a LinearProgressBar. From there, we can navigate to the desired Screen by checking the route passed in the arguments with the _mainNavigatorKey as our Navigator.
Navigator(
// key: _navigatorKey,
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
// Manage your route names here
// switch (settings.name) {
switch (widget.route) {
/// Default page displayed on Home Screen
case Nemo.home:
builder = (BuildContext context) => _homePage();
break;
case Nemo.post:
builder = (BuildContext context) => _postPage();
break;
case Nemo.profile:
builder = (BuildContext context) => _profilePage();
break;
case Nemo.settings:
builder = (BuildContext context) => _settingsPage();
break;
default:
builder = (BuildContext context) => const UnknownPage();
}
return MaterialPageRoute(
builder: builder,
settings: settings,
);
},
),
While we're still rebuilding a page on the main Navigator. A workaround for the janky animation when the Navigation Drawer is closed is to add a delay of at least 300ms to wait for the animation to finish before executing the navigation. You can adjust the delay as you see fit.
ListTile(
title: const Text('Home'),
onTap: () {
// Close the drawer
Navigator.pop(context);
/// [drawerDelay] gives time to animate the closing of the Drawer
Timer(Duration(milliseconds: drawerDelay), () async {
widget.navigatorKey.currentState!.pushNamed(Nemo.home);
});
},
),
Demo
Sample Code
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
final _mainNavigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Navigator Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// home: const NavigatorPage(title: 'Flutter Demo Home Page'),
navigatorKey: _mainNavigatorKey,
routes: {
/// [title] updates the title on the main AppBar
/// [route] NavigatorPage Router depends on route defined on this parameter
/// [showDrawer] show/hide main AppBar drawer
Nemo.home: (context) => NavigatorPage(
title: 'Home',
route: Nemo.home,
navigatorKey: _mainNavigatorKey,
showDrawer: true,
),
Nemo.post: (context) => NavigatorPage(
title: 'Post',
route: Nemo.post,
navigatorKey: _mainNavigatorKey,
showDrawer: true),
Nemo.profile: (context) => NavigatorPage(
title: 'Profile',
route: Nemo.profile,
navigatorKey: _mainNavigatorKey,
showDrawer: true),
Nemo.settings: (context) => NavigatorPage(
title: 'Settings',
route: Nemo.settings,
navigatorKey: _mainNavigatorKey,
showDrawer: true),
},
);
}
}
class NavigatorPage extends StatefulWidget {
const NavigatorPage(
{Key? key,
required this.title,
required this.route,
required this.navigatorKey,
required this.showDrawer})
: super(key: key);
final String title;
final String route;
final bool showDrawer;
final GlobalKey<NavigatorState> navigatorKey;
#override
State<NavigatorPage> createState() => _NavigatorPageState();
}
class _NavigatorPageState extends State<NavigatorPage> {
// final _navigatorKey = GlobalKey<NavigatorState>();
/// Drawer delay let's us have the Navigation Drawer close first
/// before the navigating to the next Screen
int drawerDelay = 300;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: widget.showDrawer
? Drawer(
/// TODO return null to hide Drawer if in Login/Registration page
// Add a ListView to the drawer. This ensures the user can scroll
// through the options in the drawer if there isn't enough vertical
// space to fit everything.
child: ListView(
// Important: Remove any padding from the ListView.
padding: EdgeInsets.zero,
children: <Widget>[
const DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue,
),
child: Text('Drawer Header'),
),
ListTile(
title: const Text('Home'),
onTap: () {
// Close the drawer
Navigator.pop(context);
/// [drawerDelay] gives time to animate the closing of the Drawer
Timer(Duration(milliseconds: drawerDelay), () async {
widget.navigatorKey.currentState!.pushNamed(Nemo.home);
});
},
),
ListTile(
title: const Text('Profile'),
onTap: () {
// Close the drawer
Navigator.pop(context);
Timer(Duration(milliseconds: drawerDelay), () async {
widget.navigatorKey.currentState!
.pushNamed(Nemo.profile);
});
},
),
ListTile(
title: const Text('Settings'),
onTap: () {
// Close the drawer
Navigator.pop(context);
Timer(Duration(milliseconds: drawerDelay), () async {
widget.navigatorKey.currentState!
.pushNamed(Nemo.settings);
});
},
),
],
),
)
: null,
body: Navigator(
// key: _navigatorKey,
/// initialRoute needs to be set to '/'
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
// Manage your route names here
// switch (settings.name) {
switch (widget.route) {
/// Default page displayed on Home Screen
case Nemo.home:
builder = (BuildContext context) => _homePage();
break;
case Nemo.post:
builder = (BuildContext context) => _postPage();
break;
case Nemo.profile:
builder = (BuildContext context) => _profilePage();
break;
case Nemo.settings:
builder = (BuildContext context) => _settingsPage();
break;
default:
builder = (BuildContext context) => const UnknownPage();
}
return MaterialPageRoute(
builder: builder,
settings: settings,
);
},
),
);
}
Widget _homePage() =>
HomePage(title: 'Home', navigatorKey: widget.navigatorKey);
Widget _postPage() =>
PostPage(title: 'Post', navigatorKey: widget.navigatorKey);
Widget _profilePage() =>
ProfilePage(title: 'Profile', navigatorKey: widget.navigatorKey);
Widget _settingsPage() =>
SettingsPage(title: 'Settings', navigatorKey: widget.navigatorKey);
}
class Nemo {
static const home = '/';
static const login = '/login';
static const register = '/register';
static const post = '/post';
static const profile = '/profile';
static const settings = '/settings';
}
/// Constant values for UI elements
class Constants {
static const String webVersion = 'web-0.1.9-dev';
static const double paddingSmall = 8.0;
static const double paddingNormal = 16.0;
static const double heightNormal = 64.0;
static const double heightThreadCard = 72.0;
static const double heightButtonNormal = 42.0;
static const double widthButtonNormal = 160.0;
}
class UnknownPage extends StatefulWidget {
const UnknownPage({Key? key}) : super(key: key);
#override
State<UnknownPage> createState() => _UnknownPageState();
}
class HomePage extends StatefulWidget {
const HomePage({Key? key, required this.title, required this.navigatorKey})
: super(key: key);
final String title;
final GlobalKey<NavigatorState> navigatorKey;
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text(widget.title),
// ),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Home',
),
ElevatedButton(
child: const Text('View Post Page'),
onPressed: () {
widget.navigatorKey.currentState!.pushNamed(Nemo.post);
},
),
],
),
),
);
}
}
class PostPage extends StatefulWidget {
const PostPage(
{Key? key, required this.title, this.id, required this.navigatorKey})
: super(key: key);
final String title;
final String? id;
final GlobalKey<NavigatorState> navigatorKey;
#override
State<PostPage> createState() => _PostPageState();
}
class _PostPageState extends State<PostPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text(widget.title),
// ),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Id from Route: ${widget.id}',
),
],
),
),
);
}
}
class SettingsPage extends StatefulWidget {
const SettingsPage(
{Key? key, required this.title, required this.navigatorKey})
: super(key: key);
final String title;
final GlobalKey<NavigatorState> navigatorKey;
#override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text(widget.title),
// ),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Settings',
),
ElevatedButton(
child: const Text('View Details'),
onPressed: () {
widget.navigatorKey.currentState!.pushNamed(Nemo.post);
},
),
],
),
),
);
}
}
class ProfilePage extends StatefulWidget {
const ProfilePage({Key? key, required this.title, required this.navigatorKey})
: super(key: key);
final String title;
final GlobalKey<NavigatorState> navigatorKey;
#override
State<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text(widget.title),
// ),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Profile',
),
ElevatedButton(
child: const Text('View Details'),
onPressed: () {
widget.navigatorKey.currentState!.pushNamed(Nemo.post);
},
),
],
),
),
);
}
}
class _UnknownPageState extends State<UnknownPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text(widget.title),
// ),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text(
'404',
),
],
),
),
);
}
}
If you'd like to have a sample with basic Login and Register Screen included. I've created this template that you can check: https://github.com/omatt/flutter-navdrawer-template
It's not a direct answer, just maybe a reference of how to solve this problem.
Flutter recently updated routing example and in that example, they use InheritedWidget with ChangeNotifier to push new route to route delegate like RouteStateScope.of(context)!.go('/book/${book.id}'); while keeping state of routing alive.
I have an app with three routes and uses the bottomNavigationBar to navigate between them. In one of the routes I have a button in the page that will also navigate to one of the pages.
Heres my main page
import 'package:flutter/material.dart';
import 'page_two.dart';
import 'page_three.dart';
void main() {
return runApp(MyApp());
}
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
List<Widget> _widgetOptions = <Widget>[
Text('Main'),
PageTwo(),
PageThree(),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home, color: Colors.black),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.business, color: Colors.black),
title: Text('Business'),
),
BottomNavigationBarItem(
icon: Icon(Icons.business, color: Colors.black),
title: Text('Business'),
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
}
Page Two
import 'package:flutter/material.dart';
import 'main.dart';
class PageTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
child: Text('Go page 1'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MyApp()),
);
},
),
);
}
}
and Page Three with a button that navigates to page two
import 'package:flutter/material.dart';
import 'page_two.dart';
class PageThree extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
child: Text('Go page 1'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => PageTwo()),
);
},
),
);
}
}
When I press the button on Page Three, it will go to Page Two without the AppBar and the BottomNavigationBar
Use GlobalKey and In PageTwo Widget call MyStatefulWidgetState's _onItemTapped function
You can see working demo and full code below
code snippet
final scakey = new GlobalKey<_MyStatefulWidgetState>();
...
child: Text('Go page 2'),
onPressed: () {
scakey.currentState._onItemTapped(1);
full code
import 'package:flutter/material.dart';
void main() {
return runApp(MyApp());
}
final scakey = new GlobalKey<_MyStatefulWidgetState>();
/// This Widget is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyStatefulWidget(key: scakey),
);
}
}
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
final myKey = new GlobalKey<_MyStatefulWidgetState>();
List<Widget> _widgetOptions = <Widget>[
Text('Main'),
PageTwo(),
PageThree(),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: myKey,
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: Center(
child: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home, color: Colors.black),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.business, color: Colors.black),
title: Text('Business'),
),
BottomNavigationBarItem(
icon: Icon(Icons.business, color: Colors.black),
title: Text('Business'),
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
}
class PageTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
child: Text('Go page 1'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MyApp()),
);
},
),
);
}
}
class PageThree extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
child: Text('Go page 2'),
onPressed: () {
scakey.currentState._onItemTapped(1);
/*Navigator.push(
context,
MaterialPageRoute(builder: (context) => PageTwo()),
);*/
},
),
);
}
}
When using navigation bar to navigate between pages, you are tapping on BottomNavigationBarItem to change the index by calling setState() and as the result, build method is triggered with a new _selectedIndex and that index is used to render your appropriate widget.
_widgetOptions.elementAt(_selectedIndex)
Navigator.push on the other hand is just pushing a new route on top of the navigation stack. You are not getting an AppBar or BottomNavigationBar since you don't have them on PageTwo. What I would recommend you is to create a callback function in PageTwo and call that function on button tap. You can now use that callback in MyStatefulWidget to change the index with setState. Here is an example
Declare a final like below in your pages.
final void Function(int index) pageChanged;
In the onTap event of your button, call this function.
widget.pageChanged(1); // PageTwo
In MyStatefulWidget, when you are creating pages, pass the function.
PageTwo(pageChanged:(index){
setState(){_selectedIndex = index;}
});