Flutter异步数据构建插件listenable_future_builder的使用

Flutter异步数据构建插件listenable_future_builder的使用

引言

我们经常使用 ChangeNotifierValueNotifier 作为 StatelessWidget 的控制器。然而,存在两个问题:

  • 一个普通的 StatelessWidget 不能持有控制器,因为它可能会在任何时间重建并失去状态。
  • 我们有时需要在使用控制器之前完成一些异步工作。

ListenableFutureBuilder 通过结合 AnimatedBuilderFutureBuilder 的功能解决了这些问题。

示例

您可以在此处查看实时示例: https://dartpad.dev/?id=8ea8e0e52e26be59d4cb8c056de53617

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

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(
        useMaterial3: true,
        primarySwatch: Colors.blue,
      ),
      home: ListenableFutureBuilder<ValueNotifier<int>>(
        listenable: getController,
        builder: (context, child, snapshot) => Scaffold(
          appBar: AppBar(),
          body: Center(
            child: switch (snapshot) {
              AsyncSnapshot(hasData: true) => Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    const Text(
                      'You have pushed the button this many times:',
                    ),
                    Text(
                      '${snapshot.data!.value}',
                      style: Theme.of(context).textTheme.headlineMedium,
                    ),
                  ],
                ),
              AsyncSnapshot(hasError: true) => const Text('Error'),
              AsyncSnapshot() => const CircularProgressIndicator.adaptive()
            },
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () => snapshot.data?.value++,
            tooltip: 'Increment',
            child: const Icon(Icons.add),
          ),
        ),
      ),
      debugShowCheckedModeBanner: false,
    ),
  );
}

Future<ValueNotifier<int>> getController() async =>
    Future.delayed(const Duration(seconds: 2), () => ValueNotifier<int>(0));

与之相比,不使用 ListenableFutureBuilder 的版本更为冗长:

import 'package:flutter/material.dart';

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

class MyApp extends StatefulWidget {
  const MyApp({
    super.key,
  });

  [@override](/user/override)
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  ValueNotifier<int>? _controller;

  [@override](/user/override)
  void initState() {
    super.initState();
    _getController();
  }

