Flutter: Child Widget's looses state when parent's sate changes - flutter

I am learning Flutter and struggling with some state management issue.
I have a HomeScreen widget which contains Scaffold and a BottomNavigationBar. To switch pages based on the selected tab in BottomNavigationBar I am using PageView with PageController. Here is how the build method of HomeScreen widget looks:
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: _currentIndex == 2,
appBar: AppBar(...properties),
body: PageView(
controller: _pageController,
children: _pages,
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[...items],
currentIndex: _currentIndex,
onTap: _onItemTapped, //This changes state _currentIndex and calls the method _pageController.jumpToPage(_currentIndex);
),
);
}
_currentIndex initially is 0.
_pages is a list containing 3 widgets.
_pageController is simple PageController with initialPage set to 0.
If you notice I am using a property extendBodyBehindAppBar: _currentIndex == 2 which is using _currentIndex state and this is causing issue.
When I tap on the last Tab on the BottomNavigationBar the state the _currentIndex changes to 2 and thus the extendBodyBehindAppBar is set as true which makes entire Scaffold rebuild itself and the state of the PageView is lost.
If comment out the line extendBodyBehindAppBar: _currentIndex == 2, then even if the Scaffold rebuilds the state of the PageView widget is preserved.
As of my understanding the Flutter should keep the state of child Widgets even if the parent rebuilds because the WidgetTree is not changed or re-arranged. I tried using Key on PageView but nothing worked.
Any help is very much appreciated.

refer to Lucas Salton Cardinali post on medium
you need to use PageStorage to persit the state of the child you need after being destroyed.
here the example retrieved from the same page:
import 'package:flutter/material.dart';
// ...
void main() => runApp(MyApp());
// ...
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final List<Widget> pages = <Widget>[
ColorBoxPage(
key: PageStorageKey('pageOne'),
),
ColorBoxPage(
key: PageStorageKey('pageTwo'),
)
];
int currentTab = 0;
final PageStorageBucket _bucket = PageStorageBucket();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Persistence Example"),
),
body: PageStorage(
child: pages[currentTab],
bucket: _bucket,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentTab,
onTap: (int index) {
setState(() {
currentTab = index;
});
},
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'page 1',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'page2',
),
],
),
);
}
}
class ColorBoxPage extends StatelessWidget {
ColorBoxPage({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemExtent: 250.0,
itemBuilder: (context, index) => Container(
padding: EdgeInsets.all(10.0),
child: Material(
color: index % 2 == 0 ? Colors.cyan : Colors.deepOrange,
child: Center(
child: Text(index.toString()),
),
),
),
);
}
}

Related

Why doesn't BottomNavigatorBarItem currentIndex get updated?

