API 调用完成后显示 CircularProgressIndicator() 不会停止
Posted
技术标签:
【中文标题】API 调用完成后显示 CircularProgressIndicator() 不会停止【英文标题】:Displaying CircularProgressIndicator() will not stop after API call is completed 【发布时间】:2021-03-25 22:05:21 【问题描述】:我正在尝试在进行 API 调用时显示 CircularProgressIndicator。导航到 OrdersScreen 时,CircularProgressIndicator 会显示并且不会停止。
点击错误时,它会将我引导至我的 API 调用中的 try catch 块中的 catch。
这是我遇到的错误:
I/flutter (22500): Invalid argument(s) (input): Must not be null
E/flutter (22500): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: Invalid argument(s) (input): Must not be null
[38;5;248mE/flutter (22500): #0 Orders.getOrders[39;49m
E/flutter (22500): <asynchronous suspension>
[38;5;248mE/flutter (22500): #1 _OrdersScreenState.initState.<anonymous closure> (package:shop_app/screens/order_screen.dart)[39;49m
E/flutter (22500): <asynchronous suspension>
E/flutter (22500):
这是我的 API 调用:
class Orders with ChangeNotifier
List<OrderItem> _orders = [];
List<OrderItem> get orders
return [..._orders];
//make a copy of private class _orders
//establishing so that we cannot modify the private class
//READ API call
Future<void> getOrders() async
final url = Uri.https(
'shop-app-flutter-49ad1-default-rtdb.firebaseio.com', '/products.json');
//note that for the post URL when using this https package we had to remove the special characters (https://) in order to properly post via the API
//establish the URL where the API call will be made
try
final response = await http.get(url);
// print(json.decode(response.body));
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
//retrieve the json response data stored in firebase, translate to a Map, and store that map in the jsonResponse variable
if (jsonResponse == null)
return;
//if there is no data returned in the jsonResponse (the db is empty) then we do nothing, avoiding an app crash on an empty API call
final List<OrderItem> orderProducts = [];
//establish an empty list in preparation to store the new Order values retrieved from the API call
jsonResponse.forEach((orderID, orderData)
//forEach will exectue a function on every value that is housed within that Map
orderProducts.insert(
0, //insert at index 0 inserts the newest added product at the beginning of the list
OrderItem(
id: orderID,
amount: orderData['amount'],
dateTime: DateTime.parse(orderData['dateTime']),
products: (orderData['products'] as List<dynamic>)
.map(
(item) => CartItem(
id: item['id'],
title: item['title'],
quantity: item['quantity'],
price: item['price'],
),
)
.toList(),
//since products is stored on the db as a map, we have to retrieve those values and define how the properties of the items stored in the db should be mapped --> recreating our CartItem as it's stored in the db
));
//retrieve the values for each of the given properties and Map them according to the values stored on the server
);
_orders = orderProducts;
notifyListeners();
//set the value of the _items list - that is the primary data of the ProductsProvider to tell the different areas of the app the data to show - equal to the values retrieved from the API call
catch (error)
print(error);
throw (error);
带有 CircularProgressIndicator 的代码:
class OrdersScreen extends StatefulWidget
static const routeName = '/orders';
@override
_OrdersScreenState createState() => _OrdersScreenState();
class _OrdersScreenState extends State<OrdersScreen>
bool _isLoading = false;
@override
void initState()
setState(()
_isLoading = true;
);
// when the state of the screen is initialized set the value of _isLoading to true
// by setting _isLoading to true we are establishing another state while the API call is being made
Provider.of<Orders>(context, listen: false).getOrders().then((_)
setState(()
_isLoading = false;
);
);
// we are making the API call and then setting the state of _isLoading back to false indicating the change of the _isLoading variable means a completed API call
// --> by changing the value of _isLoading prior to and after the API call it allows us to put additional functionality while the API call is made --> we established a CircularProgressIndicator which may be found in the body
super.initState();
@override
Widget build(BuildContext context)
final orderData = Provider.of<Orders>(context);
return Scaffold(
appBar: AppBar(
title: Text('Your Order'),
),
body: _isLoading == true
? Center(
child: CircularProgressIndicator(
backgroundColor: Theme.of(context).primaryColor),
)
: ListView.builder(
itemCount: orderData.orders.length,
itemBuilder: (ctx, index) => OrderCard(
order: orderData.orders[index],
),
//populate the order card UI element with data provided by the orders method within orders.dart
//this data is retrieved by calling the provider of type orders
),
drawer: SideDrawer(),
);
供参考:
订单项:
class OrderItem
OrderItem(
@required this.id,
@required this.amount,
@required this.products,
@required this.dateTime,
);
final String id;
final double amount;
final List<CartItem> products; //CartItem from cart.dart
final DateTime dateTime;
购物车项目:
class CartItem
CartItem(
@required this.id,
@required this.title,
@required this.quantity,
@required this.price,
);
final String id;
final String title;
final int quantity;
final double price;
【问题讨论】:
你能检查一下jsonResponse.forEach
中的orderData
,方法吗?显然,根据从端点收到的响应 I r fr,它不包含 orderData['amount']
、'dateTime'
和 'products'
等的任何密钥。
【参考方案1】:
要充分利用您已经设置的 Provider,您应该将脚手架的主体设置为 Consumer<Orders>
小部件。保持内部相同的逻辑,但它需要基于 Orders
类中的布尔值(初始化为 true)。
Consumer<Orders>(builder: (context, orderData, child)
return orderData.isLoading == true
? Center(
child: CircularProgressIndicator(
backgroundColor: Theme.of(context).primaryColor),
)
: ListView.builder(
itemCount: orderData.orders.length,
itemBuilder: (ctx, index) => OrderCard(
order: orderData.orders[index],
),
//populate the order card UI element with data provided by the orders method within orders.dart
//this data is retrieved by calling the provider of type orders
);
);
在getOrders()
函数中处理isLoading
的值,这将通知Consumer<Orders>
小部件在isLoading
更新为false 后返回CircularProgressIndicator
或ListView.builder
。
您仍然在 initState
中调用该函数,但该类中的本地布尔值会消失。
【讨论】:
成功了,谢谢!使用Consumer
而不是Provider
背后的原因是什么?这是因为我们只在OrdersScreen
的构建中专门针对这个小部件吗?
没问题。 Consumer
的目的是在调用相应类的notifyListeners()
时重建其子/子。如果您只需要变量的值或从ChangeNotifier
类调用方法,那么Provider.of...
就可以了。但是,当您需要响应式重建时,就像您在这种情况下所做的那样,这就是 Consumer
的用途。我在Consumer
中放入的orderData
等同于您最初在构建方法中放入的orderData
对象。以上是关于API 调用完成后显示 CircularProgressIndicator() 不会停止的主要内容,如果未能解决你的问题,请参考以下文章
已解决请先调用 init 完成初始化后再调用其他云 API。init 方法可传入一个对象用于设置默认配置,详见文档。; at cloud.callFunction api 解决方案