Flutter - appbar bottom property not updating properly with setState - flutter

I'm building an app with an appbar that has the bottom property that will only be visible if the user selects the middle item of the 3 items (item index = 1) in the bottom navigation bar. here's the code below.
class _MainPageState extends State<MainPage> {
int index = 2;
TabBar bottomBar = null;
#override
Widget build(BuildContext context) => DefaultTabController(
initialIndex: 0,
length: 3,
child: Scaffold(
appBar: AppBar(title: Text(widget.title), bottom: bottomBar),
bottomNavigationBar: BottomNavigationBar(
currentIndex: index,
selectedItemColor: Colors.teal,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.bolt),
label: ('Devices'),
),
BottomNavigationBarItem(
icon: Icon(Icons.pie_chart),
label: ('Cluster'),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: ('Settings'),
),
],
onTap: (int index) => setState(() => this.index = index),
),
body: buildPages(),
),
);
Widget buildPages() {
// bottomNavBarPageBuilder
switch (index) {
case 0:
setState(() {
this.bottomBar = null;
print(index);
});
return DeviceListPage();
case 1:
setState(() {
print(index);
this.bottomBar = TabBar(
tabs: [
Tab(
icon: Icon(Icons.power),
),
Tab(
icon: Icon(Icons.power),
),
Tab(
icon: Icon(Icons.power),
)
],
);
});
return TabBarView(
children: [UserLocalPage(), Container(), Container()]);
case 2:
setState(() {
print(index);
this.bottomBar = null;
});
return SettingsPage();
default:
setState(() {
print(index);
this.bottomBar = null;
});
return Container();
}
}
}
the problem is that sometimes the setState doesn't update the "bottom" property of the appBar. here's a video demonstrating the problem
how can I make the bottom property of the appBar be updated properly? any help would be much appreciated thankyou 🙏🙏

Sorry for a late answer. Would you kindly try this code for me?
Also, although I didn't change that in the code below, I noticed that your index == 2, but initialIndex == 0. I would recommend creating a const variable to represent initialIndex, like this:
static const int initialIndex = 0;
int index = initialIndex;
//skipped code
initialIndex: initialIndex,
class _MainPageState extends State<MainPage> {
int index = 2;
TabBar bottomBar = null;
#override
Widget build(BuildContext context) => DefaultTabController(
initialIndex: 0,
length: 3,
child: Scaffold(
appBar: AppBar(title: Text(widget.title), bottom: bottomBar),
bottomNavigationBar: BottomNavigationBar(
currentIndex: index,
selectedItemColor: Colors.teal,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.bolt),
label: ('Devices'),
),
BottomNavigationBarItem(
icon: Icon(Icons.pie_chart),
label: ('Cluster'),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: ('Settings'),
),
],
onTap: _onTap,
),
body: buildPages(),
),
);
void _onTap(int index) {
if(index != this.index) {
setState(() {
this.index = index;
switch(index) {
case 1:
bottomBar = TabBar(
tabs: [
Tab(
icon: Icon(Icons.power),
),
Tab(
icon: Icon(Icons.power),
),
Tab(
icon: Icon(Icons.power),
)
],
);
break;
default:
bottomBar = null;
}
});
}
}
Widget buildPages() {
// bottomNavBarPageBuilder
switch (index) {
case 0:
return DeviceListPage();
case 1:
return TabBarView(
children: [UserLocalPage(), Container(), Container()]);
case 2:
return SettingsPage();
default:
return Container();
}
}
}

Related

How to keep drawer always open

