Bloc: Awkwardness around 2 screens listening to the same BLoC

Created on 15 Apr 2019  路  4Comments  路  Source: felangel/bloc

Is your feature request related to a problem? Please describe.
If two screens both listen to the same bloc (maybe they're multiple steps in an onboarding wizard that are identical but for their actual text fields), both screens continue to fire and respond to actions, even after navigating to the second screen.

See distilled code at bottom.

Describe the solution you'd like
It makes sense that both are still active because they're both in the navigation stack, but it would be nice for the first screen to know whether or not it is the active navigation frame.

Describe alternatives you've considered
For now, I am getting around this by subclassing the OnboardingBloc for each step of the wizard and including an instance of each subclass in the BlocProviderTree. This all makes me extremely suspicious that I'm not thinking about screen deactivation correctly.

Additional context

class WorkflowPage extends StatefulWidget {

  OnboardingWizard onboardingWizard;
  WorkflowPage(this.onboardingWizard);

  @override
  State createState() => WorkflowPageState();
}

class WorkflowPageState extends State<StatefulWidget> {

  OnboardingBloc getBloc(BuildContext context) {
    // Home of the smelly workaround
    if (widget.onboardingWizard.values == 'values') {
      return BlocProvider.of<OnboardingBloc>(context);
    } else if (widget.onboardingWizard.values == 'other values') {
      return BlocProvider.of<SubclassedOnboardingBloc>(context);
    }
  }

  @override
  Widget build(BuildContext context) {
    // Without the workaround, this print twice (once per page) upon navigating to `/onboarding/2`
    print('Building $widget'); 

    // In the workaround, this line has to know which OnboardingBloc to get
    // based on which Screen this is
    final OnboardingBloc bloc = getBloc(context);
    return Scaffold(
      body: BlocBuilder(
        bloc: bloc,
        builder: (BuildContext context, OnboardingState state) {
          // `state` checks and such
          return Text('$widget.onboardingWizard stuff goes here');
        }
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  final OnboardingBloc bloc = OnboardingBloc();
  final SubclassedOnboardingBloc bloc2 = SubclassedOnboardingBloc();  // My current workaround is to add something like this

  @override
  Widget build(BuildContext context) {
    return BlocProviderTree(
      blocProviders: [
        BlocProvider<OnboardingBloc>(bloc: bloc),
        BlocProvider<SubclassedOnboardingBloc>(bloc: bloc2),  // My current workaround obviously also includes adding this
      ]
      child: MaterialApp(
        routes: {
          '/onboarding/1': (context) {
            return WorkflowPage(OnboardingWizard('values'))
          },
          '/onboarding/2': (context) {
            return WorkflowPage(OnboardingWizard('other values'))
          },
        },
      ),
    );
  }

void main() {
  runApp(MyApp());
}
question

All 4 comments

Hi @craiglabenz thanks for opening this issue!

I would recommend having two different bloc instances and providing each instance to the workflow pages separately rather than using a global bloc provider.

class MyApp extends StatelessWidget {
  final OnboardingBloc blocA = OnboardingBloc();
  final OnboardingBloc blocB = OnboardingBloc();

  @override
  Widget build(BuildContext context) {
     return MaterialApp(
        routes: {
          '/onboarding/1': (context) {
            return WorkflowPage(OnboardingWizard('values'), blocA)
          },
          '/onboarding/2': (context) {
            return WorkflowPage(OnboardingWizard('other values'), blocB)
          },
        },
      ),
    );
  }

Does that help or do you want to have the same bloc state for both pages? I would imagine you'd want each page to have it's own state so that when you navigate, you have a clean state on the new page. If that's the case, then I would just have two instances of the same bloc and inject them individually into the components that need them.

I hope that I understood your question properly. Let me know if that helps 馃憤

In my case I retrieve a json object list at the root page and I am navigating to a 'details' type screen which should update the specific object's details. When I make changes to the details of an object, both the initial screen's BlocBuilder and the second screen's BlocBuilder rebuild (global bloc provider). I am too wondering if there is a way to only fire a rebuild if the current screen's bloc builder has focus. Both screens should be sharing the same data objects so the rebuild makes sense, but since the page isn't exposed I fear this may eventually cause bad performance. Would the solution be to instead have separate bloc instances that read from the same cached data? If so then how would the first page's bloc know its data changed as a result from the second bloc's events.

Sorry if that doesn't make good sense, I actually have a more complicated use case of a bunch of nested screens, each accessing a deeper level of the object's json but I just wanted to articulate the main idea. Thanks! @felangel

@hawkinsjb1 I personally would not worry about optimizing for rebuilds unless you're running into performance issues. Flutter is designed to rebuild widgets many times and if both pages depend on the same bloc state stream then I'd say don't worry (unless, like I said, you're seeing performance issues).

Many external factors can trigger a new widget build, such as:

  • Route pop/push, for in/out animations
  • Screen resize, usually due to keyboard appearance or orientation change
  • Parent widget recreated its child
  • An InheritedWidget the widget depends on (Class.of(context) pattern) change

Hope that helps! 馃憤

Closing this for now but feel free to comment with additional concerns/questions and I'm happy to continue the conversation 馃憤

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Reidond picture Reidond  路  3Comments

nhwilly picture nhwilly  路  3Comments

krusek picture krusek  路  3Comments

RobPFarley picture RobPFarley  路  3Comments

nerder picture nerder  路  3Comments