0Pricing
Flutter Mobile Development · Lezione

Da Provider a Riverpod: migrazione dello stato legacy

Converti il codice esistente basato su ChangeNotifier e Provider nel grafo di provider di Riverpod, verificato in compilazione.

Da Provider a Riverpod: migrazione dello stato legacy è una lezione Flutter Mobile Development gratuita su CoddyKit. Questa è la lezione 1 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Flutter Mobile Development, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Flutter Mobile Development include 4 lezioni in totale.

Parti di questa lezione non sono ancora state tradotte e vengono mostrate in inglese.

Why Migrate to Riverpod?

The classic provider package solved dependency injection in Flutter, but it has real weaknesses you have probably hit in production:

  • Runtime crashes — calling context.read<T>() for a type that was never provided throws a ProviderNotFoundException at runtime, not compile time.
  • BuildContext coupling — you can only read providers where you have a BuildContext.
  • No combining — depending on one ChangeNotifier from another is awkward and error-prone.

Riverpod is a rewrite by the same author. Providers are top-level globals that the compiler can verify, so a missing dependency becomes a compile error. This lesson walks you through migrating a legacy ChangeNotifier + Provider app to Riverpod 2.0, piece by piece.

The Legacy Code We Are Migrating

Here is a typical legacy counter feature using ChangeNotifier. It exposes a value and a method that calls notifyListeners(). This is the pattern we will convert step by step.

Notice that the state (_count) and the mutation logic live together inside a class that extends ChangeNotifier.

// LEGACY — provider package
import 'package:flutter/foundation.dart';

class CounterModel extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }

  void reset() {
    _count = 0;
    notifyListeners();
  }
}

How the Legacy Model Was Wired Up

In the old setup you register the model with ChangeNotifierProvider high in the widget tree, then read it via context.watch / context.read. The problem: if you forget to register it, the app compiles fine and crashes only when that screen opens.

// LEGACY wiring
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CounterModel(),
      child: const MyApp(),
    ),
  );
}

// In a widget:
final count = context.watch<CounterModel>().count;
context.read<CounterModel>().increment();

Step 1 — Install Riverpod and Add ProviderScope

Add flutter_riverpod to pubspec.yaml. The single most important wiring change is to wrap your app in a ProviderScope. This is the container that stores the state of every provider — it replaces the nest of ChangeNotifierProvider widgets at the root.

  • Old: many provider widgets wrapping MyApp.
  • New: one ProviderScope. Providers themselves are declared as globals, not in the tree.
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
  );
}

Step 2 — ChangeNotifier becomes Notifier

Riverpod 2.0 introduces Notifier as the modern, type-safe replacement for ChangeNotifier. The differences:

  • You hold state in a single state field instead of private fields + getters.
  • You never call notifyListeners() — reassigning state rebuilds listeners automatically.
  • The build() method returns the initial state.

Here is the migrated counter as a Notifier<int>:

import 'package:flutter_riverpod/flutter_riverpod.dart';

class CounterNotifier extends Notifier<int> {
  @override
  int build() => 0; // initial state

  void increment() => state = state + 1;
  void reset() => state = 0;
}

final counterProvider = NotifierProvider<CounterNotifier, int>(
  CounterNotifier.new,
);

Step 3 — Read State in the UI with ConsumerWidget

Replace context.watch/context.read with a WidgetRef. The cleanest path is to make your widget a ConsumerWidget, which adds a ref parameter to build.

  • ref.watch(provider) — subscribe and rebuild on change (use in build).
  • ref.read(provider.notifier) — get the notifier to call methods (use in callbacks).
