I'm new to flutter and I'm trying to implement a persistent bottom navigation bar with the "persistent_bottom_nav_bar 4.0.2" plugin.
The issue I'm running into is, that I have to declare my AppBar in the page that implements the bottom bar, since that is where my scaffold is and the screens are just widgets loaded by the plugin into this scaffold. Now, if I'm navigating to a different page from one of the tabs, I want the AppBar to show a back button. For that I need to add an AppBar in the page I'm navigating to, but then I have two AppBars stacked. Thus, I need to hide the AppBar from the bottom bar page when I push pages into the navigator stack of the active tab.
Currently I'm trying to get the name of the route with ModalRoute.of(context)?.settings.name and compare it with the name of the route of the page that holds the bottom bar, and if they're not equal I can pass an argument back and hide the app bar. (I'm open for better solutions, that's the first I came up with). The problem is, that the named route does not work on the navigators of the different tabs. I'm getting something like this as the name: /9f580fc5-c252-45d0-af25-9429992db112.
My HomePage looks like this:
#override
Widget build(BuildContext context) {
print(ModalRoute.of(context)?.settings.name); // this gives me the correct route name
return Scaffold(
appBar: _showAppBar ? MyAppBar("") : null,
body: PersistentTabView(
context,
controller: _controller,
screens: _buildScreens(),
items: _navBarsItems(),
confineInSafeArea: true,
popAllScreensOnTapOfSelectedTab: true,
popActionScreens: PopActionScreensType.all,
navBarStyle: NavBarStyle.style6,
));
}
List<PersistentBottomNavBarItem> _navBarsItems() {
return [
PersistentBottomNavBarItem(
routeAndNavigatorSettings: const RouteAndNavigatorSettings(onGenerateRoute:
router.generateRoute),
icon: const Icon(Icons.add_circle_outline),
title: ("Add"))
];
}
List<Widget> _buildScreens() {
return [
const AddPage(),
];
}
On the AddPage (which is one of the tab views and only a widget without scaffold) I want a button, that navigates to a different screen. If that happens I want to hide the AppBar from the home_page and show the AppBar of this page to have the back navigation.
I do the navigation like this:
child: IconButton(
icon: const Icon(Icons.add),
color: Colors.white,
onPressed: () {
Navigator.pushNamed(context, AddCollectionRoute, arguments: 'test');
RouteSettings? routeSettings = ModalRoute.of(context)?.settings;
},
),
But here I now have "/9f580fc5-c252-45d0-af25-9429992db112" as name and null as argument.
Routes are generated like this:
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case AddCollectionRoute:
return MaterialPageRoute(builder: (context) => const AddCollectionPage());
case HomeRoute:
return MaterialPageRoute(builder: (context) => const HomePage());
}
}
So my questions are:
Why are the routes in the tabs anonymous and why are the arguments gone? Up until the HomePage everything works as expected.
Is there a better approach to hide the AppBar on navigation?
Related
I have navigate from one page to another page like from a button which is on the page
1st Page <---> 2nd Page <---> 3rd Page <---> 4th Page
If i am on the any page after clicking on the back button of android i want to navigate to the homepage instead of going to the previous page.
Like if i am on page 2 if i press back button i go to the page 1 but i want to pop all the elements on the stack and go to the main home page same for all.
Future<bool> pushPage(BuildContext context) {
// return Navigator.of(context).pop(true);
return Navigator.of(context).pushNamedAndRemoveUntil('/quiz2',
ModalRoute.withName("/landingPage"));
}
class _Quiz1State extends State<Quiz1> {
bool hasSolved = false;
int solvedOption = 0;
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
pushPage(context);
return false;
},
child: Scaffold(
body: Stack(
alignment: Alignment.topCenter,
children: [
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Color(0xffBD00FF),
Color(0xffF1509E),
],
),
),
),
mainQuiz(),
Container(
child: uiElements(context),
margin: EdgeInsets.only(top: 16),
),
],
),
),
);
}
mainquiz() function is the 2nd Page Function
We know that Navigator.pop() removes to topmost page available on the stack. There is another function named Navigator.popUntil() which will keep on popping out the pages from the stack until the desired page is reached.
For that first (If you have not done this yet) you need to give names to all your pages. These will act like an ID card for the page when you reference them in you functions. For that on the topmost part of your tree where the widget MaterialApp is located, do like this :
MaterialApp(
home: new Screen1(),
routes: <String, WidgetBuilder> {
'/screen1': (BuildContext context) => new Screen1(),
'/screen2' : (BuildContext context) => new Screen2(),
'/screen3' : (BuildContext context) => new Screen3(),
'/screen4' : (BuildContext context) => new Screen4()
},
)
Basically what you do here is you pre-define the names for all the routes you will be taking in your app.
Now say the above example's navigation works like this :
Screen1 -> Screen2 -> Screen3 -> Screen4
Now say I am at Screen4() ..... then my current stack will be [Screen4,Screen3,Screen2,Screen1] .....(latest first, oldest last), and on hitting the back button I need to directly pop out to Screen1().... (Home page in your case). Then instead of out simple Navtigator.pop() I will do something like this :
Navigator.popUntil(context, ModalRoute.withName('/screen1'));
This will keep popping out the widgets until it reaches screen1.
Hope this solves your porblem.
NOTE:
Once you have preDefined all your routes at the start, instead of calling Navigator.push(context,MaterialPageRoute((context) => Screen1());
You can do this :
Navigator.pushNamed(context, '/screen1');
Edit :
Say if your HomePage was not already present in stack and you want to show it after popping out all the screens. You can do :
Navigator.of(context).pushNamedAndRemoveUntil('/homepage', (Route<dynamic> route) => false);
This will add Homepage() after removing all the other screens present in the stack. [" (Route route) => false " will pop out all the screens from the stack. ].
Then if you want to pop out the screen only till a given screen is reached(say till we reach screen1) you can do something like:
Navigator.of(context).pushNamedAndRemoveUntil('/homepage','/screen1');
After 1st page use Navigator.pushReplacement instead Navigator.push
You can also use this to go first page:
Navigator.of(context).popUntil((route) => route.isFirst);
I have this problem. My home page has a bottomNavigationBar with 2 pages A and B.
Pages A and B have buttons that when tapped navigate to other pages which don't have bottom Navigation Bars. Now, when I am in these other pages that don't have bottom navigation bars and would like to return to my home page, that is the pages with bottom Navigation bars (A & B), using "Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context) => A()), (Route route) => false),", the bottom Navigation Bar in page A disappears. The same happens when I navigate to page B.
How can I "pushAndRemoveUntil" to either pages (A & B) in my home page without losing the bottom Navigation Bar?
This is how I have implemented the bottom Navigation Bar:
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
body: PageView(
children: <Widget>[
A(),
B(),
],
controller: pageController,
onPageChanged: onPageChanged,
physics: NeverScrollableScrollPhysics(),
),
bottomNavigationBar: CupertinoTabBar(
backgroundColor: appbar_Color,
currentIndex: pageIndex,
onTap: onTap,
activeColor: Theme.of(context).primaryColor,
items: [
BottomNavigationBarItem(
title: Text("Page A"),
icon: Icon(Icons.whatshot),
),
BottomNavigationBarItem(
title: Text("Page B"),
icon: Icon(Icons.notifications_active),
),
],
),
);
my onTap function:
onTap(int pageIndex) {
pageController.animateToPage(pageIndex,
duration: Duration(milliseconds: 100), curve: Curves.easeInOut);
}
Thank you so much.
You just need to call Navigator.pop(context) on other pages as they are just a stack on top of your current Scaffold containing PageView of 2 Pages. Just pop the page out and you will be redirected with the same screen.
It doesn't seem that you want to use pushAndRemoveUntil that push the given route onto the navigator, and then remove all the previous routes until the predicate returns true. It seems that you want to use pop (one view back) or popUntil (back until finding requested one)
This command will take you directly to the first view in the stack
Navigator.of(context).popUntil((route) => route.isFirst);
This other command will go one view back in the stack
Navigator.of(context).pop();
I'm learning flutter and starting with a simple 2 tab, bottom navigation bar app (specifically Cupertino based). This video does a decent job of explaining the fundamentals but I am missing something.
The example provided in the video suggests that when you tap on a BottomNavigationBarItem, you don't actually navigate to a new screen, you simply re-render the widgets on the existing screen. The video only invokes Navigator after he constructs a button widget in the page container.
Is a BottomNavigationBarItem in a CupertinoTabBar supposed to invoke Navigator? Or am I misunderstanding the use case of CupertinoTabBar entirely?
Here is my example code:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget
{
#override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: <BottomNavigationBarItem> [
BottomNavigationBarItem(
icon: Icon(Icons.menu),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.person_solid),
),
],
),
tabBuilder: (BuildContext context, int index) {
switch (index) {
case 0:
return new Container(
color: Colors.red
); break;
case 1:
return new Container(
color: Colors.white
); break;
default: {
return new Container(
color: Colors.white
);
}
}
},
);
}
}
I'm wondering how I would alter the switch to invoke Navigator or not.
So the pattern that seems to be correct is:
The CupertinoTabBar indices / screens which are navigated to via BottomNavigationBarItem are really just CupertinoPageScaffold widgets. Then within those CupertinoPageScaffold screens, you can use the Navigator methods to navigate to other routes / screens.
The main reason why this makes sense is each index / screen maintains its own navigation stack so you can be on screen (index) 1, navigate a few pages, go to index 0 and back and your navigation stack state is preserved.
It took me a few times reading the documentation on CupertinoTabScaffold to drill it in to my brain but it makes sense.
Let me know if I'm wrong on this.
I am using interceptor https://pub.dartlang.org/packages/back_button_interceptor to execute a method when the page 1 is back from page 2.
If I come back from page 2 to page 1 using device back button, the method is executed.
But if I come back from page 2 to page 1 using the arrow button at appBar I am not able to execute the method.
How can the back arrow button functionality default to the device back button?
You can surround your scaffold on Page 2 with WillPopScope, set onWillPop to false to prevent the page from being popped by the system and then add your own back button into the app bar's leading widget and perform your pop in there.
#override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: () async => false,
child: new Scaffold(
appBar: new AppBar(
title: new Text("data"),
leading: new IconButton(
icon: new Icon(Icons.ac_unit),
onPressed: () => Navigator.of(context).pop(),
),
),
),
);
}
code for the answer from this post
Edit: Addition to Page 2 to control navigation
In addition to the above code you'll add the below code to page 2. Change
Navigator.of(context).pop()
to
Navigator.of(context).pop('upload_files')
Then in your page 1 where you navigate you'll await the navigation and use the result returned from the pop on page 2 and run your logic
var navigationResult = await Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => Page2()));
if(navigationResult == 'upload_files') {
uploadFiles(); // Perform your custom functionality here.
}
The default back button in AppBar is BackButton widget from material.dart. You may create it manually and pass your own onPressed to do what you want:
return Scaffold(
appBar: AppBar(
leading: BackButton(onPressed: _onBackPressed),
title: Text('Title'),
),
body: Container(),
);
If you do not specify leading in AppBar, then it creates a BackButton with the handler that does Navigator.maybePop(context).
How do I create a fullscreenDialog that covers my bottomnavigationbar?
My mainscreen looks like this, where I have a bottomnavigationbar which navigates to three different screens.
#override
Widget build(BuildContext context) {
return new Scaffold(
body: PageView(
children: [new HomeTab(), new PresentationsTab(), new TestTab()],
controller: _pageController,
onPageChanged: pageChanged,
),
bottomNavigationBar: new BottomNavigationBar(
currentIndex: _page,
onTap: tapBottomNav,
items: [
new BottomNavigationBarItem(
icon: new Icon(Icons.home),
title: new Text('Home'),
),
new BottomNavigationBarItem(
icon: new Icon(Icons.pregnant_woman),
title: new Text('Presentation'),
),
new BottomNavigationBarItem(
icon: new Icon(Icons.pregnant_woman),
title: new Text('Presentation'),
)
],
),
);
}
And somewhere I have a screen which navigates to another screen with the fullscreenDialog flag set to true like this.
Navigator.push(
context,
new MaterialPageRoute(
builder: (BuildContext context) => new AddAudio(),
fullscreenDialog: true,
),
);
On my appbar I can see that the flag actually works because my backbutton arrow will become an x, but my bottomnavigationbar will still be visible, how do I resolve this?
I have an answer for you on my post: Flutter MaterialPageRoute as fullscreenDialog appears underneath BottomNavigationBar
But, to recap for you:
I assume you are presenting your modal from a page which is itself a Scaffold? In which case you have nested Scaffold objects, and this is where the problem lies. To ensure your modal appears above the BottomNavigationBar you need to include that widget in the child Scaffold and not in the root one.
This seems like an utter pain in the arse, and not a great solution with loads of boilerplate and code duplication; but it isn't. You just need to build a custom composition widget for BottomNavigationBar and then use ChangeNotifier with a state class to manage state. I've explained it in the post I reference above, and it is probably better to read the answer there so you understand the context.
That is probably the expected behavior, you should push a PageRoute (with a PageRouteBuilder) and use a custom transition to have the bottom to top slide animation.