I want to put a drawer like this in my Flutter app:
just like https://m3.material.io/develop/flutter
I'm using NavigationRail and it's said that a menu button can be added to open a navigation drawer. Does any knows how to add the menu button and the drawer?
menu button of NavigationRail
thanks.
It's a bit hard to use a regular Scaffold Drawer without the regular scaffold controls, as far as I can tell.
I came up with a solution for your problem, if I understood it correctly. Looks a lot like the spec site, needs a bit of styling.
Took the example from the NavigationRail documentation and added a Visibility widget. Now clicking on the destinations, you can show and hide their child widgets(drawer). No drawer animation though.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true),
home: const NavRailExample(),
);
}
}
class NavRailExample extends StatefulWidget {
const NavRailExample({super.key});
#override
State<NavRailExample> createState() => _NavRailExampleState();
}
class _NavRailExampleState extends State<NavRailExample> {
int _selectedIndex = 0;
NavigationRailLabelType labelType = NavigationRailLabelType.all;
bool showLeading = false;
bool showTrailing = false;
double groupAligment = -1.0;
bool _isClosed = false;
Widget _getWidget(int index) {
switch (index) {
case 1:
return GestureDetector(
child: const Text('Tap!'),
onTap: () => setState(() {
_isClosed = true;
}),
);
case 2:
return const Text('empty');
default:
return ListView(
children: const [
ExpansionTile(
title: Text('whatev'),
children: [Text('1'), Text('2')],
),
ListTile(
title: Text('adfafdafaf'),
)
],
);
}
}
Widget _getPage(int index) {
switch (index) {
case 1:
return const Center(child: Text('sheeesh'));
case 2:
return const Center(child: Text('empty'));
default:
return const Center(child: Text('yolo'),);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: <Widget>[
NavigationRail(
selectedIndex: _selectedIndex,
groupAlignment: groupAligment,
onDestinationSelected: (int index) {
setState(() {
_isClosed = (_selectedIndex == index || _isClosed)
? !_isClosed
: _isClosed;
_selectedIndex = index;
});
},
labelType: labelType,
leading: showLeading
? FloatingActionButton(
elevation: 0,
onPressed: () {
// Add your onPressed code here!
},
child: const Icon(Icons.add),
)
: const SizedBox(),
trailing: showTrailing
? IconButton(
onPressed: () {
// Add your onPressed code here!
},
icon: const Icon(Icons.more_horiz_rounded),
)
: const SizedBox(),
destinations: const <NavigationRailDestination>[
NavigationRailDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: Text('First'),
),
NavigationRailDestination(
icon: Icon(Icons.bookmark_border),
selectedIcon: Icon(Icons.book),
label: Text('Second'),
),
NavigationRailDestination(
icon: Icon(Icons.star_border),
selectedIcon: Icon(Icons.star),
label: Text('Third'),
),
],
),
Visibility(
maintainState: false,
visible: !_isClosed,
child: Row(
children: [
const VerticalDivider(thickness: 1, width: 1),
SizedBox(
height: double.infinity,
width: 200,
child: _getWidget(_selectedIndex),
)
],
),
),
const VerticalDivider(thickness: 1, width: 1),
// This is the main content.
Expanded(
child: _getPage(_selectedIndex),
),
],
),
),
);
}
}

How to add sliding transition animation to IndexedStack?

I'm working with IndexedStack and I would like to add a sliding transition animation when the page is changed with the Bottom Navigation Bar (NOT fade animation).
This is an abstract of my code:
class _LoggedHandleState extends State<LoggedHandle> {
int _selectedPage = 1;
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("title"),
),
bottomNavigationBar: BottomNavigationBar(
unselectedItemColor: Colors.white60,
backgroundColor: Colors.red,
selectedItemColor: Colors.white,
currentIndex: _selectedPage,
onTap: (int index) {
setState(() {
_selectedPage = index;
});
},
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Hello',
),
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle),
label: 'Account',
),
]),
body: IndexedStack(
index: _selectedPage,
children: [HelloView(), HomeView(), UserView()],
),
);
}
}
PS: I need to use IndexedStack in order to mantain the state, so I can't use PageBuilder
At the end of the day, IndexedStack is just a Stack of elements that will show the current tab on top. To achieve what you want I'd suggest to do something similar, yet different, like this:
class LoggedHandle extends StatefulWidget {
final _pages = <Widget>[HelloView(), HomeView(), UserView()];
#override
State<StatefulWidget> createState() => _LoggedHandleState();
}
class _LoggedHandleState extends State<LoggedHandle> {
var _selectedPage = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('title'),
),
bottomNavigationBar: BottomNavigationBar(
unselectedItemColor: Colors.white60,
backgroundColor: Colors.red,
selectedItemColor: Colors.white,
currentIndex: _selectedPage,
onTap: (i) {
setState(() {
_selectedPage = i;
});
},
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Hello',
),
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle),
label: 'Account',
),
],
),
body: widget._pages[_selectedPage],
);
}
}
Then, for the animation, there's a Widget for that (AnimatedSwitcher). By exploiting that, along with an AnimatedWidget of your choice (or a custom one), you'll be good to go.
body: AnimatedSwitcher(
duration: const Duration(milliseconds: 650),
transitionBuilder: (child, animation) =>
ScaleTransition(scale: animation, child: child),
child: widget._pages[_selectedPage],
),

