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.
Related
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
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
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(...);
}
I am trying to modify my Navigation Transition code to use PageRouteBuilder. See method _gotoThirdPage().
The problem is that I cannot pass parameters to new Page. For example, I crash when passing title. Also looking for examples of sending multiple parameters, such as a map.
REQUEST: Could someone Modify the _gotoThirdPage() method to send parameters to Class SecondPage.
//====================. Code below ===================
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
// Added ===
routes: <String, WidgetBuilder>{
SecondPage.routeName: (BuildContext context) => new SecondPage(title: "SecondPage"),
},
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new RaisedButton(
onPressed: _gotoSecondPage,
child: new Text("Goto SecondPage- Normal"),
),
new RaisedButton(onPressed: _gotoThirdPage,
child: new Text("goto SecondPage = with PRB"),
),
new Text(
'You have pushed the button this many times:',
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
void _gotoSecondPage() {
//=========================================================
// Transition - Normal Platform Specific
print('==== going to second page ===');
print( SecondPage.routeName);
Navigator.pushNamed(context, SecondPage.routeName);
}
void _gotoThirdPage() {
//=========================================================
// I believe this is where I would be adding a PageRouteBuilder
print('==== going to second page ===');
print( SecondPage.routeName);
//Navigator.pushNamed(context, SecondPage.routeName);
//=========================================================
//=== please put PageRouteBuilderCode here.
final pageRoute = new PageRouteBuilder(
pageBuilder: (BuildContext context, Animation animation,
Animation secondaryAnimation) {
// YOUR WIDGET CODE HERE
// I need to PASS title here...
// not sure how to do this.
// Also, is there a way to clean this code up?
return new SecondPage();
},
transitionsBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return SlideTransition(
position: new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.0, 0.0),
).animate(secondaryAnimation),
child: child,
),
);
},
);
Navigator.of(context).push(pageRoute);
}
}
class SecondPage extends StatefulWidget {
SecondPage({Key key, this.title}) : super(key: key);
static const String routeName = "/SecondPage";
final String title;
#override
_SecondPageState createState() => new _SecondPageState();
}
/// // 1. After the page has been created, register it with the app routes
/// routes: <String, WidgetBuilder>{
/// SecondPage.routeName: (BuildContext context) => new SecondPage(title: "SecondPage"),
/// },
///
/// // 2. Then this could be used to navigate to the page.
/// Navigator.pushNamed(context, SecondPage.routeName);
///
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
//======== HOW TO PASS widget.title ===========
title: new Text(widget.title),
//title: new Text('==== second page ==='),
),
body: new Container(),
floatingActionButton: new FloatingActionButton(
onPressed: _onFloatingActionButtonPressed,
tooltip: 'Add',
child: new Icon(Icons.add),
),
);
}
void _onFloatingActionButtonPressed() {
}
}
I am not quite sure what you are trying to do. The easiest way to pass parameters to new Page for me is using the constructor of the new page if I need to do some processing- show/ use/ pass them later on. Although I dont think it is appropriate if you have large number of parameters.
So in some cases I would use SharedPrefences. I think this will be better for sending multiple parameters, such as a map.
Btw about your navigation I advise you to use Navigate with named routes. Something like:
MaterialApp(
// Start the app with the "/" named route. In our case, the app will start
// on the FirstScreen Widget
initialRoute: '/',
routes: {
// When we navigate to the "/" route, build the FirstScreen Widget
'/': (context) => FirstScreen(),
// When we navigate to the "/second" route, build the SecondScreen Widget
'/second': (context) => SecondScreen(),
},
);
And then:
Navigator.pushNamed(context, '/second');
Documentation.
In Method gotoThirdScreen(), I am tryiing to pass parameters to a class.
I believe the issue starts at:
//=========================================================
//=== please put PageRouteBuilderCode here.
The Class SecondPage has... widget.title. But not sure how to pass titel from _gotoThirdPage().
This code works
//===============================
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
// Added ===
routes: <String, WidgetBuilder>{
SecondPage.routeName: (BuildContext context) => new SecondPage(title: "SecondPage"),
},
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(28.0),
child: new RaisedButton(
onPressed: _gotoSecondPage,
child: new Text("Goto SecondPage- Normal"),
),
),
Padding(
padding: const EdgeInsets.all(38.0),
child: new RaisedButton(
onPressed: _gotoThirdPage,
child: new Text("goto SecondPage = with PRB"),
),
),
new Text(
'You have pushed the button this many times:',
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
void _gotoSecondPage() {
//=========================================================
// Transition - Normal Platform Specific
print('==== going to second page ===');
print( SecondPage.routeName);
Navigator.pushNamed(context, SecondPage.routeName);
}
void _gotoThirdPage() {
//=========================================================
// I believe this is where I would be adding a PageRouteBuilder
print('==== going to second page ===');
print( SecondPage.routeName);
//Navigator.pushNamed(context, SecondPage.routeName);
//=========================================================
//=== please put PageRouteBuilderCode here.
final pageRoute = new PageRouteBuilder(
pageBuilder: (BuildContext context, Animation animation,
Animation secondaryAnimation) {
// YOUR WIDGET CODE HERE
// I need to PASS title here...
// not sure how to do this.
// Also, is there a way to clean this code up?
return new SecondPage();
},
transitionsBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return SlideTransition(
position: new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.0, 0.0),
).animate(secondaryAnimation),
child: child,
),
);
},
);
Navigator.of(context).push(pageRoute);
}
}
class SecondPage extends StatefulWidget {
SecondPage({Key key, this.title}) : super(key: key);
static const String routeName = "/SecondPage";
final String title;
#override
_SecondPageState createState() => new _SecondPageState();
}
/// // 1. After the page has been created, register it with the app routes
/// routes: <String, WidgetBuilder>{
/// SecondPage.routeName: (BuildContext context) => new SecondPage(title: "SecondPage"),
/// },
///
/// // 2. Then this could be used to navigate to the page.
/// Navigator.pushNamed(context, SecondPage.routeName);
///
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
//======== HOW TO PASS widget.title ===========
title: new Text(widget.title),
//title: new Text('==== second page ==='),
),
body: new Container(),
floatingActionButton: new FloatingActionButton(
onPressed: _onFloatingActionButtonPressed,
tooltip: 'Add',
child: new Icon(Icons.add),
),
);
}
void _onFloatingActionButtonPressed() {
}
}
you can use onGenerateRoute here the example
main.dart
void main() {
runApp(new App());
}
app.dart
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new HomeScreen(),
routes: <String, WidgetBuilder>{
'/home': (BuildContext context) => new HomeScreen(),
//other routes
},
//onGenerateRoute Custom
onGenerateRoute: getGenerateRoute);
}
}
Route<Null> getGenerateRoute(RouteSettings settings) {
final List<String> path = settings.name.split('/');
if (path[0] != '') return null;
if (path[1].startsWith('team')) {
if (path.length != 3) return null;
String _title = path[2];
return new MaterialPageRoute<Null>(
settings: settings,
builder: (BuildContext context) => new TeamScreen(
title: _title,
),
);
}
// The other paths we support are in the routes table.
return null;
}
home_screen.dart
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Home Screen'),
),
body: new Center(
child: new Column(
children: <Widget>[
new RaisedButton(
onPressed: () {
String _title = "Team 1";
Navigator.pushNamed(context, '/team/$_title');
},
child: new Text('Go Team 1'),
),
new RaisedButton(
onPressed: () {
String _title = "Team 2";
Navigator.pushNamed(context, '/team/$_title');
},
child: new Text('Go Team 2'),
)
],
),
),
);
}
}
team.dart
import 'package:flutter/material.dart';
class TeamScreen extends StatelessWidget {
final String title;
const TeamScreen({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Team screen'),
),
body: new Center(
child: new Text(title),
),
);
}
}
Pass title value to SecondPage constructor:
void _gotoThirdPage() {
String page_title = "Yet Another Page";
final pageRoute = new PageRouteBuilder(
pageBuilder: (BuildContext context, Animation animation,
Animation secondaryAnimation) {
return new SecondPage(title: page_title);
},