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&lt;Orders&gt; 小部件。保持内部相同的逻辑,但它需要基于 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&lt;Orders&gt; 小部件在isLoading 更新为false 后返回CircularProgressIndicatorListView.builder

您仍然在 initState 中调用该函数,但该类中的本地布尔值会消失。

【讨论】:

成功了,谢谢!使用Consumer 而不是Provider 背后的原因是什么?这是因为我们只在OrdersScreen 的构建中专门针对这个小部件吗? 没问题。 Consumer 的目的是在调用相应类的notifyListeners() 时重建其子/子。如果您只需要变量的值或从ChangeNotifier 类调用方法,那么Provider.of... 就可以了。但是,当您需要响应式重建时,就像您在这种情况下所做的那样,这就是 Consumer 的用途。我在Consumer 中放入的orderData 等同于您最初在构建方法中放入的orderData 对象。

以上是关于API 调用完成后显示 CircularProgressIndicator() 不会停止的主要内容,如果未能解决你的问题,请参考以下文章

在我尝试显示结果后启动改造 API 调用

如何调用百度地图API

如何仅在第一个完成后才发出后续 API 请求?

已解决请先调用 init 完成初始化后再调用其他云 API。init 方法可传入一个对象用于设置默认配置,详见文档。; at cloud.callFunction api 解决方案

在 API 调用 Node express 服务器后显示错误消息

剑道自动完成数据源显示未找到数据