Riverpod Flutter: Multiple providers for one screen - flutter

I'm new to riverpod, and I want to check that I am doing things correct.
I have a screen on my Flutter app where the use inputs lots of information about a session. Like start time, end time, notes, duration, date etc etc. All this data in the end is stored in a dart complex object MySession(), will all the above properties.
My question is, in the meantime, I am creating a seerate provider for each field. Like this:
final selectedDateProvider = StateProvider((ref) => DateTime.now());
final sessionDurationMinutesProvider = StateProvider<int>((ref) => 0);
Now in the class, I call the providers like this in the build method:
selectedDate = ref.watch(selectedDateProvider);
sessionDurationMinutes = ref.watch(sessionDurationMinutesProvider);
Then I display them in the widgets.
When they are editing, I save the state like this:
ref.read(selectedDateProvider.notifier).state = datePick;
My question is, I have lots and lots of fields on this page. So I have to create lots of providers for each field. Is this the correct practise? Can I not make a customclass for all these fields, and then make one provider which will return this custom class?
On the riverpod docs it says: You should not use StateProvider if: your state is a complex object (such as a custom class, a list/map, ...)
https://riverpod.dev/docs/providers/state_provider
I hope its clear!
Thanks

You already answered your question ;)
Yes, you can.
Make a class that will store the state of all the input fields and expose it through StateProvider.
To do it effectively you will probably need a copyWith method which can be written manually or generated. One of the possible approaches is to use freezed.

Related

Forcing the MobX state to be changed through declared actions only in the Flutter framework

I'm experimenting with MobX state management solution in Flutter. I really like its conciseness and its simplicity but I'm a bit concerned in the drawbacks its way of mutating the state can introduce.
Theoretically , we can force the state to be mutated only using actions and this is great because we can have a history of the changes using the spy feature the package provides.
However, actions are automatically created by the framework if we do not use a specific one. For example, in the code below, I created a counter store class and I used it in the main function to assign a new value for the counter without using a predeclared action. The stric-mode has been previously set to "always".
abstract class _Counter with Store {
#observable
int value = 0;
#action
void increment() {
value++;
}
}
final counter = Counter(); // Instantiate the store
void main() {
mainContext.config = mainContext.config
.clone(isSpyEnabled: true, writePolicy: ReactiveWritePolicy.always);
mainContext.spy((event) {
print("event name : " + event.name);
print("event type : " + event.type);
});
Counter counter = Counter();
counter.value = 10;
}
I was expecting an error to raise saying that it is not possible to change the state outside of action but this does not happen because an ad hoc action is created , called value_set, automatically. I'm wondering ,then, which is the point of forcing to use actions if it is possible to avoid using them. I mean, if we directly change the state, an action is created automatically and seen by the spy functionality making all the process more robust and predictable but, what if I want the state to be changed only by actions I previously implemented? Is there a way of doing so? For example, in the counter code I just provided , is there a way of making the counter incrementalbe only by 1? Because, in the way Mobx works, I could write everywhere in the code something like
counter.value = 10
and this will work just fine without any problems. Forcing people to use preset actions could increase the predicatability a lot and facilitate the teamwork too I think.
I tried to make the value variable private but it still remains accessible from the outside , also if annotated with #readonly.
Hm, that looks weird for me too, because it does work differently in MobX JS (exactly like you describing), but it seems that Dart MobX changed behaviour for single field values and they are automatically wrapped in actions now, yes.
Maybe there should be a optional rule to turn strict checking on again, it would make sense. I suggest you to create and issue or discussion on Dart MobX Github.
More info there: https://github.com/mobxjs/mobx.dart/issues/206
At the end, I found out that the behaviour I was looking for was obtainable with the #readonly annotation. Marking a variable with #readonly annotation avoids automatically creating its setter and getter methods. Moreover it requires the annotated variable to be private, denying the variable to be set directly.
Note: By the way, the way the Dart language is implemented, it is necessary to locate the Counter in a separate file to provide the desired behaviour, otherwise its private fields would be accessible anyway.

How to copy a Map in dart without getting Reference of the old one