  Future<void> _getController() async {
    final controller = await getController();
    setState(() {
      _controller = controller;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) => MaterialApp(
        theme: ThemeData(
          useMaterial3: true,
          primarySwatch: Colors.blue,
        ),
        home: Scaffold(
          appBar: AppBar(),
          floatingActionButton: FloatingActionButton(
            onPressed: () => _controller!.value++,
            tooltip: 'Increment',
            child: const Icon(Icons.add),
          ),
          body: _controller != null
              ? AnimatedBuilder(
                  animation: _controller!,
                  builder: (context, child) => Center(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: <Widget>[
                        const Text(
                          'You have pushed the button this many times:',
                        ),
                        Text(
                          '${_controller!.value}',
                          style: Theme.of(context).textTheme.headlineMedium,
                        ),
                      ],
                    ),
                  ),
                )
              : const CircularProgressIndicator.adaptive(),
        ),
        debugShowCheckedModeBanner: false,
      );
}

Future<ValueNotifier<int>> getController() async =>
    Future.delayed(const Duration(seconds: 2), () => ValueNotifier<int>(0));

开始使用

pubspec.yaml 中添加 listenable_future_builder 依赖项:

dependencies:
  flutter:
    sdk: flutter
  listenable_future_builder: ^[CURRENT-VERSION]

在 Dart 文件中导入包:

import 'package:listenable_future_builder/listenable_future_builder.dart';

使用方法

ListenableFutureBuilder 可以与任何 Listenable(如 ChangeNotifierValueNotifier)一起使用。要使用 ListenableFutureBuilder,提供一个返回 Listenable 控制器的 Future 函数和一个根据 AsyncSnapshot 状态定义如何构建小部件的 builder 函数。

以下是如何使用 ListenableFutureBuilderValueNotifier 的示例。builder 函数应该检查 AsyncSnapshot 的状态,以确定数据是否准备好、是否发生错误或仍在加载。我们在等待时显示一个 CircularProgressIndicator,如果发生错误则显示错误消息,并在数据可用时显示值。

点击浮动操作按钮将弹出输入对话框,如果您输入了一个值,它将在新列表中创建一个新的项目。ListView 将显示当前列表中的所有项目。

您可以在此处查看实时示例: https://dartpad.dev/?id=8bb84e817cf5f1245eb25d1c52c7c217

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

void main() => runApp(
      MaterialApp(
        theme: ThemeData(
          useMaterial3: true,
          primarySwatch: Colors.purple,
        ),
        debugShowCheckedModeBanner: false,
        home: ListenableFutureBuilder<ValueNotifier<List<String>>>(
          listenable: () => Future<ValueNotifier<List<String>>>.delayed(
              const Duration(seconds: 2),
              () => ValueNotifier<List<String>>([])),
          builder: (context, child, snapshot) => Scaffold(
            appBar: AppBar(title: const Text('To-do List')),
            body: Center(
              child: switch (snapshot) {
                AsyncSnapshot(hasData: true) => ListView.builder(
                    itemCount: snapshot.data!.value.length,
                    itemBuilder: (context, index) =>
                        ListTile(title: Text(snapshot.data!.value[index])),
                  ),
                AsyncSnapshot(hasError: true) => const Text('Error'),
                AsyncSnapshot() => const CircularProgressIndicator.adaptive()
              },
            ),
            floatingActionButton: FloatingActionButton(
              onPressed: () => showDialog(
                context: context,
                builder: (context) => AlertDialog(
                  title: const Text('Add a new to-do item'),
                  content: TextField(
                    onSubmitted: (value) {
                      Navigator.of(context).pop();
                      List<String> updatedList =
                          List<String>.from(snapshot.data!.value);
                      updatedList.add(value);
                      snapshot.data!.value = updatedList;
                    },
                  ),
                ),
              ),
              tooltip: 'Add a new item',
              child: const Icon(Icons.add),
            ),
          ),
        ),
      ),
    );

处理清理

在某些情况下,当 ListenableFutureBuilder 被销毁时,可能需要执行清理操作。这在监听器持有资源需要在小部件不再使用时释放时是必要的。要处理清理,提供一个 disposeListenable 函数。此函数将在 ListenableFutureBuilder 被销毁时调用监听器实例。

在这个例子中,我们创建了一个自定义的 ChangeNotifier 类来管理一个计时器,并使用 ListenableFutureBuilder 显示计时器的当前值。我们还提供了 disposeListenable 函数,在小部件不再使用时停止计时器并清理资源。当 ListenableFutureBuilder 被销毁时,disposeListenable 函数停止计时器并释放由 TimerNotifier 持有的资源。这有助于防止资源泄漏并确保 Listenable 的正确清理。这个例子是有状态的,因此我们可以使用浮动操作按钮切换 _showListenableFutureBuilder。点击此按钮将移除 ListenableFutureBuilder,从而触发 disposeListenable 函数。

您可以在此处查看实时示例: https://dartpad.dev/?id=bf318c5a7e87e0fa1ace796247d96620

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

class TimerNotifier extends ChangeNotifier {
  Timer? _timer;
  int _seconds = 0;

  TimerNotifier() {
    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      _seconds++;
      notifyListeners();
    });
  }

  int get seconds => _seconds;

  void disposeTimer() {
    _timer?.cancel();
    _timer = null;
  }
}

void main() {
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      home: MyApp(),
      theme: ThemeData(
        useMaterial3: true,
        primarySwatch: Colors.orange,
      ),
    ),
  );
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  [@override](/user/override)
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool _showListenableFutureBuilder = true;

  [@override](/user/override)
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(),
        body: Center(
          child: _showListenableFutureBuilder
              ? ListenableFutureBuilder<TimerNotifier>(
                  listenable: getTimerNotifier,
                  builder: (context, child, snapshot) => switch (snapshot) {
                    AsyncSnapshot(hasData: true) => 
                      Text('Elapsed seconds: ${snapshot.data!.seconds}'),
                    AsyncSnapshot(hasError: true) => const Text('Error'),
                    AsyncSnapshot() => 
                      const CircularProgressIndicator.adaptive()
                  },
                  disposeListenable: (timerNotifier) async =>
                      timerNotifier.disposeTimer(),
                )
              : const Text('ListenableFutureBuilder removed.'),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => setState(
            () => _showListenableFutureBuilder = !_showListenableFutureBuilder,
          ),
          tooltip: 'Toggle ListenableFutureBuilder',
          child: const Icon(Icons.toggle_on),
        ),
      );
}

Future<TimerNotifier> getTimerNotifier() async {
  await Future.delayed(const Duration(seconds: 2));
  return TimerNotifier();
}

自定义监听器

