Comments 30
/// 4. Вынесем инкремент за пределы виджета...
И получим глобальную переменную
onCounterUpd
и глобальную функцию incrementCounter()
. Вот только зачем?Обратите внимание, это для простоты восприятия. Никто не мешает вам сделать класс, и убрать его в business-слой.
class _Counter {
int count;
_Counter(this.count);
/// 3. Создадим евент.
final onCounterUpd = BehaviorSubject<int>();
/// 4. Вынесем инкремент за пределы виджета, добавим генерацию события.
Future incrementCounter() async {
onCounterUpd.add(++count);
}
}
Дело в том, что реактивщина
не только про стейты. Ведь она позволяет обмениваться событиями между любыми объектами, и невизуальными в бизнес-слое, и все это в едином стиле. Кроме того, мощь reactive-way
проявляется и в потоковой обработке данных, используя операторы. Внося реактивность в проект даже на стадии обмена сообщениями, можно впоследствии постепенно начинать применять ее все шире.
Ну, во-первых, насколько часто нам приходится писать счетчики? Начиная даже самый простой проект, потихоньку приходишь к усложнению. А вот представьте, Вам потребовалось вставить обратный отсчет для блокировки кнопки между нажатиями. Обойдетесь без фонового потока? А если нет, как будете реализовывать?
Позвольте представить решение в рамках концепции данной статьи:
/// делаем задержку в 3 секунды, с уведомлением через каждую секунду.
class _Counter {
int _count;
/// Счетчик обратного отсчета
int _countdown = 0;
int get count => _count;
_Counter(this._count)
: this.onCounterUpd = BehaviorSubject<int>.seeded(_count),
this.onCountdownUpd = BehaviorSubject<int>.seeded(0);
final BehaviorSubject<int> onCounterUpd;
/// Евент обратного отсчета
final BehaviorSubject<int> onCountdownUpd;
/// Вынесем инкремент за пределы виджета, добавим генерацию события.
Future incrementCounter() async {
if(_countdown <= 0) {
onCounterUpd.add(++_count);
/// Запуск таймера, с вочдогом и генерацией евентов.
_countdown = 3;
onCountdownUpd.add(_countdown);
Observable
.periodic(Duration(seconds: 1), (_) => --_countdown)
.take(3)
.listen((e) => onCountdownUpd.add(_countdown));
}
}
}
после чего делаем FAB реактивным,
/// Кнопка стала реактивной
floatingActionButton: StreamBuilder<int>(
initialData: _counter.onCountdownUpd.value,
stream: _counter.onCountdownUpd,
builder: (context, snapshot) {
return FloatingActionButton(
onPressed: snapshot.data <= 0 ? _counter.incrementCounter : null,
tooltip: 'Increment',
backgroundColor: snapshot.data <= 0
? Theme.of(context).primaryColor
: Colors.grey,
child: Icon(Icons.add),
);
}
), // This trailing comma makes auto-formatting nicer for build methods.
и добавляем рекативное же упоминание
/// Реактивная надпись
StreamBuilder<int>(
stream: _counter.onCountdownUpd,
builder: (context, snapshot) {
return Text(
'Rest ${snapshot.data} seconds',
style: Theme.of(context).textTheme.title,
);
}),
Ошибки
1. инициализация значения в BehaviorSubject не через named конструктор .seeded(initValue)
2. эта ошибка вытекла из первой, но показала непонимание основ Flutter.
Нельзя ничего инициировать в методе build() любого типа виджета
Да, согласен, косяк. Даже два.
- Поправимо
- Не отменяет самого принципа и эффективности применения
api.flutter.dev/flutter/widgets/StatelessWidget-class.html
The build method of a stateless widget is typically only called in three situations: the first time the widget is inserted in the tree, when the widget's parent changes its configuration, and when an InheritedWidget it depends on changes.
api.flutter.dev/flutter/widgets/StatelessWidget/build.html
The implementation of this method must only depend on:
the fields of the widget, which themselves must not change over time
Не зная первой цитаты ты стреляешь себе в ногу в будущем(ближайшем)
Ты не читал документацию или не перечитал с пониманием
final onCounterUpd = BehaviorSubject.seeded(count)
убери из конструктора
_Counter(this.count) {
onCounterUpd.add(count);
}
убери из StreamBuilder'а
StreamBuilder(
initialData: _counter.onCounterUpd.value,
stream: _counter.onCounterUpd,
Реквестирую следующую статью, где будет проиллюстрирована эта идея на примере какой то популярной архитектуры.
в процессе. Есть прикольная архитектура MVI, я ее применяю и в процессе слегка модернизирую. Вот спустя месяцок накидаю статью с реальным примером.
Вы в статье против BLoС, но вы не в курсе что flutter_bloc — это дериватив MVU(родоначальник подхода ELM TEA)
guide.elm-lang.org/architecture
In fact, projects like Redux have been inspired by The Elm Architecture, so you may have already seen derivatives of this pattern
MVI это то же дериватив MVU и бойлерплейта там тоже много
flutter_bloc, MVU(TEA), Redux, MVI эквивалентны (просто разные реализации)
Получается что пчёлы против мёда и ваши статьи противоречят вашему же коментарию выше
Да я в курсе и нет, я не против. Каждый выбирает для себя. Я за стиль, простоту, изящество решений, по мне так этому всему отвечает именно ReactiveX. Он закрывает для меня почти все вопросы по взаимодействию и с View и между всеми слоями архитектуры с минимумом кода и максимумом выразительности. Это я еще не беру в расчет мощь его цепочек операторов.
Я честно наваял часть проекта на MVU, но потом убился о количество кода, которое надо добавлять. Да, можно было написать генератор, но код-то никуда бы ни делся и болтался, мозоля глаза, да и хороший генератор писать тоже надо дофига времени на отладку.
MVI я не использую в чистом виде по этой же причине, но мне импонирует immutable-way, и передача упакованного состояния в одном флаконе. Позже я черкану статью о своей реализации.
UDF + Rx
Как тебе угодил MVI, но не угодил BLoC?
Intent -> Model -> View
Event -> State -> View
эквивалентные вещи и через тот же Rx
Приходится повторяться специально для Вас, что мне не нравится раздутость кода, в которое обернуты оба решения, и я не применяю MVI в том виде, в котором он есть. Вы не вчитываетесь, а видимо, просто еще на утреннем автовзводе от общения в telegram
.
Я бы с удовольствием увидел Ваши решения счетчика на BLoC, MVU, и сравнить количество строк, при одинаковом результате. Попробуйте принять, как данность, что это статься просто про другой подход к решению задач программирования. Пожалуйста. Спасибо.
BLoC & MVI смысл один один поток на вход, один на выходе
+ предсказуемый стейт
Вы пишете свой велосипед, который может потерять этот +, если не один единственный поток на вход(очередь событий)
Почитайте про синдром NIH
Жду статью про велосипед дериватив MVU(TEA), Redux, MVI, BLoC
Я вношу вклад в сообщество помогая в чатике и критикуя явные проблемы, особенно если это публичная статья для новичков с грубыми ошибками.
В своей статье Вы сравниваете архитектуру (стейт менеджмент), виджет (ui элемент), di и все это с оберткой над dart:async. Прям целая каша в голове.
Никто не сомневается, что можно написать hello world вообще не оперируя такими понятиями и вообще используя только setState и Future builder.
Собственно, в этом и состоит к вам претензия.
PS: если хотите, распишу вам подробнее, зачем нужен DI (Provider, оберточка над Inheriting Widget), зачем стейт менеджмент (BLoC, который, между прочим, на rxdart).
Давайте так, спасибо конечно, но мне тут не надо ничего расписывать, и так уже простыня-простынища.
Предлагаю Вам написать статью, в которой Вы представите свой взгляд. свои подходы, и свое видение.
Я достаточно знаю и про DI, и про BLoC, статья не об этом. Статья о том, что можно эффективно и просто достигать целей, используя очень простые решения. Я писал и с использованием Provider, и с использованием MVU, и в конечном итоге остановился на том, что безумное количество дополнительного кода не может служить оправданием того, что я использую хайповые решения. Видите ли, я сам себе режиссер, и мне не надо ходить на собеседование и ожидать тупых вопросов типа "а знаете ли вы вот эти стопицот модных технологий?", а в реальной жизни дауншифтинг на rxdart уменьшил мой код вдвое(!), не затронув производительность.
Вот о чем статья. Для сравнения технологий, практик, паттернов. Так что пишите свою, (и наполучаете от хейтеров в карму :) ).
PS. Да, вдогон. Про DI, Provider я вообще не упоминал, а на КДПВ указан концепт провайдера для BLoC, по аналогии с тем, что в этой статье. Пожалуйста, читайте статьи, которые комментируете, а не додумывайте свое за автора. Спасибо.
минусатор, есть аргументированные возражения этому коменту?
Наружу только Observable или Stream, потому что я могу миновать incrementCounter() и сунуть туда какое захочу value и оно минует вашу логику
Это как раз вопрос велосипеда, он уже несостоятелен и его приходится рихтовать.
Зачем здесь async функция?
Напрашивается аналогия с козой и баяном, если не забавнее.
Неоднократно доказано, что желание везде подстелить соломки не защищает от идиотов или любознательных вредителей.
Но раз уж Вы за чистоту рядов, то что скажете про реализацию toMap/fromMap
в дарте "из коробки"? Отсутствие как минимум признака типа в карте делает ее худо-бедно применяемой только в узком локальном контексте, не позволяя передавать никуда дальше, ибо иначе может приводить к интересным коллизиям типа
test('Когда все пошло не по плану', () async {
// в точке А.
final manBefore = Straight(name: 'Serg', age: 25);
final map = manBefore.toMap();
print('man before is: ${manBefore.runtimeType} as $map');
// в точке Б.
final manAfter = Gay.fromMap(map);
print('man after is: ${manAfter.runtimeType} as $map');
});
LOG:
man before is: Straight as {name: Serg, age: 25}
man after is: Gay as {name: Serg, age: 25}
для схожих типов
class Straight {
final String name;
final int age;
...
class Gay {
final String name;
final int age;
...
и персонаж даже не узнает об этом, пока не нагнется за мылом....
Мир, он не черно-белый, и не становитесь упоротым адептом навязанных извне технологий, даже если очень надо или сильно хочется.
Реактивность это очень несложно. Гораздо проще InheritedWidgets, BLoC Provider, etc.
Насколько я могу судить то базово ты как раз таки реализовал BLoC
Тут хорошо объясняется как им пользоваться: www.youtube.com/watch?v=hTExlt1nJZI
Для данного пакета уже создана целая инфраструктура:
pub.dev/packages/hydrated_bloc
pub.dev/packages/bloc_test
pub.dev/packages/sealed_flutter_bloc
Прекрасно, когда есть из чего выбрать. Мне фломастеры из rxdart
-коробки больше по душе, потому что я так или иначе последние три года пользуюсь ReactiveX в составе RxJava
, RxQt
, RxCpp
в той или иной степени, с их подкупающей мощью обработки данных посредством цепочек операторов, и поэтому я убиваю двух зайцев, используя rxdart
как управление состоянием UI и для работы с данными.
RxDart для самых маленьких… проектов