Bloc: BlocProvider.of method uses listen:false

Created on 9 Sep 2020  路  20Comments  路  Source: felangel/bloc

Hi everyone,

Static method BlocProvider.of uses a Provider.of method to get the bloc instance but with parameter listen:false.
Do you have some restrictions to observe the bloc changes, otherwise would you explain why it's hard coded and behaves different than Provider by default?

Best regards,
Vladimir

question

Most helpful comment

I've created a basic example of an approach with flutter_bloc and freezed since you seem to like unions. I tried to follow your initial approach as much as possible to keep you on familiar grounds. You can find it in this PR.

Since I'm using a different AuthBloc the button is not synchronized with your bloc but that's just a matter of adding an event to your bloc as well.

listen:false means that flutter_bloc library has a restriction, and it doesn't support bloc rebuilding. Would be good to understand is it correct?

Bloc has no restrictions whatsoever, but it doesn't make sense to update the instance of a bloc since that would mean you'd lose that bloc's state. As already mentioned the bloc drives its changes internally through a Stream<State> not by updating it's instance.

All 20 comments

Hi @vysarafanov 馃憢

Bloc itself is a Stream<State> which is used to observe state changes.
listen:false is hard coded since an instance of a bloc will never change once instantiated, since it would go against the whole idea of maintaining state. If a certain bloc instance would be replaced by other one then you'd lose the state.

Another benefit of grabbing blocs with listen:false is that you can access them inside initState lifecycle hook of a StatefulWidget.

Hope that sheds some light on your concerns 馃憤

Hi @RollyPeres

Thank you for the reply.

I understand that Bloc will lose the state if the instance will be replaced. That's the behaviour I want to get.
Let me explain the idea, maybe it's wrong.

Application supports a several users which can be changed during the app lifecycle. There are service to fetch user details, widget to show them and bloc to connect service and widget.

While the user doesn't change - I'm user the same instance of the Bloc which fetch the user data through the service.
When the user was changed - I'm waiting that the Bloc will be recreated with the new instance and start to fetching the data of the new user.

The following code works good, but I don't understand why the BlocProvider.of method doesn't have a configurable listen parameter.

class User {
  final String id;
  ...
}

class UserService {
  final String userId;

  Future<String> fetchUserName() => ....
}

class UserBloc ... {
  final UserService service;
  ...
}

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MultiProvider(
        providers: [
          StreamProvider<User>(
            create: (BuildContext context) => stream with a current user, for example FirebaseAuth.onAuthStateChanged
          ),
          ProxyProvider<User, UserService>(
            update: (_, User user, __) =>
                UserService(user.id),
          ),],
          child: ProxyProvider<UserService, UserBloc>(
            update: (_, service, __) => UserBloc(service),
            child: BlocBuilder<UserBloc>(
              cubit: Provider.of(context),
              builder: (context, state) => ...,
            ),
          ),
        );
}

Best regards,
Vladimir

Normally you wouldn't need to use a StreamProvider<User> to keep tabs on your current user.
You would normally use an AuthBloc and you'd store the User on it's Authenticated state.
You never want to replace this bloc instance but instead yield a new Authenticated state with a new User if that's something you need.

You'd expose a stream in your repository from FirebaseAuth.onAuthStateChanged that you'd later on subscribe to in your AuthBloc. Whenever this stream emits you'd add an event to your bloc that will result in a new state: Unauthenticated if it emits null or Authenticated if you're getting a User. This is a general approach which should be tackled based on custom requirements.

yep, StreamProvider used just to simplify the example.
anyway result of StreamProvider/AuthBloc is a User.

Let's look futher.

  • Service depends on user id and expose a method to fetch user's data (name for example)
  • bloc which depends on service
  • widget uses bloc to show the user data (name for example).

As I see it:
User is changed => new service created => new bloc created (lose the state, fetches data from scratch) => new user data is shown on the screen

Losing previous state is correct at this place - new user doesn't need to see and operate the data of the previous in any cases.

There are a Provider and BlocProvider clases. Both has a static of method. Provider listen changes by default, BlocProvider - not and doesn't have an option to do that.

Seems like we have a 2 different behaviors in a similar libraries.

BlocBuilder uses BlocProvider.of method under the hood, means it's not observing the changes of the bloc. By my opinion it breaks the logic of reaction to changes, on which Flutter is based.

Hi @vysarafanov 馃憢
Thanks for opening an issue!

Can you please describe what behavior you're trying to achieve that is not possible with the current implementation? As @RollyPeres mentioned, setting listen: false was done because the bloc's state stream is the thing that is changing and not the bloc itself.

BlocBuilder uses BlocProvider.of method under the hood, means it's not observing the changes of the bloc. By my opinion it breaks the logic of reaction to changes, on which Flutter is based.

I'm not sure what you mean by this. BlocBuilder does in fact observe changes in the bloc but for general developer usage, performing a bloc lookup shouldn't introduce a dependency on that bloc for the widget because the widget should be reacting to state changes in the bloc rather than changes in the bloc itself. Again it would be very helpful if you could provide a concrete example of what you are trying to achieve and the problem you are facing, thanks! 馃憤

The whole misunderstanding comes from the fact that the bloc itself drives changes through a Stream<State> and the bloc instance doesn't need to be updated in order for changes to happen.

