Navigation with custom navigator and globalkey - flutter

I did what was suggested here for a persistent bottom bar using a custom navigator.
class _MainScreenState extends State<MainScreen> {
final _navigatorKey = GlobalKey<NavigatorState>();
// ...
#override
Widget build(BuildContext context) {
return Scaffold(
body: Navigator(
key: _navigatorKey,
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
// Manage your route names here
switch (settings.name) {
case '/':
builder = (BuildContext context) => HomePage();
break;
case '/page1':
builder = (BuildContext context) => Page1();
break;
case '/page2':
builder = (BuildContext context) => Page2();
break;
default:
throw Exception('Invalid route: ${settings.name}');
}
// You can also return a PageRouteBuilder and
// define custom transitions between pages
return MaterialPageRoute(
builder: builder,
settings: settings,
);
},
),
bottomNavigationBar: _yourBottomNavigationBar,
);
}
}
Now, if I have child widgets on HomePage with a button that needs to navigate me to a Page2, How would I achieve that from inside HomePage - because?

some button or GestureDetector
onTap : ()=>Navigator.of(context).pushNamed('/page2')
or
onPress : ()=>Navigator.of(context).pushNamed('/page2')

Related

Prefered form of navigation with persistent BottomNavigationBar

I am making an app that has a persistent BottomNavigationBar. Depending on user != null the user gets into the LoginScreen or HomeScreen
There are two ways to get this done by routing:
First approach: include LoginScreen inside the nested Navigator from the MainScaffold
the mainScaffold the user gets redirected from, will have a body that has its own Navigator class inside that body.
App
class AppName extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainScaffold(),
);
}
}
RouteGenerator
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
final args = settings.arguments;
dynamic builder;
switch (settings.name) {
case '/':
builder = LoginScreen();
break;
case '/mainScaffold':
builder = MainScaffoldBody();
break;
case '/mainScaffold/page2':
builder = (BuildContext context) => Page2();
break;
...
default:
return _errorRoute();
}
return PageRouteBuilder(
...
pageBuilder: (BuildContext context, _animation, __animation) => builder,
...
);
}
MainScaffold
class MainScaffold extends StatelessWidget {
...
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBarSelector(),
body: Navigator(
key: _navigatorKey,
initialRoute: '/mainScaffold/home',
onGenerateRoute: RouteGenerator.generateRoute
},
),
bottomNavigationBar: CustomBottomNavigationBar(controller: _controller),
);
}
}
Alternative: don't include LoginScreen inside the nested Navigator from the MainScaffold but seperate it
The alternative approach would be to have MainScaffold as initialRoute and LoginScreen inside the bodyof the MainScaffold.
If I want the BottomNavigation to dissapear on for example LoginScreen, I could simply toggle the sized/visibility of that bar. The RouteGenerator would look like such.
App
class AppName extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
onGenerateRoute: RouteGenerator.generateRoute, // this static creates possibility of using parameters
);
}
}
RouteGenerator
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
final args = settings.arguments;
dynamic builder;
switch (settings.name) {
case '/':
builder = MainScaffold();
break;
case '/login':
builder = LoginScreen();
break;
case '/mainScaffold/home':
builder = HomeScreenArguments();
break;
...
default:
return _errorRoute();
}
return PageRouteBuilder(
...
pageBuilder: (BuildContext context, _animation, __animation) => builder,
...
);
}
So what is the best approach here:
LoginScreen is part of the nested Navigator from MainScaffold
LoginScreen is not part of the nested Navigator from MainScaffold

Best approach to Android navigation back button on Nested Navigator

