在 Flutter 中,你可能遇到过需要等待语句执行的情况。异步操作让程序在等待另一个操作完成的同时完成工作。一些常见的例子是:
- 从 Internet 获取数据
- 读取、创建或保存文件
- 实例化复杂对象
在 Dart 中,为了执行异步操作,我们使用 Future 类、async 和 await 关键字。要在 widget 中使用 Future,我们使用 FutureBuilder widget。
Future 简介
Future 是 Dart 中 Future 类的一个实例。 Future 表示异步操作的结果,可以有两种状态:未完成或已完成。
我们使用 async 将函数声明为异步函数,其返回类型会自动转换为返回数据类型的 Future。在这里,添加 async 关键字后,返回类型从 String 更改为 Future< String >。使用 await 关键字,我们等待任何返回 Future 的事件的响应。在这里,http.get 函数返回一个 Future< Response >。
import 'package:http/http.dart' as http;
Future<String> getData() async {
http.Response res = await http.get(Uri.parse("https://www.google.com");
return res.body;
}
现在在我们要获取数据的地方,我们将使用以下代码:
Future<void> main() async {
String data = await getData();
print(data);
}
或者,我们也可以使用 .then 关键字,使用以下代码:
void main2(){
getData().then((value){
print(value);
});
}
现在我们对 Futures 以及何时使用它们有了基本的了解。让我们看看如何在我们的应用程序中使用它们。
传统的异步方法
对于我们的示例,我们将发出 API 请求并查看代码的外观。我们将发出一个获取请求,在这种情况下不需要身份验证。
我们将使用的 API 是:
https://api.catboys.com/img
响应示例:
{
"url": "https://cdn.catboys.com/images/image_73.jpg",
"artist": "CoverDesign1",
"artist_url": "https://www.deviantart.com/coverdesign1",
"source_url": "https://www.deviantart.com/coverdesign1/art/Artiste-Iya-Chen-Render-396950935",
"error": "none"
}
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
// Variables to update user regarding the request status
bool isLoading = false;
bool errorOccured = false;
String imageUrl = '';
@override
void initState() {
super.initState();
getData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.refresh),
onPressed: getData,
),
appBar: AppBar(
title: const Text("Traditional Async"),
),
body: Center(
child: isLoading
? const CircularProgressIndicator()
: errorOccured
? const Text("Error Occured")
: Image.network(imageUrl)),
);
}
Future<void> getData() async {
// Set Fields
errorOccured = false;
isLoading = true;
setState(() {});
http.Response res;
try {
res = await http.get(Uri.parse("https://api.catboys.com/img"));
} catch (e) {
// Error Occured while making request
errorOccured = true;
isLoading = false;
setState(() {});
debugPrint(e.toString());
return;
}
// Request Sucessfull
Map<String, dynamic> data = json.decode(res.body);
imageUrl = data['url'];
isLoading = false;
setState(() {});
}
}
在代码中,可以观察到后端和前端是紧密耦合的。此外,我们可以看到我们必须一次又一次地调用 setState((){}) 来更新状态。我们可以在这里使用 ValueNotifier 来更新加载和错误状态,但是我们还必须管理 ValueNotifier。
为了处理所有这些加载、错误状态并减少样板代码,Flutter 有一个 FutureBuilder widget 。
Future Builder 简介
FutureBuilder 是一个基于任何 Future 函数的响应重建自身的 widget 。 FutureBuilder 订阅 Future 并在从 Future 接收到值时重建。它本质上是有状态的,即它在内部管理自己的状态,就像我们在有状态 widget 中所做的那样。
我们使用以下构造函数来创建 FutureBuilder:
FutureBuilder({
Key? key,
this.future,
this.initialData,
required this.builder,
})
我们将 future 值传递给 future 参数。 builder 接受一个函数,该函数有两个参数 BuildContext、AsyncSnapshot< T > 并返回一个 Widget。 initialData 参数用于将初始数据传递给 FutureBuilder。
实现如下所示:
FutureBuilder(
future: getData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return Container();
},
);
AsyncSnapshot 包含一些关于连接、数据、错误等的重要数据。它的一些重要字段是:
- connectionState
- hasData
- hasError
- data
- error
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class FutureBuilderExample extends StatefulWidget {
const FutureBuilderExample({Key? key}) : super(key: key);
@override
_FutureBuilderExampleState createState() => _FutureBuilderExampleState();
}
class _FutureBuilderExampleState extends State<FutureBuilderExample> {
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.refresh),
onPressed: () {
setState(() {});
},
),
appBar: AppBar(title: const Text("Future Builder Example")),
body: FutureBuilder(
future: getData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// Future resolved check if it has error
if (snapshot.hasError) {
return _getErrorText();
} else if (snapshot.hasData) {
//NOTE: isEmpty can only be used on Maps and Strings
if (snapshot.data!.isEmpty) {
return const Center(
child: Text("Data Received, but is empty"),
);
}
return _getImageWidget(snapshot);
} else {
return _getNoDataReceivedText();
}
} else if (snapshot.connectionState == ConnectionState.waiting) {
// Future in progress
return _getLoadingIndicator();
} else {
return Text("State: ${snapshot.connectionState}");
}
},
),
);
}
Center _getImageWidget(AsyncSnapshot<dynamic> snapshot) {
return Center(
child: Image.network(snapshot.data),
);
}
_getNoDataReceivedText() => const Center(child: Text("No Data Received"));
_getLoadingIndicator() {
return const Center(
child: CircularProgressIndicator(),
);
}
_getErrorText() {
return const Center(
child: Text("An Error Occured"),
);
}
}
Future<String> getData() async {
http.Response res;
try {
res = await http.get(Uri.parse("https://api.catboys.com/img"));
} catch (e) {
return "";
}
Map<String, dynamic> data = json.decode(res.body);
return data['url'];
}