I would like to be able to change the navigation bar tab programmatically. I have a button inside Page1 that navigates to Page2. When I perform this, the navigation bar disappears because I didn't select page2 using the navigation bar.
I have 4 dart files along the lines of navigationbar.dart, page1.dart, page2.dart, page3.dart
The Navigation page is the home page of the application with the children:
final List<Widget> _children = [
Page1(),
Page2(),
Page3(),
];
return Scaffold(
backgroundColor: Colors.black,
body: _children[_selectedPage],
bottomNavigationBar: _bottomNavigationBar(context),
resizeToAvoidBottomPadding: true,
);
You have to change a TabControlller like this
1* Create TabController instance
TabController _tabController;
2* in initState methode use this
#override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: 3);
}
3* add a Mixin to _HomeState
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {....}
4* assign the TabController to your TabBar
TabBar(
controller: _tabController,
tabs: _yourTabsHere,
),
5* Pass controller to your Pages
TabBarView(
controller: _tabController,
children:<Widget> [
Page1(tabController:_tabController),
Page2(tabController:_tabController),
Page3(tabController:_tabController),
];
6* call tabController.animateTo() from Page1
class Page1 extends StatefulWidget {
final TabController tabController
Page1({this.tabController});
....}
class _Page1State extends State<Page1>{
....
onButtonClick(){
widget._tabController.animateTo(index); //index is the index of the page your are intending to (open. 1 for page2)
}
}
Hope it helps
You can do that using a GlobalKey.
var bottomNavigatorKey = GlobalKey<State<BottomNavigationBar>>();
put that key in widget,
new BottomNavigationBar(
key: bottomNavigatorKey,
items: [...],
onTap: (int index) {...},
),
put below lines in change tab method,
BottomNavigationBar navigationBar = bottomNavigatorKey.currentWidget as BottomNavigationBar;
navigationBar.onTap(1);
in the button :
onPressed: (){
_currentIndex = 1;
//or which value you want
setState((){});
}
in the bottom Navigation bar :
currentIndex = _currentIndex
You can Use IndexStack.
Demostration.
Related
I am working on the bottom navigation bar feature. I have 4 screens associated with a bottom navigation bar.
Page1 Index0 (It has a button)
Page2 Index1
Page3 Index2
Page4 Index3
Each click on the bottom icon will change the index of a bottom navigation bar and triggers the specific screen to load.
Now I want to click on the button of screen 1 which is index 0 of a bottom navigation bar, this should make a call to load page2 as if I select the second icon of a bottom navigation bar.
I tried using the navigator.push(context,page()) which loaded the page however the bottom navigation bar was not visible. This is not the way, Is there any way to do it correctly?
Thanks
Use IndexedStack widget for this. Example is as follows:
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _currentIndex = 0; //Current selected index of the bottom navigation bar tab
void changeTab(int index) {
setState(() => _currentIndex = index);
}
#override
Widget build(BuildContext context) {
//List of Screens that you want to put
final screens = [
const Screen1(),
const Screen2(),
const Screen3(),
];
return Scaffold(
body: IndexedStack(
index: _currentIndex,
children: screens,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) => changeTab(index),
type: BottomNavigationBarType.fixed,
unselectedItemColor: Colors.grey,
selectedItemColor: Colors.red,
showUnselectedLabels: false,
showSelectedLabels: false,
iconSize: 20,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.foo),
label: 'Screen1',
),
BottomNavigationBarItem(
icon: Icon(Icons.bar),
label: 'Screen2',
),
BottomNavigationBarItem(
icon: Icon(Icons.baz),
label: 'Screen3',
),
],
),
);
}
}
I fixed this issue using a Provider. The issue has been resolved now.
I've read your question and that's what I found out:
You want to change the bottom navigation bar pages by clicking on a button.
To do so, simply change the index variable that you are passing to the selected index of the bottom navigation to the index of the page you want, and you will need to make it inside a setState function so the variable get updated.
Like the code below:
onTap: () {
setState((() => this.index = 0));
},
You can change bottom navigation bar index from every screen
static int bottomNavigationTabIndex = 0;
static void selectPage(BuildContext context,int index) {
BottomNavigationScreenState? stateObject = context.findAncestorStateOfType<BottomNavigationScreenState>();
stateObject?.setState((){
bottomNavigationTabIndex = index;
});
}
I have a DefaultTabController containing a TabBarView with a TabController, so far nothing special.
I have 1 tab which needs to make a request to the server as soon as it's initialized, let's call it the explore tab.
I'm making the request in didChangeDependencies if it wasn't already initialized (I have a boolean flag for initialized).
It's indeed making a request everytime the tab's widget is created, however there's one problem.
Let's say there are 3 tabs, while the explore tab is the second tab in between 1 and 3.
When going from tab 1 to tab 3, or from tab 3 to tab 1, it seems that the explore tab is created during the transition (even though it's not displayed at all), and a request to the server is consequently made.
I'd like to avoid that and only make that request in the explore tab's didChangeDependencies when the user is specifically going to the explore tab.
Any ideas?
Ok, so, you have 3 tabs
[tab1],[tab2],[tab3]
Where you would like to triger an event (request to the server) only when [tab2] was pressed.
To achieve this, create a tabController isolated to add an event listener in a StateFull widget, example:
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(
length: 2,
vsync: this,
);
_tabController.addListener(() {
if (_tabController.indexIsChanging == false) {
if(_tabController.index == 1) {
//Trigger your request
}
}
});
}
Note: indexIsChanging == false is to ensure that tab already finished the job of change currentIndex of that controller, full example below:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: BodyWidget(),
);
}
}
class BodyWidget extends StatefulWidget {
#override
BodyWidgetState createState() {
return new BodyWidgetState();
}
}
class BodyWidgetState extends State<BodyWidget>
with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(
length: 3,
vsync: this,
);
_tabController.addListener(() {
if (_tabController.indexIsChanging == false) {
if (_tabController.index == 1) {
//Trigger your request
print('Triger 2 selected, do the http call now.');
}
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('test'),
bottom: TabBar(
controller: _tabController,
tabs: <Widget>[
Tab(text: 'Tab 1'),
Tab(text: 'Tab 2'),
Tab(text: 'Tab 3'),
],
),
),
body: TabBarView(
controller: _tabController,
children: <Widget>[
Container(child: Center(child: Text('tab1'))),
Container(child: Center(child: Text('tab2'))),
Container(child: Center(child: Text('tab3'))),
],
),
);
}
}
I'm working on a project with flutter, I have a bottom navigation bar , this bar allow me to move on different scenes . I would like to remove my bottom navigation bar when i navigate to a detail page, there is any method? I'm trying all the possible solution , but i can't solve the problem, maybe i've implemented wrong my navigation bar !
Some Code:
My HomePage where i implemented the bottom navigation bar and where i call all the others pages:
class homeScreen extends StatefulWidget {
#override
_homeScreenState createState() => _homeScreenState();
}
class _homeScreenState extends State<homeScreen> {
TextEditingController _linkController;
int _currentIndex = 0;
final _pageOptions = [
meetingScreen(),
dashboardScreen(),
notificationScreen(),
profileScreen(),
];
#override
void initState() {
// TODO: implement initState
super.initState();
_linkController = new TextEditingController();
}
#override
Widget build(BuildContext context) {
ScreenUtil.instance = ScreenUtil(width: 1125, height: 2436)..init(context);
return MaterialApp(
title: myUi.myTitle,
theme: myUi.myTheme ,
debugShowCheckedModeBanner: false,
home:Scaffold(
bottomNavigationBar: _buildBottomNavigationBar(),
body: _pageOptions[_currentIndex] ,
) ,
);
}
Widget _buildBottomNavigationBar() {
return BottomNavigationBar(
backgroundColor: Colors.white,
selectedItemColor: Theme.of(context).primaryColor,
//fixedColor: gvar.secondaryColor[gvar.colorIndex],
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.explore,size: ScreenUtil().setWidth(80),),
title: new Text("Esplora"),
),
BottomNavigationBarItem(
icon:Icon(Icons.dashboard,size: ScreenUtil().setWidth(80),),
title: new Text("Attività "),
),
BottomNavigationBarItem(
icon:Icon(Icons.notifications,size: ScreenUtil().setWidth(80),),
title: new Text("Notifiche"),
),
BottomNavigationBarItem(
icon:Icon(Icons.person,size: ScreenUtil().setWidth(80),),
title: new Text("Profilo"),
),
],
);
}
}
This is the code i use into the dashboardScreen to push to my detail page:
onTap: () {
//method: navigate to Meeting
Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) => new meetingScreen()),
);
},
In the new meetingScreen i have some ui , but i cant remove my old bottom navigation bar, i tried implementing a new navigation bar but it isn't displayed.
A partial working solution was to do like so:
Navigator.of(context, rootNavigator: true).pushReplacement(MaterialPageRoute(builder: (context) => new meetingScreen()));
But now I can't do Navigator.pop with the cool animation , because the new page is the root!
Thanks guys for the support !!
To navigate to a different screen without the bottom Navigation bar, you'd need to call Navigator.push() from _homeScreenState. For you to be able to do this, you need to create a NavigatorKey in _homeScreenState and set it in MaterialApp().
final _mainNavigatorKey = GlobalKey<NavigatorState>();
...
MaterialApp(
navigatorKey: _mainNavigatorKey,
...
)
You can then pass the NavigatorKey to your pages. To use _mainNavigatorKey, fetch its current state to be able to invoke push().
navigatorKey.currentState!.push(...)
Here's a sample that I have. Though instead of a bottom Navigation bar, I'm using a Navigation Drawer. The same method applies in this sample.
I have a default page with two tabs, using TabBar and TabController. All is working fine, except when I enter the page, the second tab's build method is only called when I click to change to the second tab. The problem is that when I enter the page I want both tabs to be already created (a.k.a their build methods executed). Any thoughts?
Illustration code:
//This widget is used inside a Scaffold
class TabsPage extends StatefulWidget {
#override
State<StatefulWidget> createState() => new TabsPageState();
}
class TabsPageState extends State<TabsPage> with TickerProviderStateMixin {
List<Tab> _tabs;
List<Widget> _pages;
TabController _controller;
#override
void initState() {
super.initState();
_tabs = [
new Tab(text: 'TabOne'),
new Tab(text: 'TabTwo')
];
_pages = [
//Just normal stateful widgets
new TabOne(),
new TabTwo()
];
_controller = new TabController(length: _tabs.length, vsync: this);
}
#override
Widget build(BuildContext context) {
return new Padding(
padding: EdgeInsets.all(10.0),
child: new Column(
children: <Widget>[
new TabBar(
controller: _controller,
tabs: _tabs
),
new SizedBox.fromSize(
size: const Size.fromHeight(540.0),
child: new TabBarView(
controller: _controller,
children: _pages
),
)
],
),
);
}
}
I had a similar issue, I'm using Tabs with Forms and I wanted to validate all of them, my solution was to switch to the second tab programmatically, It doesn't make sense to not visit a tab with a form that requires validation.
So given a global key for each form:
final _formKey = GlobalKey<FormState>();
The reason I wanted all tabs to be built was to use:
_formKey.currentState.validate()
So my fix is
if (_formKey.currentState == null) { //The tab was never visited, force it
_tabController.animateTo(1);
return;
}
I was able to achieve this.
In my case, I had 2 tabs and I wanted to preload the other tab.
So what I did was, I initialised the tab controller in initState like this:
tabController = TabController(
length: 2,
vsync: this,
initialIndex: 1,
);
I set the initialIndex to the other tabs index, and then set the index back in after the build was done like this:
WidgetsBinding.instance.addPostFrameCallback((_) {
tabController.index = 0;
});
But this may only work if you have 2 tabs
In fluter, I have 2 tab pages that show different view. I wanted to have the floatingactionbutton to show only in one of the view and hidden in other tabs.
But the floatingactionbutton stays floating even when the view is switched.
Can anyone help me on this? If there is any code or tutorial, it would be appreciated.
demo pic:
tab1 show fab
tab2 hide fab
You can set floatingActionButton to null when _selectedIndex == 0, then FAB is gone with animation, so cool. And remember to change _selectedIndex in BottomNavigationBar onTap method.
Here is the example code with some comments:
final _tabTitle = [
'Title 1',
'Title 2'
]; // BottomNavigationBarItem title
final _tabIcon = [
Icon(Icons.timer_off),
Icon(Icons.timeline),
]; // BottomNavigationBarItem icon
class _MyHomePageState extends State<MyHomePage> {
int _selectedIndex = 0;
String title = _tabTitle[0];
// tap BottomNavigationBar will invoke this method
_onItemTapped(int index) {
setState(() {
// change _selectedIndex, fab will show or hide
_selectedIndex = index;
// change app bar title
title = _tabTitle[index];
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: Center(
child: Text(_tabTitle[_selectedIndex]),
),
// two tabs
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(title: Text(_tabTitle[0]), icon: _tabIcon[0]),
BottomNavigationBarItem(title: Text(_tabTitle[1]), icon: _tabIcon[1])
],
currentIndex: _selectedIndex,
onTap: _onItemTapped,
),
// key point, fab will show in Tab 0, and will hide in others.
floatingActionButton: _selectedIndex == 0 ? FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
) : null,
);
}
}
You can create a list of FloatingActionButtons for each page.
Call index method on the TabController variable to know the index of the tab that is active and use that to select a fab from the list.
Don't forget to call addListener on the TabController variable.
here is snippet code of how I did it:
// in the main statefulwidget class
TabController tabController;
var fabIndex;
#override
void initState() {
super.initState();
tabController = new TabController(length: 3, vsync: this,initialIndex: 0);
tabController.addListener(_getFab);
fabIndex=0;
}
void dispose() {
tabController.dispose();
super.dispose();
}
final List<FloatingActionButton> fabs=[
new FloatingActionButton(child: new Icon(Icons.access_time),onPressed: (){},),
new FloatingActionButton(child: new Icon(Icons.account_balance),onPressed: (){},),
new FloatingActionButton(child: new Icon(Icons.add_alert),onPressed: (){},)
];
void _getFab(){
setState((){`enter code here`
fabIndex=tabController.index;
});
}
Use the fabIndex in the scaffold's floatingActionButton property as follows:
floatingActionButton: fabs[fabIndex],
I would wrap the tab you want with a scaffold and give it a fab.
TabBarView(
children:[
Text("tab1"),
Scaffold(body:Text("tab2"),
floatingActionButton: FloatingActionButton(...)
)
)
The easiest way is to just give your each tab it's own Scaffold as the top widget, and provide it's own FAB there (Instead of the Scaffold where each tabs are kept, the main Scaffold, place it individually inside every tab's Scaffold, and remove from the top Scaffold). If you already have one, you can wrap it inside another. But if you want to keep the FAB the same in every other screen except some, you then can follow other solutions written here.