Bloc: Multi instance of a block doesn't work fine

Created on 22 Dec 2019  路  2Comments  路  Source: felangel/bloc

Hi I have a Scaffold screen with a TabView and an AppBar.
I want change AppBar title when TabView index changed.

there is an AppFramebloc and there are to kind of events and states one for changing tabs and the others for AppBar title.
I used two BlocProvider for both the TabView and the AppBar separately to pass them AppFramebloc.

so everything works fine when app initialized for the first time. but when I navigate to a new screen and come back to the first screen the AppBar bloc works only and have interact with AppFramebloc and TabView bloc doesn't get any state.

here is the line that fails to work when come back from new screen (poping)
https://github.com/navidshad/foodbar_app/blob/master/lib/screens/screen_appFrame.dart#L43

here are widgets code:

main screen

class AppFrame extends StatefulWidget {
  @override
  _AppFrameState createState() => _AppFrameState();
}

class _AppFrameState extends State<AppFrame>
    with SingleTickerProviderStateMixin {
  AppFrameBloc bloc;
  FrameTabType currentTab;
  TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(vsync: this, length: 2);
    _tabController.addListener(onTabViewChanged);
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    bloc = BlocProvider.of<AppFrameBloc>(context);
  }

  @override
  Widget build(BuildContext context) {
    print('build appframe');

    return Scaffold(
      appBar: CustomAppBar(),
      body: BlocBuilder(
        bloc: bloc,
        condition: (old, state) {
          return AppFrameBloc.blocCondition(state, [InitialAppFrameState, ShowingTabAppFrameState]);
        },
        builder: (stateContext, AppFrameState state) {
          currentTab = state.tabType;

          if (state is ShowingTabAppFrameState)
            _tabController.index = getTypeIndex(state.tabType);

          return TabBarView(
            controller: _tabController,
            physics: NeverScrollableScrollPhysics(),
            children: <Widget>[
              MenuTab(),
              CartTab(),
            ],
          );
        },
      ),
    );
  }

  int getTypeIndex(FrameTabType type) {
    if (type == FrameTabType.MENU)
      return 0;
    else
      return 1;
  }

  void onTabViewChanged() {
    //FrameTabType type = AppFrameBloc.switchType(currentTab);
    //sbloc.add(ChangeAppBarAppFrameEvent(type));
  }

  @override
  void dispose() {
    bloc.close();
    super.dispose();
  }
}

custom AppBar

class CustomAppBar extends StatefulWidget with PreferredSizeWidget {
  @override
  _CustomAppBarState createState() => _CustomAppBarState();

  @override
  Size get preferredSize => new Size.fromHeight(55);
}

class _CustomAppBarState extends State<CustomAppBar> {
  FrameTabType currentTab;
  AppFrameBloc bloc;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    bloc = BlocProvider.of<AppFrameBloc>(context);
  }

  @override
  Widget build(BuildContext context) {
    return BlocBuilder(
      bloc: bloc,
      condition: (old, state) {
        return AppFrameBloc.blocCondition(
            state, [InitialAppFrameState, ChangingAppBarAppFrameState]);
      },
      builder: (stateContext, AppFrameState state) {
        return buildAppBar(state.title, state.tabType);
      },
    );
  }

  AppBar buildAppBar(String title, FrameTabType type) {
    Widget actionBtn;
    currentTab = type;

    // setup title and action button according to tabType
    if (type == FrameTabType.MENU) {
      actionBtn = FlatButton(
        child: Icon(FoodBarIcons.shopping_bag),
        onPressed: onAppBarActionButtonPressed,
      );
    } else if (type == FrameTabType.CART) {
      actionBtn = FlatButton(
        child: Icon(FoodBarIcons.spoon_and_fork),
        onPressed: onAppBarActionButtonPressed,
      );
    }

    // build appbar
    return AppBar(
      title: Text(title),
      actions: <Widget>[actionBtn],
      elevation: 4,
    );
  }

  void onAppBarActionButtonPressed() {
    FrameTabType type = AppFrameBloc.switchType(currentTab);
    bloc.add(ChangeTabAppFrameEvent(type));
  }

  @override
  void dispose() {
    bloc.close();
    super.dispose();
  }
}
question

All 2 comments

@navidshad this is because you are closing the bloc when it is still being used. When providing a bloc via BlocProvider you do not need to close it manually as it is closed by BlocProvider automatically.

If you refactor your screen_appFrame.dart and all other screens like so:

import 'package:Food_Bar/screens/screens.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart';

import 'package:Food_Bar/bloc/bloc.dart';
import 'package:Food_Bar/settings/settings.dart';
import 'package:Food_Bar/widgets/widgets.dart';
import 'package:Food_Bar/screens/tab_menu.dart';

class AppFrame extends StatefulWidget {
  @override
  _AppFrameState createState() => _AppFrameState();
}

class _AppFrameState extends State<AppFrame>
    with SingleTickerProviderStateMixin {
  FrameTabType currentTab;
  TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(vsync: this, length: 2);
    _tabController.addListener(onTabViewChanged);
  }

  @override
  Widget build(BuildContext context) {
    print('build appframe');

    return Scaffold(
      appBar: CustomAppBar(),
      body: BlocBuilder<AppFrameBloc, AppFrameState>(
        condition: (old, state) {
          return AppFrameBloc.blocCondition(
              state, [InitialAppFrameState, ShowingTabAppFrameState]);
        },
        builder: (stateContext, AppFrameState state) {
          currentTab = state.tabType;

          if (state is ShowingTabAppFrameState)
            _tabController.index = getTypeIndex(state.tabType);

          return TabBarView(
            controller: _tabController,
            physics: NeverScrollableScrollPhysics(),
            children: <Widget>[
              MenuTab(),
              CartTab(),
            ],
          );
        },
      ),
    );
  }

  int getTypeIndex(FrameTabType type) {
    if (type == FrameTabType.MENU)
      return 0;
    else
      return 1;
  }

  void onTabViewChanged() {
    //FrameTabType type = AppFrameBloc.switchType(currentTab);
    //bloc.add(ChangeAppBarAppFrameEvent(type));
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }
}

then everything should work because the bloc will not be closed prematurely.

Hope that helps! Closing for now but feel free to comment with additional questions and I'm happy to continue the conversation 馃憤

Thanks for response, I was in a hurry and I wrote my own BLoC module. But I will use BLoC package in production.

Was this page helpful?
0 / 5 - 0 ratings