Im using the Provider/ChangenNotifier Pattern to handle state as described in the official docs.
I have a state field which I want to set after a Widget is build. However if I try to set in in the build method. I get an Error:
setState() or markNeedsBuild() called during build.
Where can I call something like:
var state = Provider.of<StateModel>(context);
state.field = 'new Val';
You can not set the state during a build, if you want to test a change of state, put this code state.field = 'new Val'; inside a button event, like FloatActionButton or a event after build completed (with Future.delayed or add post fram callback, see Is there any callback to tell me when "build" function is done in Flutter?)
Warning If you are invoking notifyListeners() inside your state.field set, and listening changes in your widget with provider, It would cause an infinite cycle of rebuild... this is another reason that you can not set state during build...
Related
This is my simple ChangeNotifier:
class Settings extends ChangeNotifier{
void changedSettings(){
notifyListeners();
}
}
I have this in my build method:
Provider.of<Settings>(context);
and the widget rebuilds as expected when I call a function that calls notifyListeners in Settings. However, I only want to run an expensive data reload if the rebuild was because of the Provider, and not because some other Flutter management reason.
My ideal solution to this is I can register some sort of callback function when I register the Provider.of in my build method...for example:
Provider.of<Settings>(context).withCallback(this._getData(this.state_settings));
Instead, what I am doing now is:
class Settings extends ChangeNotifier{
late int lastChange;
Settings(this.lastChange);
void changedSettings(){
lastChange = DateTime.now().microsecondsSinceEpoch;
notifyListeners();
}
}
...
int lastChange = Provider.of<Settings>(context).lastChange;
if(lastChange != lastSettingsChange){
lastSettingsChange = lastChange;
_getDataFuture = getData();
}
Irrespective of what triggered a rebuild, the choice to do/not do an "expensive data reload" must be managed differently. For example, you should be setting a state (that you listen to) which directs your build to do a data reload or not, based on that state.
This is how we use reactive programming and state management. The "state" must not be concerned with the "actions" taken on that state - the typical examples are the "loading", "loaded" and "error" states on performing a network read/write.
In the initState you can register a listener to this provider like Provider.of<T>(context,listen:false).addListener and this callback will be called whenever you do a notifyListener.
Or you can create your own listeners e.g look at the addStatusListener on the Animation class.
Also, if you have Provider.of<Settings>(context); in your build that means you are depending on it so the didChangeDependencies will be called when the Provider is notified.
I used the BLOC architecture inside my app. One method inside my bloc resolves a picked address-ID from a listView and return the city name. After this I want my Text-Editing-Controller to have this picked value and show it on the texteditingfield:
The BlocA uses a simple ID and look it up using Google-Places API. BlocA then return a new state with the resolved address as a state property. Running the code below did not show the correct city after tapping on a listView-item.
Await do not work unfortunately.
// ListView.builder code
onTap: () {
context
.read<BlocA>()
.add(PickAddress(id: state.searchResults[index].placeId));
FocusScope.of(context).unfocus(); //hide Keyboard
_textController.value = TextEditingValue(text: state.address.city);
the code its not clear enough
remember to use "await" you have to set Async on onTap like this
onTap: () async { ....
if you did that and still not working send the whole code
Do you know what your Bloc actually returns? Use the debugger and step through your code to see what actually happens in your code.
When it comes to using Bloc's to build your state you usually don't access the state with by calling .state.
The line _textController.value = TextEditingValue(text: state.address.city); is not the way you access your state. When the Bloc sends the new state, the widget will rebuild and render based on the new state.
I just started learning Flutter, and I'm going through the Udemy course "Flutter & Dart - The Complete Guide". In that course, there is a section about building a shopping app, which uses providers. In one instance of that app, where the user swipes to delete a product from the Cart page (or screen/route) with the help of the Dismissible widget, he uses a function inside the provider class, which takes a product ID, to delete the item from the cart.
Here is the thing that I don't understand. The Dismissible widget is connected to the provider via this code in the onDismissed property (which fires after the swipe):
Provider.of<Cart>(context, listen: false).removeItem(productId);
And it all works just fine like this. But if you remove the listen parameter (hence turning it into it's default state which is true), then the Dismiss animation still takes place, but the removeItem() method doesn't work, and the cart still stays the same.
Why does this happen?
When we use listen: false we are telling to not rebuild the widget after we remove an item, but they know that we are removing an item so we don't need to listen any value here it's just doing the action of removing
I'll refer to the method Provider.of(context, listen: true) as x throughout this answer.
x is expected to be used ONLY for properties of a Widget that is
expected to change; and
can be rebuilt. For example:
SizedBox(
width: Provider.of<MyLogic>(context, listen: true).width,
)
When used this way, x will ONLY be called when the context owner is being built/rebuilt.
To ensure that it is being used properly, x performs a sanity check every time it is called, making sure that the owner of the context you passed is actually being built/rebuilt. When you call x from within your onPressed or whatever method it is you call it from, x sees that the context owner is not in the "build" phase, and throws this error.
There are a few more details to this, but you don't actually need to know more about it (especially you're just beginning) unless you want to contribute to the package, in which case you should read their documentation.
Side note: you can now use context.watch() and context.read() instead of Provider.of().
The thing I am doing is calling setState() method and then updating the state after a delay of 5 seconds. Even then, i am seeing the State getting updated.
setState(() {
print("callback");
});
sleep(const Duration(seconds: 5));
_randomNumber = Random().nextInt(100);
print("Number : $_randomNumber");
As you can see after the delay, _randomNumber is changing & I have set this _randomNumber value to a Text Widget. After 10 seconds, text is getting updated with the new _randomNumber. Then what is the use of calling setState(() {}) & wrapping the state changes only inside setState() ?
setState is the way to force an update immediately. It is your chance to control the timing. It is not the only way that the tree can get rebuild. Any number of other events can result in another call to your build function. But then you are not in control. It will get updated as a side-effect of something else happening.
Is there any strategy for having you code run only once(like initState()) and have the context also available (to use .of(context)). For example I am getting Bloc.of(context) and I want to subscribe to it (do some stuff like showing an alert dialog, etc). Subscribing in build(), means subscribing multiple times
I can use didChangeDependencies() and set the subscription ??= bloc.listen, but I was wondering if there is another good strategy.
there is another way to do that , by add a "bool" variable and set it as "false" in the "initState" , and lets named (isExec)
2 - in the build method check if "isExec" == false , then run your code that you want to excute it once (alert , subscribe .. etc)
3 - change "isExec" to true (isExec = true);
now if your app state change the build method well avoid that code in the if statment