Поскольку шаблон BLoC (Business Logic Component) был представлен в Google I / O 2018, на эту тему было написано много статей, таких как эта, и эта блочная библиотека, на которую сильно повлияли редукторы Redux, и многие другие. .

После повторного просмотра Google I / O video я понял, что BLoC - это дисциплинированный способ доступа и обновления данных между виджетами в дереве, таких как глобальная переменная.

Очевидно, что есть несколько способов реализовать BLoC. Поразмыслив еще немного, я обнаружил, что нам не нужно использовать ReactiveX / rxDart или Streams. Они сами по себе приходят со своими жаргонами и строительными блоками, что ведет к определенной кривой обучения.

В этой статье я представляю один из способов реализации BLoC с использованием пакета Provider. Вы обнаружите, что ChangeNotifier + провайдера достаточно для реализации BLoC.

Если вам интересно, ChangeNotifier является частью библиотеки Flutter Foundation, поэтому нам не придется полагаться на сторонние библиотеки, такие как rxdart.

Приложение, которое мы создадим, представляет собой простое приложение «Корзина покупок с подарочными картами».

Требования просты:

  • На главной странице представлены шесть подарочных карт. Каждый раз, когда пользователь нажимает на элемент, приложение добавляет его в корзину.
  • Значок корзины покупок вверху справа снабжен значком, который показывает текущее общее количество товаров в корзине.
  • Щелкнув значок корзины покупок, вы переместите приложение на страницу сведений о корзине. В нем указано, сколько карточек добавлено.
  • Нажав кнопку «Очистить» на странице сведений, очистите счетчик элементов.

При создании этого приложения есть одна сложная вещь: главная страница и страница корзины покупок могут читать и изменять объект корзины покупок. Пользователи ожидают, что, если одна страница изменит корзину, другая страница сразу же увидит обновленную корзину.

Этот тип обмена состояниями между страницами часто встречается во многих других типах приложений. Паттерн BLoC предназначен для ее решения. Мы можем рассматривать BLoC как глобальную переменную, которая создается в корневом виджете, передается дочерним элементам в дереве виджетов и доступна для всех своих дочерних элементов.

Теперь поговорим о коде.

Во-первых, давайте посмотрим, как BloC используется на главной странице. Код имеет следующую структуру:

Если мы сосредоточимся на логике страницы, которая отображает пользовательский интерфейс, мы увидим, что страница:

  • считывает общее количество из объекта блока, предоставленного Provider
  • отображает количество и изображения на странице

Здесь есть два взаимодействия с пользователем:

  • Когда пользователь нажимает на изображение, запускается функция bloc.addToCart для обновления корзины.
  • Если пользователь щелкает значок корзины покупок в AppBar, приложение просто переходит на страницу корзины покупок. В вызове Navigator.push состояние не передается явно.

Страница корзины покупок имеет аналогичную структуру:

Точно так же страница корзины считывает счетчик из блока и вызывает функцию bloc.clear(), если пользователь нажимает кнопку очистки.

Наконец, блочная реализация:

Что CartBloc делает здесь просто: у него есть внутренний объект корзины, чтобы отслеживать текущую корзину. Мы можем рассматривать cart как глобальную переменную, которая совместно используется виджетами / страницами.

Теперь следующее, что нужно учитывать, это то, что если CartBloc изменяется, он должен уведомить любой виджет, который читает блок. Для этого класс CartBloc использует миксин ChangeNotifier.

Последний шаг по подключению BLoC, чтобы он был доступен для всех страниц:

В пакете provider фактически есть другие доступные провайдеры, такие как ListenableProvider и т. Д. Но я обнаружил, что ChangeNotifierProvider проще всего реализовать BloC.

ОБНОВЛЕНИЕ: как указал Джозеф в комментарии, вызов Provider.of() внутри build()function приведет к перестройке страницы всякий раз, когда изменяется состояние предоставленного объекта. В этом примере с игрушкой страница не такая сложная, поэтому ее можно перестраивать много раз. Для сложных страниц, восстановление которых обходится дорого, мы можем рассмотреть возможность использования Consumer / Selector для выборочного восстановления частей страницы. Это задокументировано в файле readme для провайдера.

Спасибо за чтение!

Весь исходный код доступен в Github.

Вопросов? Не стесняйтесь, напишите мне.