TransWikia.com

How to fetch initial data using provider in flutter effectievly

Stack Overflow Asked by Jithin on November 27, 2020

Recently, i did a flutter course.
The instructor was making the get request from an API so difficult. For a hybrid framework like flutter i never thought it’s so difficult.

below are my code. I am using provider for state management.

Future<void> fetchAndSetProducts() async {
    try {
      const url = 'fetch-url';
      final response = await http.get(url);
      final data = json.decode(response.body) as Map<String, dynamic>;
      final List<Product> loadedProducts = [];
      data.forEach((key, value) {
        loadedProducts.add(Product(
          id: key,
          title: value['title'],
          description: value['description'],
          imageUrl: value['imageUrl'],
          price: value['price'],
          isFavorite: value['isFavorite'],
        ));
      });

      _items = loadedProducts;
      notifyListeners();
    } catch (error) {
      throw (error);
    }
  }

And in the products overview screen were I am showing the products page this method is called like below:

bool _isInit = true;
  bool _isLoading = false;

  @override
  void didChangeDependencies() {
    if (_isInit) {
      setState(() {
        _isLoading = true;
      });
      Provider.of<Products>(context).fetchAndSetProducts().then((_) => {
            setState(() {
              _isLoading = false;
            })
          });
    }
    _isInit = false;
    super.didChangeDependencies();
  }

The other method included a sneaky way of using duration of zero just like we use in javascript set timeout giving a zero time.

It’s worth noting that in didChangeDependencies we could not use async await, so most probably a call back hell awaits.
Also a variable needs to be initialized just for calling the api once upon loading.

Is there no easy solution to this? Or an industry way of dealing with this?

4 Answers

here is a minimal working example of what you should do, it's not the best thing in the world, but this is what works for me, let me know if you can make it any better.

The answer to you problem is really simple, BUT, you need to rearrange some stuff first.

A Flutter app can be split into multiple layers which are (just for example) data, state and UI, in the data layer you will have all methods that communicate with the API, and you call them inside the state (which is provider in your case), the result will be accessible from the provider which will save the data in a variable, then the UI will be able to retrieve these data from the provider, this seems a bit redundant I know, but there is a reason why we do that, if you put the API call inside the provider itself, and there is somewhere else in your app that uses the same endpoint then you will have duplicate code, as for the provider, it's the place where your data is stored in the runtime, these data are what makes the state of your app, finally, the UI can handle displaying data from the provider relatively easily, just make a boolean in the provider that indicated is the API call is executing or not, and inside the consumer in the UI display different widgets based in the boolean.

If we were to visualize the flow of the operation it would be like that:
1- action from the UI that triggers a method from the provider.
2- inside the provider method you will set the boolean indicating that the API call is executing to true and call notifyListeners().
3- call the API request and call .then() on it.
4- inside the .then() set the boolean to false to notify that the call os over and set the received data to a variable inside the provider and call notifyListeners again.
5- in the UI you should have a consumer listening to your provider and handling the boolean, if its true then display a CircularProgressIndicator for example, and if it's false then display your desired widget


Regarding the context in the initState you can fix this problem in 3 ways:
1- using WidgetsBinding.instance .addPostFrameCallback((_) => yourProviderFunction(context));
2- by registering your provider in a service locator so you don't have to use a context at all. (which is what I used in the example project I posted above)
3- by executing the desired function in the constructor of the provider, so when its initialized the API request will be called

Answered by Basel Abuhadrous on November 27, 2020

sometimes

Provider.of<'providerClassName'>(context, listen : false).'providerFunction'

might help.

Answered by Nithesh K on November 27, 2020

  //your model , product_model.dart
  import 'dart:convert';

   List<Product> availableTicketsFromJson(String str) => List<Product>.from(json.decode(str).map((x) => Product.fromJson(x)));

  class Product {
  String title;
  String description;
  String imageUrl;
  double price;
  bool isFavorite;

  Product(
      {this.title,
      this.description,
      this.imageUrl,
      this.price,
      this.isFavorite});

  factory Product.fromJson(Map<String, dynamic> json) => Product(
        title: json['title'] as String,
        description: json['description'] as String,
        imageUrl: json['imageUrl'] as String,
        price: json['price'] as double,
        isFavorite: json['isFavorite'] as bool,
      );
}



 //viewmodel class
 final String url = "test.com";
 Future<List<Product> fetchProducts() async {
 List<Product> products = List<Product>();
 try {
       final request = await http.get(url);
       if(request.statusCode == 200) {
            products = productsFromJson(request.body.toString());
            notifyListeners();
         } else {
            print(request.statusCode.toString());
         }
    } catch(e) {
      return List<Product>();
    }
    return products;
 } 
 

 //fetch_data.dart
 Create your instance of provider in the page that you wanna fetch the data:
 Under State<yourWidget>

 => FetchDataViewModel _model;
    List<Product> products = [];
 under build method
 => _model = Provider.of<FetchDataViewModel>(context,listen: false);

    Make a http request with FutureBuilder
    FutureBuilder(future:_model.fetchProducts()),
    builder: (context,snapshot)){
       if(snapshot.connectionState == ConnectionState.done) {
       products = snapshot.data;
       if(products.length > 0) {
           return ListView.builder(
              itemCount: products.length,
              itemBuilder : (context,index) {
               return _items();
           }
         );
      } else {return _noSavedDataWidget();}
     }
    }

    You can test such a code

Answered by Kadir BEKAR on November 27, 2020

Is this the Academind course?

Also this is the correct way.

For using a Provider you need the context.

EDIT: Added BaselAbuhadrous' comment to the answer.

You need to use didChangeDependencies because the initState actually provides the context, but the screen layout isn't built yet, so you get an error, but if you used WidgetsBindings.instance and call the provider inside of it, then you won't get the error.

Answered by Arsh Shaikh on November 27, 2020

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP