如何在Flutter中有效地使用Provider获取初始数据

7gcisfzg  于 6个月前  发布在  Flutter
关注(0)|答案(4)|浏览(101)

最近,我参加了一个flutter课程。老师把从一个API中获取请求变得如此困难。对于像flutter这样的混合框架,我从来没有想过它会如此困难。
下面是我的代码。我使用provider进行状态管理。

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

字符串
在产品概述屏幕中,我正在显示产品页面,此方法如下所示:

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();
  }


另一种方法包括一种使用零持续时间的偷偷摸摸的方法,就像我们在JavaScript中使用的设置超时的方法一样。
值得注意的是,在didChangeDependencies中我们不能使用await,所以很可能会有一个回调地狱等待。另外,一个变量需要初始化,以便在加载时调用API一次。
有没有简单的解决办法?或者行业的解决办法?

bnl4lu3b

bnl4lu3b1#

here is a minimal working example of what you can do,这不是世界上最好的东西,但这是什么为我工作,让我知道,如果你可以使它更好。
你的问题的答案很简单,但是,你需要先重新安排一些东西。
Flutter应用程序可以分为多个层,(仅举个例子)数据、状态管理和UI,在数据层中,您将拥有与API通信的所有方法,您可以在状态管理解决方案中调用它们(在您的情况下是provider),则可以从提供程序访问结果,该提供程序将数据保存在变量中,然后UI将能够从提供者检索这些数据,这似乎有点多余,我知道,但我们这样做是有原因的,如果你把API调用放在提供者本身内部,并且你的应用中还有其他地方使用相同的端点,那么你将有重复的代码,至于提供者,它是你的数据在运行时存储的地方,这些数据是你的应用程序的状态,最后,UI可以轻松地处理显示来自提供者的数据,只需在提供者中设置一个布尔值,指示API调用是否正在执行/加载,并在UI中的消费者中显示基于布尔值的不同小部件。
如果我们想象一下操作流程,它会是这样的:
1-UI中触发提供程序中的方法的操作。
2-在provider方法中,您将把指示API调用正在执行的布尔值设置为true,并调用notifyListeners()。
3-调用API请求并对其调用.then()。
4-在.then()内部,将boolean设置为false以通知调用结束,并将接收到的数据设置为提供程序内部的变量,然后再次调用notifyListeners。
5-在UI中,你应该有一个消费者监听你的提供者并处理布尔值,如果为真,那么显示一个CircularProgressIndicator,如果为假,那么显示你想要的小部件
关于initState中的上下文,您可以通过3种方式解决此问题:
1-使用WidgetsBinding.instance .addPostFrameCallback((_)=> yourProviderFunction(context));
2-通过在服务定位器中注册你的提供者,这样你就不需要使用上下文了。(这就是我在上面发布的示例项目中使用的)
3-通过在提供程序的构造函数中执行所需的函数,因此当其初始化时,将调用API请求

yhxst69z

yhxst69z2#

这是学术课程吗?
这也是正确的方式。
要使用Provider,您需要上下文。

编辑:在答案中添加了BaselAbuhadrous的评论。

您需要使用didChangeDependencies,因为initState实际上提供了上下文,但屏幕布局尚未构建,因此您会得到一个错误,但如果您使用WidgetsBindings.instance并在其中调用提供程序,则不会得到错误。

lo8azlld

lo8azlld3#

//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

字符串

zynd9foi

zynd9foi4#

有时
提供者.of的('providerClassName '>(上下文,侦听:false).'提供者函数'
可能会有帮助。

相关问题