I have a Scaffold that holds a Navigator object. Each route has a possibility of holding an appBar.
MainScaffold
Scaffold(
appBar: AppBarSelector(),
...
body: WillPopScope(...},
child: Navigator(
key: _navigatorKey,
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
Widget builder;
switch (settings.name) {
case '/':
ref.read(routeNameProvider.state).state = '/';
builder = HomeScreenBody(
automotive: content["stuff"],
vehicleTypes: content["more_stuff"]["types"],
);
break;
case '/users':
ref.read(routeNameProvider.state).state = '/users';
builder = UsersScreenBody(
stuff: content["stuff"],
moreStuff: content["more_stuff"],
);
break;
default:
throw Exception('Invalid route: ${settings.name}');
}
return PageRouteBuilder( ...
pageBuilder: (BuildContext context, _animation, __animation) => builder,
...
bottomNavigationBar: CustomBottomNavigationBar(navigatorKey: _navigatorKey),
);
AppBarSelector
class AppBarSelector extends AppBarIcon {
#override
Widget build(BuildContext context, WidgetRef ref) {
return selectAppBar(ref);
}
Widget selectAppBar(WidgetRef ref) {
String routeName = ref.watch(routeNameProvider);
switch (routeName) {
case '/garage':
return AppBarIcon(
titleText: "Garage",
icon: Icons.add,
route: VehicleRegistrationScreen(),
);
break;
case '/home/list':
return AppBarIcon(
titleText: ref.read(appBarTitleProvider),
icon: Icons.add,
route: SomeList(),
);
break;
default:
return SizedBox(height: 0);
}
}
Problem
Whenever I route to a page, the appBar shows/dissapears just fine. But once I press the Android Navigation BackButton there is no routing happening, thus the appBar does not change how it should be and remain after popping the route on a screen that should not have an appBar.
Question
How do I make AppBarSelector() respond to my Android Navigation BackButton?
Approach made
I have tried the persistent_bottom_nav_bar 4.0.2 package but that lakcs things like a persistent appBar or the possibility to have a transparant BottomNavigationBar.
I wonder how other people have dealt with this situation.

Using Navigator with a bottomNavigationBar

What's the correct way of setting up navigation architecture named routes while using a bottomNavigationBar?
Here's my current setup but I feel there's a better way of doing it:
main.dart:
onGenerateRoute: (settings) {
return MaterialPageRoute(
settings: settings,
builder: (context) {
switch (settings.name) {
case NamedRoutes.splashScreen:
return SplashScreen();
case NamedRoutes.login:
return LoginPage();
case NamedRoutes.mainApp:
return NavigatorSetup();
default:
throw Exception('Invalid route: ${settings.name}');
}
});
navigatorSetup.dart:
IndexedStack(
index: Provider.of<RoutesProvider>(context).selectedViewIndex,
children: [FirstMain(), SecondMain(), ThirdMain(), FourthMain()],
), bottomNavigationBar...
in each main files there is the following setup
class FirstMain extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Navigator(
key: Provider.of<RoutesProvider>(context).homeKey,
onGenerateRoute: (settings) {
return MaterialPageRoute(
settings: settings,
builder: (context) {
switch (settings.name) {
case '/':
case NamedRoutes.mainPage:
return MainPage();
case NamedRoutes.singleMainPage:
return SingleMainPage();
default:
throw Exception('Invalid route: ${settings.name}');
}
},
);
},
);
}
}
Then my routes provider looks like this:
class RoutesProvider extends ChangeNotifier {
int _selectedViewIndex = 0;
get selectedViewIndex => _selectedViewIndex;
set selectedViewIndex(int newIndex) {
_selectedViewIndex = newIndex;
notifyListeners();
}
GlobalKey _mainKey = GlobalKey<NavigatorState>();
GlobalKey _homeKey = GlobalKey();
GlobalKey _secondKey = GlobalKey();
GlobalKey _thirdKey = GlobalKey();
GlobalKey _fourthKey = GlobalKey();
get mainKey => _mainKey;
get homeKey => _homeKey;
get secondKey => _secondKey;
get thirdKey => _thirdKey;
get fourthKey => _fourthKey;
}
The way I'm currently changing routes when on another page of the indexedStack
final RoutesProvider routesProvider = Provider.of<RoutesProvider>(context, listen: false);
final GlobalKey thirdKey = routesProvider.thirdKey;
routesProvider.selectedViewIndex = 2;
Navigator.pushReplacementNamed(thirdKey.currentContext, NamedRoutes.third);
The better way to navigate
Creating a route_generator
import 'package:flutter/material.dart';
import 'package:routing_prep/main.dart';
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
// Getting arguments passed in while calling Navigator.pushNamed
final args = settings.arguments;
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) => FirstPage());
case SecondPage.routeName:
// Validation of correct data type
if (args is String) {
return MaterialPageRoute(
builder: (_) => SecondPage(
data: args,
),
);
}
// If args is not of the correct type, return an error page.
// You can also throw an exception while in development.
return _errorRoute();
default:
// If there is no such named route in the switch statement, e.g. /third
return _errorRoute();
}
}
static Route<dynamic> _errorRoute() {
return MaterialPageRoute(builder: (_) {
return Scaffold(
appBar: AppBar(
title: Text('Error'),
),
body: Center(
child: Text('ERROR'),
),
);
});
}
}
As you can see, you've moved from having bits of routing logic everywhere around your codebase, to a single place for this logic - in the RouteGenerator. Now, the only navigation code which will remain in your widgets will be the one pushing named routes with a navigator.
Before you can run and test the app, there's still a bit of a setup to do for this RouteGenerator to function.
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
...
// Initially display FirstPage
initialRoute: '/',
onGenerateRoute: RouteGenerator.generateRoute,
);
}
}
class FirstPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
...
RaisedButton(
child: Text('Go to second'),
onPressed: () {
// Pushing a named route
Navigator.of(context).pushNamed(
SecondPage.routeName,
arguments: 'Hello there from the first page!',
);
},
)
...
}
}
class SecondPage extends StatelessWidget {
static const routeName = "/second";
// This is a String for the sake of an example.
// You can use any type you want.
final String data;
SecondPage({
Key key,
#required this.data,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Routing App'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'Second Page',
style: TextStyle(fontSize: 50),
),
Text(
data,
style: TextStyle(fontSize: 20),
),
],
),
),
);
}
}

