Flutter异步操作实现流程详解
作者:聂大哥
在Flutter中,借助 FutureBuilder
组件和 StreamBuilder
组件,可以非常方便地完成异步操作。
一、FutureBuilder
在讲解FutureBuilder之前,你首先要知道Future
是什么,了解了这个,后面再了解该组件就轻松许多。
在不同的编程语言中会有不同的名词来定义,在Dart语言中 选择使用Future类型配合async、await关键字来实现异步支持。
Future 表示一个现在不确定,但以后应该可以确定的值。这个值可以是任意类型,如 Future<int>
表示一个未来获取到的整型值,Future<String>
表示一个未来获取到的字符串。
我们通常会在定时器、网络请求中使用Future,它会有三种状态:uncompleted(未完成)、completed with data(获取到一个数据)、completed with error(捕获到一个错误)
,所以在实战过程中,我们需要根据这三种状态来判断当前界面应该是怎样的,加载中、数据正常显示、提示错误 重新操作。
小示例:
定义一个getValue方法:
Future<String> getValue() async { await Future.delayed(Duration(seconds: 3)); return "100"; }
结合FutureBuilder组件来调用方法:
FutureBuilder( future: getValue(), builder: (BuildContext context, AsyncSnapshot<String> snapshot) { if (snapshot.hasData) { return Text( "${snapshot.data}", style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold), ); } else if (snapshot.hasError) { return Text("${snapshot.error}"); } else { return Container( height: 50, width: 50, margin: EdgeInsets.only(top: 10), child: CircularProgressIndicator( strokeWidth: 8.0, ) ); } }),
我们逐步来分解上面的示例代码:
- 这里使用async关键字将函数标为异步函数,这样函数的返回值就会被封装为异步。
- FutureBuilder组件的future属性是此组件的必传参数。FutureBuilder组件得到future之后,便开始通过future的then等方法追踪它(监听future执行的结果),当其状态改变时自动调用builder函数重绘。
- builder函数。每次绘制时,FutureBuilder都会调用这里的builder回传函数,并提供BuildContext(上下文)和AsyncSnapshot<>(异步快照)。在这里AsyncSnapshot<> 封装的类型就是future参数里的Future<> 所封装的类型。像上例一样,Future返回一个String,那么对应的AsyncSnapshot也是String类型。
- AsyncSnapshot 中含有 Future的最新状态及被封装的数据或异常。另外ConnectionState 属性描述了 Future 的四种状态,其分别为 none、waiting、active、done。
- 大多数时间做异步操作都是为了获取最终的数据,那么这个数据的获取即是在Future的done(完成)之后,所以我们的逻辑代码可以这样写。
FutureBuilder( future: getValue(), builder: (BuildContext context, AsyncSnapshot<String> snapshot) { if(snapshot.connectionState == ConnectionState.done) { if (snapshot.hasData) { return Text( "${snapshot.data}", style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold), ); } else { return Text("${snapshot.error}"); } }else { return Container( height: 50, width: 50, margin: const EdgeInsets.only(top: 10), child: const CircularProgressIndicator( strokeWidth: 8.0, ) ); } }),
首先判断Future的状态是否完成,未完成的情况下就加载动画组件。加载完成的情况下再进行判断snapshot返回的是正确数据还是异常,此时data和error必有一个且只有一个不是空。
总结一下:在Future完成之前,data和error都为空,Future完成之后,data和error有且仅有一个为空。所以这个时候我们可以不检查Future的状态是否完成,而是直接通过snapshot的hasData
数据和hasError
异常来判断。如果既没有数据又没有异常,那就是当前的Future还未完成,可直接返回加载动画组件。这个时候的代码就如最开始的那个示例一样直接检查hasData和hasError。
初始值 initialData
在Future完成之前,initialData属性提供一个数据的初始值给FutureBuilder组件使用。在有初始值的情况下,Future完成之前hasData会返回true,并且data中存储着所设置的初始值。当Future完成之后,FutureBuilder组件会自动切换到Future的真实值并重新渲染。
FutureBuilder( future: getValue(), initialData: "加载中...", builder: (BuildContext context, AsyncSnapshot<String> snapshot) { if (snapshot.hasData) { return Text( "${snapshot.data}", style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold), ); } else { return Text("${snapshot.error}"); } }),
二、StreamBuilder
StreamBuilder组件与FutureBuilder组件相同之外的不同之处在于它是一个可以自动跟踪Stream的状态,并在Stream有变化时自动重绘的组件。
那么问题来了,什么是Stream?在这里,我们称之为数据流或事件流。Data Stream、Event Stream。
顾名思义,既然称之为“流”,那可以想象出这是一种不间断的操作。
我们可以通过使用Stream.periodic构造函数
,并借助其count
参数(当前Stream已被调用的次数,从0开始递增),制作一个一秒加一的计数器数据流。
方法:
Stream<int> counter() { return Stream.periodic( const Duration(seconds: 1), (count) => count ); }
在组件中使用:
StreamBuilder( stream: counter(), builder: (BuildContext context, AsyncSnapshot<int> snapshot) { switch(snapshot.connectionState) { case ConnectionState.none: return Text("无数据流"); case ConnectionState.done: return Text("数据流关闭"); case ConnectionState.waiting: return Text("等待数据"); case ConnectionState.active: if(snapshot.hasData) { return Text("${snapshot.data}"); }else { return Text("${snapshot.error}"); } default: throw "connect error"; } })
这种用法有点像js中的 setInterval() 一样。
StreamBuilder组件与FutureBuilder组件用法类似,只是在这里所传的参数不是future,而是stream
,同时可以通过builder回传函数 获取Stream的4中状态,编写业务逻辑。用法与FutureBuilder组件中的状态类似,这里不过多重复。
上面这种普通数据流的写法只监听count()方法,如果需要支持多个监听者同时监听,则可以通过控制器的StreamController.broadcast
构造函数创建一个广播数据流。实现界面多处位置监听并重绘StreamBuilder组件。
例如:
final _stream = StreamController<int>(); StreamBuilder( stream: _stream.stream.map((event) => "value:${event}"), builder: (context, snapshot) { if(snapshot.connectionState == ConnectionState.done) { return Text("close stream"); } if(snapshot.hasData) return Text("${snapshot.data}"); if(snapshot.hasError) return Text("${snapshot.error}"); return CircularProgressIndicator(); }), Row( children: [ ElevatedButton(onPressed: () => _stream.add(666), child: Text("添加666")), SizedBox(width: 20,), ElevatedButton(onPressed: () => _stream.add(888), child: Text("添加888")), SizedBox(width: 20,), ElevatedButton(onPressed: () => _stream.close(), child: Text("关闭数据流")), ], )
在最开始,Stream没有开始释放任何事件,这时StreamBuilder会先渲染一个加载组件,当点击第一个按钮,界面将会显示 value:666 字样。只要不关闭数据流,StreamBuilder就会一直监听,任何一处controller的变化。
到此这篇关于Flutter异步操作实现流程详解的文章就介绍到这了,更多相关Flutter异步操作内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!