【问题标题】:How to fetch initial data using provider in flutter effectievly如何在颤振中有效地使用提供者获取初始数据
【发布时间】:2020-12-10 16:13:31
【问题描述】:

最近,我做了一个颤振课程。 讲师使来自 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 set timeout 中使用的一样,给出零时间。

值得注意的是,在didChangeDependencies 中我们不能使用异步等待,所以很可能是回调地狱等待。 此外,还需要初始化一个变量,以便在加载时调用一次 api。

没有简单的解决方案吗?还是一种行业处理方式?

【问题讨论】:

标签: api flutter provider


【解决方案1】:

这是 Academind 课程吗?

这也是正确的方法。

要使用 Provider,您需要上下文。

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

你需要使用didChangeDependencies,因为initState实际上提供了上下文,但是屏幕布局还没有构建,所以你会得到一个错误,但是如果你使用WidgetsBindings.instance并调用它里面的提供者,那么您将不会收到错误消息。

【讨论】:

  • 请问,我想更正这个答案的上下文部分,initState 实际上提供了上下文,但是屏幕布局还没有构建,所以你会得到一个错误,但是如果你使用WidgetsBindings.instance 并在里面调用provider,就不会报错
  • @BaselAbuhadrous 谢谢你,我也比较陌生,不知道这一点。
  • 如何使用小部件绑定?为什么它如此复杂和东西。我理解关于上下文不存在的评论,因为没有构建布局,但是为什么我们需要一个变量 isInit 来检查它是否已加载?
  • 我们需要该变量,因为与 initState 不同, didChangeDependencies 被多次调用,因此您将一次又一次地将相同的数据添加到列表中。我也无法帮助使用 WidgetsBindings,因为我自己不知道如何使用它。
  • @basel 你能帮忙吗?
【解决方案2】:
  //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

【讨论】:

  • 现在不是更复杂了吗?
  • 首先我们在另一个 dart 文件中创建了我们的模型,这样我们就可以在其中包含用于该模型的特殊方法,然后创建一个新文件,它是视图模型,以通过这种方式向 api 服务发出 http 请求毕竟,当您想要发出 http 请求时,您的 ui 将不知道任何关于 ui 和 http 请求之间发生的事情,只需创建您的提供者的实例,然后将您的方法放在您需要的地方。
  • 对于一个简单的应用程序来说,它可能看起来很复杂,但是当你的应用程序开始成长时,它将是可维护的,并且可读可写
  • 顺便说一句,现在我们通常没有 api 类,你应该按照我说的解释创建它
【解决方案3】:

有时

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

可能会有帮助。

【讨论】:

  • 感谢兄弟的努力,但这不是解决方案的需要。
【解决方案4】:

here is a minimal working example of what you can do,这不是世界上最好的东西,但这对我有用,如果你能做得更好,请告诉我。

你的问题的答案很简单,但是,你需要先重新安排一些东西。

Flutter 应用程序可以分为多个层(例如)数据、状态管理和 UI,在数据层中,您将拥有与 API 通信的所有方法,并在状态管理解决方案中调用它们(在你的情况下是提供者),结果将可以从提供者访问,它将数据保存在一个变量中,然后用户界面将能够从提供者那里检索这些数据,我知道这似乎有点多余,但是有这就是我们这样做的一个原因,如果您将 API 调用放在提供程序本身内部,并且您的应用程序中的其他地方使用相同的端点,那么您将有重复的代码,至于提供程序,它是您的数据所在的地方存储在运行时中,这些数据构成了应用程序的状态,最后,UI 可以轻松处理来自提供程序的显示数据,只需在提供程序中创建一个布尔值,指示 API 调用是否正在执行/加载,和消费者在UI显示里面的不同t 个基于布尔值的小部件。

如果我们将操作流程可视化,它会是这样的:
1- UI 中触发提供程序方法的操作。
2- 在提供程序方法中,您将指示 API 调用正在执行的布尔值设置为 true 并调用 notifyListeners()。
3- 调用 API 请求并在其上调用 .then()。
4- 在 .then() 内部将布尔值设置为 false 以通知调用结束,并将接收到的数据设置为提供程序内部的变量并再次调用 notifyListeners。
5- 在 UI 中,您应该让消费者聆听您的提供者并处理布尔值,如果它为 true,则显示 CircularProgressIndicator 例如,如果它为 false,则显示您想要的小部件


关于 initState 中的上下文,您可以通过 3 种方式解决此问题:
1-使用 WidgetsBinding.instance .addPostFrameCallback((_) => yourProviderFunction(context));
2-通过在服务定位器中注册您的提供者,因此您根本不必使用上下文。 (这是我在上面发布的示例项目中使用的)
3-通过在提供者的构造函数中执行所需的函数,因此在其初始化时将调用API请求

【讨论】:

  • 巴塞尔的好答案。我使用了#3,它解决了我遇到的一个问题。谢谢。
  • 我正在尝试做#3,但我不知道怎么做,因为请求是异步的,我看不到等待它的方法。你是怎么做到的?
  • @L.Heinrichs 您可以在上述示例中的 state_management_session/lib/features/products/products_provider.dart 第 38 行找到您的请求
  • 是的,我最终设法触发了请求。我想我的问题更多是提供者需要来自另一个提供者(Auth)的数据,我不确定如何正确链接它们,但这与这个问题并没有真正的关系。
猜你喜欢
  • 2020-12-24
  • 2021-11-01
  • 1970-01-01
  • 2021-10-01
  • 2021-01-28
  • 2019-12-08
  • 2020-05-10
  • 2022-06-29
  • 2022-01-15
相关资源
最近更新 更多