HTTP call on screen load in flutter - flutter

We have a Features class that we are trying to fill when a screen loads. Its an http call that returns the object. Im struggling with how to do this. All of our http calls are done on a button click:
here is the call
Future<Features> getFeatureStatus(String userID) async {
Features _features;
final response =
await http.post('http://url/api/Features',
headers: {"Content-Type": "application/json",
'Accept': 'application/json',},
body: json.encode({'userID' : userID }));
_features = Features.fromJson(json.decode(response.body));
return _features;
}
When i try to call it at the top of the class I get errors and cant get to the values.
class FlutterReduxApp extends StatelessWidget {
static final User user;
static final Features features = getFeatureStatus(user.userId);
The error I get is -- "A value of type 'Future' can't be assigned to a variable of type 'Features'.
Try changing the type of the variable, or casting the right-hand type to 'Features'.dart(invalid_assignment)"
Im sure im doing something incorrect here but I havent done a screen load call yet.

The getFeatureStatus function is returning a Future<Features> while you're trying to read it as type Features in the stateless widget.
There are different ways to read the value but since you have a button, you could convert the widget into a StatefulWidget then use the onPressed function to read the value and update the state afterwards such as.
onPressed: () async {
features = await getFeatureStatus(user.userId);
setState((){
// do something
});
}
In this case, the value features cannot be a static final so you'll have to change it to Features features.
Edit based on comment:
You could also do this inside an initState:
Features features;
#override
void initState () {
super.initState();
_asyncMethod();
}
_asyncMethod() async {
features = await getFeatureStatus(user.userId);
setState((){});
}
so in the widget build method you could do:
return (features == null)
? CircularProgressIndicator()
: MyWidget(...); // where features is used.

getFeatureStatus(user.userId).than((features)
{
// you will get the features object
//you can work on that object
}
);
calling the getFeaturesStatus method in the initState() when using the statefull.

First thing first, this line static final Features features = getFeatureStatus(user.userId); will not work as you are trying to assign a type Future to the type Features.
The solution for this is to await the future so that it resolves and returns a Feature data type that satisfies your variable named 'features'.
This goes as follows: static final Features features = await getFeatureStatus(user.userId); but this has to be in a separate function which is explicitly defined with the async parameter.
This solves the error in the respect of code that you have written, but as you stated that you want this to load after the screen loads (Or technically, when the main widget is "mounted").
The solution for this logical aspect can be the use of this.mounted.
All widgets have a bool this.mounted property. It turns true when the buildContext is assigned.
In short, suppose you want to run a function after any widget is mounted/loaded, you can test it via
if(this.mounted){
//Whatever you want to do when the widget has been mounted...
}

Related

I can't store data in a variable Flutter/Dart [duplicate]

I get the following error:
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
It might be another type instead of int, but basically the pattern is:
A value of type 'Future<T>' can't be assigned to a variable of type 'T'
So:
What exactly is a Future?
How do I get the actual value I want to get?
What widget do I use to display my value when all I have is a Future<T>?
In case you are familiar with Task<T> or Promise<T> and the async/ await pattern, then you can skip right to the "How to use a Future with the widgets in Flutter" section.
What is a Future and how do I use it?
Well, the documentation says:
An object representing a delayed computation.
That is correct. It's also a little abstract and dry. Normally, a function returns a result. Sequentially. The function is called, runs and returns it's result. Until then, the caller waits. Some functions, especially when they access resources like hardware or network, take a little time to do so. Imagine an avatar picture being loaded from a web server, a user's data being loaded from a database or just the texts of the app in multiple languages being loaded from device memory. That might be slow.
Most applications by default have a single flow of control. When this flow is blocked, for example by waiting for a computation or resource access that takes time, the application just freezes. You may remember this as standard if you are old enough, but in today's world that would be seen as a bug. Even if something takes time, we get a little animation. A spinner, an hourglass, maybe a progress bar. But how can an application run and show an animation and yet still wait for the result? The answer is: asynchronous operations. Operations that still run while your code waits for something. Now how does the compiler know, whether it should actually stop everything and wait for a result or continue with all the background work and wait only in this instance? Well, it cannot figure that out on it's own. We have to tell it.
This is achieved through a pattern known as async and await. It's not specific to flutter or dart, it exists under the same name in many other languages. You can find the documentation for Dart here.
Since a method that takes some time cannot return immediately, it will return the promise of delivering a value when it's done.
That is called a Future. So the promise to load a number from the database would return a Future<int> while the promise to return a list of movies from an internet search might return a Future<List<Movie>>. A Future<T> is something that in the future will give you a T.
Lets try a different explanation:
A future represents the result of an asynchronous operation, and can have two states: uncompleted or completed.
Most likely, as you aren't doing this just for fun, you actually need the results of that Future<T> to progress in your application. You need to display the number from the database or the list of movies found. So you want to wait, until the result is there. This is where await comes in:
Future<List<Movie>> result = loadMoviesFromSearch(input);
// right here, you need the result. So you wait for it:
List<Movie> movies = await result;
But wait, haven't we come full circle? Aren't we waiting on the result again? Yes, indeed we are. Programs would be utterly chaotic if they did not have some resemblence of sequential flow. But the point is that using the keyword await we have told the compiler, that at this point, while we want to wait for the result, we do not want our application to just freeze. We want all the other running operations like for example animations to continue.
However, you can only use the await keyword in functions that themselves are marked as async and return a Future<T>. Because when you await something, then the function that is awaiting can no longer return their result immediately. You can only return what you have, if you have to wait for it, you have to return a promise to deliver it later.
Future<Pizza> getPizza() async {
Future<PizzaBox> delivery = orderPizza();
var pizzaBox = await delivery;
var pizza = pizzaBox.unwrap();
return pizza;
}
Our getPizza function has to wait for the pizza, so instead of returning Pizza immediately, it has to return the promise that a pizza will be there in the future. Now you can, in turn, await the getPizza function somewhere.
How to use a Future with the widgets in Flutter?
All the widgets in flutter expect real values. Not some promise of a value to come at a later time. When a button needs a text, it cannot use a promise that text will come later. It needs to display the button now, so it needs the text now.
But sometimes, all you have is a Future<T>. That is where FutureBuilder comes in. You can use it when you have a future, to display one thing while you are waiting for it (for example a progress indicator) and another thing when it's done (for example the result).
Let's take a look at our pizza example. You want to order pizza, you want a progress indicator while you wait for it, you want to see the result once it's delivered, and maybe show an error message when there is an error:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
/// ordering a pizza takes 5 seconds
/// and then gives you a pizza salami with extra cheese
Future<String> orderPizza() {
return Future<String>.delayed(
const Duration(seconds: 5),
() async => 'Pizza Salami, Extra Cheese');
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: Scaffold(
body: Center(
child: PizzaOrder(),
),
),
);
}
}
class PizzaOrder extends StatefulWidget {
#override
_PizzaOrderState createState() => _PizzaOrderState();
}
class _PizzaOrderState extends State<PizzaOrder> {
Future<String>? delivery;
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: delivery != null
? null
: () => setState(() {
delivery = orderPizza();
}),
child: const Text('Order Pizza Now')
),
delivery == null
? const Text('No delivery scheduled')
: FutureBuilder(
future: delivery,
builder: (context, snapshot) {
if(snapshot.hasData) {
return Text('Delivery done: ${snapshot.data}');
} else if(snapshot.hasError) {
return Text('Delivery error: ${snapshot.error.toString()}');
} else {
return const CircularProgressIndicator();
}
})
]);
}
}
This is how you use a FutureBuilder to display the result of your future once you have it.
Here's a list of analogies to Dart's Future from other languages:
JS: Promise
Java: Future
Python: Future
C#: Task
Just like in other languages Future is a special type of object which allows to use async/await syntax sugar, write asynchronous code in synchronous/linear way. You return Future from an async method rather than accept a callback as a parameter and avoid the callback hell - both Futures and callbacks solve same problems (firing some code at a latter time) but in a different way.
Future<T> returning the potential value which will be done by async work
Eg:
Future<int> getValue() async {
return Future.value(5);
}
Above code is returning Future.value(5) which is of int type, but while receiving the value from method we can't use type Future<int> i.e
Future<int> value = await getValue(); // Not Allowed
// Error
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
To solve above getValue() should be received under int type
int value = await getValue(); // right way as it returning the potential value.
I hope this key point will be informative, I show it in two different Async methods:
Note the following method where showLoading(), getAllCarsFromApi() and hideLoading() are inner Async methods.
If I put the await keyword before showLoading(), the Operation waits until it's done then goes to the next line but I intentionally removed the await because I need my Loading dialog be displayed simultaneously with getAllCarsFromApi() is being processed, so it means showLoading() and getAllCarsFromApi() methods are processed on different Threads. Finally hideLoading() hides the loading dialog.
Future<List<Car>> getData() async{
showLoading();
final List<Car> cars = await getAllCarsFromApi();
hideLoading();
return cars;
}
Now look at this another Async method, here the getCarByIdFromApi() method needs an id which is calculated from the getCarIdFromDatabase(), so there must be an await keyword before the first method to make the Operation wait until id is calculated and passed to the second method. So here two methods are processed one after another and in a single Thread.
Future<Car> getCar() async{
int id = await getCarIdFromDatabase();
final Car car = await getCarByIdFromApi(id);
return car;
}
A simple answer is that if a function returns its value with a delay of some time, Future is used to get its value.
Future<int> calculate({required int val1, required int val2}) async {
await Future.delayed(const Duration(seconds: 2));
return val1 + val2;
}
if we call the above function as
getTotal() async {
int result = calculate(val1: 5, val2: 5);
print(result);
}
we will get the following error:
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
but if we use await before function call it will give the actual returned value from the function after a delay
getTotal() async {
int result = await calculate(val1: 5, val2: 5);
print(result);
}
the keyword async is required to use await for the Future to get returned value
I am trying to give very simple example. Suppose you have ordered something online, let it be a shirt. then you have to wait until the order is dispatched and delivered to your home. In the meanwhile you will not stop working your daily activities/work anything you do and after a day if it delivered to your home you will collect it and wear it. Now, look at the following example.
Ok, now let's make a function which handles our order delivery.(Read Comments Also)
//order function which will book our order and return our order(which is our shirt). don't focus on Order object type just focus on how this function work and you will get to know about future definitely.
Future<Order> orderSomething(){
//here our order processing and it will return our order after 24 hrs :)
await Future.delayed(const Duration(hours: 24),() => Order('data'));
}
Now
void main() {
//now here you have called orderSomething() and you dont want to wait for it to be delivered
//you are not dependent on your order to do your other activities
// so when your order arrives you will get to know
orderSomething()
wearSomething()
goingCollege()
}
Now if you are dependent on your order then you have to add await async ( i will show you where)
void main() async{
//now you're dependent on your order you want to wait for your order
await orderSomething()
wearOrderedShirt() // :)
goingCollege()
}
Now most of the times in flutter applications you will have to await for your API calls(Network), for background task for downloading/uploading, for database calls etc.

