I'm trying to make navigation inside the body of the widget where the bottomNavigationBar remains outside, here is my code:
class WrapperController extends GetxController {
int currentIndex = 0;
List<BottomNavigationElement> items = [];
Widget navigationTab({GlobalKey<NavigatorState> naviKey, Widget widget}) {
return Navigator(
key: naviKey,
onGenerateRoute: (routeSettings) {
return GetPageRoute(page: () => widget);
},
);
}
Widget bottomNavigationBar() {
return BottomNavigationBar(
selectedItemColor: Get.theme.accentColor,
type: BottomNavigationBarType.fixed,
currentIndex: currentIndex,
backgroundColor: Get.theme.primaryColor,
onTap: (int index) => _selectTab(index),
items: items.map((e) => e.bottomBarItem).toList(),
);
}
void _selectTab(int index) {
if (index == currentIndex) {
items[index]
.navigationKey
.currentState
.popUntil((route) => route.isFirst);
} else {
currentIndex = index;
}
update();
}
Future<bool> onWillPop() async {
final isFirstRouteInCurrentTab =
!await items[currentIndex].navigationKey.currentState.maybePop();
if (isFirstRouteInCurrentTab) {
if (currentIndex != 0) {
// _selectTab(1);
return false;
}
}
return isFirstRouteInCurrentTab;
}
#override
void onInit() {
super.onInit();
items = [
//Каталог
BottomNavigationElement(
bottomBarItem: BottomNavigationBarItem(
icon: Icon(Icons.search),
label: "Home",
),
bottomBarView: HomePage(),
navigationKey: Get.nestedKey('0'),
),
//Любимое
BottomNavigationElement(
bottomBarItem: BottomNavigationBarItem(
icon: Icon(Icons.favorite_border),
label: "Favorites",
),
bottomBarView: FavoritesPage(),
navigationKey: Get.nestedKey('1'),
),
//Корзина
BottomNavigationElement(
bottomBarItem: BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart_outlined),
label: "Cart",
),
bottomBarView: CartPage(),
navigationKey: Get.nestedKey('2'),
),
//Заказы
BottomNavigationElement(
bottomBarItem: BottomNavigationBarItem(
icon: Icon(Icons.history),
label: "History",
),
bottomBarView: HistoryPage(),
navigationKey: Get.nestedKey('3'),
),
//Меню
BottomNavigationElement(
bottomBarItem: BottomNavigationBarItem(
icon: Icon(Icons.menu),
label: "Menu",
),
bottomBarView: MenuPage(),
navigationKey: Get.nestedKey('4'),
),
];
}
}
class BottomNavigationElement {
Widget bottomBarView;
BottomNavigationBarItem bottomBarItem;
GlobalKey<NavigatorState> navigationKey;
BottomNavigationElement({
#required this.bottomBarView,
#required this.bottomBarItem,
#required GlobalKey<NavigatorState> navigationKey,
});
}
and here is the Wrapper widget :
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetBuilder<WrapperController>(builder: (controller) {
return Scaffold(
body: IndexedStack(
index: controller.currentIndex,
children: controller.items
.map((e) => controller.navigationTab(
naviKey: e.navigationKey,
widget: e.bottomBarView,
))
.toList(),
),
bottomNavigationBar: controller.bottomNavigationBar(),
);
});
}
}
here is the HomePage("Search") which has a navigation button:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home"),
),
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Detail(),
),
),
child: Text("Detail"),
),
),
);
}
}
the question is, when I use Navigator.of (context) Get.to () instead, then the navigation is done entirely and not inside the body. I need internal navigation using Get.to (), how do I do this?
There is one quick solution
Create a separate navigator for the bottom navbar (if you want to separate auth from the main logic).
Code for that is:
Expanded(
child: Navigator(
key: Get.nestedKey(1),
initialRoute: controller.pageName.value,
onGenerateRoute: HomeRouter.generateRoute,
)
)
For Navigation
void gotoPage(String page, BuildContext context) {
pageName.value = page;
print("Request to go to ${pageName.value}");
Get.keys[1]!.currentState!.pushNamed(page);
//if you want to change the URL in browser as well
html.window.history.pushState(null, "/", page);
}
Related
The routing starts at the main().I dont think the mistake is here.
The first page is CredentialPage() because its set to home
In CredentialPage(), I check if the user is logged in, if he is. I send him to NavigatorView(). The issue could be here maybe.
Once the CredentialPage loads, the user is routed to AllJobsView()
here is a small video of the issue: https://www.youtube.com/watch?v=qkBueUr_gN0
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(
MaterialApp(
title: 'Vendor Management',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const CredentialPage(), // <=======
routes: {
loginRoute: (context) => const LoginView(),
registerRoute: (context) => const RegisterView(),
verifyEmailRoute: (context) => const VerifyEmailView(),
//
allJobsRoute: ((context) => const AllJobsView()),
myJobsRoute: (context) => const MyJobsView(),
newJobRoute: (context) => const CreateUpdateJobView(),
myJobApplicationsRoute: (context) => const JobApplicationView(),
navigatorViewRoute: (context) => const NavigatorView(),
},
),
);
}
class CredentialPage extends StatelessWidget {
const CredentialPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: AuthService.firebase().initialize(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
final user = AuthService.firebase().currentUser;
if (user != null) {
if (user.isEmailVerified) {
return const NavigatorView(); // <============
} else {
return const VerifyEmailView();
}
} else {
return const LoginView();
}
default:
return const CircularProgressIndicator();
}
},
);
}
}
Once the CredentialPage loads, the user is routed to AllJobsView()
class _NavigatorViewState extends State<NavigatorView> {
late final FirebaseCloudStorage _jobsService;
String get userId => AuthService.firebase().currentUser!.id;
#override
void initState() {
_jobsService = FirebaseCloudStorage();
super.initState();
}
int currentIndex = 0;
final screens = [
AllJobsView(), <========
MyJobsView(),
CreateUpdateJobView(),
JobApplicationsView(),
Center(
child: Text('Profile'),
),
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: screens[currentIndex],
bottomNavigationBar: BottomNavigationBar( <=== bottom nav
currentIndex: currentIndex,
selectedItemColor: Colors.white,
backgroundColor: Colors.blue,
type: BottomNavigationBarType.fixed,
selectedFontSize: 10,
onTap: (index) => setState(() => currentIndex = index),
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.dashboard),
label: 'Open jobs',
),
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'My jobs',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'Create Job',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Job Applications',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Profile',
),
],
),
);
}
}
the issue was with the nagivation route when i clicked logg in.
instead of sending me to the all jobs, i changed it to navigator view!
I would like to call function between another clas. So when the menu tapped from grabDrawer it will change the currentIndex at Main() class. Do you know how to do that? Here is so far I have tried.
main.dart
class _MainState extends State<Main> {
int currentIndex = 0;
Map<String,dynamic> searchParameter = {};
List screens = [
Home(),
Search({}),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
actions: [
Builder(builder: (context){
return IconButton(
onPressed: (){
Scaffold.of(context).openEndDrawer();
},
icon: const Icon(Icons.menu),
);
}),
],
),
endDrawer: const Drawer(
child:DrawerObject(),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.arrow_upward),
onPressed: () async{
await Future.delayed(Duration(milliseconds: 100),(){
globals.scrollController.animateTo(0, duration: Duration(milliseconds: 500), curve: Curves.fastOutSlowIn);
});
},
),
body: screens[currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentIndex,
onTap: (index) => setState(() {
if (index == 1) {
getSearchForm(context);
} else {
currentIndex = index;
searchParameter = {};
}
}),
selectedItemColor: Colors.white,
unselectedItemColor: Colors.grey[100],
type: BottomNavigationBarType.shifting,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
backgroundColor: Colors.blue[500],
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: 'Pencarian',
backgroundColor: Colors.orange[500],
),
],
),
);
}
//main function ===> NEED TO CALL THIS FUNCTION INSIDE grabDrawer.dart
Future UpdateIndex({int Index = 0}) async{
setState(() {
currentIndex = Index;
});
}
Future getSearchForm(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SearchForm(parameter:searchParameter)),
);
setState(() {
if (result != null) {
currentIndex = 1;
if(result!=searchParameter){
searchParameter = result;
screens[1] = CallLoading(show: ''); //set default to load
//set to new parameter (rebuilding widget)
Future.delayed(Duration(milliseconds: 500),(){
setState(() {
screens[1] = Search(searchParameter);
});
});
}
}
else{
}
});
}
}
Under this file, I need to call function from Main.UpdateIndex.
grabDrawer.dart
class DrawerObject extends StatelessWidget {
const DrawerObject({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: ListView(
children: [
ListTile(
leading: Icon(Icons.home),
title: Text('Cari Properti?'),
onTap: (){
===> CALL IT HERE
}
),
],
),
);
}
}
I really appreciate any answers. Thank you.
Change your grabDrawer.dart like this
class DrawerObject extends StatelessWidget {
void Function()? UpdateIndex;
DrawerObject({
this.UpdateIndex,
});
#override
Widget build(BuildContext context) {
return Container(
child: ListView(
children: [
ListTile(
leading: Icon(Icons.home),
title: Text('Cari Properti?'),
onTap: (){
UpdateIndex!();
}
),
],
),
);
}
}
And in your main.dart, call Drawer class like this
endDrawer: const Drawer(
child:DrawerObject(
UpdateIndex: UpdateIndex,
);
),
Hope this works for you.
Here is the clear way to pass data between one class to another class
void main() {
runApp(MaterialApp(
home: Modalbtn(),
));
}
class Modalbtn extends StatefulWidget {
#override
_ModalbtnState createState() => _ModalbtnState();
}
class _ModalbtnState extends State<Modalbtn> {
String value = "0";
// Pass this method to the child page.
void _update(String newValue) {
setState(() => value = newValue);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
IconButton(
onPressed: () {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
height: 200,
child: Column(
children: [StatefulModalbtn(update: _update)],
),
);
});
},
icon: Icon(Icons.add),
iconSize: 20,
),
Text(
value,
style: TextStyle(fontSize: 40),
),
],
),
),
);
}
}
import 'package:flutter/material.dart';
class StatefulModalbtn extends StatelessWidget {
final ValueChanged<String> update;
StatefulModalbtn({required this.update});
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => update("100"), // Passing value to the parent widget.
child: Text('Update (in child)'),
);
}
}
I am working with bottom navigation bar in flutter. I want to refresh every tab when tabs are switched. First I tried to reuse one stateless widget for all the tabs. But it is rerendering pages. My code is as follows:
class _CreateOrderState extends State<CreateOrder> {
int _currentTabIndex = 0;
#override
Widget build(BuildContext context) {
final _kTabPages = <Widget>[
FoodCategory(foodCategory: 'nationalFood'),
FoodCategory(foodCategory: 'fastFood'),
FoodCategory(foodCategory: 'dessert'),
FoodCategory(foodCategory: 'drinks'),
];
final _kBottomNavBarItems = <BottomNavigationBarItem>[
const BottomNavigationBarItem(
icon: Icon(Icons.fastfood_outlined),
label: 'Традиционная',
),
const BottomNavigationBarItem(
icon: Icon(Icons.alarm),
label: 'Фаст Фуд',
),
const BottomNavigationBarItem(
icon: Icon(Icons.food_bank_outlined),
label: 'Дессерты',
),
const BottomNavigationBarItem(
icon: Icon(Icons.emoji_food_beverage),
label: 'Напитки',
),
];
assert(_kTabPages.length == _kBottomNavBarItems.length);
final bottomNavBar = BottomNavigationBar(
items: _kBottomNavBarItems,
currentIndex: _currentTabIndex,
type: BottomNavigationBarType.fixed,
onTap: (int index) {
setState(() => _currentTabIndex = index);
},
);
return WillPopScope(
onWillPop: () => _onWillPop(),
child: Scaffold(
appBar: AppBar(
title: const Text('Создание заказа'),
backgroundColor: Theme.of(context).primaryColor,
actions: [
Container(
child: Stack(
children: [
IconButton(
icon: Icon(Icons.shopping_bag_outlined),
onPressed: () =>
Navigator.pushNamed(context, '/orderReview'),
iconSize: 30,
),
],
),
)
],
),
body: _kTabPages[_currentTabIndex],
// body: IndexedStack(
// index: _currentTabIndex,
// children: _kTabPages,
// ),
bottomNavigationBar: bottomNavBar,
),
);
}
This is my stateless widget:
import 'package:counter/blocs/food/food_bloc.dart';
import 'package:counter/data/repository/food_repository.dart';
import 'package:counter/presentation/widgets/Loading.dart';
import 'package:counter/presentation/widgets/MenuItem.dart';
import 'package:counter/presentation/widgets/network_error.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class FoodCategory extends StatelessWidget {
final String foodCategory;
FoodCategory({#required this.foodCategory});
#override
Widget build(BuildContext context) {
FoodRepository foodRepository = FoodRepository(category: this.foodCategory);
return BlocProvider<FoodBloc>(
create: (BuildContext context) =>
FoodBloc(foodRepository: foodRepository)..add(FoodLoadEvent()),
child: Scaffold(
body: BlocBuilder<FoodBloc, FoodState>(
builder: (context, state) {
if (state is FoodInitial) {
return Text('Initial state');
}
if (state is FoodLoadingState) {
return CustomLoading();
}
if (state is FoodLoadedState) {
return ListView.builder(
itemBuilder: (BuildContext context, index) {
return MenuItem(foodItem: state.loadedFoodItems[index]);
},
itemCount: state.loadedFoodItems.length,
);
} else {
return NetworkErrorWidget();
}
},
),
),
);
}
}
But when I used different widgets for all the tabs, it has started to work properly and refreshed.
final _kTabPages = <Widget>[
NationalFood(foodCategory: 'nationalFood'),
FastFoodScreen(foodCategory: 'fastFood'),
DessertsScreen(foodCategory: 'dessert'),
DrinksScreen(foodCategory: 'drinks'),
];
Inside the initState of your FoodCategory or NationalFood widget, add the function the retrieves your data.
Since you are placing the _kTabPages and _kBottomNavBarItems initialization within the build() method, these variables are assigned new values every time there's a change in state (when you change tabs). This is why the tabs keep re-rendering.
To stop this, place your initialization within the initState(). Something like this:
import 'package:flutter/material.dart';
import 'package:test_flutter_app/test.dart';
class CreateOrder extends StatefulWidget {
#override
_CreateOrderState createState() => _CreateOrderState();
}
class _CreateOrderState extends State<CreateOrder> {
int _currentTabIndex = 0;
List<Widget> _kTabPages;
List<BottomNavigationBarItem> _kBottomNavBarItems;
BottomNavigationBar bottomNavBar;
_updateTabs() {
_kTabPages = <Widget>[
FoodCategory(key: UniqueKey(), foodCategory: 'nationalFood'),
FoodCategory(key: UniqueKey(), foodCategory: 'fastFood'),
FoodCategory(key: UniqueKey(), foodCategory: 'dessert'),
FoodCategory(key: UniqueKey(), foodCategory: 'drinks'),
];
_kBottomNavBarItems = <BottomNavigationBarItem>[
const BottomNavigationBarItem(
icon: Icon(Icons.fastfood_outlined),
label: 'Традиционная',
),
const BottomNavigationBarItem(
icon: Icon(Icons.alarm),
label: 'Фаст Фуд',
),
const BottomNavigationBarItem(
icon: Icon(Icons.food_bank_outlined),
label: 'Дессерты',
),
const BottomNavigationBarItem(
icon: Icon(Icons.emoji_food_beverage),
label: 'Напитки',
),
];
bottomNavBar = BottomNavigationBar(
items: _kBottomNavBarItems,
currentIndex: _currentTabIndex,
type: BottomNavigationBarType.fixed,
onTap: (int index) {
setState(() => _currentTabIndex = index);
},
);
assert(_kTabPages.length == _kBottomNavBarItems.length);
}
#override
void initState() {
_updateTabs();
super.initState();
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => true,
child: Scaffold(
appBar: AppBar(
title: const Text('Создание заказа'),
backgroundColor: Theme.of(context).primaryColor,
),
body: _kTabPages[_currentTabIndex],
// body: IndexedStack(
// index: _currentTabIndex,
// children: _kTabPages,
// ),
bottomNavigationBar: bottomNavBar,
),
);
}
}
I am trying to display the BottomNavigationBar on every View I have, it's working but this is a "dumb" way to do that...
I have a custom BottomNavigationBar which I am inserting in every View.
var selectedIndex = 0;
class CustomBottombar extends StatefulWidget {
CustomBottombar({Key key}) : super(key: key);
#override
_CustomBottombarState createState() => _CustomBottombarState();
}
class _CustomBottombarState extends State<CustomBottombar> {
List _viewList = [FirstView(), SecondView()];
#override
Widget build(BuildContext context) {
return BottomNavigationBar(
currentIndex: selectedIndex,
onTap: _onItemTapped,
items: _items,
);
}
void _onItemTapped(int index) {
setState(() {
selectedIndex = index;
Navigator.of(context).popUntil((route) => route.isFirst
);
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => _viewList[index]),
);
});
}
final _items = [
BottomNavigationBarItem(
icon: Icon(
Icons.refresh,
color: Color(0xFFACACAC),
size: 35,
),
title: Text("first")),
BottomNavigationBarItem(
icon: Icon(
Icons.phone,
color: Color(0xFFACACAC),
size: 35,
),
title: Text("second"),
),
BottomNavigationBarItem(
icon: Icon(
Icons.add_shopping_cart,
color: Color(0xFFACACAC),
size: 35,
),
title: Text("thrid"),
),
];
}
in the _onItemTapped function I pop everything from the "Navigationstack" and then I am displaying the Screen that is in my Items.
in my FirstView() I have then this code
class FirstView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(""),
bottomNavigationBar: CustomBottombar(),
endDrawer: CustomDrawer(),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ContactView()),
);
},
child: Text('First'),
),
),
);
}
}
Now I want to move to "ContactView" which is not an Item in the BottomNavigationBar
class ContactState extends State<ContactView> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar("title"),
endDrawer: CustomDrawer(),
bottomNavigationBar: CustomBottombar(),
body: SafeArea(
bottom: true,
child: SingleChildScrollView(
child: Container(child: Text("Contact"),),
)),
);
}
}
I'll also have a lot of other views which are not in the items array but I want to display the BottomNavigationBar on.
My Issue is really this function.
void _onItemTapped(int index) {
setState(() {
selectedIndex = index;
Navigator.of(context).popUntil((route) => route.isFirst
);
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => _viewList[index]),
);
});
}
because here I'm deleting the navigation history to display the View which is in the Items Array.
Is there a standard way to do this, Hopefully, someone can help.
EDIT:
For clarification: I have like 10 Screens. Only 3 of those are navigatiable via BottomNavigationBar, Let's say the first 3 of those 10. now I want to Navigate to Screen4 from Screen1. The navigationbar disappears on screen4. I want Want to keep the Navigationbar on all Screens.
Edit 2
#Dhaval Kansara answer worked for me but I got a new Problem.
I have an enddrawer, before the fix it was above the BottomNavigationBar now the BottomNavigationBar is above.
but I want it like this
Use CupertinoTabBar as shown below for the static BottomNavigationBar.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:mqttdemo/Screen2.dart';
import 'package:mqttdemo/Screen3.dart';
import 'Screen1.dart';
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _currentIndex;
List<Widget> _children;
#override
void initState() {
_currentIndex = 0;
_children = [
Screen1(),
Screen2(),
Screen3(),
];
super.initState();
}
#override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
currentIndex: _currentIndex,
onTap: onTabTapped,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Screen 1"),
),
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Screen 2"),
),
BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text("Screen 3")),
],
),
tabBuilder: (BuildContext context, int index) {
return CupertinoTabView(
builder: (BuildContext context) {
return SafeArea(
top: false,
bottom: false,
child: CupertinoApp(
home: CupertinoPageScaffold(
resizeToAvoidBottomInset: false,
child: _children[_currentIndex],
),
),
);
},
);
}
);
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
}
Navigate to screen4 from Screen3 as shown below:
class Screen3 extends StatefulWidget {
#override
_Screen3State createState() => _Screen3State();
}
class _Screen3State extends State<Screen3> {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.black,
child: Center(
child: RaisedButton(
child: Text("Click me"),
onPressed: () {
Navigator.of(context, rootNavigator: false).push(MaterialPageRoute(
builder: (context) => Screen4(), maintainState: false));
},
),
),
);
}
}
The Flutter Gallery example of BottomNavigationBar uses a Stack of FadeTransitions in the body of the Scaffold.
I feel it would be cleaner (and easier to animate) if we could switch pages by using a Navigator.
Are there any examples of this?
int index = 0;
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Stack(
children: <Widget>[
new Offstage(
offstage: index != 0,
child: new TickerMode(
enabled: index == 0,
child: new MaterialApp(home: new YourLeftPage()),
),
),
new Offstage(
offstage: index != 1,
child: new TickerMode(
enabled: index == 1,
child: new MaterialApp(home: new YourRightPage()),
),
),
],
),
bottomNavigationBar: new BottomNavigationBar(
currentIndex: index,
onTap: (int index) { setState((){ this.index = index; }); },
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
icon: new Icon(Icons.home),
title: new Text("Left"),
),
new BottomNavigationBarItem(
icon: new Icon(Icons.search),
title: new Text("Right"),
),
],
),
);
}
You should keep each page by Stack to keep their state.
Offstage stops painting, TickerMode stops animation.
MaterialApp includes Navigator.
Output:
Code:
int _index = 0;
#override
Widget build(BuildContext context) {
Widget child;
switch (_index) {
case 0:
child = FlutterLogo();
break;
case 1:
child = FlutterLogo(colors: Colors.orange);
break;
case 2:
child = FlutterLogo(colors: Colors.red);
break;
}
return Scaffold(
body: SizedBox.expand(child: child),
bottomNavigationBar: BottomNavigationBar(
onTap: (newIndex) => setState(() => _index = newIndex),
currentIndex: _index,
items: [
BottomNavigationBarItem(icon: Icon(Icons.looks_one), title: Text("Blue")),
BottomNavigationBarItem(icon: Icon(Icons.looks_two), title: Text("Orange")),
BottomNavigationBarItem(icon: Icon(Icons.looks_3), title: Text("Red")),
],
),
);
}
Here is an example how you can use Navigator with BottomNavigationBar to navigate different screen.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// This navigator state will be used to navigate different pages
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
int _currentTabIndex = 0;
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Navigator(key: _navigatorKey, onGenerateRoute: generateRoute),
bottomNavigationBar: _bottomNavigationBar(),
),
);
}
Widget _bottomNavigationBar() {
return BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Home"),
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle), title: Text("Account")),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Text("Settings"),
)
],
onTap: _onTap,
currentIndex: _currentTabIndex,
);
}
_onTap(int tabIndex) {
switch (tabIndex) {
case 0:
_navigatorKey.currentState.pushReplacementNamed("Home");
break;
case 1:
_navigatorKey.currentState.pushReplacementNamed("Account");
break;
case 2:
_navigatorKey.currentState.pushReplacementNamed("Settings");
break;
}
setState(() {
_currentTabIndex = tabIndex;
});
}
Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case "Account":
return MaterialPageRoute(builder: (context) => Container(color: Colors.blue,child: Center(child: Text("Account"))));
case "Settings":
return MaterialPageRoute(builder: (context) => Container(color: Colors.green,child: Center(child: Text("Settings"))));
default:
return MaterialPageRoute(builder: (context) => Container(color: Colors.white,child: Center(child: Text("Home"))));
}
}
}
Here is example:
int _currentIndex = 0;
Route<Null> _getRoute(RouteSettings settings) {
final initialSettings = new RouteSettings(
name: settings.name,
isInitialRoute: true);
return new MaterialPageRoute<Null>(
settings: initialSettings,
builder: (context) =>
new Scaffold(
body: new Center(
child: new Container(
height: 200.0,
width: 200.0,
child: new Column(children: <Widget>[
new Text(settings.name),
new FlatButton(onPressed: () =>
Navigator.of(context).pushNamed(
"${settings.name}/next"), child: new Text("push")),
],
))
),
bottomNavigationBar: new BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (value) {
final routes = ["/list", "/map"];
_currentIndex = value;
Navigator.of(context).pushNamedAndRemoveUntil(
routes[value], (route) => false);
},
items: [
new BottomNavigationBarItem(
icon: new Icon(Icons.list), title: new Text("List")),
new BottomNavigationBarItem(
icon: new Icon(Icons.map), title: new Text("Map")),
]),
));
}
#override
Widget build(BuildContext context) =>
new MaterialApp(
initialRoute: "/list",
onGenerateRoute: _getRoute,
theme: new ThemeData(
primarySwatch: Colors.blue,
),
);
You can set isInitialRoute to true and pass it to MaterialPageRoute. It will remove pop animation.
And to remove old routes you can use pushNamedAndRemoveUntil
Navigator.of(context).pushNamedAndRemoveUntil(routes[value], (route) => false);
To set current page you can have a variable in your state _currentIndex and assign it to BottomNavigationBar:
Glad You asked, I experimented with this a couple of months back and tried to simplify this through a blog post. I won't be able to post the complete code here since it is pretty long, But I can certainly link all the resources to clarify it.
Everything about the BottomNavigationBar in flutter
complete sample code
Dartpad demo
If you prefer you can also depend on this package https://pub.dev/packages/navbar_router
Here's the resulting output of what the article helps you build
Navigator.of(context).pushNamedAndRemoveUntil(
routes[value], (route) => true);
I had to use true to enable back button.
NB: I was using Navigator.pushNamed() for navigation.
This is the code I am using in my project. If you try to avoid page viewer so you can try this
import 'package:flutter/material.dart';
class Dashboard extends StatefulWidget {
const Dashboard({Key? key}) : super(key: key);
#override
State<Dashboard> createState() => _DashboardState();
}
class _DashboardState extends State<Dashboard> {
int _selectedIndex = 0;
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sample'),
),
body: SingleChildScrollView(
child: Column(
children: [
if (_selectedIndex == 0)
// you can call custom widget here
Column(
children: const [
Text("0"),
],
)
else if (_selectedIndex == 1)
Column(
children: const [
Text("1"),
],
)
else
Column(
children: const [
Text("2"),
],
),
],
),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.headphones),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
unselectedItemColor: Colors.grey,
onTap: _onItemTapped,
),
);
}
}
Happy Coding