К содержимому

Automatic disposal

В Riverpod можно настроить автоматическое освобождение ресурсов, связанных с provider, когда он больше не используется.

Включение и отключение автоматического освобождения ресурсов

Если вы используете генерацию кода, эта возможность включена по умолчанию. Её можно отключить через аннотацию:

// Отключить автоматическое освобождение ресурсов
(keepAlive: true)
String helloWorld(Ref ref) => 'Hello world!';

Если генерация кода не используется, автоматическое освобождение ресурсов можно включить с помощью параметра isAutoDispose: true при создании provider:

final helloWorldProvider = Provider<String>(
// Включить автоматическое освобождение ресурсов
isAutoDispose: true,
(ref) => 'Hello world!',
);
примечание

Включение или отключение автоматического освобождения ресурсов не влияет на то, будет ли состояние уничтожено при пересчёте provider.
При пересчёте provider его состояние всегда уничтожается.

предупреждение

Если provider принимает параметры, рекомендуется включать автоматическое освобождение ресурсов. В противном случае для каждой комбинации параметров будет создаваться отдельное состояние, что может привести к утечкам памяти.

Когда срабатывает автоматическое освобождение ресурсов?

Когда автоматическое освобождение ресурсов включено, Riverpod отслеживает, есть ли у provider слушатели. Для этого учитываются вызовы Ref.watch, Ref.listen и некоторых других методов.

Когда количество слушателей становится равным нулю, provider считается "неиспользуемым", и вызывается Ref.onCancel. После этого Riverpod ожидает один кадр (ср. await null). Если по прошествии этого кадра provider всё ещё не используется, он уничтожается, после чего вызывается Ref.onDispose.

Реакция на уничтожение состояния

В Riverpod существует несколько встроенных сценариев, при которых состояние может быть уничтожено:

  • provider больше не используется и работает в режиме автоматического освобождения ресурсов (подробнее об этом позже). В этом случае всё состояние, связанное с provider, уничтожается.
  • provider пересчитывается, например из-за ref.watch. В этом случае предыдущее состояние уничтожается, а вместо него создаётся новое.

В обоих случаях может потребоваться выполнить какую-либо логику при уничтожении состояния.
Для этого существует ref.onDispose. Этот метод позволяет зарегистрировать обработчик, который будет вызываться каждый раз при уничтожении состояния.

Например, его можно использовать для закрытия активного StreamController:

final provider = StreamProvider<int>((ref) {
final controller = StreamController<int>();

// При уничтожении состояния закрываем StreamController.
ref.onDispose(controller.close);

// TO-DO: Добавьте значения в StreamController
return controller.stream;
});
предупреждение

Колбэк, передаваемый в ref.onDispose не должен вызывать побочные эффекты. Изменение состояния providers внутри onDispose может привести к непредсказуемому поведению.

к сведению

Существуют и другие полезные события жизненного цикла:

  • ref.onCancel — вызывается, когда у provider удаляется последний слушатель.
  • ref.onResume — вызывается, когда после вызова onCancel к provider добавляется новый слушатель.
к сведению

Вы можете вызывать ref.onDispose столько раз, сколько потребуется. Не стесняйтесь вызывать его отдельно для каждого объекта, требующего освобождения ресурсов, внутри provider. Такой подход упрощает поиск ситуаций, когда какой-либо объект был забыт и не был корректно освобождён.

Принудительное уничтожение provider с помощью ref.invalidate

Иногда может потребоваться принудительно уничтожить provider. Это можно сделать с помощью ref.invalidate, который можно вызывать как из другого provider, так и из виджета.

При вызове ref.invalidate текущее состояние provider уничтожается. После этого возможны два варианта:

  • если provider отслеживается, для него будет создано новое состояние.
  • если provider не отслеживается, он будет полностью уничтожен.
class MyWidget extends ConsumerWidget {

Widget build(BuildContext context, WidgetRef ref) {
return ElevatedButton(
onPressed: () {
// При нажатии уничтожить provider.
ref.invalidate(someProvider);
},
child: const Text('dispose a provider'),
);
}
}
к сведению

Провайдеры также могут инвалидировать сами себя с помощью ref.invalidateSelf. Однако в этом случае всегда будет создаваться новое состояние.

подсказка

Если вы хотите инвалидировать provider, принимающий параметры, можно инвалидировать только одну конкретную комбинацию параметров или сразу все комбинации параметров:

final provider = Provider.autoDispose.family<String, String>((ref, name) {
return 'Hello $name';
});

// ...

void onTap() {
// Инвалидировать все возможные комбинации параметров этого provider.
ref.invalidate(provider);
// Инвалидировать только конкретную комбинацию параметров.
ref.invalidate(provider('John'));
}

Тонкая настройка освобождения ресурсов с ref.keepAlive

Как уже упоминалось выше, при включённом автоматическом освобождении ресурсов состояние уничтожается, если у provider нет слушателей в течение одного полного кадра.

Однако иногда требуется более точный контроль над этим поведением. Например, вы можете захотеть сохранять результат успешных сетевых запросов, но не кэшировать неудачные.

Это можно реализовать с помощью ref.keepAlive, после включения автоматического освобождения. С его помощью вы сами решаете когда состояние перестаёт автоматически уничтожаться.

final provider = FutureProvider.autoDispose<String>((ref) async {
final response = await http.get(Uri.parse('https://example.com'));
// Делаем provider "живым" только после успешного завершения запроса.
// Если запрос завершится с ошибкой (и будет выброшено исключение),
// то когда provider перестанет использоваться, его состояние будет уничтожено.
final link = ref.keepAlive();

// С помощью `link` можно вернуть автоматическое освобождение ресурсов:
// link.close();

return response.body;
});
примечание

Если provider пересчитывается, автоматическое освобождение ресурсов снова включается.

Также можно использовать возвращаемое значение ref.keepAlive для возврата к автоматическому освобождению ресурсов.

Пример: сохранение состояния на заданное время

В настоящее время Riverpod не предоставляет встроенного механизма для сохранения состояния на строго заданный промежуток времени.
Однако реализовать такую логику легко и удобно с использованием уже рассмотренных инструментов.

С помощью Timer + ref.keepAlive можно удерживать состояние provider активным в течение определённого времени. Чтобы сделать этот подход переиспользуемым, можно оформить его в виде метода расширения:

extension CacheForExtension on Ref {
/// Сохраняет provider активным в течение [duration].
void cacheFor(Duration duration) {
// Сразу предотвращаем уничтожение состояния.
final link = keepAlive();
// После истечения времени снова включаем автоматическое освобождение ресурсов.
final timer = Timer(duration, link.close);

// Опционально: при пересоздании provider (например, через ref.watch)
// отменяем запланированный таймер.
onDispose(timer.cancel);
}
}

Затем мы можем использовать его следующим образом:

final provider = FutureProvider.autoDispose<Object>((ref) async {
/// Сохраняет состояние активным в течение 5 минут
ref.cacheFor(const Duration(minutes: 5));

return http.get(Uri.https('example.com'));
});

Эту логику можно адаптировать под ваши нужды. Например, можно использовать ref.onCancel/ref.onResume, чтобы уничтожать состояние только в том случае, если provider не использовался (не имел слушателей) в течение определённого промежутка времени.