Bloc: `mapEventToState` is not being called

Created on 12 Oct 2019  路  4Comments  路  Source: felangel/bloc

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);
  }
}
question

Most helpful comment

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"),
        ),
      ),
    ];

All 4 comments

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:

Was this page helpful?
0 / 5 - 0 ratings

Related issues

hivesey picture hivesey  路  3Comments

MahdiPishguy picture MahdiPishguy  路  3Comments

nhwilly picture nhwilly  路  3Comments

wheel1992 picture wheel1992  路  3Comments

rsnider19 picture rsnider19  路  3Comments