异步UI更新
很多时候我们会依赖一些异步数据来动态更新UI,比如在打开一个页面时我们需要先从互联网上获取数据,在获取数据的过程中我们显示一个加载框,等获取到数据时我们再渲染页面;又比如我们想展示Stream(比如文件流、互联网数据接收流)的进度。
之前已经介绍过 Dart 中的异步操作主要是 Future 和 Stream,也介绍了相关的用法,这片文章将介绍与之对应的构建异步UI的两个方法 FutureBuilder 与 StreamBuilder。
FutureBuilder
Future 必须在更早之前获取,例如在State.initState、State.didUpdateWidget 或 State.didChangeDependencies 期间得到。它不能在构造 FutureBuilder 时调用 State.build 或 StatelessWidget.build 方法时创建。如果 Future 是与 FutureBuilder 同时创建的,那么每当 FutureBuilder 的父节点重建时,异步任务就会重新启动。
构建方法:
1 2 3 4 5 6 7 8 9 10 11
| const FutureBuilder<T>({ Key? key, required Future<T>? future, T? initialData, required AsyncWidgetBuilder<T> builder })
AsyncWidgetBuilder<T> = Widget Function( BuildContext context, AsyncSnapshot<T> snapshot )
|
snapshot会包含当前异步任务的状态信息及结果信息 ,比如我们可以通过snapshot.connectionState获取异步任务的状态信息, 通过snapshot.hasError判断异步任务是否有错误等等,完整的定义读者可以查看AsyncSnapshot类定义。另外,FutureBuilder的builder函数签名和StreamBuilder的builder是相同的。
查看源码可知 snapshot.connectionState 属性是一个枚举,用于表示异步操作的连接状态:
- ConnectionState.none:表示异步操作未启动,或者 Future 对象尚未创建。
- ConnectionState.waiting:表示异步操作正在进行中,等待 Future 完成。
- ConnectionState.active:(用于Stream)表示数据流处于活动状态,已经开始发送事件。在接收到第一个事件之后,connectionState 的值将变为 active,但是还没有完成。
- ConnectionState.done:表示异步操作已经完成或抛出了异常。
可以根据状态返回不同的UI:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| FutureBuilder<String>( future: myFuture, builder: (BuildContext context, AsyncSnapshot<String> snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return CircularProgressIndicator(); } else if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasData) { return Text('Data: ${snapshot.data}'); } else if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } }
return Text('Press the button to start'); }, )
|
完整代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| late Future<List<Album>> futureAlbums;
Future<List<Album>> fetchAlbum() async { final response = await http.get( Uri.parse('https://jsonplaceholder.typicode.com/albums'), headers: { 'Content-Type': 'application/json; charset=UTF-8', HttpHeaders.authorizationHeader: 'Basic your_api_token_here', }, );
if (response.statusCode == 200) { final List<dynamic> jsonList = jsonDecode(response.body); return jsonList.map((json) => Album.fromJson(json)).toList(); } else { throw Exception('Failed to load album'); } }
@override void initState() { super.initState(); futureAlbums = fetchAlbum(); }
...
Widget build(BuildContext context) { return Center( child: FutureBuilder<List<Album>>( future: futureAlbums, builder: (context, AsyncSnapshot snapshot) { if (snapshot.hasData) { return Scrollbar( child: ListView.builder( itemCount: snapshot.data!.length, itemBuilder: (BuildContext context, int index) { return ListTile( title: Text(snapshot.data![index].title), ); }, )); } else if (snapshot.hasError) { return Text('${snapshot.error}'); } return const CircularProgressIndicator(); }, ); ); }
|
StreamBuilder
我们知道,在Dart中Stream 也是用于接收异步事件数据,和Future 不同的是,它可以接收多个异步操作的结果,它常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。StreamBuilder正是用于配合Stream来展示流上事件(数据)变化的UI组件。
构建方法:
1 2 3 4 5 6
| const StreamBuilder<T>({ Key? key, T? initialData, required Stream<T>? stream, required AsyncWidgetBuilder<T> builder })
|
创建一个异步数据流,每隔1秒发送一个数据,获取10个数字(0-9)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| final Stream<int> counterStream = Stream.periodic(const Duration(seconds: 1), (count) => count).take(10);
final broadcastStreamController = StreamController<int>.broadcast();
@override void initState() { super.initState(); futureAlbums = fetchAlbum(); counterStream.pipe(broadcastStreamController); }
Widget build(BuildContext context) { return Center( child: StreamBuilder<int>( stream: broadcastStreamController.stream, builder: (context, AsyncSnapshot<int> snapshot) { switch (snapshot.connectionState) { case ConnectionState.waiting: return const Text('waiting'); case ConnectionState.none: return const Text('none'); case ConnectionState.active: return Text('Count: ${snapshot.data}'); case ConnectionState.done: return const Text('done'); } }, ); ); }
|