I am new to flutter and I did find similar questions on SO but am too novice to understand the nuance so apologies if this question is too similar to an already asked one.
I have a BottomNavigationBar which has 3 icons (Home, Play and Create) which should navigate between these 3 routes/pages.
main.dart
routes: {
"/home": (context) => MyHomePage(title: "STFU"),
"/play": (context) => Play(),
"/create": (context) => Create(),
"/settings": (context) => Settings(),
},
I extracted my navbar into a custom class so my 3 separate pages could use it:
bottom-nav.dart
class MyBottomNavBar extends StatefulWidget {
MyBottomNavBar({
Key? key,
}) : super(key: key);
#override
State<MyBottomNavBar> createState() => _MyBottomNavBarState();
}
class _MyBottomNavBarState extends State<MyBottomNavBar> {
int _selectedIndex = 0;
void _onTapped(int index) => {
print("_onTapped called with index = $index"),
setState(
() => _selectedIndex = index,
)
};
#override
Widget build(BuildContext context) {
return BottomNavigationBar(
backgroundColor: Colors.orangeAccent[100],
currentIndex: _selectedIndex,
onTap: (value) => {
print("value is $value"),
// find index and push that
_onTapped(value),
if (value == 0)
{Navigator.pushNamed(context, "/home")}
else if (value == 1)
{Navigator.pushNamed(context, "/play")}
else if (value == 2)
{Navigator.pushNamed(context, "/create")}
},
items: [
BottomNavigationBarItem(label: "Home", icon: Icon(Icons.home_filled)),
BottomNavigationBarItem(
label: "Play", icon: Icon(Icons.play_arrow_rounded)),
BottomNavigationBarItem(label: "Create", icon: Icon(Icons.create)),
],
);
}
}
so now i just set this MyBottomNavBar class to the bottomNavigationBar property of the Scaffold widget my inside Home page, Play page and Create page for eg.
home.dart
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home"),
),
body: Column(
children: [
Container(
padding: EdgeInsets.all(20.00),
child: Text("inside home"),
),
],
),
bottomNavigationBar: MyBottomNavBar(),
);
}
}
play.dart
class Play extends StatefulWidget {
const Play({super.key});
#override
State<Play> createState() => _PlayState();
}
class _PlayState extends State<Play> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text("inside play page"),
SizedBox(
height: 30.00,
),
Text("some text"),
],
),
),
bottomNavigationBar: MyBottomNavBar(),
);
}
}
The nav bar buttons work to switch between pages but for some reason the currentIndex value isn't getting updated and stays at 0 (i.e on the "Home" icon). When I debug it I can see _selectedIndex getting updated inside inside the _onTapped function which should update the currentIndex value but it doesn't appear to do so. Any help would be appreciated
What you have here is three different pages with their separate BottomNavBar class instance. Instead you should have a shared Scaffold and one bottomNavBar so that when you navigate bottomNavbar state does not reset.
You can use PageView to do this.
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
super.initState();
}
List<Widget> pages = const [Home(), Play(), Create()];
final _pageController = PageController();
int _selectedIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: (index) {
_pageController.jumpToPage(index);
_selectedIndex = index;
setState(() {});
},
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.play_arrow), label: 'Play'),
BottomNavigationBarItem(icon: Icon(Icons.create), label: 'Create'),
],
backgroundColor: Colors.greenAccent,
),
body: PageView(
controller: _pageController,
children: pages,
),
);
}
}
class Home extends StatelessWidget {
const Home({super.key});
#override
Widget build(BuildContext context) {
return const Placeholder();
}
}
class Play extends StatelessWidget {
const Play({super.key});
#override
Widget build(BuildContext context) {
return const Placeholder();
}
}
class Create extends StatelessWidget {
const Create({super.key});
#override
Widget build(BuildContext context) {
return const Placeholder();
}
}

Change AppBar content dynamically on Navigation through App?

I've got my Navigation in my App working, but I would like to change the AppBar Title dynamically, depending on which page is currently displayed.
Main Problem is, I'm using a TabBarView, to show the main pages, but I can also get to some sub pages. That part is now working fine, but I can't figure out, how I could make the AppBar change depending on the displayed page / sub-page
Here is the main class, which holds the AppBar:
class AppView extends StatefulWidget {
const AppView({Key? key}) : super(key: key);
#override
State<AppView> createState() => _AppViewState();
}
class _AppViewState extends State<AppView> with TickerProviderStateMixin{
late TabController _controller;
#override
void initState(){
super.initState();
_controller = TabController(length: 6, vsync: this);
_controller.addListener(() {
if(navKey.currentState!.canPop()){
navKey.currentState!.pop();
}
});
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if(navKey.currentState!.canPop()) {
navKey.currentState!.pop();
}
return false;
},
child: Scaffold(
appBar: AppBar(
title: HeadLine(
"Test",
color: white,
),
),
body: Navigator(
key: navKey,
onGenerateRoute: (_) => MaterialPageRoute(
builder: (_) => TabBarView(
controller: _controller,
physics: NeverScrollableScrollPhysics(),
children: [
HomeScreen(),
ChatOverviewScreen(),
ForumOverviewScreen(),
CalendarScreen(),
GroupScreen(),
ProfileScreen()
],
)
),
),
bottomNavigationBar: BottomBar(
controller: _controller,
)),
);
}
}
For Testing, theres currently just a button which calls a sub page on one of these pages:
class ChatOverviewScreen extends StatelessWidget {
const ChatOverviewScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: PrimaryButton(
text: "Do Stuff",
onPressed: () {
navKey.currentState!.push(ChatDetailScreen.route());
},
),
);
}
}
navKey is set as global variable in my main file via
final GlobalKey<NavigatorState> navKey = GlobalKey<NavigatorState>();
Basicaly I want to set the Title of the AppBar depending on which page / subpage is shown and when it is a sub page, there should also be an arrow to get back.
You should do something like this:
Widget _buildAppBarTitle(int index) {
String title = '';
switch (index) {
case 0:
title = 'YOUR TITLE';
break;
... other case...
}
return Text(title);
}
Scaffold(
appBar: AppBar(
title: _buildAppBarTitle(_currentIndex),
),
body: Navigator(
...
),
bottomNavigationBar: BottomNavigationBar(
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
currentIndex: _currentIndex,
items: [...],
),
)
You can use enum to identify the pages, also, you can use some state manager.
Flutter State management