how to access PageViewController outside of widget

TabScreen is my main widget in my app that includes a BottomNavigationBar and a PageView to transition between pages.
PageController allows me to change views and everything is fine here, but there is one thing missing. How can I change my PageView controller outside of TabScreen? I'd like to put a button to access my main PageView controller from within my pages.
class _TabsScreenState extends State<TabsScreen> {
bool _isloading = true;
int? _selectedIndex;
List<Widget>? _pages = [
HomeScreen(),
CurrenPlanDetail(),
Container(),
ProfileScreen(),
SettingScreens(),
];
PageController? _pageController;
#override
void initState() {
_selectedIndex = 0;
_pageController = PageController(initialPage: _selectedIndex!);
}
}
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
controller: _pageController,
children: _pages!,
physics: NeverScrollableScrollPhysics(),
),
bottomNavigationBar: Visibility(
visible: !_isloading,
child: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
selectedItemColor: kNewPurple,
//unselectedItemColor: Colors.grey,
currentIndex: _selectedIndex!,
onTap: (value) {
setState(() {
_selectedIndex = value;
_pageController!.jumpToPage(_selectedIndex!);
});
},
backgroundColor: Colors.grey[300],
items: [
BottomNavigationBarItem(
icon: Icon(
Ionicons.home_outline,
size: 15.sp,
),
label: 'Home'),
BottomNavigationBarItem(
icon: Icon(
Ionicons.reader_outline,
size: 15.sp,
),
label: 'Plan'),
BottomNavigationBarItem(
icon: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: CircleBorder(),
fixedSize: Size(50, 50),
),
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) {
return FloatingButton();
});
},
child: Icon(Ionicons.add)),
label: ''),
BottomNavigationBarItem(
icon: Icon(
Ionicons.happy_outline,
size: 15.sp,
),
label: 'Profile'),
BottomNavigationBarItem(
icon: Icon(
Ionicons.settings_outline,
size: 15.sp,
),
label: 'Setting'),
],
),
),
);
}
}
for example, somewhere in my app, I'd like to put a button and pass a function like this :
_pageController!.jumpToPage(1);
Define PageController as static and
TabScreen.pageController.jumpToPage(1);

How to toggle visibility of TabBar with Bottom Navigation Items in Flutter

