Чтобы раскрыть миф о том, что BLoC - это путь прямо с пути: не существует идеального способа обработки состояния.
Каждая архитектура управления государством решает некоторые проблемы лучше, чем другие; всегда есть компромиссы, и важно учитывать их при выборе архитектуры.
Как правило, хорошая архитектура практична : она масштабируема и расширяема, но требует минимальных накладных расходов.
Поскольку взгляды людей на осуществимость различаются, архитектура всегда включает в себя мнение, поэтому примите следующее с большой долей соли, так как я изложу свое личное мнение о том, как использовать BLoC для вашего приложения.
BLoC - очень многообещающий подход к управлению состоянием во Флаттере из-за одного компонента подписи: потоков.
Они позволяют отделить пользовательский интерфейс от бизнес-логики и хорошо сочетаются с подходом Flutter-ish, заключающимся в восстановлении целых поддеревьев виджетов после их устаревания.
Естественно, что каждое сообщение от и до BLoC должно использовать потоки, верно?
+----+ Stream +------+
| UI | --------> | BLoC |
| | <-------- | |
+----+ Stream +------+
Ну, вроде как.
Важно помнить, что архитектура управления состоянием - это средство для достижения цели ; Вы должны не просто делать что-то ради этого, но быть непредвзятым и тщательно оценивать плюсы и минусы каждого варианта.
Причина, по которой мы отделяем BLoC от пользовательского интерфейса, заключается в том, что BLoC не нужно заботиться о том, как структурирован пользовательский интерфейс, он просто предоставляет несколько простых простых потоков, и все, что происходит с данными, является обязанностью пользовательского интерфейса.
Но хотя потоки оказались фантастическим способом передачи информации из BLoC в пользовательский интерфейс, они добавляют ненужные накладные расходы в другом направлении:
Потоки были предназначены для транспортировки непрерывных потоков данных (даже в названии), но в большинстве случаев пользовательский интерфейс просто должен инициировать отдельные события в BLoC. Вот почему иногда вы видите Stream<void>
s или аналогичные хакерские решения¹, просто чтобы придерживаться строго BLoC-y способа делать вещи.
Кроме того, если бы мы выдвигали новые маршруты на основе потока из BLoC, BLoC в основном контролировал бы поток пользовательского интерфейса, но наличие кода, который непосредственно контролирует как пользовательский интерфейс, так и бизнес-логику, - это именно то, что мы пытались предотвратить!
Вот почему некоторые разработчики (включая меня) просто ломают голову над полностью потоковым решением и используют пользовательский способ запуска событий в BLoC из пользовательского интерфейса.
Лично я просто использую вызовы методов (которые обычно возвращают Future
s), чтобы вызвать события BLoC:
+----+ method calls +------+
| UI | ----------------> | BLoC |
| | <---------------- | |
+----+ Stream, Future +------+
Здесь BLoC возвращает Stream
с для данных, которые являются «живыми», и Future
с как ответы на вызовы методов.
Давайте посмотрим, как это может сработать для вашего примера:
- BLoC может предоставить
Stream<bool>
того, вошел ли пользователь в систему или даже Stream<Account>
, где Account
содержит информацию об учетной записи пользователя.
- BLoC может также предоставить асинхронный метод
Future<void> signIn(String username, String password)
, который ничего не возвращает, если вход выполнен успешно или в противном случае выдает ошибку.
- Пользовательский интерфейс может самостоятельно обрабатывать управление вводом и запускать что-то вроде следующего при нажатии кнопки входа в систему:
try {
setState(() => _isLoading = true); // This could display a loading spinner of sorts.
await Bloc.of(context).signIn(_usernameController.text, _passwordController.text);
Navigator.of(context).pushReplacement(...); // Push logged in screen.
} catch (e) {
setState(() => _isLoading = false);
// TODO: Display the error on the screen.
}
Таким образом, вы получаете хорошее разделение интересов:
- BLoC действительно просто делает то, что должен делать - обрабатывать бизнес-логику (в данном случае вход пользователя).
- Пользовательский интерфейс просто заботится о двух вещах:
- Отображение пользовательских данных от
Stream
s и
- реагирование на действия пользователя путем их запуска в BLoC и выполнения действий пользовательского интерфейса в зависимости от результата .²
Наконец, я хочу отметить, что это единственное возможное решение, которое эволюционировало с течением времени, испробовав различные способы обработки состояний в сложном приложении.
Важно узнать различные точки зрения о том, как может работать управление состоянием, поэтому я призываю вас углубиться в эту тему, возможно, наблюдая за сеансом"Прагматическое управление состояниями во флаттере" * от Google I / O .
EDIT : только что нашел эту архитектуру в образцах архитектуры Брайана Эгана , где она называется "Simple BLoC". Если вы хотите познакомиться с различными архитектурами, я действительно рекомендую взглянуть на репозиторий.
¹ Это становится еще страшнее при попытке предоставить несколько аргументов для действия BLoC - потому что тогда вам нужно будет определить класс-обертку, чтобы просто передать его в поток.
² Я делаю признаю, что он становится немного уродливым при запуске приложения: вам понадобится своего рода заставка, которая просто проверяет поток BLoC и перенаправляет пользователя на соответствующий экран в зависимости от того, он / она вошел или нет. Это исключение из правила происходит из-за того, что пользователь выполнил действие - запускает приложение, - но среда Flutter напрямую не позволяет нам подключиться к этому (по крайней мере, не так элегантно, насколько я знаю).