Flutter Setstate called multiple times (GestureDetector & PageView)

basically I have a swiping screen with elements, where user is able to swipe in left or right direction. When the user is swiping, im calling some functions. Im using GestureDetector for gesture recognitions and PageView.Custom for my items. Probably ListView.Custom does also work, but it doesn't fix my issue I have.
I need a PageController, because I have to control the navigation programatically. And I think the PageController maybe is the reason behind my issue that my functions are called multiple times. How to fix it? Does somebody know why setstate is called that often and what to do to prevent it?
Im providing you a fully working example (minified version) with a print on the swiping right actions, where you can see that its beeing called multiple times.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int currentIndex = 0;
#override
Widget build(BuildContext context) {
// Page selector for tab list
void _selectPage(int index) {
print('page index: $index');
setState(() {
currentIndex = index;
});
}
// Routes list for tab navigation Android
final List<Widget> _pages = [
ScreenA(),
ScreenB(func: _selectPage),
];
return Scaffold(
appBar: AppBar(),
body: _pages[currentIndex],
bottomNavigationBar: SafeArea(
child: BottomNavigationBar(
onTap: _selectPage,
iconSize: 22,
currentIndex: currentIndex,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
backgroundColor: Theme.of(context).primaryColor,
icon: Icon(Icons.description),
label: 'ScreenA',
),
BottomNavigationBarItem(
backgroundColor: Theme.of(context).primaryColor,
icon: Icon(Icons.ac_unit_outlined),
label: 'ScreenB'),
],
),
),
);
}
}
class ScreenA extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Text('HOME'),
);
}
}
class ScreenB extends StatefulWidget {
Function func;
ScreenB({Key key, #required this.func})
: super(key: key);
#override
_ScreenBState createState() => _ScreenBState();
}
class _ScreenBState extends State<ScreenB> {
_ScreenBState();
var _controller = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.access_alarm_sharp),
onPressed: () async {
widget.func(0);
},
),
],
),
body: PageView.custom(
dragStartBehavior: DragStartBehavior.start,
controller: _controller,
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
childrenDelegate: SliverChildBuilderDelegate((ctx, pageIndex) =>
GestureDetector(
onPanUpdate: (details) async {
if (details.delta.dx < 0) {
_controller.nextPage(
duration: Duration(milliseconds: 200),
curve: Curves.easeInOut);
print('function called');
}
},
child: Center(
child: Container(
width: 200,
height: 200,
color: Colors.red,
child: Text('hi')))))),
);
}
}
Thanks in advance!
The problem is that you are using the onPanUpdate method, which is triggered every time a user drags their finger either right or left. You should use the onPanEnd method, which is only triggered when the user's finger is off the screen after dragging either left or right. The function below will work fine.
onPanEnd: (details) async { if (details.velocity.pixelsPerSecond.dx < 0) { _controller.nextPage( duration: Duration(milliseconds: 200), curve: Curves.easeInOut); print('function called'); } }
Please write this function out of widget build function
// Page selector for tab list
void _selectPage(int index) {
print('page index: $index');
setState(() {
currentIndex = index;
});
}
like this
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int currentIndex = 0;
// Page selector for tab list
void _selectPage(int index) {
print('page index: $index');
setState(() {
currentIndex = index;
});
}
// Routes list for tab navigation Android
final List<Widget> _pages = [
ScreenA(),
ScreenB(func: _selectPage),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: _pages[currentIndex],
bottomNavigationBar: SafeArea(
child: BottomNavigationBar(
onTap: _selectPage,
iconSize: 22,
currentIndex: currentIndex,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
backgroundColor: Theme.of(context).primaryColor,
icon: Icon(Icons.description),
label: 'ScreenA',
),
BottomNavigationBarItem(
backgroundColor: Theme.of(context).primaryColor,
icon: Icon(Icons.ac_unit_outlined),
label: 'ScreenB'),
],
),
),
);
}
}
class ScreenA extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Text('HOME'),
);
}
}
class ScreenB extends StatefulWidget {
Function func;
ScreenB({Key key, #required this.func})
: super(key: key);
#override
_ScreenBState createState() => _ScreenBState();
}
class _ScreenBState extends State<ScreenB> {
_ScreenBState();
var _controller = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.access_alarm_sharp),
onPressed: () async {
widget.func(0);
},
),
],
),
body: PageView.custom(
dragStartBehavior: DragStartBehavior.start,
controller: _controller,
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
childrenDelegate: SliverChildBuilderDelegate((ctx, pageIndex) =>
GestureDetector(
onPanUpdate: (details) async {
if (details.delta.dx < 0) {
_controller.nextPage(
duration: Duration(milliseconds: 200),
curve: Curves.easeInOut);
print('function called');
}
},
child: Center(
child: Container(
width: 200,
height: 200,
color: Colors.red,
child: Text('hi')))))),
);
}
}

