Управление состоянием приложения во Flutter
Управление состоянием во Flutter — горячая тема. Возможных вариантов решения задачи много и запутаться в них, выбирая наиболее подходящий под ваши потребности, — крайне просто. Я сам путался, но нашел подходящее решение. Позвольте поделиться им с вами.
Чтобы найти решение, подходящее к вашим потребностям, необходимо определить сами потребности. В моем случае это:
- Иметь возможность развития проекта без ущерба качеству кода.
- Разделить логику отображения от бизнес-логики.
- Иметь понятный код, который сложно поломать.
- Предсказуемость и понятность кода.
Учитывая эти требования, подходящими вариантами остаются:
- Использование метода
setState() и Stateful-виджетов. - Библиотека ScopedModel.
- Применение паттерна BLoC (Компоненты бизнес-логики).
- Redux.
Разница между локальным и глобальным состоянием
Перед тем, как погрузиться в анализ отобранных решений, необходимо понять разницу между локальным и глобальным состоянием. Для этого подойдет практический пример.
Представим форму авторизации, где пользователю предлагается ввести логин и пароль и получить объект «личности пользователя» после отправки формы. В этом примере любая проверка данных, вводимых в поля формы, будет являться частью локального состояния виджета формы авторизации, и остальная часть приложения не должна знать об этом. А возвращаемый сервером авторизации объект «личности» — частью глобального состояния. Так как от этого объекта зависят другие компоненты, меняющие поведение в зависимости от того, авторизован ли пользователь.
Краткие выводы для тех, кто устал ждать
Если вы не хотите ждать, или не заинтересованы в моих исследованиях, то вот краткий обзор полученных результатов:
Моя рекомендация — использовать BLoC для управления локальным состоянием и Redux для глобального состояния, особенно если вы создаете сложное приложение, которое будет расти со временем.
Почему не стоит использовать setState()
Использование
ScopedModel — шаг в верном направлении
ScopedModel — библиотека стороннего разработчика Brian Egan. Она дает возможность создавать специальные объекты Models, а также использовать метод
class CounterModel extends Model { int _counter = 0; int get counter = _counter; void increment() { _counter++; notifyListeners(); } }
В наших виджетах мы сможем реагировать на изменения в модели с помощью предоставляемого данной библиотекой виджета ScopedModelDescendant:
class CounterApp extends StatelessWidget { @override Widget build(BuildContext context) { return new ScopedModel<CounterModel>( model: new CounterModel(), child: new Column(children: [ new ScopedModelDescendant<CounterModel>( builder: (context, child, model) => new Text('${model.counter}'), ), new Text("Другой виджет, который не зависит от CounterModel") ]) ); } }
В противовес использованию подхода
- Если Model становится сложной, то сложным становится и определение того, когда нужно использовать метод
notifyListeners() , а когда нет — чтобы избежать лишнего обновления интерфейса. - API, предоставляемый Model, в общем не точно описывает асинхронную природу интерфейса приложений.
Учитывая все это, если состояние вашего приложения не легкое для управления, я не рекомендую использовать данных подход. Я просто не верю, что он способен продуктивно обеспечить рост и сложность приложений.
Мощное решение — BLoC
Данный паттерн был придуман в Google и там же его используют. Он поможет нам достичь следующих целей:
- Разделение логики отображения от бизнес-логики.
- Использование асинхронной природы для отображения интерфейса.
- Возможность переиспользования в разных Dart-приложениях, таких как Flutter или AngularDart.
Идея данного подхода очень проста:
- BLoC использует
Sink<T>
Api для описания асинхронно поступающих в наши компоненты данных;
- BLoC использует
Stream<T>
Api для описания асинхронно возвращаемых нашими компонентами данных;
- наконец, мы можем использовать виджет StreamBuilder для управления потоками данных без приложения усилий с нашей стороны к подпискам на обновления данных и перерисовку виджетов.
У Google есть хорошие примеры использования данного паттерна управления состоянием, потому что он широко используется и очень рекомендуется компанией.
Я и сам очень рекомендую использовать данный подход для управления локальным состоянием, однако он подойдет даже и для управления глобальным состоянием. Однако в последнем случае вы столкнетесь с проблемой — где и как правильно внедрять BLoC, чтобы к нему имели доступ разные компоненты, и тут на сцену выходит Redux.
Redux и BLoC — идеальный микс для меня
Одной из целей, которые я описывал в начале статьи, был поиск чего-то, широко используемого и предсказуемого, и это Redux — паттерн и набор инструментов, которые вместе помогают нам управлять глобальным состоянием. Он имеет три базовых принципа в основе:
- Единственный источник истины — все состояние
state вашего приложения хранится в древовидном объекте в единственном хранилищеstore . - Состояние доступно только для чтения — единственный способ изменить состояние — это вызвать специальный объект action, описывающий, что должно произойти с состоянием.
- Изменения производятся с помощью чистых функций — для определения того, что изменяется в состоянии вы пишите чистую функцию
reducer , которая не должна вызывать никаких побочных эффектов. Ссылка на пример кода: https://pub.dartlang.org/packages/redux.
Данный подход к управлению состоянием широко принят web-разработчиками, и его появление на мобильных устройствах поможет получить преимущества web и mobile-application разработчикам.
Brian Egan разрабатывает как оригинальный Redux, так и flutter_redux, а также он сделал потрясающее Todo приложение, в котором он применил множество архитектурных паттернов, включая Redux.
Учитывая все качества Redux, я очень сильно советую использовать его для управления глобальным состоянием, но вы должны быть уверены, что не используете его для управления локальным состоянием, если хотите масштабировать свое приложение.
Последние слова
В данной статье нет полностью правильного или неправильного решения. Чтобы определиться с тем, какой подход применять в вашем проекте, вам необходимо определиться с вашими потребностями. Для меня и моих целей комбинация Redux и BLoC позволяет моим проектам быстро и безопасно расти, а также облегчает вход сторонних разработчиков в эти проекты, благодаря доступным и понятным инструментам. Однако не у всех одинаковые потребности и с течением времени можно находить как проблемы в текущих инструментах, так и еще лучшие решения. Очень важно всегда оставаться любопытным, учиться и думать, подходит ли вам тот или иной инструмент.
Первоисточник: https://medium.com/flutter-community/let-me-help-you-to-understand-and-choose-a-state-management-solution-for-your-app-9ffeac834ee3.