最近,我参加了一个颤振课程。教练让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
中,我们不能使用异步等待,所以很可能会有一个回调地狱等待。此外,需要初始化一个变量,以便在加载时调用api一次。
没有简单的解决办法吗?还是一种行业处理方式?
这里有一个关于你能做什么的简单例子,这不是世界上最好的事情,但这对我来说是有效的,如果你能让它变得更好,请告诉我。
你的问题的答案很简单,但是,你需要先重新安排一些东西。
Flutter应用程序可以分为多个层,这些层是(例如(数据、状态管理和UI,在数据层中,您将拥有与API通信的所有方法,并在状态管理解决方案(在您的情况下是提供商(内部调用它们,结果将可以从将数据保存在变量中的提供商访问,然后UI将能够从提供程序检索这些数据,我知道这似乎有点多余,但我们这样做是有原因的,如果你将API调用放在提供程序本身内部,并且你的应用程序中有其他地方使用同一个端点,那么你将有重复的代码,对于提供程序,这些数据决定了你的应用程序的状态,最后,UI可以轻松地处理来自提供者的显示数据,只需在提供者中生成一个布尔值,指示API调用是否正在执行/加载,并且在UI中的消费者内部显示基于布尔值的不同窗口小部件。
如果我们要可视化操作的流程,它将是这样的:
1-来自UI的操作,该操作触发来自提供程序的方法
2-在提供程序方法中,您将把指示API调用正在执行的布尔值设置为true,并调用notifyListeners((
3-调用API请求并对其调用.then((。
4-在.then(
5-在UI中,您应该让消费者倾听您的提供者并处理布尔值,如果为true,则显示CircularProgressIndicator
,如果为false,则显示您想要的小部件
关于initState中的上下文,您可以通过三种方式解决此问题:
1-使用WidgetsBinding.instance.addPostFrameCallback((_(=>您的ProviderFunction(上下文((
2-通过在服务定位器中注册您的提供商,您根本不必使用上下文。(这就是我在上面发布的示例项目中使用的(
3-通过在提供程序的构造函数中执行所需的函数,因此当它初始化时,API请求将被称为
这是Academind课程吗?
这也是正确的方式。
要使用提供程序,您需要上下文。
编辑:在答案中添加BaselAbuhadrous的评论
您需要使用didChangeDependencies
,因为initState
实际上提供了上下文,但屏幕布局尚未构建,因此会出现错误,但如果您使用WidgetsBindings.instance
并调用其中的提供程序,则不会出现错误。
//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
有时
提供程序,共<'providerClassName'>(上下文,listen:false(。'providerFunction’
可能会有所帮助。