Ответ
Scoped Model — это паттерн управления состоянием, основанный на InheritedWidget. Я использовал его в небольших проектах для простого разделения состояния между виджетами без необходимости прокидывать его через конструкторы.
Как это работает:
- Создаётся модель, расширяющая класс
Model. - Модель помещается в дерево виджетов с помощью
ScopedModel. - Виджеты, которым нужно состояние, оборачиваются в
ScopedModelDescendantи автоматически перестраиваются при вызовеnotifyListeners().
Пример из моего опыта — счётчик:
// Модель состояния
class CounterModel extends Model {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // Уведомляет подписчиков об изменении
}
}
// В корне приложения
ScopedModel(
model: CounterModel(),
child: MyApp(),
);
// Виджет, который использует состояние
ScopedModelDescendant<CounterModel>(
builder: (context, child, model) {
return ElevatedButton(
onPressed: model.increment,
child: Text('Count: ${model.count}'),
);
},
);
Почему я его выбирал:
- Минимальный boilerplate-код — быстрее начать, чем с Provider или Bloc.
- Автоматическое обновление UI — не нужно вручную вызывать
setState. - Простота для небольших приложений — идеально для прототипов или проектов с 2-3 экранами.
Однако для сложных приложений с множеством моделей и бизнес-логикой я переходил на Provider или Riverpod из-за лучшей масштабируемости и тестируемости.
Ответ 18+ 🔞
А, Scoped Model, говоришь? Ну это такая штука, которая заставляет тебя вспомнить, что Flutter — это всё-таки про виджеты, ёпта. По сути, это как взять InheritedWidget, который обычно выглядит страшнее, чем хуй в пальто, и завернуть его в конфетку, чтобы не так пугало.
Как эта магия работает, если по-простому:
- Ты лепишь свою модель, которая наследуется от
Model. Это типа хранилище твоего состояния. - Потом ты эту модель засовываешь куда-то повыше в дерево виджетов, обернув всё в
ScopedModel. Это как сказать: "Вот, дети, ваш папа-состояние, пользуйтесь". - А дальше любой виджет, которому вдруг приспичило знать, что там в папе-состоянии, оборачивается в
ScopedModelDescendant. И когда ты в модели крикнешьnotifyListeners(), все эти виджеты-подсосы автоматически перерисуются. Волнение ебать, как просто!
Вот тебе живой пример — счётчик, классика жанра:
// Это наша модель, где живёт счётчик
class CounterModel extends Model {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // Кричим всем: "Эй, обновитесь, пацаны!"
}
}
// В корне приложения вешаем модель на всё дерево
ScopedModel(
model: CounterModel(),
child: MyApp(),
);
// А это виджет-потребитель, который слушает модель
ScopedModelDescendant<CounterModel>(
builder: (context, child, model) {
return ElevatedButton(
onPressed: model.increment,
child: Text('Count: ${model.count}'),
);
},
);
А почему я его вообще брал в работу?
- Бойлерплейта — овердохуища меньше. По сравнению с тем же Bloc, где нужно городить события, состояния и прочую лапшу, тут всё в пару строк. Для быстрого прототипа — идеально.
- UI сам обновляется. Не надо самому дёргать
setStateв каждом углу. СказалnotifyListeners()— и поехали. - Для мелких проектов — огонь. Если у тебя два экрана и одна модель, то это хуй с горы, а не архитектура. Всё понятно и быстро.
Но вот когда проект начинает расти, как на дрожжах, тут уже подозрение ебать чувствую. Моделей становится много, они начинают друг на друга ссылаться, логика расползается... И тут уже понимаешь, что доверия ебать ноль к этой простоте. Для серьёзных вещей я потом всё равно переползал на Provider или Riverpod — там и тестировать проще, и масштабируется всё куда лучше. А Scoped Model остаётся в памяти как милая, но немного манда с ушами для старта.