Flutter数据流与值传递插件stream_with_value的使用

发布于 1周前 作者 htzhanglong 来自 Flutter

Flutter数据流与值传递插件 stream_with_value 的使用

stream_with_value 是一个用于简化Flutter中数据流和值传递的插件。它提供了对单个订阅流(Single-Subscription Stream)的最新值进行访问的能力,并解决了在使用 StreamBuilder 时遇到的一些常见问题,如处理未加载值的情况。

关于 stream_with_value

如果你曾经遇到过以下情况:

  • 需要监听单个订阅流并按需访问最新的值;
  • 在使用 StreamBuilder 处理懒加载值时感到困惑和繁琐,特别是在处理值未加载的情况(例如显示进度指示器)。

那么这个插件非常适合你。它提供了一个封装了 Stream 和类型为 T 的值的接口,以及一组方便的实现和扩展。最常用的实现是 StreamWithLatestValue,它可以自动跟踪流发出的最新值。

核心类

abstract class StreamWithValue<T> {
  /// The stream.
  Stream<T> get updates;

  /// The value.
  T get value;

  /// Whether the value is initialized.
  bool get loaded;
}

class StreamWithLatestValue<T> implements StreamWithValue<T> {
  StreamWithLatestValue(Stream<T> sourceStream) { /* ... */ }

  factory StreamWithLatestValue.withInitialValue(
    Stream<T> sourceStream, {
    required T initialValue,
  }) { /* ... */ }
}

注意:由于订阅单个订阅流可能是一个昂贵的操作(例如,它可能会启动网络连接并开始下载),StreamWithLatestValue 不会在你通过 updates.listen() 或将其传递给 StreamBuilder 之前订阅流。这意味着在订阅激活并且流发出第一个值之前,value 将不会被 loaded

对于Flutter用户的特别说明

在Flutter中,常见的模式是在UI元素加载时显示进度指示器(例如从远程数据库加载)。Flutter提供了方便的 StreamBuilder,允许你在流未加载时自定义行为,但你需要从头实现它并插入分支逻辑。如果值在屏幕旋转时已经加载过,重新构建UI时可能会导致闪烁。StreamWithValue 正是为了解决这些问题而设计的。你可以使用便利的小部件 StreamBuilderWithValueDataStreamWithValueBuilder 来避免这些问题。

如何使用

首先,在你的项目中安装 stream_with_value 插件:

dependencies:
  stream_with_value: ^latest_version

然后,你可以使用以下代码来改进你的项目:

示例代码

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

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(title: 'Flutter Demo Home Page'),
      );
}

class MyHomePage extends StatefulWidget {
  final String title;

  MyHomePage({required this.title});

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _counterStreamController = StreamController<int>.broadcast();
  late StreamWithLatestValue<int> _streamWithValue;

  @override
  void initState() {
    _streamWithValue = StreamWithLatestValue.withInitialValue(
        _counterStreamController.stream,
        initialValue: 0);
    super.initState();
  }

  @override
  void dispose() {
    _counterStreamController.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              const Text(
                'You have pushed the button this many times:',
              ),
              const Text('StreamBuilderWithValue example'),
              StreamBuilderWithValue<int>(
                streamWithValue: _streamWithValue,
                builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
                  return (snapshot.hasData)
                      ? Text(
                          '${snapshot.data}',
                          style: Theme.of(context).textTheme.headline4,
                        )
                      : CircularProgressIndicator();
                },
              ),
              const Text('DataStreamWithValueBuilder example'),
              DataStreamWithValueBuilder<int>(
                streamWithValue: _streamWithValue,
                builder: (context, int value) => Text(
                  '$value',
                  style: Theme.of(context).textTheme.headline4,
                ),
                onData: (newValue) {
                  print('New value arrived callback: $newValue');
                },
                nullValueBuilder: (BuildContext context) =>
                    CircularProgressIndicator(),
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            // Increment the latest value and add to the StreamController
            _counterStreamController.add(_streamWithValue.value + 1);
          },
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      );
}

解释

  1. 初始化:在 initState 方法中,我们创建了一个 StreamWithLatestValue 实例,并为其提供了初始值。
  2. UI构建:在 build 方法中,我们使用了两种不同的小部件来展示流中的值:
    • StreamBuilderWithValue:当有数据时显示文本,没有数据时显示进度指示器。
    • DataStreamWithValueBuilder:同样用于显示文本或进度指示器,并且可以设置回调函数以处理新值的到来。

通过这种方式,你可以轻松地管理和显示流中的值,同时避免常见的UI闪烁问题。


更多关于Flutter数据流与值传递插件stream_with_value的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter数据流与值传递插件stream_with_value的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter中使用stream_with_value插件来处理数据流与值传递的一个简单示例。这个插件允许你在Flutter应用中更方便地管理和监听数据流以及传递值。

首先,你需要在你的pubspec.yaml文件中添加stream_with_value依赖:

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

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

接下来,让我们创建一个简单的Flutter应用,演示如何使用stream_with_value

1. 创建一个Flutter项目

如果你还没有一个Flutter项目,可以使用flutter create my_app来创建一个。

2. 创建一个数据提供者

我们将创建一个简单的数据提供者,它使用StreamWithValue来管理一个整数值的流。

// data_provider.dart
import 'package:flutter/material.dart';
import 'package:stream_with_value/stream_with_value.dart';

class DataProvider with ChangeNotifier {
  final StreamWithValueController<int> _controller = StreamWithValueController<int>();

  // 获取当前值
  int get currentValue => _controller.value;

  // 获取流
  Stream<int> get stream => _controller.stream;

  // 更新值
  void updateValue(int newValue) {
    _controller.value = newValue;
  }

  // 释放资源
  void dispose() {
    _controller.close();
  }
}

3. 在UI中使用数据提供者

接下来,我们将创建一个简单的UI,展示当前的值并监听值的变化。

// main.dart
import 'package:flutter/material.dart';
import 'data_provider.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider.value(value: DataProvider()),
      ],
      child: MaterialApp(
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final dataProvider = Provider.of<DataProvider>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('StreamWithValue Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Current Value: ${dataProvider.currentValue}',
              style: TextStyle(fontSize: 24),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                dataProvider.updateValue(dataProvider.currentValue + 1);
              },
              child: Text('Increment'),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 监听流的变化
          dataProvider.stream.listen((newValue) {
            print('New Value: $newValue');
          });
        },
        tooltip: 'Listen to Stream',
        child: Icon(Icons.play_arrow),
      ),
    );
  }
}

4. 释放资源

在适当的时机(如页面销毁时),确保释放StreamWithValueController的资源:

// 如果你需要在页面销毁时释放资源,可以在一个StatefulWidget的dispose方法中这样做
@override
void dispose() {
  final dataProvider = Provider.of<DataProvider>(context, listen: false);
  dataProvider.dispose();
  super.dispose();
}

注意:上面的代码示例中,我们使用了Provider包来管理状态。如果你没有安装Provider包,可以通过flutter pub add provider来安装。

这个示例展示了如何使用stream_with_value插件在Flutter应用中管理数据流和值传递。你可以根据需要扩展和修改这个示例以适应你的具体需求。

回到顶部