Flutter Difference between InitState and just putting inside Widget Build function - flutter

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.

Related

Post Frame Callback - Looping

I have been checking performance on my app and noticed that one of the widget constantly loops. in that Widget I am using the following code to retrieve data from Firestore DB, however for this example I have simplified it with the same looping result.
Question: Is there a reason why Widget Binding is called so many times and in a loop? I was under the impression it was called once on widget build complete. Should I be using something else for a one off post build function?
I have 7 of these widgets in a listView, so I should she maximum 7 I believe.
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPersistentFrameCallback((timeStamp) async {
print(' Is callback done?');
if (mounted) {
setState(() {
isLoaded = true;
});
}
});
}
The widget itself is very simple with just a Text Widget
#override
Widget build(BuildContext context) {
return Text('Hello');
);
}
In my logs I see the following which just keeps going up and up and up.
Maybe you want addPostFrameCallback instead of addPersistentFrameCallback ?
As per documentation addPersistentFrameCallback:
Once registered, they are called for every frame for the lifetime of the application.
As per documentation addPostFrameCallback:
Post-frame callbacks cannot be unregistered. They are called exactly once.

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 How to recreate stateful widget in build()

I'm developing android/ios app using flutter with provider(state management)
in my app, i have a Main scaffold with bottom navigation menu. (so, one scaffold with many views and controll it using bottom navigation, NOT Navigator.push())
i want to know that is it possible recall initstate() from build().
for example
... Statefulwidget
void initState() {
super.initState();
MYHttp.callAPI_only_once_for_some_reason();
}
Widget build(...) {
var flag = Provider.of<MyProvider>(context).flagdata; // flag is true when push notification has been arrived
if (flag) {
initstate() // apparently it should not work, but i have to recreate whole stateful widget to call initState()
}
}
No it is not possible. The initstate() is only called each time a new widget is painted.
Instead of recalling the initstate. Create a method, add it to use init state and call wherever you want to call it.
Check the code below for an example. It works perfectly:
// create the method.
void makeRequest() {
MYHttp.callAPI_only_once_for_some_reason();
}
void initState() {
//call the created method here
makeRequest();
super.initState();
}
Widget build(...) {
var flag = Provider.of<MyProvider>(context).flagdata; // flag is true when push notification has been arrived
if (flag) {
// call the method here again. if you need to use it.
makeRequest(); // apparently it should not work, but i have to recreate whole stateful widget to call initState()
}
}
I hope this helps.

difference between running a method before super.initState() and after super.initState()

what is the difference between running a method before super.initState() and after super.initState()
#override
void initState() {
super.initState();
getCurrentUser();
}
#override
void initState() {
getCurrentUser();
super.initState();
}
Explanation for framework: Dart is class based object oriented programming language, so when you build a widget you extend it from a base class StatelessWidget or StatefulWidget. initState is lifecycle method in flutter widget (as you might know) which is only available to override in StatefulWidgets and it called only once. So, it call initState of base class which is StatefulWidget thats why you call super.initState()which in turn creates BuildContext and attached State.
Now your question: I didn't find anything different in calling a method before or after super.initState(). Event I tried adding addPostFrameCallback before super.initState() and it worked fine.
Even super.mounted is also true in both cases.
Suggestion
Dart/Flutter documentation "suggests" to call super.initState() as first method before your body implementation.
But if we look at how it is implemented Documentation
#protected
#mustCallSuper
void initState() {
assert(_debugLifecycleState == _StateLifecycle.created);
}
it's possible to see that it contains only an assert(). the assert built-in function it's called only in DEBUG mode, not in production mode. So, at the end of the day, it really doesn't matter, because the super.initState() would practically do nothing.

What is the difference between didChangeDependencies and initState?

