StreamBuilder 和 Flutter 中的流问题(接收重复数据)
Issue with StreamBuilder and streams in Flutter (receiving duplicated data)
今天我遇到了流和 StreamBuilder 的问题。
问题如下:
如果您有多个 StreamBuilder 小部件侦听同一个流,并且您将数据添加到它的接收器中,则该数据将通过流传出您拥有的 StreamBuilder 侦听器的数量,换句话说:
如果您有一个 StreamController(或 BehaviorSubject),k 个 StreamBuilder 类型的小部件,并且您尝试执行 StreamController.sink.add(event),则此事件将通过流发出 k 次,每个 StreamBuilder 一个。
这是预期的行为吗(预期行为=输入一个事件并从另一端独立地听取一次事件,而与听众的数量无关)?我能够“修复”这种将几乎所有小部件树封装到一个 StreamBuilder 中的方法,但这不如第一种方法最佳,因为您正在渲染整个树而不是一些小节点小部件。
这里我留下了一些代码,如果你想测试它(这段代码是 flutter create project_name project 的修改)。
谢谢!
(P.D:如果你只是在没有 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),
),
);
}
}
您必须使用 2 streams/sink 并将增量放在获取流之外。
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.
);
}
}
因为 currentIndex
是 getter 而您正在使用 map()
,所以每次都会创建一个新流
你打电话给 bloc.currentIndex
,StreamBuilder
会接听。
所以在原来的代码中,实际上有1个StreamControlller
和k个Streams
。 (k: StreamBuilder
的个数)
要解决您的问题,您可以创建一个事件控制器,并在一个块内监听它以执行您的逻辑。 (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();
}
}
今天我遇到了流和 StreamBuilder 的问题。 问题如下: 如果您有多个 StreamBuilder 小部件侦听同一个流,并且您将数据添加到它的接收器中,则该数据将通过流传出您拥有的 StreamBuilder 侦听器的数量,换句话说: 如果您有一个 StreamController(或 BehaviorSubject),k 个 StreamBuilder 类型的小部件,并且您尝试执行 StreamController.sink.add(event),则此事件将通过流发出 k 次,每个 StreamBuilder 一个。 这是预期的行为吗(预期行为=输入一个事件并从另一端独立地听取一次事件,而与听众的数量无关)?我能够“修复”这种将几乎所有小部件树封装到一个 StreamBuilder 中的方法,但这不如第一种方法最佳,因为您正在渲染整个树而不是一些小节点小部件。 这里我留下了一些代码,如果你想测试它(这段代码是 flutter create project_name project 的修改)。 谢谢! (P.D:如果你只是在没有 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),
),
);
}
}
您必须使用 2 streams/sink 并将增量放在获取流之外。
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.
);
}
}
因为 currentIndex
是 getter 而您正在使用 map()
,所以每次都会创建一个新流
你打电话给 bloc.currentIndex
,StreamBuilder
会接听。
所以在原来的代码中,实际上有1个StreamControlller
和k个Streams
。 (k: StreamBuilder
的个数)
要解决您的问题,您可以创建一个事件控制器,并在一个块内监听它以执行您的逻辑。 (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();
}
}