Flutter异步构建插件async_builder的使用

Flutter异步构建插件async_builder的使用

该插件提供了AsyncBuilder,一个类似于StreamBuilder/FutureBuilder的小部件,旨在减少样板代码并改进错误处理。它还提供了InitBuilder,使得安全地开始异步任务变得更加容易。

如何使用

1. 添加到依赖项

dependencies:
  async_builder: ^1.2.0

2. 导入

import 'package:async_builder/async_builder.dart';
import 'package:async_builder/init_builder.dart';

AsyncBuilder示例

Future

AsyncBuilder<String>(
  future: myFuture,
  waiting: (context) => Text('Loading...'),
  builder: (context, value) => Text('$value'),
  error: (context, error, stackTrace) => Text('Error! $error'),
)

Stream

AsyncBuilder<String>(
  stream: myStream,
  waiting: (context) => Text('Loading...'),
  builder: (context, value) => Text('$value'),
  error: (context, error, stackTrace) => Text('Error! $error'),
  closed: (context, value) => Text('$value (closed)'),
)

注意:不能同时提供流和未来。

AsyncBuilder功能

分离的构建器

AsyncBuilder允许你根据异步操作的状态指定不同的构建器:

  • waiting(context) - 在没有任何事件发生时调用。
  • builder(context, value) - 必需。在有值可用时调用。
  • error(context, error, stackTrace) - 如果出现错误则调用。
  • closed(context, value) - 如果流关闭则调用。

如果没有提供这些,则默认调用builder(可能带有空的value)。

错误处理

AsyncBuilder默认不会静默忽略错误。如果异常发生且提供了error,小部件将重建并调用该构建器。否则,如果未提供errorsilent不为真,则异常和堆栈跟踪将打印到控制台。

初始值

如果提供了initial,则在其可用之前会使用该值。

RxDart ValueStream

如果流是一个持有现有值的ValueStreamBehaviorSubject),则在第一次构建时该值将立即可用。

流暂停

该小部件的StreamSubscription可以通过pause参数暂停,这在你需要通知上游StreamController你不再需要更新时很有用。

InitBuilder

InitBuilder是一个仅在配置更改时初始化值的小部件,这非常有用,因为它允许你安全地开始异步任务而无需创建一个新的StatefulWidget

基本用法

static Future<int> getNumber() async => ...;

build(context) => InitBuilder<int>(
  getter: getNumber,
  builder: (context, future) => AsyncBuilder<int>(
    future: future,
    builder: (context, value) => Text('$value'),
  ),
);

在这种情况下,getNumber只会在第一次构建时被调用。

传递参数

你还可以向getter传递参数,例如查询共享偏好设置:

final String prefsKey;

build(context) => InitBuilder.arg<String, String>(
  getter: sharedPrefs.getString,
  arg: prefsKey,
  builder: (context, future) => AsyncBuilder<String>(
    future: future,
    builder: (context, value) => Text('$value'),
  ),
);

构造函数InitBuilder.argInitBuilder.arg7可用于向getter传递参数,这些参数将在getter或参数发生变化时重新初始化值。

完整示例

以下是一个完整的示例,展示了如何使用AsyncBuilderInitBuilder

import 'dart:async';
import 'dart:math';

import 'package:async_builder/async_builder.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blueGrey,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const MyHomePage(title: 'AsyncBuilder Example'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  [@override](/user/override)
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: ListView(
        children: [
          AsyncTestChild1(),
          AsyncTestChild2(),
          AsyncTestChild3(),
          AsyncTestChild4(),
        ],
      ),
      backgroundColor: Colors.blueGrey.shade600,
    );
  }
}

class AsyncTestChild1 extends StatefulWidget {
  [@override](/user/override)
  _AsyncTestChild1State createState() => _AsyncTestChild1State();
}

class _AsyncTestChild1State extends State<AsyncTestChild1> {
  Future<int>? randomNumber;

  [@override](/user/override)
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final textTheme = theme.textTheme;
    return TestCard(
      title: '随机数 - Future',
      desc: '此示例在2秒后完成一个返回随机数的Future。',
      child: Row(children: [
        ElevatedButton(
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.blue,
            textStyle: const TextStyle(color: Colors.white),
          ),
          child: const Text('生成'),
          onPressed: () {
            setState(() {
              randomNumber = Future.delayed(
                  const Duration(seconds: 2), () => Random().nextInt(100));
            });
          },
        ),
        const Padding(padding: EdgeInsets.only(right: 16)),
        if (randomNumber != null)
          AsyncBuilder<int>(
            waiting: (context) => const CircularProgressIndicator(),
            builder: (context, i) => Text('$i', style: textTheme.titleLarge),
            future: randomNumber,
          ),
      ]),
    );
  }
}

