【问题标题】:Issue with StreamBuilder and streams in Flutter (receiving duplicated data)Flutter 中 StreamBuilder 和流的问题(接收重复数据)
【发布时间】:2020-12-17 19:53:44
【问题描述】:

今天我遇到了流和 StreamBuilder 的问题。 问题如下: 如果您有多个 StreamBuilder 小部件侦听同一个流,并且您将数据添加到其接收器中,则此数据将通过您拥有的 StreamBuilder 侦听器的数量从流中流出,换句话说: 如果您有一个 StreamController(或 BehaviorSubject),k 个 StreamBuilder 类型的小部件,并且您尝试执行 StreamController.sink.add(event),则此事件将通过流发出 k 次,每个 StreamBuilder 一个。 这是一种预期的行为(预期的行为=输入一个事件并从另一端只听一次,与听众的数量无关)?我能够“修复”这个将几乎所有小部件树封装到一个 StreamBuilder 中的问题,但这并不像第一种方法那样最佳,因为您正在渲染整个树而不是一些小节点小部件。 这里我留下了一些代码来测试它,如果你愿意的话(这个代码是对 flutter create project_name 项目的修改)。 谢谢! (PD:如果您只收听没有 StreamBuilder 的流,这很有效,即:streamController.stream.listen..)

import 'dart:async';

import 'package:flutter/cupertino.dart';
import 'package:rxdart/subjects.dart';

class MyAppBloc with ChangeNotifier {
  int _currentIndex;
  BehaviorSubject<bool> _controller;

  MyAppBloc() {
    _currentIndex = 0;
    _controller = BehaviorSubject<bool>();
  }

  Stream<int> get currentIndex => _controller.stream.map<int>((event) {
        print('[event: $event]');
        _currentIndex++;
        return _currentIndex;
      });

  StreamSink<bool> get increment => _controller.sink;

  void close() {
    _controller.close();
  }
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:test_project/bloc/my_app_bloc.dart';

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;
  Widget leadingBuilder(MyAppBloc bloc) {
    return StreamBuilder<int>(
      initialData: 0,
      stream: bloc.currentIndex,
      builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
        print('[leadingBuilderSnapshot: $snapshot]');
        return Text(snapshot.data.toString());
      },
    );
  }

  StreamBuilder<int> counterBuilder(MyAppBloc bloc) {
    return StreamBuilder<int>(
      initialData: 0,
      stream: bloc.currentIndex,
      builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
        print('[counterBuilderSnapshot: $snapshot]');
        return Text(
          snapshot.data.toString(),
          style: Theme.of(context).textTheme.headline4,
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    print('[build]');
    final _bloc = Provider.of<MyAppBloc>(context);
    return Scaffold(
      appBar: AppBar(
        leading: Container(
          width: 30,
          height: 30,
          alignment: Alignment.center,
          child: leadingBuilder(_bloc),
        ),
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            StreamBuilder<int>(
              initialData: 0,
              stream: _bloc.currentIndex,
              builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
                return Text('${snapshot.data}');
              },
            ),
            Text(
              'You have pushed the button this many times:',
            ),
            counterBuilder(_bloc),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _bloc.increment.add(true),
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

【问题讨论】:

  • 我不明白您的问题/解释,您输入了数字 1 并在每个流构建器中收到了不同的数字(因为您正在流内进行增量,并且发生这种情况是合乎逻辑的)或者您预计会在所有流构建器中获得第一名?
  • 我只是将 true 添加到接收器以触发增量事件,我从 0 开始,当我收到将其显示到视图中的状态时,它显示 2,然后是 4,然后...就像它在索引中增加了两个,我不明白为什么会这样,因为我只是向接收器添加一个事件,它应该输出一个状态,因此会增加一个,不是吗?
  • 发生这种情况是因为增量在streambuilder的get中,所以每次调用增量都会执行。与其调用接收器,不如调用块中的一个函数,将其递增并将其添加到接收器。
  • 是的,该解决方法工作正常,但我不明白为什么如果我只向接收器添加一个事件,在这种情况下它会被处理两次。我想象的流程是添加一个事件、处理数据并在每个侦听器中侦听该单个状态,但这种行为就像我为每个侦听器添加一个事件
  • 您在流构建器的 get 中添加了增量,因此每次流构建器收到要更新的指令时,他都会获取流(他获取流)然后增量发生.. 如果您有 n 个流构建器,他会做 n 次获取和 n 次递增。

标签: flutter stream duplicates stream-builder sink


【解决方案1】:

因为currentIndex 是一个getter,而您使用的是map(),所以每次都会创建一个新流 你打电话给bloc.currentIndexStreamBuilder 会听。

所以在原始代码中,实际上有 1 个StreamControlller,和 k 个Streams。 (k:StreamBuilder的数量)

要解决您的问题,您可以创建一个 eventController,并在一个块内监听它以执行您的逻辑。 (eventStream 仅从 bloc 本身监听,它只会被创建一次)

例如:

class MyAppBloc {
  MyAppBloc() {
    _eventController.listen((event) {
      print('[event: $event]');
      _indexController.add(currentIndex.value + 1);
    });
  }

  final _indexController = BehaviorSubject<int>.seeded(0);
  final _eventController = PublishSubject<bool>();

  ValueStream<int> get currentIndex => _indexController.stream;
  StreamSink<bool> get increment => _eventController.sink;

  void close() {
    _indexController?.close();
    _eventController?.close();
  }
}

【讨论】:

    【解决方案2】:

    您必须使用 2 个流/接收器并将增量放在获取流之外。

    import 'dart:async';
    import 'package:rxdart/subjects.dart';
    
    class Bloc {
      int _counter = 0;
    
      Bloc() {
        _controller.stream.listen(_incrementStream);
      }
    
      final _counterStream = BehaviorSubject<int>.seeded(0);
    
      Stream get presentCounter => _counterStream.stream;
      Sink get _addValue => _counterStream.sink;
    
      StreamController _controller = BehaviorSubject<bool>();
      StreamSink<bool> get incrementCounter => _controller.sink;
    
      void _incrementStream(data) {
        _counter += 1;
        _addValue.add(_counter);
      }
    
      void dispose() {
        _counterStream.close();
        _controller.close();
      }
    }
    
    
    
    import 'package:flutter/material.dart';
    import 'package:increment/bloc.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      Bloc _bloc = Bloc();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'You have pushed the button this many times:',
                ),
                StreamBuilder<int>(
                    stream: _bloc.presentCounter,
                    builder: (context, snapshot) {
                      return Text(
                        '${snapshot.data}',
                        style: Theme.of(context).textTheme.headline4,
                      );
                    }),
                SizedBox(
                  height: 60,
                ),
                StreamBuilder<int>(
                    stream: _bloc.presentCounter,
                    builder: (context, snapshot) {
                      return Text(
                        '${snapshot.data}',
                        style: Theme.of(context).textTheme.headline4,
                      );
                    }),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              _bloc.incrementCounter.add(true);
            },
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2020-12-19
      • 1970-01-01
      • 1970-01-01
      • 2023-03-29
      • 2021-02-21
      • 2020-04-29
      • 2020-03-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多