文章

08.Flutter功能型组件

08.Flutter功能型组件

异步 UI 更新(FutureBuilder、StreamBuilder)

FutureBuilder

FutureBuilder 会依赖一个 Future,它会根据所依赖的 Future 的状态来动态构建自身。我们看一下 FutureBuilder 构造函数:

1
2
3
4
5
FutureBuilder({
  this.future,
  this.initialData,
  required this.builder,
})
  • future:FutureBuilder 依赖的 Future,通常是一个异步耗时任务
  • initialData:初始数据,用户设置默认数据
  • builder:Widget 构建器;该构建器会在 Future 执行的不同阶段被多次调用,构建器签名如下:
1
Widget Function(BuildContext context, AsyncSnapshot<T> snapshot)

snapshot 会包含当前异步任务的状态信息及结果信息:

  • snapshot.connectionState 获取异步任务的状态信息
1
2
3
4
5
6
7
8
9
10
11
12
13
enum ConnectionState {
  /// 当前没有异步任务,比如[FutureBuilder]的[future]为null时
  none,

  /// 异步任务处于等待状态
  waiting,

  /// Stream处于激活状态(流上已经有数据传递了),对于FutureBuilder没有该状态。
  active, // ConnectionState.active只在StreamBuilder中才会出现

  /// 异步任务已经终止.
  done,
}
  • snapshot.hasError 判断异步任务是否有错误

示例:我们实现一个路由,当该路由打开时我们从网上获取数据,获取数据时弹一个加载框;获取结束时,如果成功则显示获取到的数据,如果失败则显示错误。

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
Widget FutureBuilderWidget() {
  return Center(
    child: FutureBuilder<String>(
      future: mockNetworkData(),
      builder: (BuildContext context, AsyncSnapshot snapshot) {
        // 请求已结束
        if (snapshot.connectionState == ConnectionState.done) {
          if (snapshot.hasError) {
            // 请求失败,显示错误
            return Text("Error: ${snapshot.error}");
          } else {
            // 请求成功,显示数据
            return Text("Contents: ${snapshot.data}");
          }
        } else {
          // 请求未结束,显示loading
          return const CircularProgressIndicator();
        }
      },
    ),
  );
}
Future<String> mockNetworkData() async {
  return Future.delayed(const Duration(seconds: 2), () => "我是从互联网上获取的数据");
}

cijxz

注意:示例的代码中,每次组件重新 build 都会重新发起请求,因为每次的 future 都是新的,实践中我们通常会有一些缓存策略,常见的处理方式是在 future 成功后将 future 缓存,这样下次 build 时,就不会再重新发起异步任务。

StreamBuilder

在 Dart 中 Stream 也是用于接收异步事件数据,和 Future 不同的是,它可以接收多个异步操作的结果,它常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。StreamBuilder 正是用于配合 Stream 来展示流上事件(数据)变化的 UI 组件。下面看一下 StreamBuilder 的默认构造函数:

1
2
3
4
5
StreamBuilder({
  this.initialData,
  Stream<T> stream,
  required this.builder,
})

示例:
创建一个计时器的示例:每隔 1 秒,计数加 1。这里,我们使用 Stream 来实现每隔一秒生成一个数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Stream<int> counter() {
  return Stream.periodic(Duration(seconds: 1), (i) {
    return i;
  });
}
Widget StreamBuilderWidget() {
  return StreamBuilder<int>(
    stream: counter(), //
    //initialData: ,// a Stream<int> or null
    builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
      if (snapshot.hasError) return Text('Error: ${snapshot.error}');
      switch (snapshot.connectionState) {
        case ConnectionState.none:
          return const Text('没有Stream');
        case ConnectionState.waiting:
          return const Text('等待数据...');
        case ConnectionState.active:
          return Text('active: ${snapshot.data}');
        case ConnectionState.done:
          return const Text('Stream 已关闭');
      }
    },
  );
}

WillPopScope 导航返回拦截

1
2
3
4
5
const WillPopScope({
  // ...
  required WillPopCallback onWillPop,
  required Widget child
})
  • onWillPop 是一个回调函数,当用户点击返回按钮时被调用(包括导航返回按钮及 Android 物理返回按钮)。该回调需要返回一个 Future 对象,如果返回的 Future 最终值为 false 时,则当前路由不出栈 (不会返回);最终值为 true 时,当前路由出栈退出。我们需要提供这个回调来决定是否退出。

示例:为了防止用户误触返回键退出,我们拦截返回事件。当用户在 1 秒内点击两次返回按钮时,则退出;如果间隔超过 1 秒则不退出,并重新记时。

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
class WillPopScopeDemo extends StatelessWidget {
  const WillPopScopeDemo({super.key});
  @override
  Widget build(BuildContext context) {
    return const WillPopScopeTestRoute();
  }
}
class WillPopScopeTestRoute extends StatefulWidget {
  const WillPopScopeTestRoute({super.key});

  @override
  WillPopScopeTestRouteState createState() {
    return WillPopScopeTestRouteState();
  }
}
class WillPopScopeTestRouteState extends State<WillPopScopeTestRoute> {
  DateTime? _lastPressedAt; // 上次点击时间
  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        // 1. 返回true,表示可以返回
        // 2. 返回false,表示不可以返回
        // 3. 返回Future.value(false),表示不可以返回
        // 4. 返回Future.value(true),表示可以返回
        if (_lastPressedAt == null ||
            DateTime.now().difference(_lastPressedAt!) >
                const Duration(seconds: 1)) {
          // 两次点击间隔超过1秒则重新计时
          print('两次点击间隔超过1秒则重新计时');
          _lastPressedAt = DateTime.now();
          return false;
        }
        return true;
      },
      child: MaterialApp(
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        ),
        home: Scaffold(
          appBar: AppBar(
            title: const Text("WillPopScope Demo"),
          ),
          body: Container(
            alignment: Alignment.center,
            decoration: const BoxDecoration(
              color: Colors.transparent,
            ),
            child: const Text("1秒内连续按两次返回键退出"),
          ),
        ),
      ),
    );
  }
}
本文由作者按照 CC BY 4.0 进行授权