I am new to flutter and when I want to call my context in InitState it throws an error :
which is about
BuildContext.inheritFromWidgetOfExactType
but then I use didChangeDependencies and it works correctly.
Now I have 2 question:
1- Why calling our context in initState does not work but it works when calling from didChangeDependencies ?
(because as I read in official doc This method is also called immediately after [initState],
and both of them will be called before build method. )
2- Why do we have access to our context outside of build method ( because there we have build(BuildContext context) and we can use our context but in didChangeDependencies we don't have anything like didChangeDependencies(BuildContext context) , so from where can we call context to use it) ?
Context of a state is available to us from the moment the State loads its dependencies.
At the time build is called, context is available to us and is passed as an argument.
Now moving on,
initstate is called before the state loads its dependencies and for that reason no context is available and you get an error for that if you use context in initstate.
However, didChangeDependencies is called just a few moments after the state loads its dependencies and context is available at this moment so here you can use context.
However both of them are called before build is called.
The only difference is that one is called before the state loads its dependencies and the other is called a few moments after the state loads its dependencies.
I've found a significant difference between initState and didChangeDependencies:
initState is called only once for a widget.
didChangeDependencies may be called multiple times per widget lifecycle (in my case it was called when the keyboard appears / disappears)
initState() Called when new Widget is inserted into the tree.
The framework will call this method exactly once for each [State] object
it creates. This will be called once so perform work which required to be performed only once, but remember context can't be used here, as widget state gets loaded only initState() work is done.
Syntax:
#override
void initState() {
debugPrint('initState()');
super.initState();
}
didChangeDependencies() Called when a dependency of this [State] object changes.
So, exactly How it gets called? as by the above definition, it looks like it will be called after state changes but how we come to know the state is changed?
Example:
The below example uses the Provider state management mechanism to update the child widget from the parent widget. The Provider has an attribute called updateShouldNotify which decides whether to state is changed or not. If it's returning true then only didChangeDependencies gets called in ChildWidget class.
updateShouldNotify is returning true by default internally, as it knows the state got changed. Then Why we need updateShouldNotify? it's need because if someone wants to update the state on a specific condition,
Eg: if UI required to show only even values then we can add a condition like
updateShouldNotify: (oldValue, newValue) => newValue % 2 == 0,
Code Snippet:
class ParentWidget extends StatefulWidget {
ParentWidget({Key key, this.title}) : super(key: key);
final String title;
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Life Cycle'),
),
body: Provider.value(
value: _counter,
updateShouldNotify: (oldValue, newValue) => true,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Press Fab button to increase counter:',
),
ChildWidget()
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class ChildWidget extends StatefulWidget {
#override
_ChildWidgetState createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
int _counter = 0;
#override
void initState() {
print('initState(), counter = $_counter');
super.initState();
}
#override
void didChangeDependencies() {
_counter = Provider.of<int>(context);
print('didChangeDependencies(), counter = $_counter');
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
print('build(), counter = $_counter');
return Text(
'$_counter',
);
}
}
Output Logs:
I/flutter ( 3779): didChangeDependencies(), counter = 1
I/flutter ( 3779): build(), counter = 1
For detail explanation:
https://medium.com/#jitsm555/differentiate-between-didchangedependencies-and-initstate-f98a8ae43164?sk=47b8dda310f307865d8d3873966a9f4f
According to initState documentation
You cannot use BuildContext.inheritFromWidgetOfExactType from this method. However, didChangeDependencies will be called immediately following this method, and BuildContext.inheritFromWidgetOfExactType can be used there.
So you need to use BuildContext.inheritFromWidgetOfExactType in didChangeDependencies.
Every Widget has its own context. That is why you have access to context outside build method.
Regarding build(BuildContext context), build method accepts context from the parent widget. It means this parameter BuildContext context is not current widget's context but its parent's context.
The notion of context being mysteriously accessible outside build() was the one that bugged me. I think clarifying this subtle point supplements explanations given in other answers about the first question.
How is context accessible from outside the build() method?
The confusion stems from the (wrong) assumption that context needed to be passed to State.build() at all. Note that the State class already has a context property and according to the documentation, it is redundantly provided to build() here, so that its signature matches that of a WidgetBuilder. However, this is not the same build() method as that of a StatelessWidget.
Called when a dependency of this State object changes.
For example, if the previous call to build referenced an InheritedWidget that later changed, the framework would call this method to notify this object about the change.
This method is also called immediately after initState. It is safe to call BuildContext.dependOnInheritedWidgetOfExactType from this method.
In fact Subclasses rarely override this method because the framework always calls build after a dependency changes. Some subclasses do override this method because they need to do some expensive work (e.g., network fetches) when their dependencies change, and that work would be too expensive to do for every build.
This is a supplemental answer showing what the OP described.
The State class of a StatefulWidget has a context property. This build context is first available in didChangeDependencies. Trying to use context in initState will cause an error.
class HomeWidget extends StatefulWidget {
const HomeWidget({Key key}) : super(key: key);
#override
_HomeWidgetState createState() => _HomeWidgetState();
}
class _HomeWidgetState extends State<HomeWidget> {
#override
void initState() {
print('initState');
// print(Theme.of(context)); // ERROR!
super.initState();
}
#override
void didChangeDependencies() {
print('didChangeDependencies');
print(Theme.of(context)); // OK
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
print('build');
print(Theme.of(context)); // OK
return Container();
}
}
Running that gives the print statements in the following order:
initState
didChangeDependencies
ThemeData#93b06
build
ThemeData#93b06
See also Working with didChangeDependencies() in Flutter
you can still use context in initState() method, its hack buts works, all you need to do is sought of delay whatever you will need to execute that has context in it like so:
#override
void initState() {
Future.delayed(Duration.zero).then((_) {
// you code with context here
});
super.initState();
}