I have a bottomNavigationBar and an AppBar in my flutter app. At the bottom of the AppBar is a TabBar consisting of two items. So I want the TabBar to be invisible when some items of the BottomNavigationBar is clicked. I tried to assign the Visibility class to my TabBar with a Boolean variable but it doesn't work. It seems like I can't handle the TabBar widget separately.
How do resolve this?
class DashBoardPage extends StatefulWidget {
#override
_DashBoardPageState createState() => _DashBoardPageState();
}
class _DashBoardPageState extends State<DashBoardPage> {
SharedPreferences sharedPreferences;
bool showTabs = false;
int tabsIndex = 0;
int _currentIndex = 0;
String _appBarText = "Welcome, User";
Widget callPage(int currentIndex) {
switch (currentIndex) {
case 0:
showTabs = true;
_appBarText = "Welcome, User";
return TabBarView(
children:[
new HomePage(),
new SchedulePage()
]
);
break;
case 1:
showTabs = false;
break;
case 2:
showTabs = false;
break;
default:
return HomePage();
}
}
#override
void initState() {
super.initState();
checkLoginState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MAF Mentor',
debugShowCheckedModeBanner: false,
home: DefaultTabController(
length: choices.length,
child: Scaffold(
appBar: AppBar(
backgroundColor: Color(0xFFFFFFFF),
title: Text(
_appBarText,
style: TextStyle(
color: Color(0xFF1C2447),
fontFamily: 'Muli',
fontSize: 16.0,
),
),
bottom: showTabs? TabBar(
isScrollable: true,
tabs: choices.map<Widget>((Choice choice) {
return Tab(
text: choice.title,
icon: Icon(choice.icon),
);
}).toList(),
labelColor: Color(0xFF1C2447),
):null,
actions: <Widget>[
IconButton(
icon: Icon(
Icons.account_circle,
color: Color(0xFF1C2447),
),
onPressed: () {
Navigator.of(context).pushNamed('/profile_page');
},
),
IconButton(
icon: Icon(
Icons.notifications,
color: Color(0xFF1C2447),
),
onPressed: () {
// do something
},
),
],
), //AppBar
body: callPage(_currentIndex),
bottomNavigationBar: BottomNavigationBar(
showSelectedLabels: false,
showUnselectedLabels: false,
fixedColor: Color(0xFF1C2447),
currentIndex: _currentIndex,
onTap: (value) {
_currentIndex = value;
callPage(_currentIndex);
setState(() {
});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text("Bar 1")),
BottomNavigationBarItem(
icon: Icon(Icons.people), title: Text("Bar 2")),
BottomNavigationBarItem(
icon: Icon(Icons.history), title: Text("Bar 3"))
],
),
),
),
);
}
bottom requires a PreferredSizeWidget so you can not use the Visibility widget there. You can use a boolean variable to do that. You can see the whole code below. Since I don't know your choices and tabs I randomly put something. But the idea is if you want to show TabBar when user tap BottomNavigationBarItem
number 1 you just update your boolean variable as true. Otherwise make it false.
class TabBarExample extends StatefulWidget {
#override
_TabBarExampleState createState() => _TabBarExampleState();
}
class _TabBarExampleState extends State<TabBarExample> {
bool showTabs = false;
int selectedIndex = 0;
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
backgroundColor: Color(0xFFFFFFFF),
title: Text(
'_appBarText',
style: TextStyle(
color: Color(0xFF1C2447),
fontFamily: 'Muli',
fontSize: 16.0,
),
),
bottom: showTabs
? TabBar(
isScrollable: true,
tabs: <Widget>[
Tab(
text: 'Choice1',
icon: Icon(Icons.add_circle_outline),
),
Tab(
text: 'Choice1',
icon: Icon(Icons.add_circle),
),
],
labelColor: Color(0xFF1C2447),
)
: null,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: selectedIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text('first')),
BottomNavigationBarItem(
icon: Icon(Icons.favorite), title: Text('second')),
],
onTap: (index) {
if (index == 1) {
setState(() => showTabs = true);
} else {
setState(() => showTabs = false);
}
setState(() => selectedIndex = index);
},
),
),
);
}
}

How to persist tabs when using BottomNavigationBar?

Whenever I switch from Tab 1 to Tab 2 then back to Tab 1, content on Tab 1 is rebuilt. Anyway to prevent this?
class BottomNavBarExample extends StatefulWidget {
#override
BottomNavBarExampleState createState() {
return new BottomNavBarExampleState();
}
}
class BottomNavBarExampleState extends State<BottomNavBarExample> {
List<BottomNavigationBarItem> _tabs = [
BottomNavigationBarItem(
icon: Icon(Icons.library_books), title: Text('Library')),
BottomNavigationBarItem(icon: Icon(Icons.public), title: Text('Public')),
BottomNavigationBarItem(icon: Icon(Icons.person), title: Text('Account')),
];
var _currentIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Demo')),
bottomNavigationBar: BottomNavigationBar(
currentIndex: this._currentIndex,
items: this._tabs,
onTap: (value) {
setState(() {
this._currentIndex = value;
});
},
),
body: Stack(children: [
new Offstage(
offstage: this._currentIndex != 0,
child: new TickerMode(
enabled: this._currentIndex == 0,
child: LibraryPage(),
),
),
new Offstage(
offstage: this._currentIndex != 1,
child: new TickerMode(
enabled: this._currentIndex == 1,
child: GalleryPage(),
),
),
new Offstage(
offstage: this._currentIndex != 2,
child: new TickerMode(
enabled: this._currentIndex == 2,
child: AccountPage(),
),
),
]),
);
}
}
Then for each page, include AutomaticKeepAliveClientMixin