Флаттер: где добавить слушателей в StatelessWidget?
Если бы я использовал StatefulWidget, то я бы слушал поток, например, внутри метода initState. Где бы я сделал эквивалент в StatelessWidget (хотел бы использовать Bloc с потоками для управления состоянием)? Я мог бы сделать это в методе сборки, но так как они повторяются, я задавался вопросом, существует ли более эффективный способ, чем проверка существующих слушателей, как показано ниже. Я знаю, что это избыточный и бесполезный пример, но он просто показывает проблему.
import "package:rxdart/rxdart.dart";
import 'package:flutter/material.dart';
final counter = BehaviorSubject<int>();
final notifier = ValueNotifier<int>(0);
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (!counter.hasListener)
counter.listen((value) => notifier.value += value);
return MaterialApp(
home: Scaffold(
body: Center(
child:FlatButton(
onPressed: () => counter.add(1),
child: ValueListenableBuilder(
valueListenable: notifier,
builder: (context, value, child) => Text(
value.toString()
),
),
)
),
)
);
}
}
Ответы
Ответ 1
Ты не должен. Не обрабатывать переменные, для которых могут быть изменены их значения, является самой целью виджета без сохранения состояния:
Виджет без состояния никогда не меняется.
ОБНОВЛЕНИЕ: Я думаю, что это проблема понимания концепций управления состоянием Flutter. Этот новый рекомендуемый способ командой Flutter должен устранить некоторые недоразумения.
Ответ 2
Не существует чистого способа прослушивания StatelessWidget прослушиваемого/потока. Вам всегда понадобится StatefulWidget.
С другой стороны, вы можете использовать состав, чтобы написать этот StatefulWidget только один раз и покончить с этим.
Типичными примерами для этого шаблона являются виджеты, такие как ValueListenableBuilder
, StreamBuilder
или AnimatedBuilder
. Но можно сделать то же самое и для прослушивания.
Вы бы использовали это так:
class Foo extends StatelessWidget {
Foo({Key key, this.counter}): super(key: key);
final ValueListenable<int> counter;
@override
Widget build(BuildContext context) {
return ValueListenableListener(
valueListenable: counter,
onChange: (value) {
// TODO: do something
},
child: Something(),
);
}
}
Где ValueListenableListener
реализован следующим образом:
class ValueListenableListener<T> extends StatefulWidget {
const ValueListenableListener(
{Key key, this.valueListenable, this.onChange, this.child})
: super(key: key);
final ValueListenable<T> valueListenable;
final ValueChanged<T> onChange;
final Widget child;
@override
_ValueListenableListenerState createState() =>
_ValueListenableListenerState();
}
class _ValueListenableListenerState extends State<ValueListenableListener> {
@override
void initState() {
super.initState();
widget.valueListenable?.addListener(_listener);
_listener();
}
@override
void didUpdateWidget(ValueListenableListener oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.valueListenable != widget.valueListenable) {
oldWidget.valueListenable?.removeListener(_listener);
widget.valueListenable?.addListener(_listener);
_listener();
}
}
@override
void dispose() {
widget.valueListenable?.removeListener(_listener);
super.dispose();
}
void _listener() {
widget.onChange?.call(widget.valueListenable.value);
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
Ответ 3
просто добавляя к тому, что @Willy уже упоминало, я еще не сталкивался с кодом, который слушает поток внутри StatelessWidget, но я думаю, что это анти-паттерн, которого вам следует избегать.
Я думаю, что причина создания ValueListenableBuilder
схожа с вашей ситуацией, вам нужно только правильно ее использовать, чтобы достичь желаемого.
import "package:rxdart/rxdart.dart";
import 'package:flutter/material.dart';
final counter = BehaviorSubject<int>();
final notifier = ValueNotifier<int>(0);
void main() => runApp(ValueListenableBuilder(
valueListenable: notifier,
builder: (context, value, child) => MyApp(value),
));
class MyApp extends StatelessWidget {
final int value;
MyApp(this.value);
@override
Widget build(BuildContext context) {
if (!counter.hasListener)
counter.listen((value) => notifier.value += value);
return MaterialApp(
home: Scaffold(
body: Center(
child:FlatButton(
onPressed: () => counter.add(1),
child: Text(
value.toString()
),
)
),
)
);
}
}
Ответ 4
Вы могли бы создать экземпляры ваших потоков в StatefulWidget, а затем передать их своим StatelessWidgets в качестве опции, поэтому родительский виджет будет играть роль только управления жизненным циклом потока, в то время как дочерний элемент будет использовать поток для обновления представления.
Относительно предыдущего ответа: нет никаких проблем в использовании StreamBuilders внутри ваших StatelessWidgets, так как сам StreamBuilder является виджетом, который выходит из StatefulWidget и будет заботиться о своем собственном состоянии и корректно распоряжаться самостоятельно.
Ответ 5
Вы могли бы сделать что-то вроде этого:
class ExampleWidget extends StatelessWidget {
bool _initialized = false;
@override
Widget build(BuildContext context) {
if (!_initialized) {
_initialized = true;
// Add listeners here only once
}
return Container();
}
}
Но вы не должны! Фактически, ваша IDE выдаст вам предупреждение, потому что это не тот способ, которым нужно использовать виджет без @immutable
состояния, поскольку он помечен как @immutable
. Если вам нужно использовать методы жизненного цикла (например, initState()
), вы должны сделать это виджетом с initState()
состояния. Там нет ничего сложного.
Ответ 6
Попробуйте bloc_provider (не путать с пакетом провайдера)
Я пробовал bloc, bloc_provider и провайдера. Я нашел пакет bloc_provider, наиболее подходящий для дизайна моего приложения
В качестве альтернативы вы также можете попробовать унаследованный виджет, содержащий ваш appState, к которому вы можете легко получить доступ в своем дочернем виджете, включая виджеты без состояния.
Я использую комбинацию унаследованного виджета и bloc_provider
РЕДАКТИРОВАТЬ
ПРИМЕР с использованием пакета bloc_prvider
class FormReadyStateTransmitter extends Bloc<bool, bool>
{
@override
bool get initialState => false;
@override
Stream<bool> mapEventToState(bool event) async*
{
yield event;
}
}
class FormController
{
final FormReadyStateTransmitter _readyStateTransmitter = FormReadyStateTransmitter() ;
Widget build()
{
return BlocProviderTree
(
blocProviders:
[
BlocProvider<FormReadyStateTransmitter>( bloc: _readyStateTransmitter ),
],
child: FormWidget( button: MyDynamicButton() )
);
}
void onFormReady()
{
_readyStateTransmitter.dispatch(true);
}
}
class MyDynamicButton extends StatelessWidget
{
@override
Widget build(BuildContext context)
{
final FormReadyStateTransmitter blockReadyState = BlocProvider.of<FormReadyStateTransmitter>( context );
return BlocBuilder
(
bloc: blockReadyState,
builder: (context, bool ifReady )
{
return Text( ifReady.toString() );
}
);
}
}
дочерний виджет будет перестраивать (только если состояние будет изменено) при каждом вызове
_readyStateTransmitter.dispatch(bool);