My stateful widget does not update the ui, even though I am calling setState and passing the correct value to the widget class

I ran into a bit of a problem in flutter that I could not figure out myself.
So I have the following code (only the important parts of the code are pasted),
class _PrimaryDetailsEditState extends State<PrimaryDetailsEdit> {
String bankName; <---------- I have this variable inside my state class
void _getFormData() {
BackendApi.call(
endpoint: APIEndPoints.GET_FORM_DATA,
data: {'id': widget.jobId},
onSuccess: (data) {
setState(() {
bankName = data['clientBank']['name']; <-------------- I assign a new value to the above variable here
print('Bank name is $bankName'); <-------------- This print confirms that the new value has been indeed assigned to the variable
});
},
onFailed: (error) {
print('Failed to get data');
});
}
#override
void initState() {
_getFormData(); <------- I call the _getFormData function declared above right here.
super.initState();
}
In the above code,
👉 I have a class variable called bankName that is of data type string.
👉 _getFormData method defined below the bankName, calls another static method called BackendApi.call() which is responsible for making an http request to fetch some data. The BackendApi.call() takes 4 named parameters and two of the important parameters are onSuccess and onFailed. These two parameters take callback functions to be called in cases where the data fetching is successful or not.
👉 As you can see, on onSuccess callback I assign some of the data that is received from the http request to the bankName variable.
👉 I also call the _getFormData inside the #override initState() to make sure that request is issued before the widget is mounted.
Problem
Even though bankName holds the data I want, when that variable is passed down to a widget, my UI is not updated!
âž¡ I am passing the correct variable to the widget to update the UI. I confirmed this by commenting out the _getFormData function and initially assigning a value to the bankName a value like bankName = 'Hello'. If I do this, the UI is updated with the value Hello. So I am passing the correct variable to the widget.
Thank you!
I think that the only thing you need to do is to change the row with onSuccess to this:
onSuccess: (data) => {
When you have it the way it is now:
onSuccess: (data) {
you are assigning onSuccess to the value of the function and not to the function itself.

Mocking Hive with Mockito Flutter

So basically, I want to check whether I've passed whatever I need to pass to the HiveInterface and Box when I want to store something.
test.dart:
group('cacheStoraygeUser', () {
test(
'should call HiveInterface and Box to cache data',
() async {
when(mockHiveInterface.openBox(any)).thenAnswer((_) async => mockBox);
when(mockBox.put(0, tStoraygeUserModel))
.thenAnswer((_) async => tStoraygeUserModel);
// act
dataSourceImpl.cacheStoraygeUser(tStoraygeUserModel);
// assert
verify(mockHiveInterface.openBox(STORAYGE_USER_BOX));
verify(mockBox.put(STORAYGE_USER_ENTRY, tStoraygeUserModel));
},
);
});
My implementation for dataSourceImpl.cacheStoraygeUser():
#override
Future<void> cacheStoraygeUser(
StoraygeUserModel storaygeUserModelToCache) async {
/// Precaution to ensure that [STORAYGE_USER_BOX] has been opened.
///
/// If the box, is in fact not opened, Hive will just return the box since
/// the box is a Singleton. I think.
final box = await hiveInterface.openBox(STORAYGE_USER_BOX);
box.put(STORAYGE_USER_ENTRY, storaygeUserModelToCache);
}
When I try to run the test, it gives this error:
type 'Null' is not a subtype of type 'Future<void>'
MockBox.put
package:hive/…/box/box_base.dart:80
I already generated the mock classes for HiveInterface and Box. I think this is how I should do it if I want to test Hive, since I can't seem to generate Mock classes for Hive itself. But if you know a better or the correct solution then please tell me.
I also wrote another test for getting stuff from Hive. This works perfectly fine.
test(
'should return StoraygeUser from StoraygeUserBox when there is one in the cache',
() async {
// arrange
when(mockHiveInterface.openBox(any)).thenAnswer((_) async => mockBox);
when(mockBox.getAt(any)).thenAnswer((_) async => tStoraygeUserModel);
// act
final result = await dataSourceImpl.getCachedStoraygeUser();
// assert
verify(mockHiveInterface.openBox(any));
verify(mockBox.getAt(any));
expect(result, equals(tStoraygeUserModel));
},
);
Thanks in advance!
This problem has been fixed in Mockito 5.0.9
The problem stems from the fact that Box implements BoxBase rather than extends it.
Older versions of Mockito couldn't pick this up and thus, putAt and getAt and other methods are not generated in the mock classes.

What is a Future and how do I use it?

I get the following error:
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
It might be another type instead of int, but basically the pattern is:
A value of type 'Future<T>' can't be assigned to a variable of type 'T'
So:
What exactly is a Future?
How do I get the actual value I want to get?
What widget do I use to display my value when all I have is a Future<T>?
In case you are familiar with Task<T> or Promise<T> and the async/ await pattern, then you can skip right to the "How to use a Future with the widgets in Flutter" section.
What is a Future and how do I use it?
Well, the documentation says:
An object representing a delayed computation.
That is correct. It's also a little abstract and dry. Normally, a function returns a result. Sequentially. The function is called, runs and returns it's result. Until then, the caller waits. Some functions, especially when they access resources like hardware or network, take a little time to do so. Imagine an avatar picture being loaded from a web server, a user's data being loaded from a database or just the texts of the app in multiple languages being loaded from device memory. That might be slow.
Most applications by default have a single flow of control. When this flow is blocked, for example by waiting for a computation or resource access that takes time, the application just freezes. You may remember this as standard if you are old enough, but in today's world that would be seen as a bug. Even if something takes time, we get a little animation. A spinner, an hourglass, maybe a progress bar. But how can an application run and show an animation and yet still wait for the result? The answer is: asynchronous operations. Operations that still run while your code waits for something. Now how does the compiler know, whether it should actually stop everything and wait for a result or continue with all the background work and wait only in this instance? Well, it cannot figure that out on it's own. We have to tell it.
This is achieved through a pattern known as async and await. It's not specific to flutter or dart, it exists under the same name in many other languages. You can find the documentation for Dart here.
Since a method that takes some time cannot return immediately, it will return the promise of delivering a value when it's done.
That is called a Future. So the promise to load a number from the database would return a Future<int> while the promise to return a list of movies from an internet search might return a Future<List<Movie>>. A Future<T> is something that in the future will give you a T.
Lets try a different explanation:
A future represents the result of an asynchronous operation, and can have two states: uncompleted or completed.
Most likely, as you aren't doing this just for fun, you actually need the results of that Future<T> to progress in your application. You need to display the number from the database or the list of movies found. So you want to wait, until the result is there. This is where await comes in:
Future<List<Movie>> result = loadMoviesFromSearch(input);
// right here, you need the result. So you wait for it:
List<Movie> movies = await result;
But wait, haven't we come full circle? Aren't we waiting on the result again? Yes, indeed we are. Programs would be utterly chaotic if they did not have some resemblence of sequential flow. But the point is that using the keyword await we have told the compiler, that at this point, while we want to wait for the result, we do not want our application to just freeze. We want all the other running operations like for example animations to continue.
However, you can only use the await keyword in functions that themselves are marked as async and return a Future<T>. Because when you await something, then the function that is awaiting can no longer return their result immediately. You can only return what you have, if you have to wait for it, you have to return a promise to deliver it later.
Future<Pizza> getPizza() async {
Future<PizzaBox> delivery = orderPizza();
var pizzaBox = await delivery;
var pizza = pizzaBox.unwrap();
return pizza;
}
Our getPizza function has to wait for the pizza, so instead of returning Pizza immediately, it has to return the promise that a pizza will be there in the future. Now you can, in turn, await the getPizza function somewhere.
How to use a Future with the widgets in Flutter?
All the widgets in flutter expect real values. Not some promise of a value to come at a later time. When a button needs a text, it cannot use a promise that text will come later. It needs to display the button now, so it needs the text now.
But sometimes, all you have is a Future<T>. That is where FutureBuilder comes in. You can use it when you have a future, to display one thing while you are waiting for it (for example a progress indicator) and another thing when it's done (for example the result).
Let's take a look at our pizza example. You want to order pizza, you want a progress indicator while you wait for it, you want to see the result once it's delivered, and maybe show an error message when there is an error:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
/// ordering a pizza takes 5 seconds
/// and then gives you a pizza salami with extra cheese
Future<String> orderPizza() {
return Future<String>.delayed(
const Duration(seconds: 5),
() async => 'Pizza Salami, Extra Cheese');
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: Scaffold(
body: Center(
child: PizzaOrder(),
),
),
);
}
}
class PizzaOrder extends StatefulWidget {
#override
_PizzaOrderState createState() => _PizzaOrderState();
}
class _PizzaOrderState extends State<PizzaOrder> {
Future<String>? delivery;
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: delivery != null
? null
: () => setState(() {
delivery = orderPizza();
}),
child: const Text('Order Pizza Now')
),
delivery == null
? const Text('No delivery scheduled')
: FutureBuilder(
future: delivery,
builder: (context, snapshot) {
if(snapshot.hasData) {
return Text('Delivery done: ${snapshot.data}');
} else if(snapshot.hasError) {
return Text('Delivery error: ${snapshot.error.toString()}');
} else {
return const CircularProgressIndicator();
}
})
]);
}
}
This is how you use a FutureBuilder to display the result of your future once you have it.
Here's a list of analogies to Dart's Future from other languages:
JS: Promise
Java: Future
Python: Future
C#: Task
Just like in other languages Future is a special type of object which allows to use async/await syntax sugar, write asynchronous code in synchronous/linear way. You return Future from an async method rather than accept a callback as a parameter and avoid the callback hell - both Futures and callbacks solve same problems (firing some code at a latter time) but in a different way.
Future<T> returning the potential value which will be done by async work
Eg:
Future<int> getValue() async {
return Future.value(5);
}
Above code is returning Future.value(5) which is of int type, but while receiving the value from method we can't use type Future<int> i.e
Future<int> value = await getValue(); // Not Allowed
// Error
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
To solve above getValue() should be received under int type
int value = await getValue(); // right way as it returning the potential value.
I hope this key point will be informative, I show it in two different Async methods:
Note the following method where showLoading(), getAllCarsFromApi() and hideLoading() are inner Async methods.
If I put the await keyword before showLoading(), the Operation waits until it's done then goes to the next line but I intentionally removed the await because I need my Loading dialog be displayed simultaneously with getAllCarsFromApi() is being processed, so it means showLoading() and getAllCarsFromApi() methods are processed on different Threads. Finally hideLoading() hides the loading dialog.
Future<List<Car>> getData() async{
showLoading();
final List<Car> cars = await getAllCarsFromApi();
hideLoading();
return cars;
}
Now look at this another Async method, here the getCarByIdFromApi() method needs an id which is calculated from the getCarIdFromDatabase(), so there must be an await keyword before the first method to make the Operation wait until id is calculated and passed to the second method. So here two methods are processed one after another and in a single Thread.
Future<Car> getCar() async{
int id = await getCarIdFromDatabase();
final Car car = await getCarByIdFromApi(id);
return car;
}
A simple answer is that if a function returns its value with a delay of some time, Future is used to get its value.
Future<int> calculate({required int val1, required int val2}) async {
await Future.delayed(const Duration(seconds: 2));
return val1 + val2;
}
if we call the above function as
getTotal() async {
int result = calculate(val1: 5, val2: 5);
print(result);
}
we will get the following error:
A value of type 'Future<int>' can't be assigned to a variable of type 'int'
but if we use await before function call it will give the actual returned value from the function after a delay
getTotal() async {
int result = await calculate(val1: 5, val2: 5);
print(result);
}
the keyword async is required to use await for the Future to get returned value
I am trying to give very simple example. Suppose you have ordered something online, let it be a shirt. then you have to wait until the order is dispatched and delivered to your home. In the meanwhile you will not stop working your daily activities/work anything you do and after a day if it delivered to your home you will collect it and wear it. Now, look at the following example.
Ok, now let's make a function which handles our order delivery.(Read Comments Also)
//order function which will book our order and return our order(which is our shirt). don't focus on Order object type just focus on how this function work and you will get to know about future definitely.
Future<Order> orderSomething(){
//here our order processing and it will return our order after 24 hrs :)
await Future.delayed(const Duration(hours: 24),() => Order('data'));
}
Now
void main() {
//now here you have called orderSomething() and you dont want to wait for it to be delivered
//you are not dependent on your order to do your other activities
// so when your order arrives you will get to know
orderSomething()
wearSomething()
goingCollege()
}
Now if you are dependent on your order then you have to add await async ( i will show you where)
void main() async{
//now you're dependent on your order you want to wait for your order
await orderSomething()
wearOrderedShirt() // :)
goingCollege()
}
Now most of the times in flutter applications you will have to await for your API calls(Network), for background task for downloading/uploading, for database calls etc.

