Describe the bug
I have a home screen with a BottomNavigationBar in it. Each page has its own bloc. Since in each page, there may be a VideoPlayerController that is playing a media file, on each page changing The build and dispose methods are needed to be called so I know when start or pause the video.
if some video is playing on screen A and user clicked on screen B on the BottomNavigationBar, I need to pause the video at the dispose of the screen A and play another video on the build of the page B.
Also since there's a ListView in each page, I need to keep the last scroll position and the data in the bloc which are populating the ListView's items so when user backs to a page, everything should look like the moment he left the page.
I tried everything like IndexedStack or CupertinoTabScaffold but since their approach is hiding and showing the widget that is needed, the build and disposed will not be triggered on changing pages. So I came up with the idea keeping the pages in variables and passing it to the widget tree when they are needed. Now the methods that I need, will be called properly and bloc works fine when widget is created for the first time, but when I back to screen, mapEventToState will not be triggered when I dispatch an event to bloc.
class _HomeScreenState extends State<HomeScreen> {
int _currentBottomNavigationIndex = 0;
FeedsBloc _feedsBloc;
final PageStorageBucket bucket = PageStorageBucket();
List<Widget> pages;
ExploreBloc _exploreBloc;
@override
void initState() {
// TODO: implement initState
super.initState();
_feedsBloc = FeedsBloc(
feedsRepository: FeedsRepository(
feedsApiClient: FeedsApiMockClient(
client: Client(),
),
),
);
_exploreBloc = ExploreBloc(
exploreRepository: ExploreRepository(
exploreApiClient: ExploreApiMockClient(
client: Client(),
),
),
);
pages = [
BlocProvider(
child: FeedScreen(
key: PageStorageKey("Feed Screen"),
),
builder: (context) => _feedsBloc,
),
BlocProvider(
child: ExplorePage(
key: PageStorageKey("Explore Screen"),
),
builder: (context) => _exploreBloc,
),
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: PageStorage(
bucket: bucket,
child: pages[_currentBottomNavigationIndex],
),
bottomNavigationBar: BottomNavigationBar(
selectedItemColor: Colors.black,
unselectedItemColor: Colors.grey,
showSelectedLabels: false,
showUnselectedLabels: false,
type: BottomNavigationBarType.fixed,
currentIndex: _currentBottomNavigationIndex,
iconSize: 28,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Home"),
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
title: Text("Search"),
),
BottomNavigationBarItem(
icon: Icon(Icons.add_circle_outline),
title: Text("Add"),
),
BottomNavigationBarItem(
icon: Icon(Icons.favorite_border),
title: Text("Notification"),
),
BottomNavigationBarItem(
icon: Icon(Icons.supervised_user_circle),
title: Text("Profile"),
),
],
onTap: _onChangeTab,
),
);
}
void _onChangeTab(int index) {
setState(() {
_currentBottomNavigationIndex = index;
});
}
@override
void dispose() {
super.dispose();
_exploreBloc.dispose();
_feedsBloc.dispose();
}
}
class FeedScreen extends StatefulWidget {
FeedScreen({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() => FeedScreenState();
}
class FeedScreenState extends State<FeedScreen> {
FeedsBloc _bloc;
@override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
super.didChangeDependencies();
_bloc = BlocProvider.of<FeedsBloc>(context);
}
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_bloc.currentState is FeedsUninitialized) {
_bloc.dispatch(FeedsFetch());
}
});
return Scaffold(
appBar: AppBar(
...
),
body: BlocBuilder<FeedsBloc, FeedsState>(
builder: (context, state) {
if (state is FeedsUninitialized) {
return Center(
child: CircularProgressIndicator(),
);
} else if (state is FeedsLoaded) {
return FeedsList(
posts: state.feedPosts,
hasNextPage: state.hasNextPage,
onLoadMore: (){
if(!_bloc.loading)
_bloc.dispatch(FeedsFetch());
},
);
}
return null;
},
),
);
}
@override
void dispose() {
super.dispose();
//_bloc.dispose();
}
}
md5-1f2da6a1a8e39837469fb81abfdd2079
class ExplorePage extends StatefulWidget {
ExplorePage({Key key}) : super(key: key);
@override
State<StatefulWidget> createState() => ExplorePageState();
}
class ExplorePageState extends State<ExplorePage> {
ExploreBloc _exploreBloc;
@override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
super.didChangeDependencies();
_exploreBloc = BlocProvider.of<ExploreBloc>(context);
}
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_exploreBloc.currentState is ExploreUninitialized) {
_exploreBloc.dispatch(ExploreFetch());
}
});
return Scaffold(
appBar: AppBar(
...
),
body: BlocBuilder<ExploreBloc, ExploreState>(
builder: (context, state) {
print("Explore Bloc State : $state");
if (state is ExploreLoaded) {
return RefreshIndicator(
onRefresh: () async {
return _exploreBloc.dispatch(ExploreFetch(refresh: true));
},
child: ExploreGridView(
data: state.exploreEntries,
hasNextPage: state.hasNextPage,
onLoadMore: _onLoadMore,
),
);
} else if (state is ExploreUninitialized) {
return Center(
child: CircularProgressIndicator(),
);
}
return null;
},
),
);
}
_onLoadMore() {
if (!_exploreBloc.loading) _exploreBloc.dispatch(ExploreFetch());
}
@override
void dispose() {
super.dispose();
//_exploreBloc.dispose();
}
}
md5-b01b2746edf72b7747ff71a6e648ab75
class SimpleBlocDelegate extends BlocDelegate {
@override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print(transition);
}
@override
void onEvent(Bloc bloc, Object event) {
super.onEvent(bloc, event);
print(event);
}
}
Also I log every event which is sent to the bloc at the onEvent method of bloc itself, and the method is called every time... It means I still have the connection with the bloc but it's deciding to not pass the event to mapEventToState
Hi @k2evil 馃憢
Thanks for opening an issue!
I鈥檓 guessing the problem is the bloc is already disposed. Once a bloc is disposed you can no longer trigger state changes. You shouldn鈥檛 both use BlocProvider with builder and manually call dispose. Instead you should use BlocProvider.value if you want to manage the bloc yourself. You should also add an onError override to your BlocDelegate in case an exception is thrown.
Hope that helps!
I used BlocProvider.value and everythings seem to work fine... Thank you for the answer
pages = [
BlocProvider<FeedsBloc>.value(
value: _feedsBloc,
child: FeedScreen(
key: PageStorageKey("Feed Screen"),
),
),
BlocProvider<ExploreBloc>.value(
value: _exploreBloc,
child: ExplorePage(
key: PageStorageKey("Explore Screen"),
),
),
];
I should have read the documentation :smile:
Most helpful comment
I used
BlocProvider.valueand everythings seem to work fine... Thank you for the answer