Pull to refresh

BLoC паттерн на простом примере

Reading time 3 min
Views 49K

И еще раз про BLoC на классическом примере счетчика Flutter.


Читая некоторые статьи про реактивное программирование и используя BLoC паттерн в приложениях я понимал, что чего-то не догоняю. Как обычно на все не хватает времени, но вот, выдался свободный час и силы есть — решено, напишу простейшее приложение на Flutter с паттерном BLoC.


Под катом анимашка приложения и пояснения почему я его написал его именно так. Очень интересно мнение сообщества.


image


Да, про этот паттерн уже много раз писали, но все равно, по нему нет четких инструкций и правил применения и часто возникает вопрос, как правильно реализовать логику в приложении.


Цель статьи внести немного ясности для себя и, надеюсь, для читателей.


Итак, определение паттерна, как его озвучили инженеры Гугл — BLOC это простой класс у которого:


  1. все выходы потоки
  2. все входы потоки
  3. этот класс должен убирать логику из визуального интерфейса

Для реализации данного паттерна мы, по потребности, можем использовать библиотеку rxdart, а можем и не использовать.


Это не уберет реактивность как можно подумать. Как пишут сами создатели пакета — rxdart добавляет функциональности на уже встроенные богатые возможности языка Dart в работе с потоками.


Приложение



Итак, берем приложение счетчиками, которое создается автоматически при создании проекта и пытаемся переписать его с применением паттерна BLoC.


Задачи:


  1. Убрать всю логику из виджетов
  2. В классе BLoC получать только потоки на вход и выдавать только потоки на выход. Например функция, предложенная в комментах к статье.

image


нам не подходит, так как она нарушает правило передачи в класс только потоков.


Решение:


  1. Убираем всю логику из виджетов. Делаем класс Stateless и перестаем обновлять состояния с помощью setState.
  2. Отображаем показания счетчика в разных местах с помощью встроенного виджета, который сделан специально для отображения данных из потоков — StreamBuilder.

class MyHomePage extends StatelessWidget {
  CounterBloc counterBloc = CounterBloc();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: StreamBuilder<int>(
            stream: counterBloc.pressedCount,
            builder: (context, snapshot) {
              return Text(
                'Flutter Counter Bloc Example - ${snapshot.data.toString()}',
              );
            }),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            StreamBuilder<int>(
                stream: counterBloc.pressedCount,
                builder: (context, snapshot) {
                  return Text(
                    '${snapshot.data.toString()}',
                    style: Theme.of(context).textTheme.display1,
                  );
                }),
          ],
        ),
      ),
      floatingActionButton: Container(
        width: 100.0,
        height: 100.0,
        child: FloatingActionButton(
          onPressed: () {
            counterBloc.incrementCounter.add(null);
          },
          tooltip: 'Increment',
          child: Text(
            "+ \n send \n to BLoC",
            textAlign: TextAlign.center,
          ),
        ),
      ),
    );
  }
}

  1. Создаем отдельный класс, где реализуем BLoC паттерн:
    3.1 Все свойства и метода класса, скрыты.
    3.2 Для получения и передачи состояния используем потоки, которые видны снаружи при помощи getters (а вот классная статья про них).

class CounterBloc {
  int _counter;

  CounterBloc() {
    _counter = 1;
    _actionController.stream.listen(_increaseStream);
  }

  final _counterStream = BehaviorSubject<int>.seeded(1);

  Stream get pressedCount => _counterStream.stream;
  Sink get _addValue => _counterStream.sink;

  StreamController _actionController = StreamController();
  StreamSink get incrementCounter => _actionController.sink;

  void _increaseStream(data) {
    _counter += 1;
    _addValue.add(_counter);
  }

  void dispose() {
    _counterStream.close();
    _actionController.close();
  }
}

Все, у нас получился рабочий пример, в котором мы передаем и получаем состояние потоками и отображаем данные в нужных виджетах не применяя никакой логики в визуальной части.


Таким образом мы можем отделить все расчеты, передачи данных и т.п. от интерфейса. Например, если добавить к счетчику разные надписи в зависимости от количества и брать их из внешней базы, то вся эта логика уже будет проходить через BLoC — наш интерфейс ничего об этом не будет знать.


Примечание 1: заметьте, что мы создаем экземпляр класса > CounterBloc counterBloc = CounterBloc(); и далее получаем из него данные. Если эти данные нам нужны на разных экранах (в разнесенных классах), то мы можем либо использовать Inherited widgets для передачи или сделать из нашего класса Singleton.


Код примера на github


Всем хорошего кодинга!


Продолжение


В продолжении передаем состояние по разным экранам, сохраняем данные в постоянной памяти телефона, тестируем BLoC https://habr.com/ru/post/485002/

Tags:
Hubs:
+11
Comments 23
Comments Comments 23

Articles