Когда заходит разговор про state management, в ответ звучат три слова: Provider, Riverpod, BLoC.
ValueNotifier как будто не считается за взрослое решение.А зря. И одновременно — не зря. Разберём честно, без фанатизма.
Это встроенный в Flutter
Listenable с одним значением. Меняешь .value — слушатели уведомляются. Оборачиваешь кусок UI в ValueListenableBuilder — и пересобирается только он.final isSaving = ValueNotifier(false);
ValueListenableBuilder(
valueListenable: isSaving,
builder: (_, saving, __) => SaveButton(loading: saving),
);
— — —
✅ Где он реально хорош
1. Ноль зависимостей. Это Flutter из коробки. Не тащишь библиотеку ради одного флага.
2. Гранулярные ребилды. Перерисовывается только обёрнутый виджет, а не весь экран. Часто чище, чем
setState.3. Локальное состояние фичи. Тоггл, поле формы, шаг визарда, выбранная вкладка — то, что живёт внутри одной фичи и наружу не торчит.
4. Никакой магии. Явный контракт: вот значение, вот кто его слушает. Легко читать через полгода.
Именно поэтому он хорошо ложится на декомпозицию фич: маленький изолированный модуль наружу отдаёт пару
ValueNotifier — и всё.— — —
⚠️ Где подводит — и это важно знать заранее
1. Уведомление по
==, а не по факту изменения. Самые частые грабли — мутабельные объекты:final items = ValueNotifier<List<Task>>([]);
items.value.add(task); // ничего не произойдёт — ссылка та же
items.value = [...items.value, task]; // сработает — новый список
Пока значения примитивные — всё ок. Как только внутри список или своя модель — надо дисциплинированно возвращать новый объект.
2. Нет производного состояния. Нужно собрать UI из двух-трёх источников — начинаются вложенные
ValueListenableBuilder. Три уровня вложенности — и читается это уже хуже, чем то, от чего вы убегали.3. Нет асинхронности из коробки. Loading / data / error вы строите руками. У Riverpod это
AsyncValue бесплатно — и для экранов с загрузкой это реально экономит код и ошибки.4. Нет экосистемы. Ни DI, ни скоупинга, ни
autoDispose. Создал — сам не забудь dispose(). На одном экране — мелочь, на десятках — источник утечек.5. Соблазн раздробить состояние. Десяток нотифаеров, сшитых колбэками между собой — это ровно та «фрагментированная каша», что и монструозный стейт, только с другой стороны.
— — —
С
flutter_hooks — ещё прощеХуки убирают главный рутинный минус — ручное создание и
dispose. И тут есть два инструмента, которые часто путают:—
useState(0) — создаёт ValueNotifier и сразу подписывает виджет: поменял .value — хук-виджет пересобрался. Для состояния, которое рисует сам этот виджет.—
useValueNotifier(0) — только создаёт стабильный ValueNotifier между ребилдами, но НЕ подписывает. Когда нотифаер нужно передать вниз или слушать точечно (например, отдать в ValueListenableBuilder), не перерисовывая весь виджет.Оба переживают ребилды и сами делают
dispose. По хукам будет отдельный пост — там подробнее.— — —
Где проходит потолок
Переходить на Riverpod/BLoC стоит, когда появляется хотя бы одно:
— состояние шарится между несколькими фичами;
— значение вычисляется из нескольких источников;
— это асинхронные данные с загрузкой и ошибками;
— сложный жизненный цикл и зависимости, которые руками уже не удержать.
— — —
Вывод
ValueNotifier закрывает заметно больше, чем ему обычно доверяют. Но у него есть честный потолок, и упираться в него костылями — хуже, чем вовремя взять инструмент потяжелее.Поэтому вопрос не «какой стейт-менеджер правильный». Вопрос — видишь ли ты, где заканчивается зона комфорта простого решения. А чтобы это видеть, надо знать минимум два подхода и понимать цену каждого. Вот это и есть архитектурное решение, а не выбор библиотеки.
А вы используете
ValueNotifier? В каких случаях?