How to set Provider value from class field? - flutter

I am using Provider. I have got class. And I need to set value to Provider from field. But I do not have access to context here.
class _DateAndTimePickerDemoState extends State<DateAndTimePickerDemo>
{
DateTime _fromDate = DateTime.now();
Provider.of<AppState>(context).selected_period = date; // here
}
How to set Provider value from class field?

Option No 1
You can use override method initState()
#override
void initState() {
super.initState();
Future.delayed(Duration.zero,(){
Provider.of<AppState>(context).selected_period = /* your value */;
});
}
Option No 2
You can set value in build() method also
#override
Widget build(BuildContext context) {
Provider.of<AppState>(context).selected_period = /* your value */;
// your rest of code is write here
}

You can get context in two ways:
Inside initState by calling Future.delayed(Duration.zero,() {... context ...})
After build method has been called Widget build(BuildContext context) {...}
In your case, I would call Provider.of<AppState>(context).selected_period = date; after build method, because most of the functions I define inside build method anyways so I can easily access context.

Use Provider.of<AppState>(context, listen: false).selected_period = /* your value */;
You need to set listen to false because Provider is used outside the build method.

Related

Get Route arguments through BuildContext requires putting it inside build()?

From https://docs.flutter.dev/cookbook/navigation/navigate-with-arguments it appears the preferred way of getting the arguments from a route is using:
ModalRoute.of(context)!.settings.arguments;
But since it requires the context argument it must be called inside the build method:
Widget build(BuildContext context) {
String id = ModalRoute.of(context)!.settings.arguments as String;
var data = httpGet(id);
return Scaffold(
//...
);
}
This means that every time the widget is rebuilt, the route argument will be refetched all over again and possibly another network call too.
The obvious solution is to have a boolean like wasFetched somewhere and add a conditional inside the build method.
Are most people doing the latter?
Edit:
Based on the first reply be #miguel-ruivo, I learned that context can actually be accessed as a property on the State object, and therefore accessible from initState.
In the end I went down the rabbit hole and found that I could call it from didChangeDependencies without needing to use addPostFrameCallback from WidgetsBindings.instance.
According to:
https://api.flutter.dev/flutter/widgets/State/didChangeDependencies.html
It says:
This method is also called immediately after initState. It is safe to
call BuildContext.dependOnInheritedWidgetOfExactType from this method.
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.
And since it says dependOnInheritedWidgetOfExactType is safe to call, it would follow that anything dependent on BuildContext is safe to call.
So now I have put the code inside it:
didChangeDependencies() {
String id = ModalRoute.of(context)!.settings.arguments as String;
//do http
}
You can call it only once per widget initialisation by moving it to your initState() and scheduling a post frame so it only accesses BuildContext after rendering.
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(() {
String id = ModalRoute.of(context)!.settings.arguments as String;
var data = httpGet(id);
});
}
You can actually access context in your initState method without having to pass it as a parameter.
class _YourScreenState extends State<YourScreen> {
late String? id;
#override
void initState() {
super.initState();
id = ModalRoute.of(context)!.settings.arguments as String?;
}
}

Initializing State property on heap versus on initState function in Flutter

