Irbisa · Шпаргалка

Состояние на Riverpod

Провайдеры Riverpod, AsyncNotifier, ref, кодогенерация

Senior-разработчик · 25 вопросов

  1. 01

    Какие типы провайдеров есть в Riverpod и когда выбирать Notifier, AsyncNotifier или обычный FutureProvider?

    Тип выбирают по тому, как получается состояние и меняет ли его кто-то: Provider для вычисляемого или внедряемого значения, FutureProvider и StreamProvider для асинхронного только на чтение, Notifier для синхронного с методами, AsyncNotifier для асинхронного, которое меняет пользователь.

  2. 02

    Чем отличаются ref.watch, ref.read и ref.listen?

    watch подписывается и вызывает ребилд, read берёт текущее значение один раз без подписки, а listen подписывается, но вместо ребилда вызывает колбэк.

    class HomePage extends ConsumerWidget {  const HomePage({super.key});   @override  Widget build(BuildContext context, WidgetRef ref) {    // watch: this widget rebuilds when the count changes…
  3. 03

    Чем отличаются ConsumerWidget, ConsumerStatefulWidget и билдер Consumer?

    Все три дают WidgetRef.

    class ProductPage extends StatelessWidget {  const ProductPage({super.key});   @override  Widget build(BuildContext context) {    return Scaffold(…
  4. 04

    Чем отличаются ref.invalidate и ref.refresh?

    ref.invalidate(p) помечает провайдер устаревшим, и он перестраивается сразу, если есть слушатели, а иначе при следующем чтении; ref.refresh(p) делает то же, но перестраивает сразу и возвращает новое значение.

  5. 05

    Что такое AsyncValue и как отрисовать его состояния загрузки, ошибки и данных?

    AsyncValue<T> является sealed-типом, который отдают асинхронные провайдеры: AsyncData, AsyncLoading или AsyncError.

    class ProfileView extends ConsumerWidget {  const ProfileView({super.key});   @override  Widget build(BuildContext context, WidgetRef ref) {    final profile = ref.watch(profileProvider);…
  6. 06

    Что такое модификатор family и каким должен быть его аргумент?

    family превращает одно объявление в набор провайдеров с ключом-аргументом: productProvider(42) и productProvider(7) дают отдельные экземпляры с отдельным закешированным состоянием.

    final productProvider = FutureProvider.autoDispose.family<Product, int>(  (ref, id) => ref.watch(productRepositoryProvider).getProduct(id),); // A family notifier receives its argument through the constructor (Riverpod 3)class Basket extends Notifier<List<Item>> {…
  7. 07

    Как работает autoDispose и когда нужен ref.keepAlive()?

    По умолчанию провайдер живёт столько же, сколько его контейнер.

    final searchProvider =    FutureProvider.autoDispose.family<List<Hit>, String>((ref, query) async {  // keep results for 5 minutes after the last listener leaves  final link = ref.keepAlive();  Timer? timer;  ref.onCancel(() {…
  8. 08

    Как работает кодогенерация Riverpod (@riverpod) и зачем она нужна?

    riverpod_generator работает как шаг build_runner: вы помечаете функцию или класс аннотацией @riverpod, и он пишет объявление провайдера, выбирая его тип по сигнатуре.

    import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'products.g.dart'; // A function: generates productProvider, a FutureProvider family@riverpod…
  9. 09

    Как ограничить ребилды через select и почему это зависит от равенства?

    ref.watch(p.select((s) => s.field)) подписывается на одно производное значение и перестраивает виджет, только когда оно меняется по ==.

    // UiState is a freezed class: theme, sidebarOpen, unreadCount // wrong: rebuilds on every unread-count tickclass ThemeIconBad extends ConsumerWidget {  const ThemeIconBad({super.key});  @override…
  10. 10

    Ни add, ни addAgain не обновляют бейдж корзины на экране. Что делает Riverpod и как это исправить?

    class Cart extends Notifier<List<Item>> {  @override  List<Item> build() => [];   void add(Item item) {    state.add(item);  }   void addAgain(Item item) {    state = state..add(item);  }} final cartProvider = NotifierProvider<Cart, List<Item>>(Cart.new);

    Riverpod уведомляет слушателей, только когда вы присваиваете новый state, который не == старому.

  11. 11

    Этот экран результатов отправляет новый поисковый запрос на каждом ребилде и никогда не выходит из загрузки. Что не так?

    class SearchQuery {  SearchQuery(this.text, this.page);  final String text;  final int page;} final searchProvider = FutureProvider.autoDispose    .family<List<Hit>, SearchQuery>(  (ref, q) => ref.watch(apiProvider).search(q.text, q.page),); // inside ResultsView.build(context, ref):final hits = ref.watch(searchProvider(SearchQuery(text, 1)));

    У SearchQuery нет == и hashCode, поэтому каждый build создаёт ключ, который не совпадает ни с одним из существующих экземпляров family.

  12. 12

    Найдите баг: чип фильтра при нажатии подсвечивается, но список переключается на новый фильтр, только когда пользователь отметит какую-нибудь задачу.

    final filterProvider =    NotifierProvider<FilterNotifier, Filter>(FilterNotifier.new); final visibleTodosProvider = Provider<List<Todo>>((ref) {  final todos = ref.watch(todosProvider);  final filter = ref.read(filterProvider);  return switch (filter) {    Filter.all => todos,    Filter.active => todos.where((t) => !t.done).toList(),    Filter.done => todos.where((t) => t.done).toList(),  };}); // a chip's onSelected: ref.read(filterProvider.notifier).set(f)

    visibleTodosProvider читает фильтр через ref.read, который возвращает значение один раз и ни на что не подписывается.

  13. 13

    Как запускать навигацию, снекбары или диалоги по состоянию Riverpod?

    Через ref.listen в виджете.

    class CheckoutPage extends ConsumerWidget {  const CheckoutPage({super.key});   @override  Widget build(BuildContext context, WidgetRef ref) {    ref.listen(orderProvider, (prev, next) {…
  14. 14

    Экран пишет состояние провайдера из initState, и Riverpod бросает «Tried to modify a provider while the widget tree was building». Где должна быть эта запись?

    initState выполняется во время фазы build, и изменившийся там провайдер должен был бы уведомить виджеты, которые в этом кадре уже построены, поэтому Riverpod бросает исключение, а не показывает дерево с двумя противоречивыми версиями состояния.

    // wrong: throws "Tried to modify a provider while the widget tree was building"class _FilterScreenState extends ConsumerState<FilterScreen> {  @override  void initState() {    super.initState();    ref.read(filterProvider.notifier).set(widget.initial);…
  15. 15

    Провайдеру для вычислений нужны данные из двух других асинхронных провайдеров. Как написать его без вложенных вызовов when()?

    Ожидайте future источников внутри производного провайдера, а не сопоставляйте два объекта AsyncValue друг с другом.

    final userProvider = FutureProvider<User>((ref) => ref.watch(apiProvider).me());final settingsProvider =    FutureProvider<Settings>((ref) => ref.watch(apiProvider).settings()); // right: await .future, and one AsyncValue comes outfinal dashboardProvider = FutureProvider<Dashboard>((ref) async {…
  16. 16

    Переключение задачи заставляет весь список мигнуть полноэкранным спиннером, хотя старые строки ещё в памяти. Почему и как оставить их на экране?

    Старые строки по-прежнему лежат внутри AsyncValue; спиннер появляется потому, что обновление помечено как reload, а when() по умолчанию рисует ветку загрузки на reload.

    class TodosNotifier extends AsyncNotifier<List<Todo>> {  @override  Future<List<Todo>> build() => ref.watch(apiProvider).fetchTodos();   Future<List<Todo>> _toggle(Todo t) async {    await ref.read(apiProvider).setDone(t.id, !t.done);…
  17. 17

    Как юнит-тестировать нотифаер Riverpod через ProviderContainer и почему его состояние может сброситься между двумя вызовами?

    Создайте ProviderContainer с override для зависимостей, держите слушателя на тестируемом провайдере и вызывайте настоящий нотифаер через container.read(p.notifier).

    class Cart extends AsyncNotifier<List<Item>> {  @override  Future<List<Item>> build() => ref.watch(cartRepoProvider).load();   Future<void> addItem(Item i) async {    await ref.read(cartRepoProvider).save(i);…
  18. 18

    QA сообщает, что сумма корзины ломается после одного конкретного перехода, и никто не может воспроизвести. Как заставить Riverpod рассказать, какой провайдер изменился и когда?

    Подключите ProviderObserver к корневому ProviderScope.

    class StateLogger extends ProviderObserver {  // Riverpod 3 signature; 2.x passed (provider, previous, next, container)  @override  void didUpdateProvider(    ProviderObserverContext context,    Object? previousValue,…
  19. 19

    Как работают override в ProviderScope и что нужно, чтобы ограничить провайдер поддеревом?

    Override заменяет то, во что провайдер разрешается внутри одного контейнера: корневой ProviderScope нужен для подмен на всё приложение и фейков в тестах, вложенный ProviderScope для поддерева.

    // Root: per-flavour config and test fakesvoid main() {  runApp(ProviderScope(    overrides: [      apiBaseUrlProvider.overrideWithValue(const String.fromEnvironment('API_URL')),    ],…
  20. 20

    Ваш AsyncNotifier ждёт медленный запрос, а потом трогает ref; пользователь закрывает экран посреди запроса. Что ломается и как написать это безопасно?

    Когда экран закрывается, autoDispose-провайдер уничтожается, но ваш await продолжает выполняться.

    class UploadNotifier extends AsyncNotifier<Upload> {  @override  Future<Upload> build() async {    final token = CancelToken();    ref.onDispose(token.cancel); // an abandoned request dies with the provider…
  21. 21

    Пользователь наполовину заполнил форму, которая хранится в AsyncNotifier, обновился токен сессии, и его правки пропали. Что произошло?

    Нотифаер делал watch на сессию внутри build(), поэтому обновление токена выбросило состояние и перезапустило build() с нуля.

  22. 22

    После перехода на Riverpod 3 экран, чей запрос возвращает 400, каждые несколько секунд отправляет его снова. Кто это делает и как это остановить?

    Riverpod 3 автоматически повторяет упавшие провайдеры: по умолчанию до 10 раз, с экспоненциальной задержкой от 200 мс до 6,4 с.

    Duration? retryPolicy(int retryCount, Object error) {  if (retryCount >= 3) return null; // give up, show the error UI  if (error is ProviderException) return null; // the source retries itself   final transient = switch (error) {    DioException(:final response?) => (response.statusCode ?? 0) >= 500,…
  23. 23

    Что изменилось в Riverpod 3 и что ломается при обновлении приложения с 2.x?

    Riverpod 3.0 (сентябрь 2025) свёл API к обычным Notifier и AsyncNotifier, сделал Ref нетипизированным и поменял поведение в рантайме: фильтрация по == везде, автоматический retry, пауза слушателей у скрытых виджетов и Ref, который бросает исключение после dispose.

  24. 24

    Обработчику пушей на переднем плане и бутстрапу в main() нужны те же репозитории, что приложение читает через провайдеры. Как их разделить, не получив две копии состояния?

    Создайте ProviderContainer сами, пользуйтесь им из обычного Dart и передайте тот же контейнер в дерево виджетов через UncontrolledProviderScope.

    Future<void> main() async {  WidgetsFlutterBinding.ensureInitialized();   // one container: built in main(), warmed up, then handed to the tree  final container = ProviderContainer(    overrides: […
  25. 25

    Riverpod просит объявлять провайдеры как top-level final переменные. Разве это не то самое глобальное изменяемое состояние, от которого все предостерегают?

    Глобальная переменная не хранит состояния.