Flutter: value of currentIndex property in BottomNavigationBar doesn't update when the state updates

I have a BottomNavigationBar that navigates to other pages when pressing an icon. This works fine, except the value of the currentIndex property in BottomNavigationBar doesn't update when the state updates, which means there is no change on the acual BottomNavigationBar. enter image description here
I'm using a vaiable (_selectedPage) to keep track of the selected index in the BottomNavigationBar, and the value changes when tapping an item, but it's not updating the currentIndex property when the state updates.. why is that?
import 'package:flutter/material.dart';
import 'package:independentproject/pages/home_page.dart';
import 'package:independentproject/pages/cook_recipe.dart';
class PageNavigationBar extends StatefulWidget {
#override
_PageNavigationBarState createState() => _PageNavigationBarState();
}
class _PageNavigationBarState extends State<PageNavigationBar> {
//default page showing in bottom navigation bar will be CookRecipe()
int _selectedPage = 1;
//all pages optional in bottom navigation bar
final List<Widget> _pageOptions = [
HomePage(),
CookRecipe(),
];
void onTapped(int pageTapped) {
setState(() {
//print(pageTapped);
_selectedPage = pageTapped;
Navigator.push(context, MaterialPageRoute(builder: (context) => _pageOptions[pageTapped]));
//print(_selectedPage);
});
}
#override
Widget build(BuildContext context) {
return BottomNavigationBar(
//TODO: currentIndex: doesn't update when the state updates, why?
currentIndex: _selectedPage,
//items showing in bottom navigation bar
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Homepage'),
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
title: Text('Search recipe'),
),
],
onTap: (int pageTapped) {
onTapped(pageTapped);
},
);
}
}
import 'package:flutter/material.dart';
import 'package:independentproject/page_navigation_bar.dart';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Home page'),
),
body: Center(
child: Text('Home page'),
),
bottomNavigationBar: PageNavigationBar(),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:independentproject/page_navigation_bar.dart';
class CookRecipe extends StatefulWidget {
#override
_CookRecipeState createState() => _CookRecipeState();
}
class _CookRecipeState extends State<CookRecipe> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Search recipe'),
),
body: Center(
child: Text('Search recipes'),
),
bottomNavigationBar: PageNavigationBar(),
),
);
}
}
I would advise you to create a widget that will contain the BottomNavigationBar and also PageView that would allow you to switch pages with PageController.
For example:
class _MainScreenState extends State<MainScreen> {
PageController _pageController;
int _page = 1;
Duration pageChanging = Duration(milliseconds: 300);//this is for page animation-not necessary
Curve animationCurve = Curves.linear;//this is for page animation-not necessary
_MainScreenState();
#override
void initState() {
super.initState();
_pageController = PageController(initialPage: 1);
}
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
physics: BouncingScrollPhysics(),
scrollDirection: Axis.horizontal,
controller: _pageController,
onPageChanged: onPageChanged,
children: <Widget>[
//here are all the pages you need:
//CookRecipe(),
],
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(
Icons.message,
),
title: Container(height: 0.0),
),
BottomNavigationBarItem(
icon: Icon(
Icons.home,
),
title: Container(height: 0.0),
),
BottomNavigationBarItem(
icon: Icon(
Icons.person,
),
title: Container(height: 0.0),
),
],
onTap: navigationTapped,
selectedItemColor: Theme.of(context).backgroundColor,
currentIndex: _page,
),
),
);
}
void navigationTapped(int page) {
_pageController.animateToPage(page,duration: pageChanging,
curve: animationCurve,);
}
#override
void dispose() {
super.dispose();
_pageController.dispose();
}
void onPageChanged(int page) {
if (this.mounted){
setState(() {
this._page = page;
});
}}
You can also do this without the PageView,and use only the controller to switch pages.
BTW-you create new instance of the navigation bar when you load a page which is bad practice
it is because PageNavigationBar is a own class, when you call there a setstate only this class updates
take a look at the Provider
a very usefull state management Package
or you can also handle your Page, when the NavBar is your Main Page and you have only one Page
return MaterialApp(
home: Scaffold(
appBar: ownAppBar(_selectedPage),
body: _bodyOptions[_selectedPage],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedPage,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Homepage'),
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
title: Text('Search recipe'),
),
],
onTap: (int pageTapped) {
onTapped(pageTapped);
},
)
),
);
final List<Widget> _bodyOptions = [
HomePage(),
CookRecipe(),
];
You don't need to use Navigator to change pages, I modified your code just try.
import 'package:flutter/material.dart';
main() {
runApp(MaterialApp(home: PageNavigationBar()));
}
class PageNavigationBar extends StatefulWidget {
#override
_PageNavigationBarState createState() => _PageNavigationBarState();
}
class _PageNavigationBarState extends State<PageNavigationBar> {
//default page showing in bottom navigation bar will be CookRecipe()
int _selectedPage = 1;
//all pages optional in bottom navigation bar
final List<Widget> _pageOptions = [
HomePage(),
CookRecipe(),
];
void onTapped(int pageTapped) {
setState(() {
//print(pageTapped);
_selectedPage = pageTapped;
// Navigator.push(context, MaterialPageRoute(builder: (context) => _pageOptions[pageTapped]));
//print(_selectedPage);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _pageOptions[_selectedPage],
bottomNavigationBar: BottomNavigationBar(
//TODO: currentIndex: doesn't update when the state updates, why?
currentIndex: _selectedPage,
//items showing in bottom navigation bar
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Homepage'),
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
title: Text('Search recipe'),
),
],
onTap: (int pageTapped) {
onTapped(pageTapped);
},
),
);
}
}
class CookRecipe extends StatefulWidget {
#override
_CookRecipeState createState() => _CookRecipeState();
}
class _CookRecipeState extends State<CookRecipe> {
#override
Widget build(BuildContext context) {
return Center(
child: Text('Search recipes'),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Center(
child: Text('Home page'),
);
}
}

BottomNavigationBar page change causing StreamBuilder data reload

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.**