class AsyncTestChild2 extends StatefulWidget {
  [@override](/user/override)
  _AsyncTestChild2State createState() => _AsyncTestChild2State();
}

class _AsyncTestChild2State extends State<AsyncTestChild2> {
  StreamController<int>? randomNumber;

  void initController() {
    setState(() {
      randomNumber?.close();
      randomNumber = StreamController<int>();
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final textTheme = theme.textTheme;
    return TestCard(
      title: '随机数 - Stream',
      desc: '此示例在1秒后向流添加一个随机数。',
      child: Row(children: [
        ElevatedButton(
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.blue,
            textStyle: const TextStyle(color: Colors.white),
          ),
          child: const Text('重置'),
          onPressed: randomNumber == null
              ? null
              : () {
                  setState(() {
                    randomNumber = null;
                  });
                },
        ),
        const Padding(padding: EdgeInsets.only(right: 8)),
        ElevatedButton(
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.blue,
            textStyle: const TextStyle(color: Colors.white),
          ),
          child: const Text('添加'),
          onPressed: () async {
            if (randomNumber == null) initController();
            final ctrl = randomNumber;
            await Future<void>.delayed(const Duration(seconds: 1));
            ctrl!.add(Random().nextInt(100));
          },
        ),
        const Padding(padding: EdgeInsets.only(right: 16)),
        if (randomNumber != null)
          AsyncBuilder<int>(
            waiting: (context) => const CircularProgressIndicator(),
            builder: (context, i) => Text('$i', style: textTheme.titleLarge),
            stream: randomNumber!.stream,
          ),
      ]),
    );
  }
}

class AsyncTestChild3 extends StatefulWidget {
  [@override](/user/override)
  _AsyncTestChild3State createState() => _AsyncTestChild3State();
}

class _AsyncTestChild3State extends State<AsyncTestChild3> {
  StreamController<int>? randomNumber;

  void initController() {
    setState(() {
      randomNumber?.close();
      final ctrl = StreamController<int>();
      randomNumber = ctrl;
      final timer = Timer.periodic(const Duration(milliseconds: 500), (timer) {
        ctrl.add(Random().nextInt(100));
      });
      ctrl.onCancel = timer.cancel;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final textTheme = theme.textTheme;
    return TestCard(
      title: '随机数 - 关闭',
      desc: '此示例持续向流添加数字直到关闭。',
      child: Row(children: [
        ElevatedButton(
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.blue,
            textStyle: const TextStyle(color: Colors.white),
          ),
          child: Text(randomNumber == null ? '开始' : '重启'),
          onPressed: initController,
        ),
        const Padding(padding: EdgeInsets.only(right: 8)),
        ElevatedButton(
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.blue,
            textStyle: const TextStyle(color: Colors.white),
          ),
          child: const Text('关闭'),
          onPressed: randomNumber == null || randomNumber!.isClosed
              ? null
              : () {
                  setState(() {
                    randomNumber!.close();
                  });
                },
        ),
        const Padding(padding: EdgeInsets.only(right: 16)),
        if (randomNumber != null)
          AsyncBuilder<int>(
            waiting: (context) => const CircularProgressIndicator(),
            builder: (context, i) => Text('$i', style: textTheme.titleLarge),
            closed: (context, i) =>
                Text('$i (关闭)', style: textTheme.titleLarge),
            stream: randomNumber!.stream,
          ),
      ]),
    );
  }
}

class AsyncTestChild4 extends StatefulWidget {
  [@override](/user/override)
  _AsyncTestChild4State createState() => _AsyncTestChild4State();
}

class _AsyncTestChild4State extends State<AsyncTestChild4> {
  StreamController<int>? randomNumber;
  var pause = false;

