0Pricing
Flutter Mobile Development · บทเรียน

จาก Provider สู่ Riverpod: การย้ายสถานะแบบเดิม

แปลงโค้ด ChangeNotifier และ Provider ที่มีอยู่ให้เป็นกราฟผู้ให้บริการของ Riverpod ที่ปลอดภัยด้วยการคอมไพล์

จาก Provider สู่ Riverpod: การย้ายสถานะแบบเดิม เป็นบทเรียน Flutter Mobile Development ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Flutter Mobile Development และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Flutter Mobile Development มีบทเรียนทั้งหมด 4 บทเรียน

บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ

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.

คำถามที่พบบ่อย

บทเรียน “จาก Provider สู่ Riverpod: การย้ายสถานะแบบเดิม” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “จาก Provider สู่ Riverpod: การย้ายสถานะแบบเดิม” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Flutter Mobile Development ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Flutter Mobile Development มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “จาก Provider สู่ Riverpod: การย้ายสถานะแบบเดิม”

แปลงโค้ด ChangeNotifier และ Provider ที่มีอยู่ให้เป็นกราฟผู้ให้บริการของ Riverpod ที่ปลอดภัยด้วยการคอมไพล์ คุณปฏิบัติ Flutter Mobile Development ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Flutter Mobile Development หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Flutter Mobile Development บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน

บทเรียน “จาก Provider สู่ Riverpod: การย้ายสถานะแบบเดิม” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Flutter Mobile Development นี้ได้ไหม

ได้ บทเรียน Flutter Mobile Development ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. จาก Provider สู่ Riverpod: การย้ายสถานะแบบเดิม
  2. การสร้างโค้ดด้วย riverpod_generator และ @riverpod
  3. กระบวนการข้อมูลด้วย AsyncNotifier และ FutureProvider
  4. ขอบเขตผู้ให้บริการ การแทนที่ และ ProviderObserver
← กลับไปที่ Flutter Mobile Development