Reputation: 997
Context: I have an App that has a PageView
to navigate between five screens using BottomNavigationBar
, One page is making use of the Bloc Pattern (almost all of the pages will use Bloc in future releases) these Blocs are fetching data from backend service in filling the UI with the fetched data.
Problem: When navigating between pages at any level of the widget tree. the Bloc widget 'reset' and refetch the data from the repository.
As shown in the picture below:
Widget tree as follows:
Tried-Solutions: I added the AutomaticKeepAliveClientMixin
to the page class and to other pages but it didn't do the job.
Here is the code for the build method for the page shown in the above screenshot
@override
Widget build(BuildContext context) {
super.build(context);
return DefaultTabController(
length: 4,
child: Scaffold(
backgroundColor: Colors.white,
appBar: _builAppBarWithTabs(),
body: Center(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 1200),
child: ListView(
scrollDirection: Axis.vertical,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: 150),
child: Container(
height: 800,
child: CustomScrollView(
scrollDirection: Axis.vertical,
slivers: <Widget>[
SliverToBoxAdapter(
child: MasonryGrid(
column: getResponsiveColumnNumber(context, 1, 2, 6),
children: <Widget>[
// First Bloc
BlocProvider(
create: (context) {
BrandBloc(repository: _brandRepository);
},
child: Container(
width: 200,
alignment: Alignment.center,
height: 80,
child: BrandScreen(
brandBloc: context.bloc(),
),
),
),
CategoryScreen(
// Second Bloc
categoryBloc: CategoryBloc(
repository: context.repository(),
),
),
Container(
width: 200,
alignment: Alignment.center,
height: 330,
child: _buildFeaturedItemsList(),
),
Placeholder(
strokeWidth: 0,
color: Colors.white,
)
],
),
),
],
),
),
),
],
),
),
),
),
);
}
here is the code for class timeline.dart
here is the code for class home.dart
that contains the BottomNavigationBar
class
Question: How to stop the blocs from fetching the data each time the widget rebuilds?
Upvotes: 8
Views: 1433
Reputation: 5955
I was a stuck on a very similar problem here. In my case, I found the BlocProviders or Cubits needed to be managed outside the widget tree, and then using BlocProvider.value
to insert them into it.
List<Widget> _pages = List.empty(growable: true);
List<Cubit> _cubits = List.empty(growable: true);
@override
void dispose() {
_cubits.forEach((cubit) {
cubit.close();
});
super.dispose();
}
@override
void initState() {
super.initState();
addPage(DateTime.now());
}
void addPage(DateTime day) {
MyCubit cubit = MyCubit(day: day)..load();
_cubits.add(cubit);
pages.add(BlocProvider<MyCubit>.value(
value: cubit,
child: MyPage(),
));
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
controller: _controller,
children: _pages,
));
}
All credit and gratitude to this github issue.
Upvotes: 1
Reputation: 997
I finally got my head around the problem
As it turned out, it was not about the bloc
as much it was a problem related to PageView
.
I wrapped the PageView
with PageStorage
widget as follows:
final PageStorageBucket bucket = PageStorageBucket(); //PageStorageBucket
static List<Widget> pages = [
Timeline(pageController: pageController, key: PageStorageKey('timeline')),
Search(key: PageStorageKey('search')),
LikeScreen(key: PageStorageKey('like')),
CartScreen(key: PageStorageKey('cart')),
storageService.getFromDisk('api_token') != null
? ProfileScreen(key: PageStorageKey('profile'))
: AuthChooserScreen(key: PageStorageKey('auth')),
];
@override
Widget build(BuildContext context) {
super.build(context);
return SafeArea(
top: true,
bottom: true,
child: Scaffold(
backgroundColor: Colors.white,
key: _scaffoldKey,
//Wrapped PageView with PageStorage
body: PageStorage(
bucket: bucket,
child: PageView(
children: pages,
controller: pageController,
onPageChanged: onPageChanged,
physics: NeverScrollableScrollPhysics(),
),
),
bottomNavigationBar: _buildBottomNavigationBar(),
),
);
}
Every page is assigned a PageStorageKey which is used to save and restore values that can outlive the widget
Thanks to this great medium article which explains it perfectly: Keeping State with the Bottom Navigation Bar in Flutter
Upvotes: 8
Reputation: 309
You are using BlocProvider widget in a wrong way Instead, you should extract the BlocProvider to the upper level which is not rebuilt while navigating between pages "maby to MaterialApp widget if you are using this bloc across your app". Then use BlocBuilder or BlocConsumer to provide existing Bloc instance to the child widget and auto rebuild when bloc state change. BlocBuilder official doc: https://bloclibrary.dev/#/flutterbloccoreconcepts?id=blocbuilder
Upvotes: 4