获取异步函数的 NULL 值(在使用 await 之后)然后更新为新值

Getting NULL value for async function (after using await) then updating to the new value

当我 运行 我的应用程序抛出很多错误时,我设备上的 red/yellow 错误屏幕会自动刷新并显示预期的输出。

从日志中我可以看到,首先我的对象 return 为 null,稍后会以某种方式更新并得到输出。

我最近开始Android开发(Flutter)

我尝试遵循一些在线指南并阅读有关异步响应的相关问题,但没有任何故障排除对我有帮助。 我最大的问题是我无法弄清楚到底是什么问题。

在我的 _AppState class:

  void initState() {
    super.initState();
    fetchData();
  }

  fetchData() async {
    var cityUrl = "http://ip-api.com/json/";
    var cityRes = await http.get(cityUrl);
    var cityDecodedJson = jsonDecode(cityRes.body);
    weatherCity = WeatherCity.fromJson(cityDecodedJson);
    print(weatherCity.city);
    var weatherUrl = "https://api.openweathermap.org/data/2.5/weather?q=" +
        weatherCity.city +
        "," +
        weatherCity.countryCode +
        "&appid=" +
        //Calling open weather map's API key from apikey.dart
        weatherKey;
    var res = await http.get(weatherUrl);
    var decodedJson = jsonDecode(res.body);
    weatherData = WeatherData.fromJson(decodedJson);
    print(weatherData.weather[0].main);
    setState(() {});
  }

预期输出(终端):

Mumbai
Rain

实际输出(终端):https://gist.github.com/Purukitto/99ffe63666471e2bf1705cb357c2ea32(实际错误超过了 Whosebug 的主体限制)

屏幕截图:

您必须等到 api 的数据到达。快速修复是在保存未来数据的变量上放置一个空合并运算符,如下所示:

String text;

fetchData() async {
//...
  text = weatherData.weather[0].main ?? 'Waiting api response...';
//...
}

// in your build method
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Container(
      child: Text(text), //this will render "Waiting api response" first, and when the api result arrive, it will change
    ),
  );
}

否则,您可以使用 futureBuilder 小部件来实现。但是你必须将每个 api 放在不同的函数中,并将其更改为 Future,因此它具有 return 值。

Future fetchDataCity() async {
  // your code
  weatherCity = WeatherCity.fromJson(cityDecodedJson);
  return weatherCity;
}

Future fetchDataWeather() async {
  // your code
  weatherData = WeatherData.fromJson(decodedJson);
  return weatherData;
}

// in your build method
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Container(
      child: FutureBuilder(
        future: fetchDataWeather(), // a previously-obtained Future or null
        builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
          switch (snapshot.connectionState)
            case ConnectionState.active:
            case ConnectionState.waiting:
              return Text('Awaiting result...'); //or a placeholder
            case ConnectionState.done:
              if (snapshot.hasError){
                return Text('Error: ${snapshot.error}');
              } else {
                return Text('Error: ${snapshot.data}');
            }
         },
      ) //FutureBuilder
    ),
  );
}

asyncawait是Dart中处理异步编程的机制。

Asynchronous operations let your program complete work while waiting for another operation to finish.

因此,无论何时方法被标记为 async,您的程序都不会暂停以完成该方法,而只是假设它会在未来的某个时间点完成。

示例:错误使用异步函数

以下示例显示了使用异步函数的错误方法getUserOrder()

String createOrderMessage () {
  var order = getUserOrder(); 
  return 'Your order is: $order';
}

Future<String> getUserOrder() {
  // Imagine that this function is more complex and slow
  return Future.delayed(Duration(seconds: 4), () => 'Large Latte'); 
}

main () {
  print(createOrderMessage());
}

如果你运行上面的程序它会产生下面的输出-

Your order is: Instance of '_Future<String>'

这是因为,由于return类型的方法被标记为Future,程序将把它视为一个异步方法。

要获取用户的订单,createOrderMessage() 应该调用 getUserOrder() 并等待它完成。因为 createOrderMessage() 没有等待 getUserOrder() 完成,所以 createOrderMessage() 无法获取 getUserOrder() 最终提供的字符串值。


异步等待

async 和 await 关键字提供了一种声明方式来定义异步函数并使用它们的结果。

因此,无论何时将函数声明为 async,都可以在任何方法调用之前使用关键字 await,这将强制程序在方法完成之前不再继续。


案例

在您的例子中,fetchData() 函数被标记为 async,并且您正在使用 await 等待网络调用完成。

但是这里 fetchData() 有一个 return 类型的 Future<void> 因此当你在 initState() 中调用方法时你必须这样做而不使用 async/ await 因为 initState() 不能被标记为 async.

因此程序不会等待 fetchData() 方法作为一个整体完成,而是尝试显示本质上是 null 的数据。 并且因为你在fetchData()中加载数据后调用setState(),所以屏幕会刷新,一段时间后你可以看到详细信息。

因此红屏和黄屏错误。


解决方案

此问题的解决方案是您可以在屏幕上显示加载指示器,直到数据完全加载。

您可以使用 bool 变量并根据该变量的值更改 UI。

例子-

class _MyHomePageState extends State<MyHomePage> {
  bool isLoading = false;

  void initState() {
    super.initState();
    fetchData();
 }

 fetchData() async {
   setState(() {
     isLoading = true; //Data is loading
   });
   var cityUrl = "http://ip-api.com/json/";
   var cityRes = await http.get(cityUrl);
   var cityDecodedJson = jsonDecode(cityRes.body);
   weatherCity = WeatherCity.fromJson(cityDecodedJson);
   print(weatherCity.city);
   var weatherUrl = "https://api.openweathermap.org/data/2.5/weather?q=" + weatherCity.city + "," +
    weatherCity.countryCode +
    "&appid=" +
    //Calling open weather map's API key from apikey.dart
    weatherKey;
    var res = await http.get(weatherUrl);
    var decodedJson = jsonDecode(res.body);
    weatherData = WeatherData.fromJson(decodedJson);
    print(weatherData.weather[0].main);
    setState(() {
      isLoading = false; //Data has loaded
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: isLoading ? Center(child : CircularProgressIndicator())
      : Container(), //Replace this line with your actual UI code
    );
  }
}

希望对您有所帮助!