I'm trying to create a Bottom Navigation bar that will switch between my main Screen and my Settigns screen. I was attempting to do so, but adding a List to the top of my Stful widget doesn't seem to work, I was attempting to add a list so I could switch pages. But adding a List to the start of my body doesn't seem to help.
This is what I attempted:
class _HomeState extends State<Home> {
var temperature;
var humidity;
//Bottom navigation bar stuff
int _currentIndex = 0;
final tab = [
Home(),
Settings(),
],
#override
void initState () {
this.getWeather();
super.initState();
}
#override
Widget build(BuildContext context) {
final AuthService _auth = AuthService();
final user = Provider.of<User>(context);
return StreamBuilder<UserData>(
stream: DatabaseService(uid: user.uid).userData,
builder : ((context, snapshot){
if (!snapshot.hasData) {
return Loading();
}
UserData userData = snapshot.data;
List<bool> cardsValue = [userData.device1, userData.device2, userData.device3, false];
return Scaffold(
//Nav part
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.grey[900],
currentIndex: _currentIndex,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home, color: Colors.white,),
title: Text("Home",style: TextStyle(color: Colors.white),)
),
BottomNavigationBarItem(
icon: Icon(
Icons.settings,
color: Colors.white,
),
title: Text(
"Settings",
style: TextStyle(color: Colors.white),)
)
],
onTap: (int index){
setState(() {
this._currentIndex = index;
});
},
),
backgroundColor: Colors.grey[900],
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.grey[900],
title: Row(
...
body: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(),
child: Padding(
padding: EdgeInsets.fromLTRB(12, 20, 12, 0),
child: Column(
children: <Widget>
This is where I want to go when the settings Icon is pressed:
import 'package:flutter/material.dart';
class Settings extends StatefulWidget {
#override
_SettingsState createState() => _SettingsState();
}
class _SettingsState extends State<Settings> {
#override
Widget build(BuildContext context) {
return Scaffold(
);
}
}
You need to use a TabBarView along with a TabController.
See this cookbook to learn how to.
EDIT :
The Scaffold's body should be TabBarView(children: tab).
Switch the BottomNavigationBar for a TabBar.
Create a TabController as a state member : TabController _tabController;
Initialize it in the initState() like this :
_tabController = TabController(vsync: this, length: 2);
And finally, set the controller parameter for the TabBar and TabBarView to _tabController.
That should do it.
Related
I have a problem with my BottomNavigationBar in Flutter.
Please help me with this issue. i really need it to done.
I don't want to keep my page alive if I press jump to any page from screens, not from my BottomNavigationBar.
eg, if I have three screens that navigate from the bottom bar its works fine but if I add a button in any of the three pages that navigate to another screen that does not belong to the bottom bar then it keeps that screen alive the previous screen.
here my implementation.
BottomNavigation :
footer.dart
import 'package:flutter/material.dart';
import 'MyPage.dart';
import 'MyPage2.dart';
import 'MyPage3.dart';
import 'package:double_back_to_close_app/double_back_to_close_app.dart';
import 'Notifications.dart';
void main() {
runApp(MaterialApp(
home: Footer(),
));
}
class Footer extends StatefulWidget {
#override
_Footer createState() => _Footer();
}
class _Footer extends State<Footer> {
late List<Widget> _pages;
List<BottomNavigationBarItem> _items = [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: "Home",
),
BottomNavigationBarItem(
icon: Icon(Icons.messenger_rounded),
label: "Messages",
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: "Settings",
)
];
late int _selectedPage;
#override
void initState() {
super.initState();
_selectedPage = 0;
_pages = [
MyPage(
count: 1,
),
MyPage2(
count: 2,
),
MyPage3(
count: 3,
),
// This avoid the other pages to be built unnecessarily
//Notifications(),
// SizedBox(),
];
}
late DateTime currentBackPressTime;
#override
Widget build(BuildContext context) {
return Scaffold(
body: DoubleBackToCloseApp(
snackBar: SnackBar(
content: const Text(
'Tap back again to leave',
style: TextStyle(fontSize: 14.0, fontWeight: FontWeight.normal),
),
duration: Duration(seconds: 4),
backgroundColor: Colors.blue,
width: 340.0,
padding: EdgeInsets.all(15),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
child: _pages[_selectedPage],
),
bottomNavigationBar: BottomNavigationBar(
items: _items,
currentIndex: _selectedPage,
onTap: (index) {
setState(() {
// now check if the chosen page has already been built
// if it hasn't, then it still is a SizedBox
_selectedPage = index;
});
},
)
);
}
}
MyPage.dart:
import 'package:flutter/material.dart';
import 'MyCustomPage.dart';
import 'Notifications.dart';
class MyPage extends StatefulWidget {
final count;
MyPage({Key? key, this.count}) : super(key: key);
#override
_MyPage createState() => _MyPage();
}
class _MyPage extends State<MyPage>{
#override
Widget build(BuildContext context) {
// You'll see that it will only print once
return Navigator(
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(
builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(this.text),
actions: <Widget>[
Row(
children: [
InkWell(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (ctx) => Notifications()));
},
child: Icon(Icons.notifications),
)
],
)
],
),
body: Center(
child: RaisedButton(
child: Text('my page1'),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (ctx) => MyCustomPage()));
},
),
),
);
},
);
},
);
}
}
MyCustomPage.dart
import 'package:flutter/material.dart';
class MyCustomPage extends StatefulWidget {
MyCustomPage ({Key? key}) : super(key: key);
#override
_MyCustomPage createState() => _MyCustomPage();
}
class _MyCustomPage extends State<MyCustomPage>{
#override
Widget build(BuildContext parentContext) {
return Scaffold(
appBar: AppBar(
title: Text('custompage'),
),
body: Column(
children: [
Expanded(
child: Container(
child: ListView.builder(
itemCount: 15,
itemBuilder: (context, index) {
return Container(
width: double.infinity,
child: Card(
child: Center(
child: Text('My Custom Page'),
),
),
);
},
),
),
),
],
),
);
}
}
Here are my three files that navigate the inner screen
the flow of navigation is first load Footer.dart file that is the main file inside there is three pages in BottomNavigationBar() in the first screen on tap home icon it loads MyPage.dart file which contains a text and button. on the button press, it navigates MyCustomPage.dart file. now the issue is when I click on the home icon from BottomNavigationBar() it loads MyPage screen and on press button inside that screen it loads MyCustomPage but when MyCustomPage.dart file load it keeps home icon alive and I don't have access to press the home icon again.
I hope you understand what I am trying to say.
If anyone knows please help me.
So I have a Scaffold Key in my Scaffold:
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
And in my Scaffold I have a custom Drawer and a custom App Bar:
Scaffold(
key: _scaffoldKey,
backgroundColor: Color(0xFF3FC1C9),
drawer: HomeDrawer(),
body: StartAppBar(_scaffoldKey.currentState?.openDrawer),
),
Im passing the open Drawer function on to the custom AppBar. My custom AppBar accepts the function like this:
class StartAppBar extends StatelessWidget {
void Function()? openDrawer;
StartAppBar(this.openDrawer);
and references it here:
leading: IconButton(
onPressed: () {
openDrawer!();
// _key.currentState!.openEndDrawer();
},
icon: Icon(
Icons.view_headline_rounded,
),
),
The problem is that the drawer doesn't open from the start on. When I switch the bodies of my Screen though through clicking on my bottom bar that I have (code below) the drawer opens. Im guessing that my key has a null value when I load the app for the first time, as a consequence the drawer doesn't open. If that would be the case I would need to set a default value for the key. The whole code is below.
This is the whole code, perhaps it is more relevant then the simplified one:
My class where I create the key looks like this:
class HomeScreen extends StatefulWidget {
final marken;
const HomeScreen({Key? key, this.marken}) : super(key: key);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
late List<Widget> _widgetOptions;
#override
initState() {
_widgetOptions = <Widget>[
Favorites(),
BodyHomeScreen(
marken: widget.marken,
),
Kontakt(),
];
}
DateTime? lastPressed;
final HideNavbar hiding = HideNavbar();
int _selectedIndex = 1;
void _onItemTap(int index) {
setState(() {
_selectedIndex = index;
});
}
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
child: Scaffold(
key: _scaffoldKey,
backgroundColor: Color(0xFF3FC1C9),
drawer: HomeDrawer(),
body: StartAppBar(_selectedIndex, hiding, lastPressed, _widgetOptions,
_scaffoldKey.currentState?.openDrawer),
bottomNavigationBar: BottomBar(
_onItemTap,
_selectedIndex,
hiding,
),
),
);
}
}
As also seen above im passing the key on to the StartAppBar, that looks like this:
import 'package:flutter/material.dart';
class StartAppBar extends StatelessWidget {
final int selectedIndex;
final hiding;
List<Widget> widgetOptions;
var lastPressed;
void Function()? openDrawer;
StartAppBar(this.selectedIndex, this.hiding, this.lastPressed,
this.widgetOptions, this.openDrawer);
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final now = DateTime.now();
final maxDuration = Duration(seconds: 2);
final isWarning =
lastPressed == null || now.difference(lastPressed!) > maxDuration;
if (isWarning) {
lastPressed = DateTime.now();
final snackBar = SnackBar(
content: Container(
//color: Colors.white,
decoration: BoxDecoration(
color: Color(0xFF03DAC6),
borderRadius: BorderRadius.circular(20)),
margin: EdgeInsets.fromLTRB(0, 0, 0, 20),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Doppelklick zum verlassen',
textAlign: TextAlign.center,
),
),
),
backgroundColor: Colors.transparent,
elevation: 1000,
behavior: SnackBarBehavior.floating,
duration: maxDuration,
);
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(snackBar);
return false;
} else {
return true;
}
},
child: CustomScrollView(
controller: hiding.controller,
slivers: [
SliverAppBar(
backgroundColor: Color(0xFF3FC1C9),
automaticallyImplyLeading: false,
elevation: 0,
title: Text(
"AutoLab",
style:
TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
leading: IconButton(
onPressed: () {
openDrawer?.call();
// _key.currentState!.openEndDrawer();
},
icon: Icon(
Icons.view_headline_rounded,
),
),
centerTitle: true,
expandedHeight: 120,
floating: false,
flexibleSpace: FlexibleSpaceBar(
title: selectedIndex == 1
? Text("Marke auswählen")
: selectedIndex == 2
? Text("Schreibe uns!")
: Text("Deine Modelle"),
centerTitle: true,
),
),
SliverToBoxAdapter(child: widgetOptions.elementAt(selectedIndex)),
],
),
);
}
}
Any ideas how I can fix this behaviour?
It's not necessary that you create a GlobalKey, you only write this :
onPressed: () {
Scaffold.of(context).openDrawer();
},
Because in your widget tree, you already have only one Scaffold, so you don't need to make it unique with a key, so your IconButton knows which Scaffold and Drawer to open.
And now all that remains is to remove the void Function()? openDrawer;
I have a list in the body and bottom navigation bar. I want to hide bottom navigation bar with a slide down animation when the posts list is scrolled down and visible with a slide up animation when scrolled up. How to do it?
While Naveen's solution works perfectly, I didn't like the idea of using setState to handle the visibility of the navbar. Every time the user would change scroll direction, the entire homepage including the appbar and body would rebuild which can be an expensive operation. I created a separate class to handle the visibility that uses a ValueNotifier to track the current hidden status.
class HideNavbar {
final ScrollController controller = ScrollController();
final ValueNotifier<bool> visible = ValueNotifier<bool>(true);
HideNavbar() {
visible.value = true;
controller.addListener(
() {
if (controller.position.userScrollDirection ==
ScrollDirection.reverse) {
if (visible.value) {
visible.value = false;
}
}
if (controller.position.userScrollDirection ==
ScrollDirection.forward) {
if (!visible.value) {
visible.value = true;
}
}
},
);
}
void dispose() {
controller.dispose();
visible.dispose();
}
}
Now all you do is create a final instance of HideNavbar in your HomePage widget.
final HideNavbar hiding = HideNavbar();
Now pass the instance's ScrollController to the ListView or CustomScrollView body of your Scaffold.
body: CustomScrollView(
controller: hiding.controller,
...
Then surround your bottomNavigationBar with a ValueListenableBuilder that takes the ValueNotifier from the HideNavbar instance and then set the height property of the bottomNavigationBar to be either 0 or any other value depending on the status of the ValueNotifier.
bottomNavigationBar: ValueListenableBuilder(
valueListenable: hiding.visible,
builder: (context, bool value, child) => AnimatedContainer(
duration: Duration(milliseconds: 500),
height: value ? kBottomNavigationBarHeight : 0.0,
child: Wrap(
children: <Widget>[
BottomNavigationBar(
type: BottomNavigationBarType.fixed,
backgroundColor: Colors.blue,
fixedColor: Colors.white,
unselectedItemColor: Colors.white,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.card_giftcard),
title: Text('Offers'),
),
BottomNavigationBarItem(
icon: Icon(Icons.account_box),
title: Text('Account'),
),
],
),
],
),
),
),
This approaches keeps avoids countless rebuilds and doesn't require any external libraries. You can also implement this as a stream-based approach but that would require another library such as dart:async and would not be changing anything. Make sure to call the dispose function of HideNavbar inside HomePage's dispose function to clear all resources used.
Working code with BottomNavigationBar.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.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'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
ScrollController _hideBottomNavController;
bool _isVisible;
#override
initState() {
super.initState();
_isVisible = true;
_hideBottomNavController = ScrollController();
_hideBottomNavController.addListener(
() {
if (_hideBottomNavController.position.userScrollDirection ==
ScrollDirection.reverse) {
if (_isVisible)
setState(() {
_isVisible = false;
});
}
if (_hideBottomNavController.position.userScrollDirection ==
ScrollDirection.forward) {
if (!_isVisible)
setState(() {
_isVisible = true;
});
}
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: CustomScrollView(
controller: _hideBottomNavController,
shrinkWrap: true,
slivers: <Widget>[
SliverPadding(
padding: const EdgeInsets.all(10.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => _getItem(context),
childCount: 20,
),
),
),
],
),
),
bottomNavigationBar: AnimatedContainer(
duration: Duration(milliseconds: 500),
height: _isVisible ? 56.0 : 0.0,
child: Wrap(
children: <Widget>[
BottomNavigationBar(
type: BottomNavigationBarType.fixed,
backgroundColor: Colors.blue,
fixedColor: Colors.white,
unselectedItemColor: Colors.white,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.card_giftcard),
title: Text('Offers'),
),
BottomNavigationBarItem(
icon: Icon(Icons.account_box),
title: Text('Account'),
),
],
),
],
),
),
);
}
_getItem(BuildContext context) {
return Card(
elevation: 3,
margin: EdgeInsets.all(8),
child: Row(
children: <Widget>[
Expanded(
child: Container(
padding: EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Item',
style:
TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
)
],
),
),
),
],
),
);
}
}
Working Model
Screenshot (Null safe + Optimized)
Code:
class MyPage extends StatefulWidget {
#override
State<MyPage> createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
late final ScrollListener _model;
late final ScrollController _controller;
final double _bottomNavBarHeight = 56;
#override
void initState() {
super.initState();
_controller = ScrollController();
_model = ScrollListener.initialise(_controller);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: AnimatedBuilder(
animation: _model,
builder: (context, child) {
return Stack(
children: [
ListView.builder(
controller: _controller,
itemCount: 20,
itemBuilder: (_, i) => ListTile(title: Text('Item $i')),
),
Positioned(
left: 0,
right: 0,
bottom: _model.bottom,
child: _bottomNavBar,
),
],
);
},
),
);
}
Widget get _bottomNavBar {
return SizedBox(
height: _bottomNavBarHeight,
child: BottomNavigationBar(
backgroundColor: Colors.amber,
items: [
BottomNavigationBarItem(icon: Icon(Icons.call), label: 'Call'),
BottomNavigationBarItem(icon: Icon(Icons.message), label: 'Message'),
],
),
);
}
}
class ScrollListener extends ChangeNotifier {
double bottom = 0;
double _last = 0;
ScrollListener.initialise(ScrollController controller, [double height = 56]) {
controller.addListener(() {
final current = controller.offset;
bottom += _last - current;
if (bottom <= -height) bottom = -height;
if (bottom >= 0) bottom = 0;
_last = current;
if (bottom <= 0 && bottom >= -height) notifyListeners();
});
}
}
So I have a Flutter application with multiple pages, this is done via a PageView. Before this page view I create my AppBar so it is persistent at the top of the application and doesn't animate when scrolling between pages.
I then want on one of the pages to create a bottom App bar, but for that I need to access the App bar element, however I have no idea how to do this.
This is the main class, the page I am trying to edit the app bar on is PlanPage.
final GoogleSignIn googleSignIn = GoogleSignIn();
final FirebaseAuth auth = FirebaseAuth.instance;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: '',
home: _handleCurrentScreen()
);
}
Widget _handleCurrentScreen() {
return StreamBuilder<FirebaseUser>(
stream: auth.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
print(snapshot);
if (snapshot.connectionState == ConnectionState.waiting) {
return SplashPage();
} else {
if (snapshot.hasData) {
return Home();
}
return LoginPage();
}
}
);
}
}
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return HomeState();
}
}
class HomeState extends State<Home> {
PageController _pageController;
PreferredSizeWidget bottomBar;
int _page = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
bottom: bottomBar,
),
body: PageView(
children: [
Container(
child: SafeArea(
child: RecipesPage()
),
),
Container(
child: SafeArea(
child: PlanPage()
),
),
Container(
child: SafeArea(
child: ShoppingListPage()
),
),
Container(
child: SafeArea(
child: ExplorePage()
),
),
],
/// Specify the page controller
controller: _pageController,
onPageChanged: onPageChanged
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.book),
title: Text('Recipes')
),
BottomNavigationBarItem(
icon: Icon(Icons.event),
title: Text('Plan')
),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart),
title: Text('Shopping List')
),
BottomNavigationBarItem(
icon: Icon(Icons.public),
title: Text("Explore"),
),
],
onTap: navigationTapped,
currentIndex: _page,
),
);
}
void onPageChanged(int page){
setState((){
this._page = page;
});
}
void setBottomAppBar(PreferredSizeWidget appBar) {
this.bottomBar = appBar;
print("setBottomAppBar: "+ appBar.toString());
}
/// Called when the user presses on of the
/// [BottomNavigationBarItem] with corresponding
/// page index
void navigationTapped(int page){
// Animating to the page.
// You can use whatever duration and curve you like
_pageController.animateToPage(
page,
duration: const Duration(milliseconds: 300),
curve: Curves.ease
);
}
#override
void initState() {
super.initState();
initializeDateFormatting();
_pageController = PageController();
}
#override
void dispose(){
super.dispose();
_pageController.dispose();
}
}
The PlanPage class looks like this
class PlanPage extends StatefulWidget {
var homeState;
PlanPage(this.homeState);
#override
State<StatefulWidget> createState() {
return _PlanState(homeState);
}
}
class _PlanState extends State<PlanPage> with AutomaticKeepAliveClientMixin<PlanPage>, SingleTickerProviderStateMixin {
var homeState;
TabController _tabController;
_PlanState(this.homeState);
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
//homeState.setBottomAppBar(_buildTabBar());
return Scaffold(
appBar: AppBar(
bottom: _buildTabBar(),
),
body: TabBarView(
controller: _tabController,
children: Plan.now().days.map((day) {
return ListView.builder(
itemCount: MealType.values.length,
itemBuilder: (BuildContext context, int index){
var mealType = MealType.values[index];
return Column(
children: <Widget>[
Text(
mealType.toString().substring(mealType.toString().indexOf('.')+1),
style: TextStyle(
//decoration: TextDecoration.underline,
fontSize: 30.0,
fontWeight: FontWeight.bold
),
),
Column(
children: day.meals.where((meal) => meal.mealType == mealType).map((meal) {
return RecipeCard(meal.recipe);
}).toList(),
)
],
);
}
);
}).toList(),
)
);
}
Widget _buildTabBar() {
return TabBar(
controller: _tabController,
isScrollable: true,
tabs: List.generate(Plan.now().days.length,(index) {
return Tab(
child: Column(
children: <Widget>[
Text(DateFormat.E().format(Plan.now().days[index].day)),
Text(DateFormat('d/M').format(Plan.now().days[index].day)),
],
),
);
}, growable: true),
);
}
#override
void initState() {
super.initState();
_tabController = new TabController(
length: Plan.now().days.length,
vsync: this,
initialIndex: 1
);
}
}
However the way it works now, makes it show 2 app bars.[
Usually it's a not a best practice to have two nested scrollable areas. Same for two nested Scaffolds.
That said, you can listen to page changes ( _pageController.addListener(listener) ) to update a page state property, and build a different AppBar.bottom (in the Home widget, so you can remove the Scaffold in PlanPage) depending on the page the user is viewing.
-EDIT-
In your Home widget you can add a listener to the _pageController like so:
void initState() {
super.initState();
_pageController = PageController()
..addListener(() {
setState(() {});
});
}
to have your widget rebuilt every time the user scrolls within your PageView. The setState call with an empty function might looks confusing, but it simply allows you to have the widget rebuilt when _pageController.page changes, which is not the default behavior. You could also have a page state property and update it in the setState call to reflect the _pageController.page property, but the result would be the same.
This way you can build a different AppBar.bottom depending on the _pageController.page:
// in your build function
final bottomAppBar = _pageController.page == 2 ? TabBar(...) : null;
final appBar = AppBar(
bottom: bottomAppBar,
...
);
return Scaffold(
appBar: appBar,
...
);
Inside BottomNavigation I have 2 page. 1st Page loading data from the network and showing in SliverList and In another page showing static list data.
After moving from 1st to 2nd page then 1st page all network data are gone. I have used PageStorageKey but still, it's not working. but the 2nd page never reloaded.
Why is 1st page not saving its state which has StreamBuilder?
My code:
Bottom Navigation Page:
class MainActivity extends StatefulWidget {
#override
_MainActivityState createState() => _MainActivityState();
}
class _MainActivityState extends State<MainActivity> {
final Key keyHome = PageStorageKey('pageHome');
final Key keyChat = PageStorageKey('pageChat');
int currentTab = 0;
HomePage home;
Chat chat;
List<Widget> pages;
Widget currentPage;
final PageStorageBucket bucket = PageStorageBucket();
#override
void initState(){
home = HomePage(
key: keyHome,
);
chat = Chat(
key: keyChat,
);
pages = [home, chat];
currentPage = home;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageStorage(
child: currentPage,
bucket: bucket,),
//Bottom Navigation Bar added
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentTab,
onTap: (int index){
setState(() {
currentTab = index;
currentPage = pages[index];
});
},
type: BottomNavigationBarType.fixed,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(icon: Icon(Icons.chat), title: Text('Chat')),
BottomNavigationBarItem(
icon: Icon(Icons.payment), title: Text('Pay')),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle), title: Text('Me')),
],
//currentIndex: _selectedIndex,
fixedColor: Colors.deepPurple,
),
);
}
}
Home Page:
class HomePage extends StatefulWidget {
HomePage({
Key key,
}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>{
#override
Widget build(BuildContext context) {
final NewsCatalogBlog newsBloc = BlocProvider.of<NewsCatalogBlog>(context);
//bloc.fetchAllNews();
return Scaffold(
appBar: PreferredSize(child: HomePageGradientAppBar(),
preferredSize: const Size.fromHeight(100.0),),
body: StreamBuilder(
stream: newsBloc.outNewsList,
builder: (context, AsyncSnapshot<List<Data>> snapshot) {
Widget newsList;
newsList = new SliverList(
delegate: new SliverChildBuilderDelegate((context,index){
print("$index");
return NewsListRow(snapshot.data, newsBloc, index);
},
childCount: (snapshot.data == null ? 0 : snapshot.data.length) + 30),
);
return new CustomScrollView(
slivers: <Widget>[
SliverToBoxAdapter(child: TabPanel(),),
SliverToBoxAdapter(child: UrlButtonPanel(),),
SliverToBoxAdapter(child: ChatNowAd(),),
newsList,
],
);
},
),
);
}
}
Chat Page:
class Chat extends StatelessWidget {
Chat({
Key key,
}): super (key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemExtent: 250.0,
itemCount: 20,
itemBuilder: (context, index) => Container(
padding: EdgeInsets.all(10.0),
child: Material(
elevation: 4.0,
borderRadius: BorderRadius.circular(5.0),
color: index % 2 == 0 ? Colors.cyan : Colors.deepOrange,
child: Center(
child: Text('Mir{$index}'),
),
),
),
);
}
}
Better way is to use IndexedStack instead of PageStorage or AutomaticKeepAliveClientMixin.
class _MainActivityState extends State<MainActivity> {
int _selectedPage = 0;
List<Widget> pageList = List<Widget>();
#override
void initState() {
pageList.add(HomePage());
pageList.add(ChatPage());
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: IndexedStack(
index: _selectedPage,
children: pageList,
),
//Bottom Navigation Bar added
bottomNavigationBar: BottomNavigationBar(
.....
**IndexedStack Widget is sub-class of Stack Widget
It shows single child from list of provided Childs.
Its size as big as largest child.
It keep state of all Childs.**