Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 271 Vote(s) - 3.59 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to preserve widget states in flutter, when navigating using BottomNavigationBar?

#1
I'm currently working on building a Flutter app that will preserve states when navigating from one screen, to another, and back again when utilizing [BottomNavigationBar][1]. Just like it works in the Spotify mobile application; if you have navigated down to a certain level in the navigation hierarchy on one of the main screens, changing screen via the bottom navigation bar, and later changing back to the old screen, will preserve where the user were in that hierarchy, including preservation of the state.

I have run my head against the wall, trying various different things without success.

I want to know how I can prevent the pages in `pageChooser()`, when toggled once the user taps the BottomNavigationBar item, from rebuilding themselves, and instead preserve the state they already found themselves in (the pages are all stateful Widgets).

import 'package:flutter/material.dart';
import './page_plan.dart';
import './page_profile.dart';
import './page_startup_namer.dart';

void main() => runApp(new Recipher());

class Recipher extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new Pages();
}
}

class Pages extends StatefulWidget {
@override
createState() => new PagesState();
}

class PagesState extends State<Pages> {
int pageIndex = 0;


pageChooser() {
switch (this.pageIndex) {
case 0:
return new ProfilePage();
break;

case 1:
return new PlanPage();
break;

case 2:
return new StartUpNamerPage();
break;

default:
return new Container(
child: new Center(
child: new Text(
'No page found by page chooser.',
style: new TextStyle(fontSize: 30.0)
)
),
);
}
}

@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: pageChooser(),
bottomNavigationBar: new BottomNavigationBar(
currentIndex: pageIndex,
onTap: (int tappedIndex) { //Toggle pageChooser and rebuild state with the index that was tapped in bottom navbar
setState(
(){ this.pageIndex = tappedIndex; }
);
},
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
title: new Text('Profile'),
icon: new Icon(Icons.account_box)
),
new BottomNavigationBarItem(
title: new Text('Plan'),
icon: new Icon(Icons.calendar_today)
),
new BottomNavigationBarItem(
title: new Text('Startup'),
icon: new Icon(Icons.alarm_on)
)
],
)
)
);
}
}





[1]:

[To see links please register here]

Reply

#2
Instead of returning new instance every time you run `pageChooser`, have one instance created and return the same.

Example:

class Pages extends StatefulWidget {
@override
createState() => new PagesState();
}

class PagesState extends State<Pages> {
int pageIndex = 0;

// Create all the pages once and return same instance when required
final ProfilePage _profilePage = new ProfilePage();
final PlanPage _planPage = new PlanPage();
final StartUpNamerPage _startUpNamerPage = new StartUpNamerPage();


Widget pageChooser() {
switch (this.pageIndex) {
case 0:
return _profilePage;
break;

case 1:
return _planPage;
break;

case 2:
return _startUpNamerPage;
break;

default:
return new Container(
child: new Center(
child: new Text(
'No page found by page chooser.',
style: new TextStyle(fontSize: 30.0)
)
),
);
}
}

@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: pageChooser(),
bottomNavigationBar: new BottomNavigationBar(
currentIndex: pageIndex,
onTap: (int tappedIndex) { //Toggle pageChooser and rebuild state with the index that was tapped in bottom navbar
setState(
(){ this.pageIndex = tappedIndex; }
);
},
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
title: new Text('Profile'),
icon: new Icon(Icons.account_box)
),
new BottomNavigationBarItem(
title: new Text('Plan'),
icon: new Icon(Icons.calendar_today)
),
new BottomNavigationBarItem(
title: new Text('Startup'),
icon: new Icon(Icons.alarm_on)
)
],
)
)
);
}
}

Or you can make use of widgets like [`PageView`](

[To see links please register here]

) or [`Stack`](

[To see links please register here]

) to achieve the same.

Hope that helps!

Reply

#3
The most convenient way I have found to do so is using PageStorage widget along with PageStorageBucket, which acts as a key value persistent layer.