  void initController() {
    setState(() {
      randomNumber?.close();

      final ctrl = StreamController<int>();
      randomNumber = ctrl;

      late Timer timer;
      void start() {
        timer = Timer.periodic(const Duration(milliseconds: 500), (timer) {
          ctrl.add(Random().nextInt(100));
        });
      }

      ctrl.onPause = () => timer.cancel();
      ctrl.onCancel = () => timer.cancel();
      ctrl.onResume = start;

      start();
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final textTheme = theme.textTheme;
    return TestCard(
      title: '随机数 - 暂停',
      desc: '此示例持续向流添加数字但允许订阅暂停。',
      child: Row(children: [
        ElevatedButton(
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.blue,
            textStyle: const TextStyle(color: Colors.white),
          ),
          child: Text(randomNumber == null ? '开始' : '重启'),
          onPressed: initController,
        ),
        const Padding(padding: EdgeInsets.only(right: 16)),
        Text('暂停', style: textTheme.titleSmall),
        Switch(
            value: pause,
            onChanged: (b) {
              setState(() {
                pause = b;
              });
            },
            activeColor: Colors.blue),
        const Padding(padding: EdgeInsets.only(right: 16)),
        if (randomNumber != null)
          AsyncBuilder<int>(
            waiting: (context) => const CircularProgressIndicator(),
            builder: (context, i) => Text('$i', style: textTheme.titleLarge),
            stream: randomNumber!.stream,
            pause: pause,
          ),
      ]),
    );
  }
}

class TestCard extends StatelessWidget {
  final String title;
  final String desc;
  final Widget child;

  const TestCard({
    required this.title,
    required this.desc,
    required this.child,
  });

  [@override](/user/override)
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final textTheme = theme.textTheme;
    return Card(
      child: Padding(
          padding: const EdgeInsets.all(8),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Padding(
                child: Text(title, style: textTheme.titleLarge),
                padding: const EdgeInsets.symmetric(vertical: 8),
              ),
              const Divider(),
              Text(desc, style: textTheme.titleMedium),
              const Padding(padding: EdgeInsets.only(bottom: 16)),
              child,
            ],
          )),
    );
  }
}

更多关于Flutter异步构建插件async_builder的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter异步构建插件async_builder的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter中使用async_builder插件的详细代码案例。async_builder是一个用于处理异步构建过程的Flutter插件,它允许你在数据加载过程中显示加载指示器,并在数据加载完成后显示实际内容。

首先,确保你已经在pubspec.yaml文件中添加了async_builder依赖:

dependencies:
  flutter:
    sdk: flutter
  async_builder: ^x.y.z  # 请替换为最新版本号

然后,运行flutter pub get来安装依赖。

接下来,我们来看一个完整的代码示例,展示如何使用AsyncBuilder来异步加载数据并在UI中显示:

import 'package:flutter/material.dart';
import 'package:async_builder/async_builder.dart';
import 'dart:async';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Async Builder Example'),
        ),
        body: Center(
          child: AsyncBuilder<String>(
            future: _fetchData(),
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                // 显示加载指示器
                return CircularProgressIndicator();
              } else if (snapshot.hasError) {
                // 处理错误
                return Text('Error: ${snapshot.error}');
              } else {
                // 显示数据
                return Text('Data: ${snapshot.data}');
              }
            },
          ),
        ),
      ),
    );
  }

  // 模拟异步数据加载函数
  Future<String> _fetchData() async {
    await Future.delayed(Duration(seconds: 2)); // 模拟网络延迟
    return 'Hello, Flutter!';
  }
}

代码解释:

  1. 依赖导入:首先,我们导入了必要的Flutter和async_builder包。

  2. 主应用MyApp是一个StatelessWidget,它构建了一个简单的Material应用,其中包含一个ScaffoldAppBar,和一个居中的AsyncBuilder

  3. AsyncBuilder

    • future属性:指定一个返回Future的函数,这里我们使用_fetchData()函数来模拟异步数据加载。
    • builder属性:一个函数,它接收当前上下文和AsyncSnapshot对象。根据AsyncSnapshotconnectionState,我们可以判断当前是处于加载中、加载完成还是出错状态。
      • 如果connectionStateConnectionState.waiting,则显示一个CircularProgressIndicator作为加载指示器。
      • 如果发生错误(snapshot.hasError),则显示错误信息。
      • 如果加载完成(其他状态),则显示加载的数据。
  4. 模拟异步数据加载_fetchData()函数使用Future.delayed来模拟一个耗时操作(例如网络请求),然后返回一个字符串数据。

这样,你就可以在Flutter应用中利用async_builder插件优雅地处理异步数据加载和UI更新。希望这个示例对你有帮助!

回到顶部