Updating a Provider from a StreamProvider - flutter

I have a StreamProvider connected to Firebase, and another Provider/ChangeNotifier with functions to process the data from StreamProvider in different ways depending on app state. For this reason, whenever new data comes from the StreamProvider, I would like to immediately send it to be stored in Provider/ChangeNotifier, where it can be easily processed when the app state changes.
The problem: I don't know the best way to send the result of StreamProvider to the Provider/ChangeNotifier.
My temporary solution: Within the StreamProvider function, I can send the result to the other Provider by doing something like below, but note that I have to use 'listen: false'. Otherwise, I get an error like "Tried to listen to a value exposed with provider, from outside of the widget tree".
This seems like it is not the best solution because I have to set a variable to Provider.of(context) at all the places where I need to Consume the Provider, even though I don't actually need the result of the StreamProvider in these widgets. But without these variables defined, the widgets depending on the StreamProvider and Provider would not rebuild.
I'd like to know if there is a better way to accomplish this.
From the stream defined for the StreamProvider:
return ref.snapshots().map((items) {
// Sending the result to the other Provider
var appData = Provider.of<AppData>(context, listen: false);
appData.setVariable(items);
return items;
}

Related

Combining bloc and websockets

I'm trying to use bloc with websockets in both directions (i.e. when a message is received on the websocket, an event is fired as well as when a state is emitted, a message is sent over the web socket). I'm still fairly new to flutter but have written similar style code (message queues and websockets) in other languages but and I'm really struggling to get my head around how to structure everything so it works in flutter.
I have a basic class that opens a websocket and waits for events
class WebsocketManager {
final BuildContext context;
late IOWebSocketChannel channel;
WebsocketManager(this.context);
void connect(){
channel = IOWebSocketChannel.connect(Uri.parse(wsBaseUrl));
channel.stream.listen(
(msg) {
//process msg
BlocProvider.of<SomeBloc>(context).add(MessageReceived(msg));
}
);
}
}
This works perfectly fine (although having to pass the BuildContext in feels a bit wrong). The issue is with listening for new states. I assumed I would be able to do something like this
BlocListener<SomeBloc, SomeState>(
listener: (context, state) {
if(!sendMessage(SomeMessage()))
});
However this listener never fires. If I place that same code as a child of a Widget then it works fine so I assume a BlockListener has to be a child of a widget.
My question is, is there a way of using BlocListener (or some alternative) without being a child of a widget?
Alternatively, is there a better way I could structure my code so it can have access to both the websocket and bloc?
Thanks
First, the BlockListener must be a child of the widget being provided with the bloc.
Yeah, I wouldn't pass the BuildContext into the WebsocketManager. I'd flip it around a bit, to make it a bit cleaner.
How you do it will of course depend on how your UI should behave based on events and states. But for the sake of keeping my example simple, I have a suggestion below where a Widget (perhaps an entire route), is listening and updating based on the websocket messages. You can of course do it so that entire app is affected by the websocket, but that is "the next step".
The bloc should be the glue between your WebsocketManager and the UI. So I'd suggest to let your Bloc be created (provided) to a Widget where appropriate. The bloc holds an instance of WebsocketManager (perhaps a Singleton?). Have a method and corresponding state in the bloc that sets up the connection using WebsocketManager.connect(). But instead of doing the ....add(MessageReceived(msg)) stuff in the listen callback, have a method (perhaps it is your connect() method) that returns a Stream<type of msg> and let the listen callback yield msg. You can then in your bloc set up a StreamSubscription for the WebsocketManager's stream, and then emit states and act based on what is received from the websocket.
I'd suggest that you also convert the msg from the listen-callback to your own domain object and yield that in the stream, so that your bloc isn't strictly dependent on the msg type from the WebsocketManager.
This way your WebsocketManager only do 'websocket-stuff' in the data layer, and you let your bloc do the logic in the application layer, which in terms will let your UI update based on what you bloc emits.

In riverpod, how to write a StateNotifier that sets up (only once) asynchronously

New to riverpod here. (Using Flutter and hooks_riverpod, btw).
Using Hive to store related lists of items. I need to call Hive.initFlutter and wait for Hive to be initialized, and do the same to open hive boxes, once when my app loads. After that, calls to Hive are synchronous.
My original idea (though I'm open to better ideas) was to create a StateNotifier that holds both the lists. This notifier could have a setUp function that's asynchronous. Here's what that looked like, simplified:
class ItemsNotifier extends StateNotifier<ItemsState> {
ItemsNotifier() : super(ItemsState([], []));
setUp() async {
await Hive.initFlutter();
// What is ItemDao? It's a data accessor object singleton used to house Hive logic.
await ItemDao().openBoxes();
// Putting a breakpoint here, I can see by calling `ItemDao().list1` etc that the lists have loaded with items as expected, but setting state here does not trigger a rebuild of the consumer widget.
state.list1 = ItemDao().list1;
state.list2 = ItemDao().list2;
}
...getters and setters and other functions omitted...
}
final itemsProvider = StateNotifierProvider<ItemsNotifier, ItemsState>((ref) {
final notifier = ItemsNotifier();
notifier.setUp(); // I've never seen anything to suggest that calling an async setUp method here is supported, it's just something I tried.
return notifier;
});
class ItemsState {
List<Item> list1;
List<Item> list2;
ItemsState(this.list1, this.list2);
}
As mentioned in the comments, I call an async setUp method while constructing itemsProvider. I put a breakpoint inside the setup method and inside my consumer widget. First the breakpoint inside the widget catches, and we see that list1 is empty, as expected. Next the breakpoint inside the setup method catches. We see that ItemDao().list1 is full of items, so loading from Hive succeeded. So I'd expect calling state.list1 = would cause the consumer to reload as it usually does. But it doesn't. The breakpoint in the widget doesn't catch again, the widget remains empty. Probably because Riverpod isn't expecting async methods to change state from inside the StateNotifierProvider constructor.
So a simple solution to this question might just be an answer to where in the app should I call setUp()? It would need to be somewhere that only runs once when the app starts. In an initState in a stateful widget somewhere? That doesn't quite feel right... as I said, I'm new to using riverpod.
Or if you have an alternate (better) way to architect this out, that would also be helpful.
Solutions considered:
I'll note that I also tried using riverpod's FutureProvider. It works for loading the list. But, as stated in the docs, FutureProvider can't be extended as StateNotifier can, so I'm not sure where I'd put custom setters and getters. And if I wrote one FutureProvider per each list, that wouldn't handle the fact that Hive.initFlutter should only be called once. I could see a ways around this, but it's a bit clunky, and thought I would be better off if someone with more experience advised me. StreamProvider seems basically the same as FutureProvider. Maybe there's a way to compose a FutureProvider inside a StateNotifierProvider? Really not sure what that would look like.

Where should you place the state value in Riverpod?

To access the state of a StateProvider or StateNotifierProvider:
Sometimes in the Riverpod documentation, the state variable is added after the watch function.
int count = watch(counterProvider).state;
However, my code where I am using a StateNotifier, works only if I refer to it inside watch. i.e
watch(myNotifier.state)
What are the differences?
The widget that is consuming the provider will behave differently in the two cases.
In the first case:
watch(counterProvider).state
The consumer will look at the entire counterProvider and it will be rebuilt if anything causes a NotifyProvider.
In second case:
watch(counterProvider.state)
The consumer is looking at the state variable only and it will only be rebuilt if the state changes and cause a NotifyProvider.

Flutter Provider reinitialize model

I use MultiProvider and then create all my models. Lazy loading is enabled and as such when I open my page widget the constructor of my model is called when I call Provider.of<>(context).
This initialize my model and the model gets fresh data.
I have the following issue however, when I pop the view(widget) and revisit the view(widget) later, Provider.of<>(context) is called again, but since the model was already initialized I get the previous data from the model (This is useful because I do use this to preserve state between certain screens).
I need my model to reinitialize since I need to refresh my data and reset the page values, and since the constructor is never called again, I don't get any of these.
No matter what I do, if I call the initialize method from initState() / didChangeDependencies() it always error since I'm changing the data while the widget is building.
I'm looking for something like the following:
MyChangeNotifier variable = MyChangeNotifier();
ChangeNotifierProvider.value(
value: variable,
child: child()
)
To reinitialize my class, but from what I read this is bad and don't know where to call it.
I have no idea how to proceed and any help would be appreciated.
So I found what I was looking for in the Provider actual documentation here.
The key is to call your code that would update the UI or trigger a rebuild inside a Future.microTask(). This will only then trigger the rebuild once the future completes and not trigger the rebuild while the widget tree is still building.
#override
initState() {
super.initState();
Future.microtask(() =>
context.read<MyNotifier>(context).getMyData(); // Or in my situation initialize the page.
);
}

When to use Provider.of<X> vs. Consumer<X> in Flutter

I'm still wrapping my head around state-management techniques in flutter and am a bit confused about when and why to use Provider.of<X> vs. Consumer<X>. I understand (I think) from the documentation that when choosing between these two you would use Provider.of when we want access to the data, but you don't need the UI to change. So the following (taken from the docs) gets access to the data and updates the UI on new events:
return HumongousWidget(
// ...
child: AnotherMonstrousWidget(// <- This widget will rebuild on new data events
// ...
child: Consumer<CartModel>(
builder: (context, cart, child) {
return Text('Total price: ${cart.totalPrice}');
},
),
),
);
Whereas, where we only need the data on don't want to rebuild with UI, we'd use Provider.of<X> with the listen parameter set to false, as below:
Provider.of<CartModel>(context, listen: false).add(item); \\Widget won't rebuild
However, listen isn't required and so the following will run too:
Provider.of<CartModel>(context).add(item); \\listener optional
So this brings me to a few questions:
Is this the correct way to distinguish Provider.of<X> and Consumer<X>. Former doesn't update UI, latter does?
If listen isn't set to false will the widget be rebuilt by default or not rebuilt? What if listen is set to true?
Why have Provider.of with the option to rebuild the UI at all when we have Consumer?
It doesn't matter. But to explain things rapidly:
Provider.of is the only way to obtain and listen to an object.
Consumer, Selector, and all the *ProxyProvider calls Provider.of to work.
Provider.of vs Consumer is a matter of personal preference. But there's a few arguments for both
Provider.of
can be called in all the widgets lifecycle, including click handlers and didChangeDependencies
doesn't increase the indentation
Consumer
allows more granular widgets rebuilds
solves most BuildContext misuse
Provider.of<>
applying provider, whole widget will rebuild if listen true.
Consumer<>
using consumer only specifically allowed widget will rebuild.
For your questions:
Is this the correct way to distinguish Provider.of<X> and Consumer<X>. Former doesn't update UI, latter does?
Provider.of<X> depends on value of listen to trigger a new State.build to widgets and State.didChangeDependencies for StatefulWidget.
Consumer<X> always update UI, as it uses Provider.of<T>(context), where listen is true. See full source here.
If listen isn't set to false will the widget be rebuilt by default or not rebuilt? What if listen is set to true?
Default value is true, means will trigger a new State.build to widgets and State.didChangeDependencies for StatefulWidget. See full source here.
static T of<T>(BuildContext context, {bool listen = true}).
Why have Provider.of with the option to rebuild the UI at all when we have Consumer?
Pretty much covered by Rémi Rousselet's answer.
There should not be any performance concern by using it, moreover, we should use consumers if we want to change some specific widget only on screen. This is the best approach I can say in terms of coding practice.
return Container(
// ...
child: Consumer<PersonModel>(
builder: (context, person, child) {
return Text('Name: ${person.name}');
},
),
);
Like in the above example, we are only required to update the value of the Single Text Widget so add consumers there instead of Provider which is accessible to other widgets as well.
Note: Consumer or Provider update the only reference of your instance which widgets are using, if some widgets are not using then it will not re-drawn.
The widget Consumer doesn't do any fancy work. It just calls Provider.of in a new widget, and delegate its build implementation to [builder].
It's just syntactic sugar for Provider.of but the funny thing is I think Provider.of is simpler to use.
Look at this article for more clearance
https://blog.codemagic.io/flutter-tutorial-provider/
We have 3 things to understand here.
When you wrap Provider around a widget it sets up a reference to a widget tree and a variable whose changes you want to refer to.
using Provider.of(context) you can get access to the variable you want to monitor and make changes in it.
Provider.of(context) with and without listen gives you a reference to the above-declared Provider object and a widget tree where it can be accessed from. But as said by others, it rebuild the whole widget tree it sits on top of when listen is not false.
In the end, you can use consumer to monitor any changes that happened using the above step
The consumer acts like a more granular listener and be applied to a fixed widget to help avoid unnecessary rebuilds.
it's just a personal preference and depends on how you understand and use provider.
so the way i think of provider is it's just an object that is providing a ChangeNotifier Object that has Code and Data down the widget Tree.
So,I use :
Consumer<T>
When i want to listen to changes in Data and update/rebuild my UI according to those changes.
Provider.of<T>
When i just want to call the Code. with the listen parameter set to false.
if your problem is to know what the difference is, here is a track
Consumer
reload consumer-only child widgets
Provider.of (with listen true)
reload from the last buildcontext found in the tree
actually in a simple example
exemple1 provider.of
in exemple1 when I click on my container, his increase his size ( gesturedetector send a newvalue at h variable , h variable is in function named method in provider)
with 'provider.of' flutter rebuild at the first Buildcontext below #override
that mean the totality of tree are rebuild
exemple2 consumer
in exemple2 when I click on my container, his increase his size ( gesturedetector send a newvalue at h variable , h variable is in function named method in provider)but with consumer only sélectionned widget are rebuild
that mean one widget are rebuild the other widget doesn't "move"
I hope I could help you