简体   繁体   中英

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,
                            
                        );
                    }
                }),
          
        ],
    ),
)

There are a few things to be aware of.

  1. Your getter doesn't return a Future and it isn't wrote to do so. What happens is that your pickUpLocation = pickUpAddress expression is immediately evaluated, as it was a synchronous operation, ie before the Future gets converted to an actual value.
  2. You're using a Provider to return the Future 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:

  1. Let your getter properly return and handle a Future ;
  2. Initialize the call to your getter (ie 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 (eg 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) => // ...
}
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,
                        ),
                      ),  
                     )  

For anyone who is facing this problem You can easily click on Options+enter on Mac and click on "wrap with Builder" then just pass the context to your future function

 child: Builder(
                builder: (context) {
                  return FutureBuilder(
                      future: futureFunctionHere(context),
                      builder: (context, AsyncSnapshot snapshot) {

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM