0Pricing
Flutter Mobile Development · Leçon

De Provider à Riverpod : migration d’un état existant

Convertissez le code existant fondé sur ChangeNotifier et Provider en graphe de fournisseurs Riverpod vérifié à la compilation.

De Provider à Riverpod : migration d’un état existant est une leçon Flutter Mobile Development gratuite sur CoddyKit. Ceci est la leçon 1 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Flutter Mobile Development, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Flutter Mobile Development comprend 4 leçons au total.

Certaines parties de cette leçon n'ont pas encore été traduites et s'affichent en anglais.

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.

Questions Fréquemment Posées

La leçon « De Provider à Riverpod : migration d’un état existant » est-elle gratuite ?

Oui — le texte complet de « De Provider à Riverpod : migration d’un état existant » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Flutter Mobile Development, passe à CoddyKit PRO. Le cours Flutter Mobile Development comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « De Provider à Riverpod : migration d’un état existant » ?

Convertissez le code existant fondé sur ChangeNotifier et Provider en graphe de fournisseurs Riverpod vérifié à la compilation. Tu pratiques Flutter Mobile Development avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer Flutter Mobile Development ?

Aucune expérience préalable n'est requise. Flutter Mobile Development sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 1 sur 4.

Combien de temps prend la leçon « De Provider à Riverpod : migration d’un état existant » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon Flutter Mobile Development ?

Oui. Chaque leçon Flutter Mobile Development inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. De Provider à Riverpod : migration d’un état existant
  2. Génération de code avec riverpod_generator et @riverpod
  3. Pipelines de données avec AsyncNotifier et FutureProvider
  4. Portée des fournisseurs, remplacements et ProviderObserver
← Retour à Flutter Mobile Development