Kris
Kris

Reputation: 3361

How to share the bloc between contexts

I'm trying to access the bloc instance created near the root of my application after navigating to a new context with showDialog(). However, if I try getting the bloc like I usually do, by getting it from the context like _thisBlocInstance = BlocProvider.of<ThisBlocType>(context), I get an error that indicates there is no bloc provided in this context.

I assume this is because the showDialog() builder method assigns a new context to the widgets in the dialog that don't know about the Bloc I am trying to find, which was instantiated as soon as the user logs in:

  @override
Widget build(BuildContext context) {
  _authBloc = BlocProvider.of<AuthBloc>(context);
  _accountBloc = AccountBloc(authBloc: _authBloc);

  return BlocProvider(
    bloc: _accountBloc,

 ....

There is a button in the corner that displays a dialog:

@override
Widget build(BuildContext context) {
  return Align(
    alignment: Alignment.bottomRight,
    child: Padding(
      padding: const EdgeInsets.all(18.0),
      child: FloatingActionButton(
        onPressed: () => showDialog(
          context: context,
          builder: (newContext) => EventDialog(),
        ).then(
          (val) => print(val)
        ),
        child: Icon(Icons.add),
      ),
    ),
  );
}

And in the EventDialog, I try to find the bloc with the context again:

@override
void build(BuildContext context) {

  _accountBloc = BlocProvider.of<AccountBloc>(context);
  _userMenuItems = _accountBloc.usersInAccount
    .map((user) => DropdownMenuItem(
          child: Text(user.userName),
          value: user.userId,
        ))
    .toList();
}

And this fails, with an error 'the getter bloc was called on null', or, there is no bloc of that type attached to this context.

Is there some way to access the bloc just from the context after using showDialog(), or otherwise navigating to a new context?

This is the bloc provider class:

import 'package:flutter/material.dart';

//This class is a generic bloc provider from https://www.didierboelens.com/2018/08/reactive-programming---streams---bloc/
//it allows easy access to the blocs by ancestor widgets and handles calling their dispose method

class BlocProvider<T extends BlocBase> extends StatefulWidget {
  BlocProvider({
    Key key,
    @required this.child,
    @required this.bloc,
  }): super(key: key);

  final T bloc;
  final Widget child;

  @override
  _BlocProviderState<T> createState() => _BlocProviderState<T>();

  static T of<T extends BlocBase>(BuildContext context){
    final type = _typeOf<BlocProvider<T>>();
    BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
    return provider.bloc;
  }

  static Type _typeOf<T>() => T;
}

class _BlocProviderState<T> extends State<BlocProvider<BlocBase>>{
  @override
  void dispose(){
    widget.bloc.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context){
    return widget.child;
  }
}

abstract class BlocBase {
  void dispose();
}

Upvotes: 3

Views: 5895

Answers (2)

John King
John King

Reputation: 21

The last answer is okay but it can be simplified, that is just transfering Bloc to its child widget.

@override
Widget build(BuildContext context) {
  return Align(
    alignment: Alignment.bottomRight,
    child: Padding(
      padding: const EdgeInsets.all(18.0),
      child: FloatingActionButton(
        onPressed: () => showDialog(
          context: context,
          builder: (newContext) => EventDialog((
          accountBloc: BlocProvider.of<AccountBloc>(context),
        ),
        ).then(
          (val) => print(val)
        ),
        child: Icon(Icons.add),
      ),
    ),
  );
}


class NewEventDialog extends StatelessWidget {
  final AccountBloc accountBloc;

  NewEventDialog({this.accountBloc}) : assert(accountBloc != null);

@override
void build(BuildContext context) {

  _accountBloc = accountBloc;
  _userMenuItems = _accountBloc.usersInAccount
    .map((user) => DropdownMenuItem(
          child: Text(user.userName),
          value: user.userId,
        ))
    .toList();
}

So far I find this problem occurs when going to widget via page routing. We can transfer the Bloc widget to widget to avoid this problem.

Upvotes: 2

Kris
Kris

Reputation: 3361

The best way I found to access the original bloc in a new context is by passing a reference to it to a new bloc that manages the logic of the new context. In order to keep the code modular, each bloc shouldn't control more than one page worth of logic, or one thing (e.g. log-in state of the user). So, when I create a new screen/context with showDialog(), I should also have a new bloc that deals with the logic in that screen. If I need a reference to the original bloc, I can pass it to the constructor of the new bloc via the dialog widget's constructor, so any information in the original bloc can still be accessed by the new bloc/context:

    child: FloatingActionButton(
      onPressed: () => showDialog(
        context: context,
        builder: (newContext) => NewEventDialog(
          accountBloc: BlocProvider.of<AccountBloc>(context),
        ),
      ).then((event) => eventsBloc.addEvent(event)),

...

class NewEventDialog extends StatelessWidget {
  final AccountBloc accountBloc;
  NewEventBloc _newEventBloc;

  NewEventDialog({this.accountBloc}) : assert(accountBloc != null);

  @override
  Widget build(BuildContext context) {
    _newEventBloc = NewEventBloc(accountBloc: accountBloc);

    return BlocProvider(
      bloc: _newEventBloc,
...

Upvotes: 5

Related Questions