Cannot make bottom navigation bar and routing work together in flutter - 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

Related

Exclamation mark in front of await

One case I've looked up where this is used is Navigator with WillPopScope as the parent as mentioned in the comment of this answer.
To put my question concretely, the following code should pop from the nested navigator but instead the app exits when the back button is pressed on android.
void main() {
runApp(MaterialApp(title: 'Navigation Basics', home: PearlScreen()));
}
class PearlScreen extends StatefulWidget {
const PearlScreen({Key? key}) : super(key: key);
#override
State<PearlScreen> createState() => _PearlScreenState();
}
class _PearlScreenState extends State<PearlScreen> {
final _navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => await _navigatorKey.currentState!.maybePop(),
child: Navigator(
key: _navigatorKey,
onGenerateRoute: (settings) {
Widget screen;
switch (settings.name) {
case '/':
screen = const HomeScreen();
break;
case '/quiz':
screen = const QuizScreen();
break;
default:
throw "Invalid route in Pearl: ${settings.name}";
}
return MaterialPageRoute(
builder: (_) => Material(child: screen),
);
},
),
);
}
}
But as soon as I change it to onWillPop: () async => !await _navigatorKey.currentState!.maybePop(), it works correctly and pops from the nested navigator instead of exiting the app.
What is the significance of !await?

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.

Navigation with custom navigator and globalkey

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

Starting a Welcome Screen on first startup

I'm trying to display a few welcome screens on the first launch of my App. After which it will go to the login screen. But for efficiency, I need it to pop all the welcome screens off the 'stack' before launching the login screen, which is the reason behind this request.
My main.dart program loads into preloader2.dart which looks like this:
class PreLoad2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
String myRoute;
return Scaffold(
body: SafeArea(
child: FlatButton(
child: Text('Press me!'),
onPressed: () {
if (loginCount == 0) { // globally defined variable
myRoute = '/welcome1';
} else {
myRoute = '/login';
}
++loginCount;
Navigator.pushNamed(context, myRoute);
},
),
),
);
}
}
It works perfectly well. But I have to click on the button.
I've tried both a stateless widget and a stateful one in preload2.
Can I write some code to replace the button so it just runs with no user input?
Just place the onPressed body into the build function before returning the Scaffold
#override
Widget build(BuildContext context) {
String myRoute;
if (loginCount == 0) // globally defined variable
myRoute = '/welcome1';
else
myRoute = '/login';
++loginCount;
Navigator.pushNamed(context, myRoute);
return Scaffold(
body: SafeArea(
child: FlatButton(
child: Text('Press me!'),
onPressed: () {
// Nothing Here
},
),
),
);
}
}
I have managed to get around, rather than answer my own question.
Inside my main.dart program I have added a new Welcome Widget. What this does is to set up a new Navigator that in turn calls each of my welcome screens. Each one in turn is pushed and then popped. So it is not quite what I was after but it definitely does the trick and appears to be quite efficient. My Welcome widget looks like this:
class Welcome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Navigator(
initialRoute: 'welcome1',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch (settings.name) {
case 'welcome1':
builder = (BuildContext _) => Welcome1(
onWelcomeComplete: () {
Navigator.of(context).pop();
},
);
break;
case 'welcome2':
builder = (BuildContext _) => Welcome2(
onWelcomeComplete: () {
Navigator.of(context).pop();
},
);
break;
case 'welcome3':
builder = (BuildContext _) => Welcome3(
onWelcomeComplete: () {
Navigator.of(context).pop();
},
);
break;
case 'welcome4':
// Assume ChooseCredentialsPage collects new credentials and then
// invokes 'onSignupComplete()'.
builder = (BuildContext _) => Welcome4(
onWelcomeComplete: () {
Navigator.of(context).pop();
},
);
break;
default:
throw Exception('Invalid route: ${settings.name}');
}
return MaterialPageRoute(builder: builder, settings: settings);
},
);
}
}
Each of Welcome2(), Welcome3() and Welcome4() are held in their own files and pass back a link to the next widget, either going forward or backwards through the chain. The button code looks like this:
onPressed: () {
Navigator.of(context).pushReplacementNamed(routeName);
},
Where the route name is 'welcome2', for example, which is then linked to by the switch statement that creates a new route to the required widget.
Once on the last welcome screen (or before if required) the 'finish' button calls the onWelcomeComplete function which simply pops the complete Navigator stack leaving the code to follow the default route ('/') which I set up in the MyApp widget taking me to my Login() widget in login.dart. The code is:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/welcome',
routes: {
'/': (BuildContext context) => Login(),
'/welcome': (BuildContext context) => Welcome(),
...
I hope this answer clearly explains what I have achieved and helps someone else.
Further information about the Flutter Navigator can be found here:
Navigator Class