coming from Kotlin(Android) I wanted to know which of the below code is the recommended way to initialize a simple boolean state property in flutter
...
class _LogInPagePageState extends State<LogInPage> {
bool obscurePasswords = true; //Is it better to set the initial value of obscurePasswords here?
#override
void initState() {
super.initState();
obscurePasswords = true; //Or is it more preferred here?
}
....
Which is the best place to create the initial state of something as simple as the obscurePassword property

setState not updating variable when declared on initState

I have run into an issue with Flutter: I have a navbar which, depending on the item selected, returns me an integer number called Index. This index is then passed through a List to get the content of the body of the Scaffold, an object of Widget() class.
The default body is an object of HomePage(), that has an integer parameter called rpm. By default, the HomePage() should be the body displayed, so, as it depends on the rpm parameter, I declare the rpm parameter on the initState. I also have a setState that changes dynamically the rpm.
The weird thing is: if I declare the List<Widgets> bodyList in the initState, the setState doesn't seem to work. However, if I declare List<Widgets> bodyList in the build method, I can see the content of the HomePage() change dynamically with the rpm.
An excerpt of the code. Not working:
class _ScreenTreeState extends State<ScreenTree> {
int _index;
int _rpm;
List<Widget> bodyList;
#override
void initState() {
_isPlaying = false;
_rpm = 0;
_index = 0;
bodyList = [
HomePage(rpm: _rpm),
StatisticsScreen(),
WeightScreen(),
SettingsPage()
];
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(...
Working:
class _ScreenTreeState extends State<ScreenTree> {
int _index;
int _rpm;
List<Widget> bodyList;
#override
void initState() {
_isPlaying = false;
_rpm = 0;
_index =
0;
super.initState();
}
#override
Widget build(BuildContext context) {
bodyList = [
HomePage(rpm: _rpm),
StatisticsScreen(),
WeightScreen(),
SettingsPage()
];
return Scaffold(...
My theory is that this may have to do with the fact that if _rpm is declared as an integer, Dart reads it as a primitive and not as the object of an integer class, so if declared on the initState, I'm not actually passing an object, but a primitive. But I don't understand then why is it working when declared in the build method.
I feel this is irrelevant, but if somebody needs it, here it is the code that updates the value of _rpm. Please, don't read too much into this because I feel it is actually not relevant and may distract and confuse more than help:
onPressed: () {
if (_isPlaying) {
setState(() {
_isPlaying = false;
});
Provider.of<MicrophoneEngine>(context, listen: false)
.stopRecording();
} else {
setState(() {
_isPlaying = true;
});
Provider.of<MicrophoneEngine>(context, listen: false)
.startRecording((rpmCall) {
setState(() {
_rpm = rpmCall;
});
});
}
},
It's a bad practice to keep references for the widgets(like in the first example), in fact, you don't need that List at all.
Every time you use setState, you rebuild the widgets and you pass the new value. In the first example you pass the HomePage(rpm: _rpm) from the init every time, that's why it is not updated. Meaning, you don't rebuild HomePage with the updated value, you just passing the HomePage from the initState which was instantiated with the initial _rpm value of 0.

Accessing context in flutter

I have a screen with a form that displays a drop-down list of counties.
When the screen initially loads I want to set the default to the current country.
class _SignInScreenState extends State<SignInScreen> {
final formKey = new GlobalKey<FormState>();
String countryCode = _CountryCode();
_countryCode() {
Locale myLocale = Localizations.localeOf(context);
return myLocale.countryCode;
}
#override
Widget build(BuildContext context) {...
This results in the following error: "Only static members can be accessed in initializers". I researched this and it stated the solution was to initialise variables in initState(), as shown below:
#override
initState() {
super.initState();
countryCode = _countryCode();
}
This does not produce an error however in the widget tree the value of countryCode is null whereas in the widget tree _countryCode() displays "US" correctly.
If I set the value of countryCode in init states does this not mean it will be reset every time the widget tree is redrawn?
The main purpose of initState is for initializing variables and it'll only be called when the widget gets destroyed, so as long as dispose method of the widget is not called your variable which was initialized through initState would be alive and you can use it in build method.
Regarding build method, so whenever you change something in widget Flutter will call build method and rerender the widget through it's returned content and it has nothing to do with initState., this is essentially called 'Hot Reload'. Hope this resolves your query.
More info here: https://api.flutter.dev/flutter/widgets/State-class.html
Change your code to :
class _SignInScreenState extends State<SignInScreen> {
final formKey = new GlobalKey<FormState>();
String countryCode;
#override
initState() {
Locale myLocale = Localizations.localeOf(context);
countryCode = myLocale.countryCode;
}
// use variable here...
#override
Widget build(BuildContext context) {...

Get InheritedWidget parameter in initState

i need some help understanding how to obtain data from inherited widget.
I usually get the parameter from my widget directly from the build method using
#override
Widget build(BuildContext context) {
//THIS METHOD
var data = StateContainer.of(context).data;
return Container(child:Text("${data.parameter}"));
}
But this method cant be called from initState since there is no buildContext yet.
I need in the initState method to have that parameter (i call my fetch from server in that and i need to pass that data to my function), so, how should i do it?
#override
void initState() {
otherData = fetchData(data);
super.initState();
}
I tried using didChangeDipendencies() but it is called every time the view is rebuilt (popping from screen, etc.) so it is not what i want to use and neither the FutureBuilder widget.
Any suggestion?
First, note that you probably do want to use didChangeDependencies. But you can't just do your call there without any check. You need to wrap it in an if first.
A typical didChangeDependencies implementation should look similar to:
Foo foo;
#override
void didChangeDependencies() {
super.didChangeDependencies();
final foo = Foo.of(context);
if (this.foo != foo) {
this.foo = foo;
foo.doSomething();
}
}
Using such code, doSomething will be executed only when foo changes.
Alternatively, if you are lazy and know for sure that your object will never ever change, there's another solution.
To obtain an InheritedWidget, the method typically used is:
BuildContext context;
InheritedWidget foo = context.inheritFromWidgetOfExactType(Foo);
and it is this method that cannot be called inside initState.
But there's another method that does the same thing:
BuildContext context;
InheritedWidget foo = context.ancestorInheritedElementForWidgetOfExactType(Foo)?.widget;
The twist is:
- this method can be called inside initState
- it won't handle the scenario where the value changed.
So if your value never changes, you can use that instead.
1, If you only need InheritedWidget as a Provider of parameter for Widget.
You can using on initState as bellow:
#override
void initState() {
super.initState();
var data = context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;
}
2, If you need listener to re-render widget when data of InheritedWidget change. I suggest you wrapper your StatefulWidget insider a StatelessWidget,
parameter of StatefulWidget is passed from StatelessWidget, when InheritedWidget change data, it will notify to StatelessWidget, on StatefulWidget we will get change on didChangeDependencies and you can refresh data.
This is code guide:
class WrapperDemoWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
DemoData data = StateContainer.of(context).data;
return Container();
}
}
class ImplementWidget extends StatefulWidget {
DemoData data;
ImplementWidget({this.data});
#override
_ImplementWidgetState createState() => _ImplementWidgetState();
}
class _ImplementWidgetState extends State<ImplementWidget> {
#override
void initState() {
super.initState();
//TODO Do sth with widget.data
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
//TODO Do change with widget.data
}
#override
Widget build(BuildContext context) {
return Container();
}
}
I prefer the solution with didChangeDependencies because Future.delayed solution is a bit hack, looks unprofessional and unhealthy. However, it works out of the box.
This is the solution I prefer:
class _MyAppState extends State<MyApp> {
bool isDataLoaded = false;
#override
void didChangeDependencies() {
if (!isDataLoaded) {
otherData = fetchData(data).then((_){
this.isDataLoaded = true;
});
}
super.didChangeDependencies();
}
...
You can also get the context in initState, try using a future with duration zero. You can find some examples here
void initState() {
super.initState();
Future.delayed(Duration.zero,() {
//use context here
showDialog(context: context, builder: (context) => AlertDialog(
content: Column(
children: <Widget>[
Text('#todo')
],
),
actions: <Widget>[
FlatButton(onPressed: (){
Navigator.pop(context);
}, child: Text('OK')),
],
));
});
}
i use it to make loading screens using inherited widgets and avoid some global variables