Pull to refresh

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);
  }
}
UFO landed and left these words here

Дело в том, что реактивщина не только про стейты. Ведь она позволяет обмениваться событиями между любыми объектами, и невизуальными в бизнес-слое, и все это в едином стиле. Кроме того, мощь reactive-way проявляется и в потоковой обработке данных, используя операторы. Внося реактивность в проект даже на стадии обмена сообщениями, можно впоследствии постепенно начинать применять ее все шире.

UFO landed and left these words here

Ну, во-первых, насколько часто нам приходится писать счетчики? Начиная даже самый простой проект, потихоньку приходишь к усложнению. А вот представьте, Вам потребовалось вставить обратный отсчет для блокировки кнопки между нажатиями. Обойдетесь без фонового потока? А если нет, как будете реализовывать?
Позвольте представить решение в рамках концепции данной статьи:


/// делаем задержку в 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,
                  );
                }),
UFO landed and left these words here

Удачи в работе. Надо быть ищущим, и любознательным.

Автор непонимает основ Rx и Flutter
Ошибки
1. инициализация значения в BehaviorSubject не через named конструктор .seeded(initValue)
2. эта ошибка вытекла из первой, но показала непонимание основ Flutter.
Нельзя ничего инициировать в методе build() любого типа виджета

Да, согласен, косяк. Даже два.


  1. Поправимо
  2. Не отменяет самого принципа и эффективности применения
по пункту 2
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


Не зная первой цитаты ты стреляешь себе в ногу в будущем(ближайшем)
Не зная второй цитаты ты опираешься на внешние данные, полностью перечя ей (upd тут я неправ)

Ты не читал документацию или не перечитал с пониманием
Может, обороты-то сбавите, не? Высокомерие так и хлещет.
Автор статьи пишет, старается. Да, не все 100% идеально, а вы тут сразу тыкать начали, как вахтер какой-то
Вы пришли из телеги, сэр, здесь обороты нормальные,
ошибки обозначены и описаны, показано что надо изменить.

Не нагнетайте
поправь
final onCounterUpd = BehaviorSubject.seeded(count)


убери из конструктора
_Counter(this.count) {
onCounterUpd.add(count);
}


убери из StreamBuilder'а
StreamBuilder(
initialData: _counter.onCounterUpd.value,
stream: _counter.onCounterUpd,
Как демонстрация идеи хорошо.
Реквестирую следующую статью, где будет проиллюстрирована эта идея на примере какой то популярной архитектуры.

в процессе. Есть прикольная архитектура MVI, я ее применяю и в процессе слегка модернизирую. Вот спустя месяцок накидаю статью с реальным примером.

habr.com/ru/post/448776
Вы в статье против 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, и передача упакованного состояния в одном флаконе. Позже я черкану статью о своей реализации.

flutter_bloc (дальше упоминаю как BLoC) Felix'a Angelo это чуть по-другому реализованный MVI от Dorfman'а
UDF + Rx

Как тебе угодил MVI, но не угодил BLoC?

Intent -> Model -> View
Event -> State -> View

эквивалентные вещи и через тот же Rx

Приходится повторяться специально для Вас, что мне не нравится раздутость кода, в которое обернуты оба решения, и я не применяю MVI в том виде, в котором он есть. Вы не вчитываетесь, а видимо, просто еще на утреннем автовзводе от общения в telegram.
Я бы с удовольствием увидел Ваши решения счетчика на BLoC, MVU, и сравнить количество строк, при одинаковом результате. Попробуйте принять, как данность, что это статься просто про другой подход к решению задач программирования. Пожалуйста. Спасибо.

меньшее количество строк != читабельность && easy поддержка

BLoC & MVI смысл один один поток на вход, один на выходе
+ предсказуемый стейт

Вы пишете свой велосипед, который может потерять этот +, если не один единственный поток на вход(очередь событий)
Почитайте про синдром NIH

Жду статью про велосипед дериватив MVU(TEA), Redux, MVI, BLoC
Я не пишу решения счетчика на BLoC, оно уже есть от оригинала(Felix) в документации.

Я вношу вклад в сообщество помогая в чатике и критикуя явные проблемы, особенно если это публичная статья для новичков с грубыми ошибками.
Вы не правы.
В своей статье Вы сравниваете архитектуру (стейт менеджмент), виджет (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, по аналогии с тем, что в этой статье. Пожалуйста, читайте статьи, которые комментируете, а не додумывайте свое за автора. Спасибо.

Cделать приватным final BehaviorSubject onCounterUpd;
Наружу только Observable или Stream, потому что я могу миновать incrementCounter() и сунуть туда какое захочу value и оно минует вашу логику

Это как раз вопрос велосипеда, он уже несостоятелен и его приходится рихтовать.

Зачем здесь async функция? Future incrementCounter() 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
По мне так для управления состоянием лучше всего подходит пакет: pub.dev/packages/flutter_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 и для работы с данными.

Only those users with full accounts are able to leave comments. Log in, please.