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.
Related
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?
actually, i have fixed this problem. but it is weird implementation. maybe there are better solution for this situation.
so, i have list of screen that will show based of bottom navigation index
List<Widget> screenList = [
HomeScreen(),
KategoriScreen(),
PesananScreen(),
AccountScreen()
];
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Obx(() {
var user = Get.find<AccountController>().user.value; // <= weird Solution to trigger update
return screenList.elementAt(_mIndex);
}),
bottomNavigationBar: _bottomNavWidget(),
);
}
one of that Screen have Obx() in the widget. but when data change. the screen not updated. we need to change the screen index then return to that screen to update the widget. so, my current solution is add Obx() in Scaffold that handle the bottom navigation bar logic with useless var (var user). any better solution?
UPDATE
Sorry for the misunderstanding. my problem is when account controller is updated. the screen not updating. need to change other screen and going back. another solution is use Obx() in parent Widget. but the user value is not used
class ScreenController extends GetxController {
var tabIndex = 0;
void changeTabIndex(int index) {
tabIndex = index;
update();
}
}
This was apply to widget Screen page where you can use indexstack
GetBuilder<DashboardController>(
builder: (controller) {
return Scaffold(
body: SafeArea(
child: IndexedStack(
index: controller.tabIndex,
children: [
Home(),
HotDeals(),
ProfilePage(),
],
),
),
bottomNavigationBar: BottomNavigationBar(
unselectedItemColor: Colors.black,
selectedItemColor: Colors.redAccent,
onTap: controller.changeTabIndex,
currentIndex: controller.tabIndex,
showSelectedLabels: true,
showUnselectedLabels: true,
type: BottomNavigationBarType.fixed,
backgroundColor: Colors.white,
elevation: 0,
items: [
_bottomNavigationBarItem(
icon: CupertinoIcons.home,
label: 'Home',
),
_bottomNavigationBarItem(
icon: CupertinoIcons.photo,
label: 'Hotdeals',
),
_bottomNavigationBarItem(
icon: CupertinoIcons.person,
label: 'Profile',
),
],
),
);
},
);
This is the principle of state management. You need to set the state or notify the listeners in order to refresh the UI.
Here, only using Obx is not enough if it is not a controller that you are watching.
I would recommend that you create a class out of your screen list
class ScreenController extends GetxController{
int _selectedIndex = 0.obs;
List<Widget> _screenList = [
HomeScreen(),
KategoriScreen(),
PesananScreen(),
AccountScreen()
];
selectIndex(int index) => selectedIndex = index;
getScreen() => _screenList[_selectedIndex];
}
Then you can simply encapsulate your _selectedIndex inside your Obx widget, that will trigger the refresh when another button uses the selectIndex method.
(You need to put the ScreenController first, as usual)
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 have 3 screens - home, friends and profile. I want an AppBarand BottomNavigationBar to be always present in all my screens. Right now, I have the AppBar and BottomNavigationBar defined inside a Scaffold in the main.dart file. Whenever an option is tapped on BottomNavigationBar, the Scaffold body is replaced with the content of the screen of choice.
main.dart:
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: SafeArea(
child: Container(
child: _pageOptions[_selectedPage],
),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedPage,
onTap: (int index) {
setState(() {
_selectedPage = index;
});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(
icon: Icon(Icons.people), title: Text('Friends')),
BottomNavigationBarItem(
icon: Icon(Icons.person), title: Text('Profile'))
],
),
),
routes: <String, WidgetBuilder>{
'/home': (BuildContext context) => Home(),
'/friends': (BuildContext context) => Friends(),
},
);
}
Now, I have a custom avatar widget that I have reused in home screen. On clicking the avatar, I want to display the friends screen. I tried this code inside an onTap method in avatar:
{Navigator.pushNamed(context, '/friends')},
friends.dart:
Widget build(BuildContext context) {
return Container(
child: Text('Friends'),
);
}
On tapping avatar, I am getting the friends screen, but everything else is lost (AppBar, BottomNavigationBar etc).
How do I achieve this? Should I be returning a scaffold with AppBar, BottomNavigationBar etc. from all 3 of my screens? Does that negatively affect performance? Or is there a way to do this just by replacing the body of the Scaffold in main.dart?
Yes you need to return a scaffold with an appbar in all the three screens. You can put the appbar in a separate statelesswidget class and use it in all the three scaffolds. Same for bottom navigation bar. You can try to keep the returning widget in build method 'const'. Will not be possible for appbar since title will be varying. It won't get rebuilt so no worries on performance.