I have a class Communicator which where I've declared a few functions. Executing those function takes some time and an update of progress is required till all the functions get executed.
Now, I am using the functions of class Communicator in my stageful widget class HomeScreen. I want to pass the progress data from Communicator to HomeScreen and update the widget regularly upon receiving the progress data. How can this be achieved?
Communicator.addSomeListener(_handleSomeEvent); in init, and Communicator.removeSomeListener(_handleSomeEvent) in dispose.
void _handleSomeEvent(SomePayload data) => setState((){});
Your singleton can keep a list of these callbacks, and call them.
One way to that using callbacks like shawnblais explained. You can also do it by state management if you like. Provider will be a good place to start.
Related
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.
In my app, in a certain screen, there are 3 ToggleButtons, which represent 3 types of products, and a list of products of that category which will be given by an API call to a provider class. the list can be viewed in 2 ways, each a seperate widget. note this is a simplification as there is at least 2 widget files between the screen widget and the togglebuttons widget or the listitem widget files.
What i want to achieve is that on first load, the API is called (a future) to provide a list of products of the 1st category, which is displayed by default, and when a user clicks on the ToggleButton of the 2nd or 3rd categories, a new API call is made and the list is rebuilt with the new data. What would be the correct provider to use in this case?
I have tried using a FutureProvider, but it didn't work as it would either not build the list at all, or rebuild it constantly. StreamProvider doesn't seem to work either as it's builder is only run once.
NotifyListeners is the easiest and simplest way to work with the Provider where we just need to call the notifyListeners() method to tell the “listeners” to rebuild and get the new value.
You can just call notifyListeners() when you click on the toggle button.
Note you can only notifyListener() in the class which extends ChangeNotifer.
So in that class create a function which will do your 2nd category API and write changeNotifier in it.
class CreditCardViewModel extends ChangeNotifier {
changeCategory(String category) {
//API Call for the new category
response = ApiData;
notifyListeners();
}
getAPIData(){
return response;
}
The best provider in the end was the changeNotifierProvider.
I initialize the list of items to an empty list at first, and in the provider's constructor I call a Future, but I don't await it. the Future calls the api and sets the list to be the incoming data, then calls notifyListeners(). The list, now changed, displays the new data.
Here is my architecture, I have two statfull widgets: ProfilePicture, RoundedProfilePicture. ProfilePicture is a statefull widget and it has complicated custom code which is very prone to error so I dont want to copy paste it, it utilizes variables from Picture abstract class. (ie. fetches server data and stores them inside variables of Picture). What I want to do is I want to extend from this widgets state so that I can create a new widget RoundedProfilePicture. This widgets state will basically utilize the complicated code from ProfilePicture's state with inheritance and it will add small extra logic to it. So I thought inheritance is the best choice here. Here is what I tried so far
class ProfilePictureState extends State<ProfilePicture> implements Picture{
// SOME LONG COMPLICATED 500 LINE OF CODE
}
class RoundedProfilePictureState extends ProfilePictureState {
#override
void initState() {
super.initState(); // this calls ProfilePictureState.initState() call. I want to call State<ProfilePicture>.initState()
}
}
My problem is void initState() in RoundedProfilePictureState requires me to make a super.initState() call. This call makes a ProfilePictureState.initState() call. I want to call State<ProfilePicture>.initState() because I want to add a different logic in my init state call. So the structure is:
----abstract State class
---ProfilePictureState
--RoundedProfilePictureState
How can I make a call to abstract State class's initState method from RoundedProfilePictureState? Is this possible?
Since no one answered, here is how I solved this. I think this is impossible to achieve. What I did is I moved the code and all the variables to a mixin. Took like an hour or two to do so. I achieved my needs tho.
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.
);
}
From the docs I understood that one can call addListener() on a ChangeNotifier instance to add a custom listener to the stack.
This method accepts a callback with zero arguments (according to notifyListeners()), e.g.:
class MyClass extends ChangeNotifier {
MyClass() {
addListener(() {
// ...
});
}
}
From within the callback, how does one find out what properties or parts of MyClass have been changed?
ChangeNotifier does not have such capabilities inherently. You will have to implement your own logic. Specifically, you either have access to all of the properties of your ChangeNotifier implementation because you add the listener in its scope or you have access to it because you have a reference to it in your scope.
ChangeNotifier simply implements Listenable and provides some utilities for managing listeners. Furthermore, the documentation states the following about it:
ChangeNotifier is optimized for small numbers (one or two) of listeners. It is O(N) for adding and removing listeners and O(N²) for dispatching notifications (where N is the number of listeners).
I am not sure about options with better runtime complexity for notifying listeners, but you will not run into any issues in a regular Flutter app.
ValueNotifier
ValueNotifier is a pre-made implementation of ChangeNotifier that will notify its listeners when its value property is changed.
This is sufficient for most case, but since it appears that you want to create a custom ChangeNotifier, you can use the source code of ValueNotifier to take a look at an example implementation (it is very straight forward).
If you are just looking to do state management in general, ValueNotifiers usually work great. However, they are not applicable in every scenario. Hence, here is an extensive list with different state management options.
Considering the questions, I think the techniques that fit your needs best and the most popular options are the following:
InheritedWidget as it lets you notify dependents based on what data changed. Additionally, there is InheritedModel as an extension of this and InheritedNotifier that works with Listenable, just like ChangeNotifier does.
The BLOC pattern, which works with streams.
The provider package which is mostly a convenience wrapper for various Flutter state management techniques (InheritedWidget, StatefulWidget, ValueNotifier, etc.).