Cannot make bottom navigation bar and routing work together in flutter

I am new to flutter and i cannot navigate to new page from bottom navigation bar
I have main app
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
));
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
builder: (BuildContext buildContext, Widget widtget) => Scaffold(
body: RootNavigator(),
bottomNavigationBar: BottomNavigation(),
),
);
}
}
and Rootnavigator
class RootNavigator extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Navigator(
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
// final args = settings.arguments;
return MaterialPageRoute(
settings: settings,
builder: (BuildContext context) {
switch (settings.name) {
case '/':
return Page1();
case '/page2':
return Page2();
case '/page3':
return Page3();
default:
return RouteErrorPage();
}
});
},
);
}
}
And bottom navigator
class BottomNavigation extends StatefulWidget {
#override
BottomNavigationState createState() {
return new BottomNavigationState();
}
}
class BottomNavigationState extends State<BottomNavigation> {
int currIndex = 0;
onTap(int index) {
setState(() => currIndex = index);
switch (index) {
case 0:
Navigator.pushNamed(context, '/');
break;
case 1:
Navigator.pushNamed(context, '/page2');
break;
case 2:
Navigator.pushNamed(context, 'page3');
break;
default:
Navigator.push(
context, MaterialPageRoute(builder: (_) => RouteErrorPage()));
}
}
....
// added app bar items
}
Tabs are switching but routes are not. It stays on home page.
I feel like there is something with context but do not know how to solve it.
Can anybody help?
Thanks
p.s. if i move bottom navigation bar to each page separatly everything work sexcept selected tab (because of state) and also i want to keep one, shared app bar
Answer is - #LoVe comment was correct.
Thats how flutter works.
if you have bottom navigation you have to swipe through pages.
Redirection means moving to completely new page and there you have to define Scaffold from sratch.
If you want to have shared AppBar - make it reusable widget

Use Navigator in main.dart get current context

Is there some chance to get current context on the main.dart? I am using the sharing intent for listening applinks, and I need to redirect to specify page. But I don't know how can I use the context.
#override
Widget build(BuildContext context) {
ReceiveSharingIntent.getInitialText().then((String val){
//some logic
Navigator.pushNamed(context, ....);
});
return MaterialApp(
title: 'MyApp',
initialRoute: Routes.home,
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case Routes.home:
return SimplePageRoute(builder: (context) => HomeScreen());
break;
}
}
);
}
I have got this error
Unhandled Exception: Navigator operation requested with a context that
does not include a Navigator.
Ok I am understand, but how can I get the current context in this place?
Ok I fixed it with navigatorKey!!
final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
ReceiveSharingIntent.getInitialText().then((String val){
//some logic
navigatorKey.currentState.pushNamed(Routes.myPage);
});
return MaterialApp(
title: 'MyApp',
initialRoute: Routes.home,
navigatorKey: navigatorKey,
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case Routes.home:
return SimplePageRoute(builder: (context) => HomeScreen());
break;
}
}
);
}