Ответ
Scope (область видимости) в DI определяет время жизни и доступность зависимостей. В Flutter это особенно важно для управления ресурсами и предотвращения утечек памяти.
Основные типы scope в Flutter:
- Singleton (глобальный scope):
// GetIt пример final getIt = GetIt.instance;
getIt.registerSingleton
2. **Widget-level scope (ScopedModel/Provider):**
```dart
// Provider
Provider<MyModel>(
create: (_) => MyModel(),
child: MyWidget(),
// Уничтожается при удалении виджета
)
- Riverpod scopes:
// AutoDispose - автоматическое удаление final counterProvider = StateProvider.autoDispose<int>((ref) => 0);
// Family - разные экземпляры для разных параметров final userProvider = FutureProvider.family<User, String>((ref, userId) { return fetchUser(userId); });
**Проблемы неправильного scope:**
```dart
// ПЛОХО: Синглтон с контекстом
class BadService {
BuildContext? context; // Утечка контекста!
}
// ХОРОШО: Локальный scope
class GoodService {
void doSomething(BuildContext context) {
// Контекст передается как параметр
}
}
Best practices для scope в Flutter:
- Используйте
autoDisposeв Riverpod для автоматической очистки - Избегайте хранения
BuildContextв долгоживущих объектах -
Используйте
Disposableмиксин для ручного освобождения ресурсов:mixin Disposable { final List<StreamSubscription> _subscriptions = []; void dispose() { for (final sub in _subscriptions) { sub.cancel(); } } }
Пример жизненного цикла:
// Инициализация при создании виджета
@override
void initState() {
super.initState();
_service = locator<MyService>();
}
// Очистка при удалении
@override
void dispose() {
_service.dispose();
super.dispose();
} Ответ 18+ 🔞
Слушай, ну про область видимости в DI — это вообще отдельная песня, ебать мои старые костыли. Представь, что ты заказываешь пиццу. Scope — это как раз то, кто её ест и как долго она на столе лежит. Один экземпляр на всех — это как одна большая пицца на всю тусовку (синглтон). А локальный — это тебе личную пиццу принесли, и когда ты ушёл, её выкинули. Просто, да? А в Flutter без этого — нихуя не работает, всё потечёт, как дырявое ведро.
Основные разновидности этой кухни:
-
Singleton (ну, глобальный, мать его): Это как тот один чайник на всю квартиру, который вечно грязный. Создали один раз — и он живёт, пока приложение не прибьёшь.
// GetIt, например final getIt = GetIt.instance; getIt.registerSingleton<ApiService>(ApiService()); // Всё, один чувак на всех, и всё тут. -
Widget-level scope (типа ScopedModel или Provider): А вот это уже потоньше. Создали для виджета — живёт, пока виджет живёт. Удалили виджет — и сервис твой накрылся медным тазом, память чистая.
// Provider Provider<MyModel>( create: (_) => MyModel(), // Родился тут child: MyWidget(), // И умрёт тут же, когда виджет свернут. Красота. ) -
А Riverpod — это вообще отдельный цирк с конями: Тут можно такие финты вытворять, ёпта.
autoDispose— это вообще магия, он сам всё подчищает, когда на тебя никто не смотрит.// AutoDispose — сам умрёт, когда не нужен final counterProvider = StateProvider.autoDispose<int>((ref) => 0); // Family — для каждого параметра свой отдельный экземпляр, чтоб не путались final userProvider = FutureProvider.family<User, String>((ref, userId) { return fetchUser(userId); // Для каждого userId — свой юзер. });
А теперь смотри, где все обычно обжигаются, пидарасы шерстяные:
// ПЛОХО, НЕ ДЕЛАЙ ТАК: Синглтон, который держит контекст.
class BadService {
BuildContext? context; // Ёб твою мать! Утечка контекста гарантирована! Он же живёт вечно!
}
// ХОРОШО: Локальный scope или передача параметром.
class GoodService {
void doSomething(BuildContext context) { // Пришёл, сделал, ушёл. Молодец.
// Работаем с контекстом, но не храним его.
}
}
Лайфхаки, чтоб не было мучительно больно:
- В Riverpod юзай
autoDisposeпо умолчанию, пока не доказана обратная необходимость. Доверия ебать ноль ко всем остальным. - Никогда, блядь, не храни
BuildContextв долгоживущих объектах. Это прямой билет в мир утечек памяти и кривых интерфейсов. - Сделай себе миксин для ручной уборки, если сам управляешь ресурсами (типа стримов):
mixin Disposable { final List<StreamSubscription> _subscriptions = []; void dispose() { for (final sub in _subscriptions) { sub.cancel(); // Всё отписал — свободен. } } }
И классика жанра — жизненный цикл виджета:
// Инициализация — тут всё рождается
@override
void initState() {
super.initState();
_service = locator<MyService>(); // Достал из ящика инструмент.
}
// Очистка — тут всё должно умирать. Не забудь, а то будет тебе хиросима.
@override
void dispose() {
_service.dispose(); // Всё, прибрал за собой.
super.dispose();
}
Короче, scope — это про ответственность. Создал — прибери. Не хочешь убирать — используй autoDispose. И будет всем счастье, а не овердохуища проблем в продакшене.