'How can I use Future builder with provider?
My main objective is to show a CircularProgressIndicator
before I get a location address placename
but I keep getting this error The getter 'placeName' was called on null.
.I did try to check the null
value in futureBuilder
but I believe my implementation is wrong. Could you please take a look ?
This is my AppData
class
class AppData extends ChangeNotifier {
Address pickUpLocation;
void updatePickUpLocationAddress(Address pickUpAddress) {
pickUpLocation = pickUpAddress;
notifyListeners();
}
}
and this is the Address
class
class Address {
String placeFormattedAddress;
dynamic placeName;
String placeId;
double latitude;
double longitude;
Address(
{this.placeFormattedAddress,
this.placeName,
this.placeId,
this.latitude,
this.longitude});
}
Now in my MainScreen
I am using it like this but the error persisting.
@override
Widget build(BuildContext context) {
\\\
body: Stack(
\\\
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FutureBuilder (
future: Provider.of < AppData > (context)
.pickUpLocation
.placeName,
builder: (context, snapshot) {
if (snapshot.data == null) {
return CircularProgressIndicator();
} else {
return Text(
Provider.of < AppData > (context)
.pickUpLocation
.placeName,
style: TextStyle(fontSize: 12.0),
overflow: TextOverflow.ellipsis,
);
}
}),
],
),
)
Solution 1:[1]
There are a few things to be aware of.
- Your getter doesn't return a
Future
and it isn't wrote to do so. What happens is that yourpickUpLocation = pickUpAddress
expression is immediately evaluated, as it was a synchronous operation, i.e. before theFuture
gets converted to an actual value. - You're using a
Provider
to return theFuture
you're looking for (that's good), but doing it like that is erroneous since it would basically "mess up" with your Widget tree: say there's an update to your Provider and a Future has completed: what sub-tree should be rendered first?
To fix your situation you have to:
- Let your getter properly return and handle a
Future
; - Initialize the call to your getter (i.e. the Future call) before you call the build method, so that:
- The aforementioned problem disappears;
- You do not run into another classic problem: calling the Future (e.g. sending a back end request) on every UI re-build.
Here's how I'd change your provider model/class:
class AppData extends ChangeNotifier {
Address? pickUpLocation;
Future<void> updatePickUpLocationAddress() async {
// There should be error handling, as well (try-catch-finally clauses)
var pickUpLocation = await Future.delayed(Duration(seconds: 5)); // I'm mocking a request, here
notifyListeners();
}
}
Now, to initialize the Future you either do so in the above Provider (in the Constructor of AppData
), or you have to change your Widget to be Stateful
so that we can access to the initState()
method, in which we can initialize the Future without worrying about multiple calls. In your (now Stateful
) Widget:
var myFuture;
// ...
void initState() {
myFuture = Provider.of<AppData>(context).updatePickUpLocationAddress();
}
// ...
Widget build (BuildContext context) {
return // ...
FutureBuilder(
future: myFuture, // already initialized, won't re-initalize when build() is called
builder: (ctx, snapshot) => // ...
}
Solution 2:[2]
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (Provider.of < AppData > (context)
.pickUpLocation ==
null)
CircularProgressIndicator(),
if (Provider.of < AppData > (context)
.pickUpLocation !=
null)
Text(
Provider.of < AppData > (context)
.pickUpLocation
.placeName,
style: TextStyle(fontSize: 12.0),
overflow: TextOverflow.ellipsis,
),
),
)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Pannam T |