I am just looking forward to understanding the difference between using a normal setState(() {}) and the update() method of the Getx package.
As much as I can see from the practical point of view when setState(() {]) is used the whole page is rebuilt but when GetX has used only the part of .obs is rebuilt. What I want to understand is more in-depth difference.
setState will refresh the whole widget. But using the Getx you can refresh the particular widget.
More you can find from this link : Getx Controller
Getx's state manager, is a tool that let you control and manage your widget state from a separate place, which is the GetxController.
The Getx state management widgets, like GetBuilder(), Getx(), Obx()... are StatfulWidgets and under the hood, they also use a normal SetState(() {}), but the implementation of calling them is really different than a usual setState(() {}) call.
As example, the GetBuilder() :
class GetBuilder<T extends GetxController> extends StatefulWidget {
final GetControllerBuilder<T> builder;
final bool global;
final Object? id;
final String? tag;
final bool autoRemove;
final bool assignId;
final Object Function(T value)?
/*...*/
and under the hood, it updates the state by getting a method like this :
void getUpdate() {
if (mounted) setState(() {});
}
then store it in Map we call it from the controller with an update().
Your sentence:
As much as I can see from a practical point of view when setState is used the whole page is rebuilt but when GetX has used only the part of .obs is rebuilt
is wrong!
Try wrapping your whole page with a GetBuilder and call the update() from its controller, and you will have a full rebuild for the whole page because it's just a normal StatefulWidget that will be rebuilt by calling its build() method again and again...
You face the whole page state update because you wrap the whole of it with a StatfulWidget, the same thing with GetBuilder(), Obx()...
There is a Flutter builder widget that let you also manage the state of its child just locally, which is StatefulBuilder, give it a quick check and I recommend that you play with it to understand that approach of using a builder widget to update the state.
Besides that Getx gives you the ability to separate your logic and state management into the GetxController, it let you control which widget to update exactly with its own custom mechanisms like using the update() method ( which calls a normal SetState(() {}) ) with a specific id, like this:
update([id1, id2, id3]);
Under the hood search over a Map where it stores all the SetState(() {}) that it got from the Getx's GetBuilder(), then look for what matches that id, then call only what belongs to it, and this what causes that widget having one of those id will be updated, and other widgets will not.
you can check more about how Getx works with a quick reads of its source code, I guess it will be very helpful for you.
Related
To specify the question by an example:
If you create a new Flutter project you get the default counter-app where the _counter variable is incremented in the setState method of the State class:
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
....
But the following code works fine too:
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
_counter++;
setState(() {});
}
....
Here is a link to a working example in DartPad.
The documentation of the setState method states that
The provided callback is immediately called synchronously.
and...
Calling setState notifies the framework that the internal state of this object has changed in a way that might impact the user interface in this subtree, which causes the framework to schedule a build for this State object.
According to this definition I see no reason why not to change the state variables before calling the setState method and then provide an empty callback to setState. Are there cases where this approach doesn't work or is it more or less "just" a matter of taste ( readability, clean coding,...)?
You are absolutely right. I don't know what the exact reason is but I'll try to explain what I think.
There is one purpose setState serves at the moment. It ensures that you update your state synchronously. See Implementation
For example
Future.delayed(Duration(seconds: 3), (){
// Updated State variables
});
setState((){});
This code might not work, because the widget will be rebuild before the variables change.
I'm not saying that you cannot change the state asynchronously, nor am I saying that careful coding doesn't work. It just a primal check for beginners.
I think Flutter has kept this approach to better cope with the risks involved with changes in the framework. There might not be a need for the callback now, but in the future, the framework might need to include certain functionality that would need this approach.
Whatever the true reason is, only Flutter team can tell.
So i am writing a Flutter application that utilize the MVVM architecture. I have a viewModel for every screen(widget) with ValueNotifiers and i want to initiate the viewModel for that view.
Now most guides suggest a Provider approach, but why provide it when i can just normally initiate it.
Code:
class FooModel{
final ValueNotifier<bool> _active = ValueNotifier<bool>(false);
ValueNotifier<bool> get active => _active;
FooModel(){_active = false;}
doSomething(){_active=!_active}
}
What i want to do:
#override
Widget build(BuildContext context) {
_viewModel = FooModel();
return Scaffold(
body:ValueListenableBuilder<bool>(
valueListenable: _viewModel.active,
builder : (context,value,_){
if(value)return(Text("active");
return Text("unactive");
}
)
}
what is suggested:
Widget build(BuildContext context) {
return Provider<FooModel>(
create: (_) => FooModel(),
builder: (context, child) {
final vm = Provider.of<FooModel>(context);
return ValueListenableBuilder<bool>(
valueListenable: vm.active,
builder: (context, value) {
if (value) return Text("active");
return Text("unactive");
});
},
);
}
Now i understand that what i suggested creates the viewModel with every build, but that should only happen when screen is loaded thanks to ValueNotifier so its fine.
I guess i just don't understand the value of providing the viewModel.
Flutter has a different ideology.
yes, you can create Value Notifier and it's fine to do that but just thinking of the bigger picture.
Check this flow you want to call an API then perform parsing and filtering on that and you have 2 views on the screen to show the same data one is to showcase the data and the other one is to interact with data and this update needs to be reflected on showcased data.
to do this what we need to do?
create valuenotifier at class level that encloses both screen widgets.
Call API and filter code at the class level.
pass this valuenotifier to both screen widgets you may ask why right? well because one class need to update other class widgets. and that's only one way to push updates to the valuenotifier is the object itself. so you will need to pass this valuenotifier in both classes.
once you do that and update has been synchronized if any setState has been called to the main widget that encloses both of this widgets then you need to do all this again.
also there will be multiple instances of valuenotifier which is bad as valuenotifier is a stream and you need to close your streams once you're done with the stream so you will be needing logic to close your streams at any setState event on main widget.
What is provider exactly? and how it works? well, the provider is a change notifier class which calls setState when you call notifyDataChanged or notify method. this triggers the widget state change which is listening to that data changes.
and that widget gets rebuild. This is the same logic for each and every state management library out there Bloc, ScopedBloc, or any widget like streamBuilder or ValueListenableBuilder.
In Flutter if you want to change data you just need to call setState. Just to be testable, more readable and maintainable what we will be doing is to separate logic into different files just like we do in Android or iOS and that's where this type of libraries comes into the picture to reduce our headache of typing code all over again and focusing on the main task which is the functionality of the app.
See we can create loops in different formats
for(int i=0;i<length;i++)
while(i++<length)
for(i in 0...length)
It's up to us to provide clean and understandable code so that any other developer does not need to delete all code just because he isn't able to understand our code.
There's nothing right and wrong here in development. It's matter of what is more convenient or what makes more sense. like int i does not make sense but if we replace it with index or listIndex it will.
Also, one thing to mention is what you're doing is creating a model that is kind of the same as bloc pattern. so you're already halfway through. you just need to call state change from model check bloc and other patterns you will understand.
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.
);
}
What I want
I have a simple model. The model extends from ChangeNotifier. If the ChangeNotifier calls notifyListeners() I want to "do" something like showing a SnackBar or Dialog. I provide the model with the Provider package to my widget tree.
What is it comparable to?
I used the flutter_bloc package before. This package offers BlocListener. With BlocListener I can "do" something on state changes. Example code:
BlocListener<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container(),
)
In the above example, the child will not rebuild but I can still do something depending on the state.
Is there anything comparable to the provider package? I read in the documentation of the package that ListenableProvider would give more freedom to do stuff like "animations". But I do not know if I can use this Provider in some way to show a snack bar on a notify.
Edit: I asked Remi, the author of Provider, on Twitter. With a short amount of characters, he told me that I can use didChangeDependencies for this behavior.
Please be cautious about using didChangeDependencies for this. There are only a few circumstances where didChangeDepdnencies can be used for this, and https://github.com/flutter/flutter/pull/49527 will make it impossible even in those.
The basic problem is that didChangeDepdnencies is sometimes (or, after #49527, always) called at a point where the tree is locked against state changes. Before the pull request, it is only safe on calls that are:
Not the first time (which is called from within a build scope)
Not the time where an element is being deactivated and eventually unmounted, so is no longer in a valid spot in the tree (this call will no longer happen at all after the pull request).
A safer way to do this is:
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (Provider.of(context).whatever == someCondition) {
SchedulerBinding.instance.addPostFrameCallback(() {
// show modal or dialog
});
}
}
This code is safer to use because it is guaranteed to run at a point where state is safe to change in the tree, rather than only working in some very specific scenarios without the frame callback.
There are probably more elegant solutions than this (such as adding a callback directly on notifyListeners for your ChangeNotifier, assuming it only fires when the tree is in a mutable state).
try using listener on your provider
final myNotifier = context.read<MyNotifier>();
void listener() {
// Do something
}
myNotifier.addListener(listener);
I always aim to make my widgets Stateless instead Stateful for performance benefits. In some cases(updating the BottomNavigationBar index e.g.) notifyListeners() can provide identical functionality of the setState().
At first, I think notifyListener() is lower level, more fundemantal function comparing to setState(), so it should be more efficient. Because setState() method may triggers too many higher level framework methods, so it may spends more CPU power.
But it's hard to be sure without making a proper and detailed performance testing. So what is the answer?
Edit: Also, in some cases, notifyListeners() behaves exactly like setState(). For example, I have Text widget inside a StatelessWidget that holds a Random value and when I notify the an unrelated value inside the Class, the Text widget is also getting update. So, what is the difference?
Assuming that you're comparing ChangeNotifier.notifyListener with State.setState to rebuild the widget tree, then:
setState will always win.
The reason, notifyListener rebuilds your widget tree because it causes setState itself.
For notifyListener to work, there's usually a StatefulWidget somewhere in your tree that does the following:
class MyState extends State<T> {
ChangeNotifier notifier;
initState() {
notifier.addListener(() => setState(() {}));
}
}
In any case, this probably doesn't matter.