I have a map that i get from my service and i want to copy it into 2 new Maps without being referenced and every time i change a value into one, value change to other too.
This is the Initial Map from my service config.UserGroups and i copy it like this
SavedSettings.UserGroups = new Map.from(config.UserGroups);
UnSavedSettings.UserGroups = new Map.from(config.UserGroups);
This Map is dynamic but it has String,object
Do we have an easy way to bypass reference;
What you are asking for is called deep copying and is something you need to implement yourself for your data structures since List and Map just contains references to objects. So when you are making a copy of a List or Map, you are copying the references and not the objects inside the collection.
So the correct solution would be to make some logic which allows you to make copies of UserGroup objects and put these into your new list.
But.... if you are not scared of hacky solutions....
Section with hacky solution
If you really want to have some way to get deep copy from the Dart language (and don't care if the solution is kinda hacky) it is in theory possible by make use of the idea that we can send objects to isolates which are deep copied if possible.
So we can create an ReceivePort and send our object to our self. An example of this can be seen in this example:
class MyObject {
int value;
MyObject(this.value);
#override
String toString() => 'MyObject($value)';
}
Future<void> main() async {
final list1 = [MyObject(5)];
final list2 = await hackyDeepCopy(list1);
list2[0].value = 10;
print(list1); // [MyObject(5)]
print(list2); // [MyObject(10)]
}
Future<T> hackyDeepCopy<T>(T object) async =>
await (ReceivePort()..sendPort.send(object)).first as T;
This "solution"/hack comes with some limitations when it comes to data we can copy:
The content of message can be:
Null
bool
int
double
String
List or Map (whose elements are any of these)
TransferableTypedData
SendPort
Capability
In the special circumstances when two isolates share the same code and
are running in the same process (e.g. isolates created via
Isolate.spawn), it is also possible to send object instances (which
would be copied in the process). This is currently only supported by
the Dart Native platform.
https://api.dart.dev/stable/2.14.4/dart-isolate/SendPort/send.html
It is the "In the special circumstances when two isolates share the same code and are running in the same process" part we makes use of in hackyDeepCopy which allows us to also deep copy custom made objects.
Another limitation is that hackyDeepCopy needs to be async which is kinda annoying if your code is not already async.
And lastly, this only really works on native platforms (so no Dart-running-as JavaScript stuff).

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.

Flutter BLoC Functionallity of = adding item to list without fully rebuild list, changing a value of class instance property

Maybe my question is a nooby one, but i really try to understand how to implement the following operations:
How to add item for a list without the need of rebuilding all list.
How to update a class instance property value.
Does BLoCProvider used only once in a class?
===> I'm asking those question because i see that BLoC working with 'Equatable' and that in a the state class there is always a need to bring default value to the state we provide to the view class.
Another Question (what change the view and operate the state):
Does i need to bring always a list in order for example to update an item from the list or only changing the state+emit(in cubit) will change the state of the application, maybe it realted to BLocListener?

React: Are classes without state still considered stateless/pure?

I've been refactoring my app to make more components stateless/pure components; i.e., they're just functions. However, I noticed that some components will need to connect with the redux store via mapStateToProps. Which causes me to do something like this:
const someComp = (props) => {
const {
funcFromReduxStore,
} = props;
return (
...
<SomeComponent
func={ funcFromReduxStore(myArgs) }
...
);
};
This will not work because I am executing funcFromReduxStore. An easy solution is to wrap the prop in an arrow function. However, this causes many unnecessary re-renders b/c the function won't be bound.
The question then becomes: How do I bind a function in a stateless component?
Is it still stateless if I make it a class, without a constructor, and create a class instance field as so:
class someComp extends React.Component {
const {
funcFromReduxStore,
} = this.props,
wrapper = (x) => funcFromReduxStore(x) // equivalent way to bind w/ ES8+
render() {
...
<SomeCompnent
func={ wrapper(myArgs) }/>
...
}
}
I don't have a constructor, nor state. I want to keep the comopnent stateless, but I also want to bind the function to avoid unncessary re-renders. I also want to continue to keep it stateless b/c React has stated there will be performance benefits for stateless comopnents. Does this qualify as a workaround?
Short answer, no. Stateless functional components need to be simple functions.
You should take a look at the Recompose library for some really cool helpers that allow you to beef up your SFCs.
If you're trying to prevent unnecessary re-renders, you could look into onlyUpdateForKeys() or pure().
EDIT: So, I've been thinking about this a bit more and found this really great article on React component rendering performance. One of the key points in that article that pertains to your question:
Stateless components are internally wrapped in a class without any optimizations currently applied, according to Dan Abramov.
From a tweet in July 2016
So it appears that I was wrong. "Stateless Functional Components" are classes...for now. The confusing thing is that there have been performance improvements theorized:
In the future, we’ll also be able to make performance optimizations specific to these components by avoiding unnecessary checks and memory allocations.
At this point, I think the answer to your question becomes largely subjective. When you make a class that extends a React Component, any instances of your class get the setStateprototype method. Meaning you have the ability to set state. So does that mean it's stateful even if you're not using state? Thanks to #Jordan for the link to the code. SFCs only get a render method on the prototype when they are wrapped in a class by React.
To your point about wanting to bind functions, there's only two reasons I can think of that you'd want to bind the function:
To give the function access to this (the instance of the component). From your example, it doesn't seem like you need that.
To ensure that the function passed as a prop to a child component always retains the same identity. The wrapper function in your example seems unnecessary. The identity of the function is determined by the parent component (or mapStateToProps, or whatever HOC).
You should also take a look at React's PureComponent which does the same kind of shallow checking that the pure() HOC from recompose does.