本篇基于Flutter 3.16.4,Dart 3.2.3版本
Flutter 3.16.4 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 2e9cb0aa71 (3 days ago) • 2023-12-11 14:35:13 -0700
Engine • revision 54a7145303
Tools • Dart 3.2.3 • DevTools 2.28.4
本篇为Flutter基建的第八篇文章,主要介绍FutureBuilder、StreamBuilder和通过Dio网络请求更新UI相关的知识,都是和异步加载UI相关,下面快速进入文章的了解下吧~
Flutter系列文章
Flutter基建 - 形影不离的State**Widget
简述
FutureBuilder和StreamBuilder都是Flutter为开发者提供快速实现异步加载UI的方案,它们不同的是一个依赖的是Future另一个依赖的是Stream,一般情况下我们使用Future就可以完成大部分的异步操作,它可以异步加载我们需要的数据,那Stream是不是多余的呢?并不是,Stream专门用于异步加载流的类型,比如文件的读写和网络文件的下载等等场景,所以它们各有各的特长,小伙伴们在看完这篇文章之后可以根据实际的业务需求在它们之间选择。
接下来先看看FutureBuilder的具体使用。
FutureBuilder异步
const FutureBuilder({
super.key,
required this.future,
this.initialData,
required this.builder,
});
FutureBuilder的构造函数非常简单,只有四个参数:
- future参数用于接收一个Future类型的对象,用于处理异步请求;
- initialData参数可以定义初始值,在异步操作未完成之前可以使用此参数的值来展示页面信息;
- builder参数则是处理异步回调操作的逻辑,可以在此参数内部处理异步操作完成前后的逻辑并返回指定的Widget。
class _FutureState extends State<FutureWidgetExample> {
@override
Widget build(BuildContext context) {
return buildScaffold(
context,
Center(
child: FutureBuilder(
future: buildFuture(),
builder: (context, result) {
var state = result.connectionState;
debugPrint('future state: ${state.name}');
if (state == ConnectionState.done) {
return Text(result.data ?? "Future Error");
} else {
return const CircularProgressIndicator();
}
},
),
),
);
}
Future<String> buildFuture() async {
return Future.value('Future value');
}
}
log:
future state: waiting
future state: done
![](https://i-blog.csdnimg.cn/blog_migrate/9acd239f629cdc62346bcc76bbdc5f08.png)
这里我们通过FutureBuilder创建了一个异步加载的UI,future参数传入的是buildFuture()方法,此方法内部采用了最简单的Future.value()返回了一个字符串;然后builder参数内部根据ConnectionState类型返回了两种不同的Widget,如果state为done那么就返回一个Text来展示Future的结果,如果state非done那么就展示加载loading。
因为buildFuture()方法内部是直接返回的结果,此代码会看不到加载loading,我们可以将Future.value()改为
Future<String> buildFuture() async {
return Future.delayed(const Duration(seconds: 2), () {
return 'Future value';
});
}
这样我们先延时2s之后再返回字符串,这样就可以很清晰的看出界面的变化。
这里我们再了解下ConnectionState概念,它一个有四种状态,分别为:
- none:此状态表示FutureBuilder中future参数传入的为空,当前没有异步任务在执行;
- waiting:此状态表示异步任务正在执行,还没收到最终结果;
- active:此状态在FutureBuilder中不容易理解,在后面的StreamBuilder中可以很容易的理解,它表示的是当前异步任务已经收到了返回的数据,但是异步任务并没有结束,后面还会收到别的数据;可以用读取文件的任务来加深理解,读取文件是一个过程,从开始到结束会一直接受文件的流,在未接收完之前ConnectionState会一直是active状态,等到整个文件都接收完成之后,active状态就会变为done状态;
- done:此状态表示的是异步任务已经完成。
上面就是ConnectionState四种状态的概括性解释,上述代码中我们也是添加了debugPrint来输出state,大家可以执行下代码来看看整个任务的state是如何变化的,此时state的变化是:waiting -> done,因为我们buildFuture是一个一次性的任务,所以中途不会出现active状态,active状态我们会在StreamBuilder种介绍。
StreamBuilder异步
const StreamBuilder({
super.key,
this.initialData,
required super.stream,
required this.builder,
});
对比FutureBuilder的构造函数来看,StreamBuilder和它的区别就在于stream参数,其余的基本上是一致的,这里就不过多解析了,直接进入实战环节。
class _StreamState extends State<StreamWidgetExample> {
@override
Widget build(BuildContext context) {
return buildScaffold(
context,
Center(
child: StreamBuilder(
initialData: "init stream data",
stream: buildStream(),
builder: (context, result) {
var connectionState = result.connectionState;
debugPrint('stream state: ${connectionState.name}');
if (connectionState == ConnectionState.done) {
return Text(result.data ?? 'Stream error');
} else {
return Text(result.data ?? 'init data');
}
},
),
),
);
}
Stream<String> buildStream() {
return Stream.periodic(const Duration(seconds: 1), (i) {
return "$i";
});
}
}
使用方式也是类似于FutureBuilder,不过这里我们多传入了一个initialData参数,然后在buildStream()方法里使用的是Stream.periodic()方法返回的Stream对象,periodic表示的是周期性任务,这里定义的每隔1s返回阶段性的结果。使用periodic()方法的目的是为了可以清晰的展示initialData的作用。
通过debugPrint的日志可以看出,ConnectionState的状态在第一次周期任务结束之前一直是waiting状态,后面都是active状态,并且这里不会有done状态,因为我们Stream.periodic是一直在跑的,实际开发中读取文件或者下载文件等操作还是会有done状态的。
这里注意的是在builder参数内部我们非done状态是展示的是Text,Text的内容为result.data,试想一下周期任务的前1s还未收到阶段性结果,此时会Text的内容会展示啥呢?下面来看下具体运行的效果图。
通过上面的GIF可以看出,在前1s的时候Text展示的是initialData值,然后每隔1s显示的内容从0依次+1。
通过上面的介绍,FutureBuilder和StreamBuilder相关知识就梳理完成了,异步加载UI的内容在日常开发中还是使用的蛮多的,小伙伴们如果是第一次接触可能会感觉到有点繁琐,但是用多了就会逐渐熟练起来,最后我们通过FutureBuilder的方式加载接口数据再次熟悉一下,这种方式也是实际开发接触的比较多的~
Dio加载接口数据
dio 是一个强大的 HTTP 网络请求库,支持全局配置、Restful API、FormData、拦截器、 请求取消、Cookie 管理、文件上传/下载、超时、自定义适配器、转换器等。
以上是Dio官方的介绍,详细使用可以去阅读Dio的官方文档
引入Dio库
在使用Dio之前,我们先在工程的pubspec.yaml文件中引入Dio库
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
dio:
然后再Android模块的Manifest文件中添加网络权限,不然接口请求就不会成功的哈~
定义接口数据类
接口请求我们使用的是WanAndroid开放的Api,日常调试接口的神级接口,官方地址为:WanAndroid
以首页数据接口为例,现在浏览器中请求一次,拿到接口的返回数据,然后在Json To Dart网页中生成实体类。这里生成的实体类代码有点多,就不在文章中贴出具体代码了。
接口请求
Future<Response> fetchArticle() {
Dio dio = Dio();
return dio.get("https://www.wanandroid.com/article/list/0/json");
}
采用Dio方式实现接口请求非常简单,只需要通过Dio.get()就可以完成一个Get类型的接口请求,它返回的是Future类型的对象,看到Future类型小伙伴是不是就知道如何展示接口数据了,只需要通过FutureBuilder的形式就可以完成接口数据的展示,下面直接贴代码。
接口数据展示
class _DioState extends State<DioWidget> {
@override
Widget build(BuildContext context) {
return buildScaffold(
context,
Center(
child: FutureBuilder(
future: fetchArticle(),
builder: (context, result) {
ConnectionState state = result.connectionState;
if (state == ConnectionState.done) {
Article article = Article.fromJson(result.data?.data);
return ListView.builder(
itemCount: article.data?.size ?? 0,
itemBuilder: (context, index) {
return ListTile(
title: Text("${article.data!.datas?[index].title}"),
);
});
} else {
return const CircularProgressIndicator();
}
}),
),
);
}
}
上述代码中在第8行出将接口请求的Future传入给FutureBuilder,然后在第12行处进行数据的Json解析,Article.fromJson()方法就是通过Json To Dart生成出来的,不需要小伙伴自己手动编写,这样我们就拿到了最终想要的数据类型,通过Article对象绘制了一个列表界面,每个Item只显示文章的标题,实际效果见下方GIF。
到此为止我们就通过Dio+FutureBuilder的方式实现了一个接口请求,并且将请求后的数据展示到界面上,整体来说还是比较简单的,哈哈~得益于Flutter的响应式布局。
写在最后
本篇文章主要介绍了Flutter中FutureBuilder和StreamBuilder实现异步加载UI的功能,并且通过Dio实现了接口数据的加载和展示,希望可以给阅读的小伙伴们带来一点帮助,后续会循序渐进逐步接触Flutter更多的知识。
我是Taonce,如果觉得本文对你有所帮助,帮忙关注、赞或者收藏三连一下,谢谢😆😆~