您可能希望实现自己的 Listenable 类。这个例子展示了点击浮动操作按钮时显示随机颜色。我们创建了一个 ColorController 类,它扩展了 Listenable。此控制器允许您通过调用 changeColor 方法更改 ColoredBox 小部件的颜色。ListenableFutureBuilder 用于使用 ColorController 构建小部件树,并提供一个 FloatingActionButton 随机更改颜色。当 ListenableFutureBuilder 从小部件树中移除时,将调用 disposeListenable 函数,并释放 ColorController

您可以在此处查看实时示例: https://dartpad.dev/?id=a98cd6da42144ea581b9ef45704dcbf2

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

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

class ColorController extends Listenable {
  final List<VoidCallback> _listeners = [];

  Color _color = Colors.red;

  Color get color => _color;

  void changeColor(Color newColor) {
    _color = newColor;
    notifyListeners();
  }

  [@override](/user/override)
  void addListener(VoidCallback listener) {
    _listeners.add(listener);
  }

  [@override](/user/override)
  void removeListener(VoidCallback listener) {
    _listeners.remove(listener);
  }

  void notifyListeners() {
    for (final listener in _listeners) {
      listener();
    }
  }

  void dispose() {
    _listeners.clear();
  }
}

void main() => runApp(
      MaterialApp(
        theme: ThemeData(
          useMaterial3: true,
          primarySwatch: Colors.green,
        ),
        debugShowCheckedModeBanner: false,
        home: ListenableFutureBuilder<ColorController>(
          listenable: () => Future.delayed(
              const Duration(seconds: 2), () => ColorController()),
          builder: (context, child, snapshot) => Scaffold(
            body: Center(
              child: switch (snapshot) {
                AsyncSnapshot(hasData: true) => ColoredBox(
                    color: snapshot.data!.color,
                    child: const SizedBox(width: 100, height: 100),
                  ),
                AsyncSnapshot(hasError: true) => const Text('Error'),
                AsyncSnapshot() => const CircularProgressIndicator.adaptive()
              },
            ),
            floatingActionButton: FloatingActionButton(
              onPressed: () => snapshot.data?.changeColor(
                  Colors.primaries[Random().nextInt(Colors.primaries.length)]),
              tooltip: 'Change color',
              child: const Icon(Icons.color_lens),
            ),
          ),
          disposeListenable: (colorController) async =>
              colorController.dispose(),
        ),
      ),
    );

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

1 回复

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


当然,listenable_future_builder 是一个 Flutter 插件,它允许你使用 ListenableFuture 来构建异步数据列表。这在需要监听数据变化并实时更新 UI 时非常有用。以下是一个使用 listenable_future_builder 的示例代码,展示如何构建一个简单的列表视图,该视图会根据 ListenableFuture 的数据变化进行更新。

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

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

然后,你可以按照以下步骤在 Dart 文件中使用它:

1. 导入必要的包

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

2. 创建一个模拟的 ListenableFuture 数据源

这里我们创建一个简单的 ListenableFuture,它会在一段时间后返回一个字符串列表。

ListenableFuture<List<String>> createListenableFuture() {
  Completer<List<String>> completer = Completer<List<String>>();

  // 模拟异步数据获取
  Future.delayed(Duration(seconds: 2), () {
    List<String> data = ["Item 1", "Item 2", "Item 3"];
    completer.complete(data);
  });

  return completer.future;
}

3. 使用 ListenableFutureBuilder 构建 UI

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('ListenableFutureBuilder Demo'),
        ),
        body: Center(
          child: ListenableFutureBuilder<List<String>>(
            future: createListenableFuture(),
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return CircularProgressIndicator();
              } else if (snapshot.hasError) {
                return Text('Error: ${snapshot.error}');
              } else {
                return ListView.builder(
                  itemCount: snapshot.data?.length ?? 0,
                  itemBuilder: (context, index) {
                    return ListTile(
                      title: Text(snapshot.data?[index] ?? ''),
                    );
                  },
                );
              }
            },
          ),
        ),
      ),
    );
  }
}

解释

  1. 数据源createListenableFuture() 函数返回一个 ListenableFuture,它会在 2 秒后完成并返回一个字符串列表。
  2. UI 构建ListenableFutureBuilder 接受一个 future 和一个 builder 函数。builder 函数根据 snapshot 的状态(如等待、错误、数据)来构建相应的 UI。
    • connectionStateConnectionState.waiting 时,显示一个进度指示器。
    • hasErrortrue 时,显示错误信息。
    • 否则,使用 ListView.builder 来显示数据列表。

这样,你就可以使用 listenable_future_builder 来构建异步数据列表,并实时更新 UI。这个示例展示了基本的使用方法,你可以根据需要进一步扩展和修改。

回到顶部