จาก 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 aProviderNotFoundExceptionat runtime, not compile time. - BuildContext coupling — you can only read providers where you have a
BuildContext. - No combining — depending on one
ChangeNotifierfrom 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
statefield instead of private fields + getters. - You never call
notifyListeners()— reassigningstaterebuilds 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 inbuild).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
ProviderScopeat the root immediately — it coexists with the oldproviderpackage. - 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→ConsumerWidgetandStatefulWidget→ConsumerStatefulWidgetas you touch each screen. - Delete the old
ChangeNotifierProviderand theproviderdependency 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
ChangeNotifierinto a Notifier with astatefield and nonotifyListeners(). - Swapped
context.watch/readforref.watch(display) andref.read(provider.notifier)(actions) inside a ConsumerWidget. - Modeled async with FutureProvider +
AsyncValue.when, and combined providers viaref.watchinstead ofProxyProvider. - 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- จาก Provider สู่ Riverpod: การย้ายสถานะแบบเดิม
- การสร้างโค้ดด้วย riverpod_generator และ @riverpod
- กระบวนการข้อมูลด้วย AsyncNotifier และ FutureProvider
- ขอบเขตผู้ให้บริการ การแทนที่ และ ProviderObserver