class CounterScreen extends ConsumerWidget {
  const CounterScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Scaffold(
      body: Center(child: Text('Count: $count')),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.notifier).increment(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

watch vs read — The Migration Trap

The most common migration bug is using the wrong access method. Map the old API to the new one carefully:

  • context.watch<T>() → ref.watch(provider)  (rebuilds the UI)
  • context.read<T>() in a callback → ref.read(provider.notifier)  (no rebuild)

Rule: never call ref.watch inside an onPressed callback — it will not behave as expected and can cause unnecessary rebuilds. Use ref.read for one-off actions, ref.watch for values displayed in the UI.

Step 4 — Migrating Async State (FutureProvider)

Legacy apps often load data inside a ChangeNotifier with manual isLoading/error booleans. Riverpod replaces all of that with AsyncValue and a FutureProvider — loading and error states are modeled for you.

The UI then uses AsyncValue.when to render data, loading, and error branches exhaustively.

final userProvider = FutureProvider<User>((ref) async {
  final repo = ref.watch(userRepositoryProvider);
  return repo.fetchCurrentUser();
});

// In a ConsumerWidget:
final asyncUser = ref.watch(userProvider);
return asyncUser.when(
  data: (user) => Text(user.name),
  loading: () => const CircularProgressIndicator(),
  error: (e, st) => Text('Error: $e'),
);

Step 5 — Combining Providers (No More ProxyProvider)

In the old package, making one model depend on another required ProxyProvider and careful ordering. In Riverpod, a provider simply calls ref.watch on another provider inside its body. Dependencies are explicit, type-safe, and reactive.

Below, a derived provider recomputes automatically whenever cartProvider changes — no manual subscription, no notifyListeners chains.

final cartProvider =
    NotifierProvider<CartNotifier, List<Item>>(CartNotifier.new);

// Derived state — recomputes when the cart changes
final cartTotalProvider = Provider<double>((ref) {
  final items = ref.watch(cartProvider);
  return items.fold(0.0, (sum, item) => sum + item.price);
});

A Standalone Taste of Reactive Derivation in Dart

You do not need Flutter to understand Riverpod's core idea: derived values recompute from their inputs. This pure-Dart snippet models the same fold-the-cart logic used in cartTotalProvider, so you can run and verify the math an online judge would compute.

class Item {
  final String name;
  final double price;
  Item(this.name, this.price);
}

double cartTotal(List<Item> items) =>
    items.fold(0.0, (sum, item) => sum + item.price);

void main() {
  final cart = [
    Item('Coffee', 3.50),
    Item('Bagel', 2.25),
    Item('Juice', 4.00),
  ];
  print('Items: ${cart.length}');
  print('Total: \$${cartTotal(cart).toStringAsFixed(2)}');
}

Step 6 — Incremental Migration Strategy

You rarely rewrite a whole app at once. A safe, incremental plan:

  • Wrap once: add ProviderScope at the root immediately — it coexists with the old provider package.
  • Leaf-first: migrate self-contained features (settings, theme, counters) before tangled ones.
  • Bridge if needed: a Riverpod provider can read legacy data, and a legacy widget can stay until its screen is converted.
  • Convert widgets: change StatelessWidget→ConsumerWidget and StatefulWidget→ConsumerStatefulWidget as you touch each screen.
  • Delete the old ChangeNotifierProvider and the provider dependency only when nothing references them.

Quick Check — watch vs read

Test your understanding of the most error-prone part of the migration.

Recap — From Provider to Riverpod

You migrated a legacy ChangeNotifier + Provider feature to Riverpod 2.0:

  • Wrapped the app in a single ProviderScope instead of nested provider widgets.
  • Turned ChangeNotifier into a Notifier with a state field and no notifyListeners().
  • Swapped context.watch/read for ref.watch (display) and ref.read(provider.notifier) (actions) inside a ConsumerWidget.
  • Modeled async with FutureProvider + AsyncValue.when, and combined providers via ref.watch instead of ProxyProvider.
  • Followed a leaf-first, incremental path so the two systems coexist during the transition.

The payoff: a compile-safe provider graph where a missing dependency is a build error, not a production crash.

Domande Frequenti

La lezione «Da Provider a Riverpod: migrazione dello stato legacy» è gratuita?

Sì — il testo completo di «Da Provider a Riverpod: migrazione dello stato legacy» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Flutter Mobile Development, passa a CoddyKit PRO. Il corso Flutter Mobile Development include 4 lezioni in totale.

Cosa imparerò in «Da Provider a Riverpod: migrazione dello stato legacy»?

Converti il codice esistente basato su ChangeNotifier e Provider nel grafo di provider di Riverpod, verificato in compilazione. Eserciti Flutter Mobile Development con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Flutter Mobile Development?

Non è richiesta alcuna esperienza precedente. Flutter Mobile Development su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 1 di 4.

Quanto tempo richiede la lezione «Da Provider a Riverpod: migrazione dello stato legacy»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Flutter Mobile Development?

Sì. Ogni lezione Flutter Mobile Development include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Da Provider a Riverpod: migrazione dello stato legacy
  2. Generazione del codice con riverpod_generator e @riverpod
  3. Pipeline di dati con AsyncNotifier e FutureProvider
  4. Ambito dei provider, override e ProviderObserver
← Torna a Flutter Mobile Development