Flutter: what's the most performant, seamless way to access SharedPreferences in multiple classes?

So I call
SharedPReference prefs = await SharedPreferences.getInstance();
In my parent Main.dart. Then when the user presses a button in the ButtonBar it gets sent away using
Navigator.push(context, MaterialPageRoute(
builder: (BuildContext context) => Agenda(prefs: prefs,)));
I'm concerned this is not the most robust or performant way to use shared Preferences on child screens. For instance say the user navigated away from Main.dart before I have an instance of SharedPreferences, then the child screen - Agenda.dart - will have null passed into it as the value of prefs.
What happens then? Is the child screen stuck trying to access fields which are null? When Main.dart async process gets an instance of SharedPreferences does it feed it through to the child screen/class?
Would it be more robust to summon SharedPreferences in every class that uses it? Using:
SharedPReference prefs = await SharedPreferences.getInstance();
I feel passing prefs around is better because it reduces bottlenecks, but I don't fully understand.
When I modify values in SharedPreferences in child classes, then BACK button to it's parent screen, will the parent screen be accessing an updated SharedPreference, automagically?
getInstance() will check _completer , because _completer is static
the first time execution will keep reference in SharedPreferences._(this._preferenceCache)
the next time you call getInstance() will always directly return _completer.future; reference of cache, see picture below
no matter where you call getInstance() , even in SecondRoute
Source code of getInstance()
class SharedPreferences {
SharedPreferences._(this._preferenceCache);
static Completer<SharedPreferences> _completer;
...
static Future<SharedPreferences> getInstance() async {
if (_completer == null) {
_completer = Completer<SharedPreferences>();
try {
final Map<String, Object> preferencesMap =
await _getSharedPreferencesMap();
_completer.complete(SharedPreferences._(preferencesMap));
} on Exception catch (e) {
// If there's an error, explicitly return the future with an error.
// then set the completer to null so we can retry.
_completer.completeError(e);
final Future<SharedPreferences> sharedPrefsFuture = _completer.future;
_completer = null;
return sharedPrefsFuture;
}
}
return _completer.future;
}
What happens then? Is the child screen stuck trying to access fields which are null? When Main.dart async process gets an instance of SharedPreferences does it feed it through to the child screen/class?
This depends on your code. If you await for the SharedPreferences instance and then navigate, it's OK. But if you dont await it, then null will be passed to the next screen and no, it wont be feed through to the child screen/class.
When I modify values in SharedPreferences in child classes, then BACK button to it's parent screen, will the parent screen be accessing an updated SharedPreference, automagically?
Yes, it will, but not automagically. When you pass an object to another class or function in Dart, actually the object itself is not passed. A reference to that object is passed. A reference to an object is like an address to an object's place in memory.
And generally about your question, you can make use of the get_it and injectable packages. get_it is a simple Service Locator for Dart and Flutter projects with some additional goodies and Injectable is a convenient code generator for get_it.
Using these two packages you can register modules, which is a good place for SharedPrefrences. Take a look at this example to learn more about it.
Another way can be making use of an inherited widget. You can provide a value to the whole widget tree, and then you can access that value (that in your case would be SharedPrefrences instance) anywhere in your widgets.