Hi @felangel

Thank you for your reply.

As @RollyPeres mentioned, setting listen: false was done because the bloc's state stream is the thing that is changing and not the bloc itself.

Yep, I totally agree with you and @RollyPeres. But seems it's not covered my case)

Let's look at the multi user application, for example some e-shop.
Each user has they own cart. I'm using the Cart bloc to put/delete products. But when user changed in the app I need to create the new Bloc to operate with a data of the different user than it was.

If you will look at the my code above you will see that I'm using the ProxyProvider to create the new instance of bloc, and set the bloc explicitly for BlocBuilder, because it doesn't listen the bloc instance changes.

Looks like it's possible to implement what I want to achive. But the question is: why the BlocProvider/BlocBuilder has this restriction and behaviour is not configured?

Best regards,
Vladimir

@vysarafanov is there any reason why you prefer the multi ProxyProvider approach as opposed to having an AuthenticationBloc as @RollyPeres mentioned? I would highly recommend following the structure in the Flutter Firebase Login Example.

Why the BlocProvider/BlocBuilder has this restriction and behaviour is not configured?

For simplicity and to facilitate a single way to react to state changes.

I'll back to you with a more detailed example.

You can handle as many users as you want with a single instance of your auth bloc. All you need to do is add proper events and react to them. There's never gonna be 2 or more users simultaneously logged in, so why would you ever need more than 1 bloc ?

As for cart bloc you need to ensure it's properly persisted and hydrated based on your current user.

Hello again,

I uploaded an example, take a look please https://github.com/vysarafanov/bloc_vs_proxy_provider

There are some design ideas which I want to see in the application.

  1. I would like to have some logged in user to be always in the application: anonymous or the registered user, it doesn't matter.
    That means when registered user press the logout button it goes to be the new anon user.
  2. when user appears in the application, I would like to provider the User entity to the child widgets to easy consume it. Application has some services which are user dependend (in example it is a NameService).
  3. When user is changed I don't want to redirect the new user to the another page. Only the user depended widgets should be updated.

In the example you can find two solutions. AuthBloc based requires the rebuild whole page after user changed. Solution based on combination of StreamBuilder + ProxyProvider covered my requirements.

Do you have an idea how to do that another way?

And about the topic questions.
listen:false means that flutter_bloc library has a restriction, and it doesn't support bloc rebuilding. Would be good to understand is it correct?

Best regards,
Vladimir

I've created a basic example of an approach with flutter_bloc and freezed since you seem to like unions. I tried to follow your initial approach as much as possible to keep you on familiar grounds. You can find it in this PR.

Since I'm using a different AuthBloc the button is not synchronized with your bloc but that's just a matter of adding an event to your bloc as well.

listen:false means that flutter_bloc library has a restriction, and it doesn't support bloc rebuilding. Would be good to understand is it correct?

Bloc has no restrictions whatsoever, but it doesn't make sense to update the instance of a bloc since that would mean you'd lose that bloc's state. As already mentioned the bloc drives its changes internally through a Stream<State> not by updating it's instance.

@RollyPeres thank you very much.
I'll take a look at you PR and will back.

@vysarafanov any updates on this or do you feel this issue can be closed?

Sorry for the delay. I will write the comment later today.

@RollyPeres again, thank you very much for your solution.

With your solution the whole widget tree is every time rebuild when the user is changed. It's ok, except the NameView inside NameWidgetFlutterBlocFreezed. Because the NameBloc doesn't change, NameView doesn't rebuild. That's interesting point.
NameBloc implicitly depends on User (through NameService), but it doesn't react on user changes. To fix this problem you used BlocListener, which add the new one - additional dependency of NameView from AuthBloc (the complexity on this solution is increased).

summary:
if bloc depends from some entity, and this entity changes - bloc doesn't react on this changes automatically.

I didn't bother to optimize the tree rebuild since it's out of the scope of this talk.

I think you're missing the crucial difference between your solutions and my solution.
With your solutions:

  • the UI interacts with the service and not only that but it's also re-initializing it. Only bloc should interact with the service.
  • the service should not be re-instantiated just because the user changed, it's enough to simply call some method which will grab the new user's data for example.

With my solution:

  • there's a predictable flow of data through service -> bloc -> UI.
  • there's a single instance of the service throughout the whole lifetime of the app.
  • the freezed based unions are a lot less verbose.

I think the most important aspect is to properly layer out the app and not to try and write as simple code as possible at the cost of a poor architecture.

if bloc depends from some entity, and this entity changes - bloc doesn't react on this changes automatically.

If the entity comes through a stream then it can certainly react to it .Also, you can always inject a bloc inside other bloc and listen to changes. e.g.: you could inject auth bloc into some other bloc and listen to changes on current user.

@vysarafanov do you feel @RollyPeres's answer was sufficient?

Closing for now but feel free to comment with any additional questions and I鈥檓 happy to continue the conversation 馃憤

Was this page helpful?
0 / 5 - 0 ratings

Related issues

1AlexFix1 picture 1AlexFix1  路  3Comments

MahdiPishguy picture MahdiPishguy  路  3Comments

wheel1992 picture wheel1992  路  3Comments

timtraversy picture timtraversy  路  3Comments

hivesey picture hivesey  路  3Comments