Flutter: setState() or markNeedsBuild() called during build with Provider - flutter

I'm calling one of the ChangeNotifier functions from an initState() method and the notifyListener() call inside the function is throwing
setState() or markNeedsBuild() called during build. exception
void initState(){
Provider.of<MessengerRepository>(context, listen: false).setUnreadCount(0);
super.initState();
}
class MessengerRepository with ChangeNotifier {
int unreadCount;
void setUnreadCount(int value){
unreadCount = value;
notifyListeners();
}
I need to call the notifyListener() inside setUnreadCount() as I'm calling the setUnreadCount() function in multiple places during the execution. So can't remove the notifyListener() only inside initState().
Using provider version 4.0.4

The cause for this issue as mentioned in the log message is that the function inside initState() is requesting a rebuild through notifyListener() (the same will happen if we use setState() as well) even before the first build is completed.
The solution is to add a addPostFrameCallback and execute the rebuilding function inside it so that it will execute only after the first building of the widget
void initState() {
super.initState();
WidgetsBinding
.instance
.addPostFrameCallback((_){
Provider.of<MessengerRepository>(context, listen: false).setUnreadCount(0);
}
);
}

You can move Provider.of<MessengerRepository>(context, listen: false).setUnreadCount(0); from intStateto didChangeDependecies then it'll be called only once page finishes build process.
Or by this way :(less elegant)
void setUnreadCount(int value, {bool shouldUpdate: true}){
unreadCount = value;
if(shouldUpdate) notifyListeners();
Then
void initState(){
Provider.of<MessengerRepository>(context, listen: false).setUnreadCount(0, shouldUpdate:false);
super.initState();
}
When you call it in initState, if the build method finishes before all your variables are (re)assigned they'll not be updated until you call setState or notifyListeners
Learn more here

Related

Flutter Difference between InitState and just putting inside Widget Build function

I had an error every time I restarted my App: This widget has been unmounted, so the State no longer has a context (and should be considered defunct). and saw that something was not correct with my initstate. The initState was:
#override
void initState() {
SchedulerBinding.instance.addPostFrameCallback((_) {
BlocProvider.of<TutSkippedCubit>(context).load();
});
super.initState();
}
the methods loads the data from sharedprefs if I have already skipped the tut or not. Now I solved this issue with removing the initState method and putting the function call inside the widget build:
#override
Widget build(BuildContext context) {
BlocProvider.of<TutSkippedCubit>(context).load();
....
The widget build gets called when the pages loads, so, isn't it the same as the initial state? For what exactly is the methode initState() and I have the feeling that my way of handling this problem is a bad practise, but what would be a better way, how do I solve it?
The initState() method is to control what happens after the app is built. The problem is that you call BlocProvider before the app begins. The correct way is to put all the actions after super.initState() call and add the context to the BlocProvider inside build method. Like this:
TutSkippedCubit? tutSkippedCubitProvider;
#override
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((_) {
tutSkippedCubitProvider!.load();
});
}
#override
Widget build(BuildContext context) {
tutSkippedCubitProvider = BlocProvider.of<TutSkippedCubit>(context);
...
}
The initState and build method is called when the widget is inserted into the widget tree, but the build method also is called every time the state is changed.
You do need to have in mind that every time the state is changed your method BlocProvider.of<TutSkippedCubit>(context).load(); also is called.
Maybe, the code below can help you:
WidgetsBinding.instance.endOfFrame.then(
(_) async {
if (mounted) {
BlocProvider.of<TutSkippedCubit>(context).load();
}
},
);
You wouldn't be surprise of getting that error since you are using BlocProvider.<T>(context) out of a BuildContext. This context in bracket is the just the same as the one given in the build function.
The initState() is a method that is called when an object for your
stateful widget is created and inserted inside the widget tree.

Flutter Provider with listen false, but still get error "setState() or markNeedsBuild() called during build."

I would like to reset userProvider when networkProvider changes.
In the userProvider.reset(), I have notifyListeners.
void didChangeDependencies() async {
super.didChangeDependencies();
final NetworkProvider networkProvider = Provider.of<NetworkProvider>(context);
UserProvider userProvider = Provider.of<UserProvider>(context, listen: false);
userProvider.reset(); }
When it runs, it gives error "setState() or markNeedsBuild() called during build."
My question is, I have set the listen to false, why it still rebuild this widget?
When there is only a UserProvider, it has the same error:
void didChangeDependencies() async {
super.didChangeDependencies();
UserProvider userProvider = Provider.of<UserProvider>(context, listen: false);
userProvider.reset(); }
If my idea of usage is totally wrong, is there any suggestion to achieve the same result?
Because you mention that NetworkProvider changes, I'm guessing it's also publishing notifications in which case you'd also want this to not trigger rebuilds with the listen: false parameter. It seems like you do want those notifications though so that you can reset your UserProvider. I would suggest that you don't try and do this in a Widget's lifecycle but use a ProxyProvider to create and update your UserProvider with it being dependent on the NetworkProvider.
All right, I found the answer. It is writen in latter of their home page.
What I tried in my question is not allowed, because the state update is synchronous.
This means that some widgets may build before the mutation happens (getting an old value), while other widgets will build after the mutation is complete (getting a new value). This could cause inconsistencies in your UI and is therefore not allowed.
Instead, you should perform that mutation in a place that would affect the entire tree equally:
class MyNotifier with ChangeNotifier {
MyNotifier() {
_fetchSomething();
}
Future<void> _fetchSomething() async {
}
To use it in build:
Future.microtask(() =>
context.read<MyNotifier>().fetchSomething(someValue);
);

How to force initState every time the page is rendered in flutter?

I am adding some data into the SharedPreferenceson page2 of my app and I am trying to retrieve the data on the homepage. I have used an init function on page 1 as follows:
#override
void initState() {
super.initState();
_getrecent();
}
void _getrecent() async {
final prefs = await SharedPreferences.getInstance();
// prefs.clear();
String b = prefs.getString("recent").toString();
Map<String, dynamic> p = json.decode(b);
if (b.isNotEmpty) {
print("Shared pref:" + b);
setState(() {
c = Drug.fromJson(p);
});
cond = true;
} else {
print("none in shared prefs");
cond = false;
}
}
Since the initState() loads only once, I was wondering if there was a way to load it every time page1 is rendered. Or perhaps there is a better way to do this. I am new to flutter so I don't have a lot of idea in State Management.
you can override didChangeDependencies method. Called when a dependency of the [State] object changes as you use the setState,
#override
void didChangeDependencies() {
// your codes
}
Also, you should know that using setState updates the whole widget, which has an overhead. To avoid that you should const, declaring a widget const will only render once so it's efficient.
First thing is you can't force initState to rebuild your widget.
For that you have setState to rebuild your widget. As far as I can
understand you want to recall initState just to call _getrecent()
again.
Here's what you should ideally do :
A simple solution would be to use FutureBuilder. You just need to use _getrecent() in FutureBuilder as parent where you want to use the data you get from _getrecent(). This way everytime your Widget rebuilds it will call _getrecent().
You simply use setState() methode if you want to update widget. Here's documentation for this https://api.flutter.dev/flutter/widgets/State/setState.html
init function will render it only once initially, to avoid that -
You can use setState( ) method to re-render your whole widget.

Flutter initState not being called

When I open a chat page as a pop up the initState gets called the first time, but when I use Navigator.pop(context) and then open the chat page again the initState does not get called and I get an error for my StreamSubscription -> The method 'cancel' was called on null. But I do initialize it in the initState.
Why isn't the initState being called THE SECOND time I open the chat page, WHEN THE FIRST TIME I OPEN IT IT WORKS PERFECTLY?
// ignore: cancel_subscriptions
StreamSubscription _streamSubscription;
#override
void initState() {
if (this.chat != null) if (widget.chat.messages.isEmpty)
this._prevMessages().then((value) {
this._initMessages(); // <-- WHERE I'M INITIALIZING THE StreamBuilder
this._scrollToBtm();
});
super.initState();
}
#override
void dispose() {
this._streamSubscription.cancel(); // <-- THE ERROR
this._scrollController.dispose();
this._msgTEC.dispose();
this._msgFN.dispose();
super.dispose();
}
_initMessages() {
Funcs.log('Init Live Messages...');
this._streamSubscription = APIs().chats.messagesLive(...);
}
The exact log:
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The method 'cancel' was called on null.
Receiver: null
Tried calling: cancel()
Make sure that you initialize _streamSubscription before invoking it asynchronosuly. There must be some default value set. What's happening is that where you are calling _initMessages(), it is being invoked inside a Future,which means it is invoked after (or not, it could be at anytime) initialization completes.
You can either make sure to await this._prevMessages(), thus:
#override
void initState() async {
if (this.chat != null) if (widget.chat.messages.isEmpty)
await this._prevMessages().then((value) {
this._initMessages(); // <-- WHERE I'M INITIALIZING THE StreamBuilder
this._scrollToBtm();
});
super.initState();
}
Or you can initialize the _streamSubscription object before running the initialization code. There must be some default value set here.
Also the dispose method should not be invoked before initialization. This means that you have code somewhere that is disposing the object the moment that the item gets initialized.

Does setState() trigger itself when variables and data change just like stream and streambuilder does?

class _LocationScreenState extends State<LocationScreen> {
WeatherModel weather = WeatherModel();
String weatherMessage;
String weatherIcon;
String cityName;
int temperature;
#override
void initState() {
super.initState();
updateUI(widget.locationWeather);
}
void updateUI(dynamic weatherData) {
setState(() {
temperature=weatherData['main']['temp'].toInt();
var condition = weatherData['weather'][0]['id'];
cityName=weatherData['name'];
weatherIcon=weather.getWeatherIcon(condition);
weatherMessage=weather.getMessage(temperature);
});
}
Hi I am getting confused with what setState() does. If the value of temperature or condition or anything changes inside that of setState does setState() trigger itself to update the UI and build UI with updated temp or condition or do I have to invoke updateUI function myself to invoke setState and update the UI?
SetState Rebuild the build method whenever it called.
SetState never call it self, you have to call it. If you change value of your variable and did not use SetState then it will not reflect in UI, to do so you have to call SetState.
As you put you code in updateUI in SetState, so it will update ui whenever you call that method, where as if you don't use SetState there then it will not reflect changes in UI.
documentation states that setState(fn()), 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.
whenever a setState called the ui will be scheduled for a build based on the new state.
Eg:
class _MyWidgetState extends State<MyWidget> {
String text;
#override
void initState() {
super.initState();
text = "Hello";
}
void updateUI(String action) {
setState(() {
text = action;
});
}
#override
Widget build(BuildContext context) {
return Column(children: [
Text(text, style: Theme.of(context).textTheme.headline4),
MaterialButton(
onPressed: () {
updateUI("Pressed");
},
child: Text("Change state"))
]);
}
}
Note : initState() will be called once in the life cycle of Stateful widget.
setState():
Notify the framework that the internal state of this object has changed.
Whenever you change the internal state of a State object, make the change in a function that you pass to setState:
setState(() { _myState = newValue; });
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.
Nope, you need to call it in order to "re-draw" the current stateful widget. I believe the variable that changed doesn't need to be wrapped inside the setState either, as long as it's changed before the setState.