Context: Here's page that has a TabView
to navigate between tabs all of these tabs are making use of flutter_bloc
(version 6.0.1).
Problem: When swiping to to any tab, the state is not being preserved and the entire widget tree is being rebuilt as shown in the gif below
Here is the build()
method:
@override
Widget build(BuildContext context) {
super.build(context);
return DefaultTabController(
initialIndex: 0,
length: 3,
child: Scaffold(
backgroundColor: Colors.white,
appBar: _buildAppBarWithTabs(),
body: TabBarView(
children: <Widget>[
defaultViewforCategory(1), //Women
defaultViewforCategory(3), //Men
defaultViewforCategory(2), //Kids
],
),
),
);
}
Here is the implementation of the function defaultViewforCategory()
Widget defaultViewforCategory(int mainCategoryId) {
return PageStorage(
bucket: bucket,
key: PageStorageKey(mainCategoryId),
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: RefreshIndicator(
onRefresh: () => refreshTimeline(),
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: 90,
child: BrandScreen(
brandBloc: context.bloc(),
),
),
),
CategoryScreen(
// Second Bloc
categoryBloc: CategoryBloc(
mainCategoryId: mainCategoryId,
repository: _categoryRepository),
),
// -------------- Featured Items--------------------------
Container(
width: 200,
alignment: Alignment.center,
height: 350,
child: _buildFeaturedItemsList(mainCategoryId),
),
Placeholder(strokeWidth: 0, color: Colors.white)
],
),
),
],
),
),
),
),
],
),
),
);
}
Tried-solutions:
1 - I tried the AutomaticKeepAliveClientMixin
but it turned out this mixin
preserve the state of a page when switching to a another page using BottomNavigationBar
.
2 - PageStorage
didn't solve the problem.
Question: How to stop the TabView
from being rebuilt each time the user swipes to another tab?
As you stated, one of the problems is that the TabBarView
is rebuild every time the tab is presented. For that issue is an open topic here. Because of that, a fresh new Bloc instance is created every time when screen changes.
NOTE Because the CategoryBloc
is not passed using BlocProvider
you should dispose the bloc manually.
A simple solution here is to move the BlocProvider
up in the hierarchy outside of the TabBarView
- by example, first component in the build method.
NOTE As performance wise this is okay because the BLOCs are lazily initialized when a bloc is requested.
Now the more delicate problem is the the way the CategoryBloc
is created (because has a dynamic constructor). Here you can have two solution:
Either you modify the CategoryBloc
to be shareable by all categories screen - here I can not help you too much because I don't have the code of it. The idea is to send the mainCategoryId
through events
and emits
a new state with the results. In this case you should forward the mainCategoryId
into the state
and, on BlocBuilder
, use buildWhen
parameter to build only when the mainCategoryId
matches the CategoryScreen id (which can be passed when the screen is created). AND to not forget also to provide the CategoryBloc using BlocProvider
outside of the TabBarView
child.
OR move the CategoryBloc creation outside of the TabBarView and cache them for further access. I have created an example below to emphasize this.
// ...
///
/// Categories blocs cache.
///
Map<int, CategoryBloc> _categoriesBlocs;
///
/// Creates UNIQUE instances of CategoryBloc by id.
///
CategoryBloc getCategoryBlocById(int id) {
// If you don't already have a bloc for that particular id, create a new one
// and cache it (by saving it in the Map)
this._categoriesBlocs.putIfAbsent(
id,
() => CategoryBloc(
mainCategoryId: id,
repository: _categoryRepository,
));
// Return the cached category bloc
return this._categoriesBlocs[id];
}
///
/// This is very important. Because we manually create the BLOCs we have
/// to manually dispose them
///
@override
void dispose() {
for (var bloc in this._categoriesBlocs) {
bloc.displose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
super.build(context);
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => BrandBloc(repository: _brandRepository),
)
],
child: DefaultTabController(
initialIndex: 0,
length: 3,
child: Scaffold(
backgroundColor: Colors.white,
appBar: _buildAppBarWithTabs(),
body: TabBarView(
children: <Widget>[
defaultViewforCategory(1), //Women
defaultViewforCategory(3), //Men
defaultViewforCategory(2), //Kids
],
),
),
),
);
}
// ...
CategoryScreen(
// Second Bloc
// Now, here you will get the same BLOC instance every time
categoryBloc: getCategoryBlocById(mainCategoryId),
),
// ...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With