Go through this article for a beautiful explanation ->

[To see links please register here]

Reply

#4
Use [AutomaticKeepAliveClientMixin](

[To see links please register here]

) to force your tab content to not be disposed.

class PersistantTab extends StatefulWidget {
@override
_PersistantTabState createState() => _PersistantTabState();
}

class _PersistantTabState extends State<PersistantTab> with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
return Container();
}

// Setting to true will force the tab to never be disposed. This could be dangerous.
@override
bool get wantKeepAlive => true;
}

To make sure your tab does get disposed when it doesn't require to be persisted, make `wantKeepAlive` return a class variable. You must call `updateKeepAlive()` to update the keep alive status.

Example with dynamic keep alive:

// class PersistantTab extends StatefulWidget ...

class _PersistantTabState extends State<PersistantTab>
with AutomaticKeepAliveClientMixin {
bool keepAlive = false;

@override
void initState() {
doAsyncStuff();
}

Future doAsyncStuff() async {
keepAlive = true;
updateKeepAlive();
// Keeping alive...

await Future.delayed(Duration(seconds: 10));

keepAlive = false;
updateKeepAlive();
// Can be disposed whenever now.
}

@override
bool get wantKeepAlive => keepAlive;

@override
Widget build(BuildContext context) {
super.build();
return Container();
}
}
Reply

#5
For keeping state in `BottomNavigationBar`, you can use `IndexedStack`

@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,
),
);
}
Reply

#6

Use “**IndexedStack Widget**” with “**Bottom Navigation Bar Widget**” to keep state of Screens/pages/Widget


Provide list of Widget to **IndexedStack** and index of widget you want to show because **IndexedStack** show single widget from list at one time.


