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
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.
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.
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:
With my solution:
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 馃憤
Most helpful comment
I've created a basic example of an approach with
flutter_blocandfreezedsince 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
AuthBlocthe button is not synchronized with your bloc but that's just a matter of adding an event to your bloc as well.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.