How to preserve the state of a page in PageView or how to preserve state of widgets when used with BottomNavigationBar etcetera.
final List<Widget> _pages = const [
const Page1(key: Key("Page1"),),
const Page2(key: Key("Page2"),),
];
int _index=0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: _pages[_index],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _index,
onTap: (index) => setState(() => _index = index),
items: [
BottomNavigationBarItem(
icon: Icon(
Icons.edit_outlined,
),
),
BottomNavigationBarItem(
icon: Icon(
Icons.edit_outlined,
),),
]),
);
where Page1() and Page2() get Data from internet.
when moving to previous page, it is calling the API again.
You can use Indexed Stack to preserve the data on all pages.
Cons
All the pages (childrens) will be built regardless whether the user visits the page or not.
Alternatives
You can use PageStorageKey
Related
I'm working with FutureBuilder and I initialize _future in InitState as mentioned here.
Despite this, FutureBuilder re-build itself every time I switch page with the BottomNavigationBar.
Code Sample:
class _HomeViewState extends State<HomeView> {
late final Future<List<DocumentSnapshot<Object?>>> _futureSerieA;
#override
void initState() {
super.initState();
_futureSerieA = getScheduled("Serie A");
}
#override
Widget build(BuildContext context) {
return SizedBox.expand(
child: SingleChildScrollView(
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(0,0,0,0),
child: Container(
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: Colors.transparent)
)
),
child: Theme(
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: FutureBuilder(
future: _futureSerieA,
builder: (context, AsyncSnapshot<List<DocumentSnapshot>> snapshot) {
if (snapshot.hasData) {
List<String> scheduled = [];
for (var DOC in snapshot.data!) {
scheduled.add(DOC.id);
}
return ...
How could I disable FutureBuilder re-build when browsing between pages of BottomNavigationBar?
BottomNavBar:
class _LoggedHandleState extends State<LoggedHandle> {
);
double height = AppBar().preferredSize.height;
int _selectedPage = 1;
final _pageOptions = [
const BetView(),
const HomeView(),
const UserView()
];
#override
Widget build(BuildContext context) {
return Scaffold(
...
),
bottomNavigationBar: BottomNavigationBar(
unselectedItemColor: Colors.white60,
backgroundColor: Colors.red,
selectedItemColor: Colors.white,
currentIndex: _selectedPage,
onTap: (int index) {
setState(() {
_selectedPage = index;
});
},
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.receipt),
label: 'Schedina',
),
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle),
label: 'Account',
),
]),
body: _pageOptions[_selectedPage],
);
}
}
When browsing between pages of bottom navigation bar, your state is not maintained. This behaviour causes the widget to rebuild every time.
You can use Indexed Stack https://api.flutter.dev/flutter/widgets/IndexedStack-class.html
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: BottomNavigationBar(
onTap: (index) {
setState(() {
current_tab = index;
});
},
currentIndex: current_tab,
items: [
BottomNavigationBarItem(
...
),
BottomNavigationBarItem(
...
),
],
),
body: IndexedStack(
children: <Widget>[
PageOne(),
PageTwo(),
],
index: current_tab,
),
);
}
Although this is the best solution, it will load all your widgets once the IndexedStack is loaded.
I found a Lazy Loading Indexed Stack util to load your widgets when and only the first time they are created https://github.com/okaryo/lazy_load_indexed_stack
I'm facing an issue of "Multiple GlobalKeys in the Widget tree" while Navigation.
I have a BottomNavigationBar & a Drawer defined in the Scaffold of a Base Screen and in the body parameter of Scaffold I have multiple screens which I'm accessing with BottomNavigationBar. The thing is, I'm accessing the Drawer of the Base Screen from one of the multiple screens by using a GlobalKey, and everything's working fine but when I Navigate to the Base Screen from Another Screen then I get the above-mentioned error.
I have tried a solution of not using a static keyword while defining the key and it solves the error of navigation but then I can't access the Drawer because then I get another error of "method 'openDrawer' was called on null".
This is a separate class where I have defined the Key:
class AppKeys {
final GlobalKey<ScaffoldState> homeKey = GlobalKey<ScaffoldState>();
}
This is the Base Screen:
class Base extends StatefulWidget {
#override
_BaseState createState() => _BaseState();
}
class _BaseState extends State<Base> {
int selectedScreen = 0;
final screens = List<Widget>.unmodifiable([Home(), Cart(), Orders(), Help()]);
AppKeys appKeys = AppKeys();
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: MyDrawer(),
key: appKeys.homeKey,
body: screens[selectedScreen],
bottomNavigationBar: SizedBox(
height: 80,
child: BottomNavigationBar(
onTap: (val) {
setState(() {
selectedScreen = val;
});
},
currentIndex: selectedScreen,
selectedItemColor: AppColor.primary,
elevation: 20.0,
unselectedItemColor: Colors.grey,
showUnselectedLabels: true,
type: BottomNavigationBarType.fixed,
iconSize: 25,
selectedFontSize: 15,
unselectedFontSize: 15,
items: [
BottomNavigationBarItem(
icon: Icon(AnanasIcons.home), title: Text("Home")),
BottomNavigationBarItem(
icon: Icon(AnanasIcons.cart), title: Text("Cart")),
BottomNavigationBarItem(
icon: Icon(AnanasIcons.orders), title: Text("My Orders")),
BottomNavigationBarItem(
icon: Icon(AnanasIcons.help), title: Text("Help")),
],
),
),
);
}
}
This is the Home Screen from where I'm accessing the Drawer:
class Home extends StatelessWidget {
final AppKeys appKeys = AppKeys();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(
AnanasIcons.menu,
color: Colors.black,
),
onPressed: () {
appKeys.homeKey.currentState.openDrawer();
}),
backgroundColor: Theme.of(context).canvasColor,
title: Text("Hi"),
actions: [
IconButton(
icon: Icon(
Icons.person,
color: Colors.black,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Profile(),
));
})
],
),
body: Container(),
);
}
}
I've found the answer! Instead of pushing the route with the key, we need to remove all the routes from the stack till the screen where we have to go.
Here's the code for that:
Navigator.of(context).popUntil(ModalRoute.withName(Base.routeName));
I am using Flutter. I have bottom navigation with 3 tabs. When I use Navigator.push() to tabs 2 but the bottom navigation missing. How to solve this problem?
Here is my tabController.dart:
class _TabsControllerState extends State<TabsController> {
final List<Widget> pages = [
HomePage(
key: PageStorageKey('page1'),
),
CartPage(
key: PageStorageKey('page2'),
),
ProfilePage(
key: PageStorageKey('page3'),
),
];
final PageStorageBucket bucket = PageStorageBucket();
var _selectedIndex = 0;
Widget _bottomNavigationBar(var selectedIndex) => BottomNavigationBar(
onTap: (var index) => setState(() => _selectedIndex = index),
currentIndex: selectedIndex,
type: BottomNavigationBarType.fixed,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text('Home')),
BottomNavigationBarItem(
icon: Icon(Icons.lightbulb_outline), title: Text('Cart')),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle), title: Text('Profile')),
],
);
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: ()async{
if(_selectedIndex == 0)
return true;
else setState(() {
_selectedIndex = 0;
});
return false;
},
child: Scaffold(
bottomNavigationBar: _bottomNavigationBar(_selectedIndex),
body: PageStorage(
child: pages[_selectedIndex],
bucket: bucket,
),
));
}
}
In CartPage I have details page. In Details page, when I press button I want go to HomePage. How can I do that?
Here is my code:
new FlatButton(
child: new Text("OK"),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => HomePage()));
},
),
If you look at the example on the Flutter's API documentation on the BottomNavigationBar, on the "Sample in App" Tab of the example, you will see that it shows you that the content that is being controlled by the BottomNavigationBar is declared above in the same class. You should do the same.
Remember you can simply have your Widgets in other files, import them, and instantiate them to use as each of the Tabs.
UPDATED
Here is an explanation of how to implement it on your code:
In your main class declare a method to change the Tab index that you can use and pass down to other classes:
void setTabIndex(index){
setState((){
_selectedIndex = index;
});
}
In your CartPage class, declare a variable on the constructor for the Function type:
Function setTabIndex;
Your button would like this:
FlatButton(
child: new Text("OK"),
onPressed: () {
setTabIndex(0);
// 0 is the position of your HomePage in your pages List
},
),
When we navigate to different pages in Flutter using a BottomNavigationBar, stateful pages don't seem to rebuild themselves.
This means that we cannot trigger rebuilding a stateful widget using BottomNavigationBar, unlike a Drawer. The state remains as is, the BottomNavigationBar slides over pages and does not help with rebuilding the whole page again.
int _selectedIndex = 0;
static const TextStyle optionStyle = TextStyle(fontSize: 30, fontWeight: FontWeight.bold);
static const List<Widget> _widgetOptions = <Widget>[
Page1(),
Page2(),
Page3(),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: _widgetOptions.elementAt(_selectedIndex),
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
title: Text('Business'),
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
title: Text('School'),
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
Here the widgets namely Page1(), Page2(), Page3() are stateful widgets, they don't seem to rebuild themselves when navigated by the bottom navigation bar. Is there any way we could do that?
Instead of
static const List<Widget> _widgetOptions = <Widget>[
Page1(),
Page2(),
Page3(),
];
Use
Widget _widgetOptions(int index) {
switch (index) {
case 0:
return Page1();
break;
case 1:
return Page2();
break;
case 2:
return Page3();
break;
}
return Page1();
}
And also replace
body: _widgetOptions.elementAt(_selectedIndex),
with
body: _widgetOptions(_selectedIndex),
I am using bottomNavigationBar in my flutter project I'm new in flutter and have I no idea about pagination and use assets image icons instead of iconData. I searched about it for the last 2 days but not got satisfaction. Please help me......
I used bottomNavigationBar with a fab button from here
https://medium.com/coding-with-flutter/flutter-bottomappbar-navigation-with-fab-8b962bb55013
https://github.com/bizz84/bottom_bar_fab_flutter
I also tried to use custom icons from here
https://medium.com/flutterpub/how-to-use-custom-icons-in-flutter-834a079d977
but not got success
I just want to change icons and want to know how to use pagination. What I can do changes in the last example code for pagination.
Here's how you can use an icon from assets
ImageIcon(
AssetImage("images/icon_more.png"),
color: Color(0xFF3A5A98),
),
Try this example for BottomNavBar click
So there what you want to replace is BottomNavigationBarItem
new BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
to
new BottomNavigationBarItem(
icon: ImageIcon(
AssetImage("images/icon_more.png"),
color: Color(0xFF3A5A98),
),
title: Text('Home'),
),
You can learn about the navigation from the article I shared
UPDATE
Here's an example as you requested.
So here the _children variable holds the list of pages that you want to navigate based on the selection of BottomNavBarItem.
How we navigate is when we press a tab item we set it's index using onTabTapped function.. When the index change the view is changed accordingly as we have instructed the body show the current index of the children
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _currentIndex = 0;
final List<Widget> _children = [
Container(
color: Colors.red,
),
Container(
color: Colors.blue,
),
Container(
color: Colors.green,
)
];
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _children[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: onTabTapped, // new
currentIndex: _currentIndex, // new
items: [
new BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
new BottomNavigationBarItem(
icon: Icon(Icons.mail),
title: Text('Messages'),
),
new BottomNavigationBarItem(
icon: Icon(Icons.person), title: Text('Profile'))
],
),
);
}
}
For me, above mentioned ImageIcon option did not work. It worked by using Image.asset
BottomNavigationBarItem(
title:Text(AppLocalizations.of(context).converter), icon: Image.asset(
"images/convertericon.png",
)),