```dart
final List<Widget> _children = [
FirstClass(),
SecondClass()
];

Scaffold(
body: IndexedStack(
index: _selectedPage,
children: _children,
),
bottomNavigationBar: BottomNavigationBar(
........
........
),
);
Reply

#7
**Late to the party, but I've got a simple solution.** Use the `PageView` widget with the `AutomaticKeepAliveClinetMixin`.

The beauty of it that it doesn't load any tab until you click on it.

-------------------------

The page that includes the `BottomNavigationBar`:

var _selectedPageIndex;
List<Widget> _pages;
PageController _pageController;

@override
void initState() {
super.initState();

_selectedPageIndex = 0;
_pages = [
//The individual tabs.
];

_pageController = PageController(initialPage: _selectedPageIndex);
}

@override
void dispose() {
_pageController.dispose();

super.dispose();
}

@override
Widget build(BuildContext context) {
...
body: PageView(
controller: _pageController,
physics: NeverScrollableScrollPhysics(),
children: _pages,
),
bottomNavigationBar: BottomNavigationBar(
...
currentIndex: _selectedPageIndex,
onTap: (selectedPageIndex) {
setState(() {
_selectedPageIndex = selectedPageIndex;
_pageController.jumpToPage(selectedPageIndex);
});
},
...
}


The individual tab:


class _HomeState extends State<Home> with AutomaticKeepAliveClientMixin<Home> {
@override
bool get wantKeepAlive => true;

@override
Widget build(BuildContext context) {
//Notice the super-call here.
super.build(context);
...
}
}

I've made a video about it [here][1].


[1]:
Reply

#8
Do not use IndexStack Widget, because it will instantiate all the tabs together, and suppose if all the tabs are making a network request then the callbacks will be messed up the last API calling tab will probably have the control of the callback.

Use AutomaticKeepAliveClientMixin for your stateful widget it is the simplest way to achieve it without instantiating all the tabs together.

My code had interfaces that were providing the respective responses to the calling tab I implemented it the following way.

Create your stateful widget


class FollowUpsScreen extends StatefulWidget {
FollowUpsScreen();

@override
State<StatefulWidget> createState() {
return FollowUpsScreenState();
}
}

class FollowUpsScreenState extends State<FollowUpsScreen>
with AutomaticKeepAliveClientMixin<FollowUpsScreen>
implements OperationalControls {

@override
Widget build(BuildContext context) {
//do not miss this line
super.build(context);
return .....;
}

@override
bool get wantKeepAlive => true;
}


Reply

#9
proper way of preserving tabs state in bottom nav bar is by wrapping the whole tree with `PageStorage()` widget which takes a `PageStorageBucket bucket` as a required named parameter and for those tabs to which you want to preserve its state pas those respected widgets with `PageStorageKey(<str_key>)` then you are done !! you can see more details in this ans which i've answered few weeks back on one question :

[To see links please register here]


there's other alternatives like `IndexedWidget()` but you should beware while using it , i've explained y we should be catious while using `IndexedWidget()` in the given link answer

good luck mate ..
Reply

#10
This solution is based on `CupertinoTabScaffold`'s implementation which won't load screens unnecessary.

import 'package:flutter/material.dart';

enum MainPage { home, profile }

class BottomNavScreen extends StatefulWidget {
const BottomNavScreen({super.key});

@override
State<BottomNavScreen> createState() => _BottomNavScreenState();
}

class _BottomNavScreenState extends State<BottomNavScreen> {
var currentPage = MainPage.home;

@override
Widget build(BuildContext context) {
return Scaffold(
body: PageSwitchingView(
currentPageIndex: MainPage.values.indexOf(currentPage),
pageCount: MainPage.values.length,
pageBuilder: _pageBuilder,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: MainPage.values.indexOf(currentPage),
onTap: (index) => setState(() => currentPage = MainPage.values[index]),
items: const [
BottomNavigationBarItem(
label: 'Home',
icon: Icon(Icons.home),
),
BottomNavigationBarItem(
label: 'Profile',
icon: Icon(Icons.account_circle),
),
],
),
);
}

Widget _pageBuilder(BuildContext context, int index) {
final page = MainPage.values[index];

switch (page) {
case MainPage.home:
return ...
case MainPage.profile:
return ...
}
}
}

/// A widget laying out multiple pages with only one active page being built
/// at a time and on stage. Off stage pages' animations are stopped.
class PageSwitchingView extends StatefulWidget {
const PageSwitchingView({
super.key,
required this.currentPageIndex,
required this.pageCount,
required this.pageBuilder,
});

final int currentPageIndex;
final int pageCount;
final IndexedWidgetBuilder pageBuilder;

@override
State<PageSwitchingView> createState() => _PageSwitchingViewState();
}

class _PageSwitchingViewState extends State<PageSwitchingView> {
final List<bool> shouldBuildPage = <bool>[];

@override
void initState() {
super.initState();
shouldBuildPage.addAll(List<bool>.filled(widget.pageCount, false));
}

@override
void didUpdateWidget(PageSwitchingView oldWidget) {
super.didUpdateWidget(oldWidget);

// Only partially invalidate the pages cache to avoid breaking the current
// behavior. We assume that the only possible change is either:
// - new pages are appended to the page list, or
// - some trailing pages are removed.
// If the above assumption is not true, some pages may lose their state.
final lengthDiff = widget.pageCount - shouldBuildPage.length;
if (lengthDiff > 0) {
shouldBuildPage.addAll(List<bool>.filled(lengthDiff, false));
} else if (lengthDiff < 0) {
shouldBuildPage.removeRange(widget.pageCount, shouldBuildPage.length);
}
}

@override
Widget build(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: List<Widget>.generate(widget.pageCount, (int index) {
final active = index == widget.currentPageIndex;
shouldBuildPage[index] = active || shouldBuildPage[index];

return HeroMode(
enabled: active,
child: Offstage(
offstage: !active,
child: TickerMode(
enabled: active,
child: Builder(
builder: (BuildContext context) {
return shouldBuildPage[index] ? widget.pageBuilder(context, index) : Container();
},
),
),
),
);
}),
);
}
}

Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through