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

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?;
}
}

Related

What does the "State" class constructor look like in Flutter?

What does super.didChangeDependencies(); do in the following code?
void didChangeDependencies() {
if (_isInit) {
setState(() {
_isLoading = true;
});
Provider.of<Contests>(context).fetchAndSetContests().then((_) {
setState(() {
_isLoading = false;
});
});
}
_isInit = false;
super.didChangeDependencies();
}
I know it should call the constructor of the State class but I can't find the constructor's definition and don't understand what is the purpose of calling that?
From the Flutter docs for didChangeDependencies:
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.
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.

Best practice for passing param Riverpod's providers only once

I just want to build a Provider which asks params only one and inits correctly.
Since I am just passing params only once, I don't prefer to use .family methods.
I prefer to use .autoDispose which considered the better way.
Here my tryouts:
I tried to make my own .init() method. But it's disposing as soon as method called if it's .autodispose() and the widget not started to listen my provider yet (that's expected). Therefore I couldn't consider a safe way to do that.
I tried .overrideWith() method in a widget basis. But it's neither worked nor I am sure that it's best practice.
Here is my simple code:
class MyHomePage extends ConsumerWidget {
const MyHomePage({super.key});
final myString = 'Hey';
#override
Widget build(BuildContext context, WidgetRef ref) {
//Not worked
ProviderContainer(
overrides: [messageProvider.overrideWith(() => ViewModel(myString))]);
return Scaffold(
body: ProviderScope(
//Not worked either
overrides: [messageProvider.overrideWith(() => ViewModel(myString))],
child: Center(
//I just didn't use .when to shorter code
child: Text(ref.watch(messageProvider).value!.counter.toString()),
),
),
);
}
}
final messageProvider = AsyncNotifierProvider.autoDispose<ViewModel, Model>(
() => throw UnimplementedError());
class ViewModel extends AutoDisposeAsyncNotifier<Model> {
final String param;
ViewModel(this.param);
#override
FutureOr<Model> build() {
//Make some fetch with param, (only once!)
return Model(param.length);
}
}
When I run that. It gives UnimplementedError
Waiting your suggestions & fixes. Thanks in advance!
Expected:
Works properly.
#riverpod
ViewModel myViewModel(MyViewModelRef ref, String param){
return ViewModel(param);
}
This is autoDispose by default in Riverpod 2. If you don't want to auto dispose you can use #Riverpod(keepalive:true) instead of #riverpod
If you don't want to pass the param to the provider, you can eliminate it and hardcode the value to the ViewModel, but at that point, if there are no other dependencies, might as well make it a public final variable in some file, since it looks like this is a singleton that never changes so it is questionable what you'd achieve by making it a Riverpod provider.

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.

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.

How to set Provider value from class field?

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.