前面介绍几个Provider都是同步,如果是异步的场景怎么办?这时就要上FutureProvider了,它是Provider的异步版本,像一些网络请求场景就非常适合FutureProvider去处理,下面从几个简单Demo看看FutureProvider的使用
读取FutureProvider数据
Demo运行起来后首先看到的是加载指示器转圈2秒,然后显示数字100
void main() {
runApp(const ProviderScope(child: FutureApp()));
}
class FutureApp extends StatelessWidget {
const FutureApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(home: _Home());
}
}
// 用法和Provider差不多,多了async、await用来处理异步操作
final numberProvider = FutureProvider<int>((ref) async {
// 模拟耗时操作
int number = await Future.delayed(const Duration(seconds: 2), () {
return 100;
});
return number;
});
class _Home extends StatelessWidget {
const _Home();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Consumer(builder: (context, ref, _) {
debugPrint("build Consumer");
AsyncValue<int> future = ref.watch(numberProvider);
return future.when(
data: (value) {
return Text('total is number is $value');
},
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text("$err"));
}),
),
);
}
}
重点看下watch方法,了解一下大致过程,当第一次watch时,要走初始化值过程,FutureProvider内部会判断_create函数的返回值,就是下面这部分
(ref) async {
int number = await Future.delayed(const Duration(seconds: 2), () {
return 100;
});
return number;
}
- 是普通类型,那就当Provider使用了,没想到吧,同步的我也能兼容
- 为Future类型,分两步
- 1.先将当前状态值设置成AsyncLoading,这样页面就展示了加载指示器,
- 2.1异步初始化成功,当前状态值设置成AsyncData,页面展示正常数据
- 2.2异步初始化失败,当前状态值设置成AsyncError,页面展示错误信息。
关于AsyncValue
AsyncValue有如下三个子类
const factory AsyncValue.data(T value) = AsyncData<T>;
const factory AsyncValue.loading() = AsyncLoading<T>;
const factory AsyncValue.error(Object error, {StackTrace? stackTrace}) =
AsyncError<T>;
每个子类都复写了_map方法,转换对应的参数
AsyncData
@override
R _map<R>({
required R Function(AsyncData<T> data) data,
required R Function(AsyncError<T> error) error,
required R Function(AsyncLoading<T> loading) loading,
}) {
return data(this);
}
AsyncLoading
@override
R _map<R>({
required R Function(AsyncData<T> data) data,
required R Function(AsyncError<T> error) error,
required R Function(AsyncLoading<T> loading) loading,
}) {
return loading(this);
}
AsyncError
@override
R _map<R>({
required R Function(AsyncData<T> data) data,
required R Function(AsyncError<T> error) error,
required R Function(AsyncLoading<T> loading) loading,
}) {
return error(this);
}
when方法调用的就是_map方法,这样future是AsyncLoading返回的就是CircularProgressIndicator,future是AsyncData返回的就是TexT
AsyncValue<int> future = ref.watch(numberProvider);
return future.when(
data: (value) {
return Text('total is number is $value');
},
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text("$err"));
})
组合多个FutureProvider数据
FutureProvider另一个好用的功能是可以读取其它FutureProvider的状态值,当有多个异步操作,可以整合到一起。我们再添加一个FutureProvider,在读取的时候使用其内部的future,它可以忽略loading状态值,只获取最后状态值,原理看下面小节。最后的效果是加载指示器展示5秒后显示‘300’
final anotherNumberProvider = FutureProvider<int>((ref) async {
// 模拟耗时操作
int number = await Future.delayed(const Duration(seconds: 3), () {
return 200;
});
return number;
});
final numberProvider = FutureProvider<int>((ref) async {
// 模拟耗时操作
int number = await Future.delayed(const Duration(seconds: 2), () {
return 100;
});
// 使用future将忽略loading状态值,只获取最后状态值
int anotherNumber = await ref.watch(anotherNumberProvider.future);
return number + anotherNumber;
});
FutureProvier数据刷新
效果:在屏幕中央显示一个随机数,每次下拉刷新这个数据
// 每次初始化随机生成一个树
Random random = Random();
final randomNumberProvider = FutureProvider<int>((ref) async {
int number = await Future.delayed(const Duration(seconds: 2), () {
return random.nextInt(100);
});
return number;
});
class FutureApp extends StatelessWidget {
const FutureApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(home: _Home());
}
}
class _Home extends StatelessWidget {
const _Home();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('example')),
body: Consumer(builder: (context, ref, _) {
AsyncValue<int> number = ref.watch(randomNumberProvider);
return number.when(
data: (value) {
// 下拉刷新randomNumberProvider数据
return RefreshIndicator(child: LayoutBuilder(
builder:
(BuildContext context, BoxConstraints viewportConstraints) {
return SingleChildScrollView(
// child不超视口大小也能scroll,从而可以下拉刷新
physics: const AlwaysScrollableScrollPhysics(),
child: Container(
width: double.infinity,
height: viewportConstraints.maxHeight,// 和视口高度一致
alignment: Alignment.center,
color: Colors.lightBlue,
child: Text("${value}"),
));
},
), onRefresh: () async {
debugPrint("on Refresh");
// 立马重新初始化provider数据
return ref.refresh(randomNumberProvider);
});
},
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text("$err"));
}),
);
}
}
1.0.4的这个版本,在refresh的时候,立马重新计算,导致先收到loading状态,丢失了之前的页面,如下git图,体验不佳。2.x.x版本有所改善,先使用invalidate让数据失效,再重新read 其future数据,忽略loading过程,如下代码
onRefresh: () {
ref.invalidate(randomNumberProvider);
return ref.read(randomNumberProvider.future);
},
FutureProvider内部流程
前面也提过,其状态的分发分成两部分,
- 先设置Loading状态
- 对Future操作添加监听,有结果后更新状态
代码部分也是很清晰明了
_FutureProviderElementMixin
try {
final value = future();
先判断初始化是不是异步操作
if (value is Future<State>) {
1.先设置loading状态
setState(AsyncValue<State>.loading());
2.监听异步结果
value.then(
(event) {
if (running) setState(AsyncValue<State>.data(event));
},
// ignore: avoid_types_on_closure_parameters
onError: (Object err, StackTrace stack) {
if (running) {
setState(
AsyncValue<State>.error(err, stackTrace: stack),
);
}
},
);
} else {
return AsyncData(value);
}
return requireState;
} catch (err, stack) {
return AsyncValue.error(err, stackTrace: stack);
}
FutureProvider的future内部流程
在某些场景下,我们只需要获取FutureProvider的最后结果,而忽略loading状态,这在前面组合多个FutureProvider数据一节有所应用,那这是怎么实现的呢?
这就要用到Completer,这个类非常适合将基于回调的API转换成基于Future的,下面是其用法的伪代码演示
class NetworkHelper {
final Completer _completer = new Completer();
// 将future返回给客户端,让其等待结果
Future<T> sendData(String data) {
client.send(data);
return _completer.future;
}
// 监听后端回应,通知客户端结果
void _listenFromRemote(bool succeed,String response){
if(succeed){
_onSucceed(response);
}else{
_onError(response);
}
}
void _onSucceed(String result) {
_completer.complete(result);
}
// If something goes wrong, call this.
void _onError(error) {
_completer.completeError(error);
}
}
NetworkHelper helper=NetworkHelper();
// 发送数据后等待后端回应,阻塞到complete或completeError
String response = await helper.sendData("hello);
流程图画了一下,狗子看了都直摇头,不展示了。future的思路就是,监听FutureProvider的状态,如果是loading,返回_completer.future让客户端等待,等返回data后,使用complete结束等待。需要注意的是Completer不可重复使用,关键代码如下
void listener(AsyncValue<State>? previous, AsyncValue<State> value) {
value.when(
loading: () {
if (loadingCompleter == null) {
loadingCompleter = Completer<State>();
ref.setState(
调用future的ignore方法后返回future
loadingCompleter!.future..ignore(),
);
}
},
data: (data) {
if (loadingCompleter != null) {
loadingCompleter!.complete(data);
// completer不可重复使用
loadingCompleter = null;
} else {
ref.setState(Future<State>.value(data));
}
},
error: (err, stack) {
if (loadingCompleter != null) {
loadingCompleter!.completeError(err, stack);
loadingCompleter = null;
} else {
ref.setState(Future<State>.error(err, stack));
}
},
);
}
参考文档:
https://api.flutter-io.cn/flutter/dart-async/Completer-class.html
https://www.jianshu.com/p/a6516c67f2ce