Riverpod — ตัวจัดการสถานะและ dependencies แบบคอมไพล์สำหรับ Flutter สร้างโดย Remi Rousselet ในปี 2021 เพื่อสืบทอดต่อจาก Provider Riverpod แก้ปัญหาพื้นฐานของ Provider: ขาดการตรวจสอบในเวลาคอมไพล์ การพึ่งพา BuildContext และความซับซ้อนกับ ProviderNotFoundException ตามข้อมูลจาก pub.dev แพ็คเกจนี้มีมากกว่า 5 พันไลค์และกำลังแทนที่ Provider ในโปรเจกต์ใหม่ ๆ อย่างจริงจัง
ประเด็นสำคัญ
Riverpod เป็นไลบรารีการจัดการสถานะและการฉีด dependencies สำหรับ Flutter ที่คอมไพล์คำอธิบาย providers เป็นโค้ด Dart ที่ปลอดภัย แตกต่างจาก Provider ตรงที่ providers ของ Riverpod ไม่ผูกติดกับ BuildContext: พวกมันถูกสร้างขึ้นในระดับสากลหรือใน ProviderScope และเข้าถึงได้จากทุกที่ คอมไพเลอร์ตรวจสอบชนิด dependencies และความสมบูรณ์ของกราฟ providers ในเวลาคอมไพล์ ซึ่งกำจัดข้อผิดพลาดรันไทม์เช่น ProviderNotFoundException
Riverpod ใช้โมเดล override สำหรับการทดสอบ: แต่ละ provider สามารถถูกแทนที่ผ่าน ProviderScope.overrideWith โดยไม่ต้องสร้างคลาสย่อยหรือ mock อินเทอร์เฟซ ซึ่งทำให้การทดสอบถูกแยกออกจากกัน: แต่ละการทดสอบได้รับสำเนาของกราฟ dependencies ของตัวเองที่ถูกควบคุมอย่างสมบูรณ์
ตาม Flutter Community Survey 2025 Riverpod อยู่อันดับสามในด้านความนิยมรองจาก Provider และ BLoC อย่างไรก็ตาม Riverpod เป็นแพ็คเกจที่เติบโตเร็วที่สุด: +120% การติดตั้งในปี 2024 เหตุผลหลัก: ความปลอดภัยในเวลาคอมไพล์ ไม่มี ProviderNotFoundException รองรับอะซิงโครนัสในตัวผ่าน AsyncValue
Riverpod มี providers 8 ประเภท แต่ละประเภทสำหรับสถานการณ์เฉพาะ: Provider (ค่าคงที่/บริการ), StateProvider (สถานะพื้นฐาน), StateNotifierProvider (ตรรกะซับซ้อนกับ StateNotifier), ChangeNotifierProvider (สำหรับการย้ายจาก Provider), FutureProvider (ข้อมูลอะซิงโครนัส ครั้งเดียว), StreamProvider (สตรีมเชิงตอบสนอง), NotifierProvider (API ใหม่, Flutter 3.10+) และ AsyncNotifierProvider (Notifier แบบอะซิงโครนัส)
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
return CounterNotifier();
});
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
void decrement() => state--;
}
class CounterScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final count = ref.watch(counterProvider);
return Text('$count');
}
}ProviderRef — ออบเจกต์ที่ส่งให้แต่ละ provider เพื่อเข้าถึง providers อื่น ref.watch — การติดตามการเปลี่ยนแปลง ref.read — การอ่านครั้งเดียว ref.invalidate — การรีเซ็ตแคช ProviderRef แทนที่ BuildContext จาก Provider: provider ใดๆ สามารถอ่าน providers อื่นได้โดยไม่ต้องเข้าถึงโครงสร้างวิดเจ็ต ซึ่งช่วยให้สร้างกราฟ dependencies นอกเลเยอร์ UI
ProviderScope — วิดเจ็ตรากที่จำเป็นสำหรับการทำงานของ Riverpod ProviderScope จัดเก็บ providers ทั้งหมด จัดการวงจรชีวิตและแคชค่า หากไม่มี ProviderScope แอปจะล่มด้วย ProviderNotFoundException ProviderScope สามารถซ้อนกันได้ — ขอบเขตที่ซ้อนกันจะแทนที่ providers หลัก ซึ่งใช้สำหรับการทดสอบและการแยกคุณสมบัติ
AsyncValue — คลาส sealed ของ Riverpod สำหรับแสดงสถานะอะซิงโครนัส AsyncValue มีสามรูปแบบ: AsyncData (ข้อมูลสำเร็จ), AsyncError (ข้อผิดพลาด), AsyncLoading (กำลังโหลด) แทนที่จะสลับระหว่าง loading/error/data ด้วยตนเอง แต่ละ FutureProvider หรือ StreamProvider จะคืนค่า AsyncValue โดยอัตโนมัติ และวิดเจ็ตจัดการทั้งสามสถานะผ่าน ref.watch
final userProvider = FutureProvider((ref) async {
final api = ref.watch(apiProvider);
return await api.fetchUser();
});
class UserScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userAsync = ref.watch(userProvider);
return userAsync.when(
data: (user) => UserWidget(user),
error: (e, _) => ErrorWidget(e.toString()),
loading: () => CircularProgressIndicator(),
);
}
}AsyncValue.when — เมธอดสำหรับการจับคู่รูปแบบทั้งสามสถานะ คอมไพเลอร์ตรวจสอบว่าทั้งสามกรณีถูกจัดการ — หากคุณลืม loading หรือ error โค้ดจะไม่คอมไพล์ AsyncValue.whenData — สำหรับ data เท่านั้น (หากไม่จำเป็นต้องใช้ loading/error) AsyncValue.guard — ตัวห่อหุ้ม try-catch สำหรับแปลงข้อยกเว้นเป็น AsyncError keepAlive — แฟล็กที่ป้องกันไม่ให้แคช provider ถูกทำลายเมื่อออกจากขอบเขต
การสร้างโค้ด — คุณสมบัติหลักของ Riverpod 2.0+ คำอธิบายประกอบ @riverpod บนฟังก์ชันจะสร้าง provider โดยอัตโนมัติพร้อมชนิดที่ถูกต้อง การสนับสนุนการปรับโครงสร้าง และการเติมข้อความอัตโนมัติ การสร้างโค้ดใช้ riverpod_generator และ build_runner นักพัฒนาเขียนฟังก์ชันบริสุทธิ์ และทุกอย่างอื่น — ชนิด คลาส ตัวสร้างแบบ factory — ถูกสร้างขึ้นโดยอัตโนมัติ
@riverpod
String helloWorld(HelloWorldRef ref) {
return 'Hello World';
}
// สร้าง: final helloWorldProvider = Provider((ref) => 'Hello World');
@riverpod
class Counter extends _$Counter {
int build() => 0;
void increment() => state++;
}Notifier — API ใหม่สำหรับสถานะที่เปลี่ยนแปลงได้พร้อมการสร้างโค้ด Notifier เป็นคลาสที่มีเมธอด build() และเมธอดการเปลี่ยนสถานะ แตกต่างจาก StateNotifier ตรงที่ Notifier ไม่ต้องการคลาสสถานะแยกต่างหากและให้การเข้าถึง state โดยตรงผ่าน getter/setter Riverpod สร้าง NotifierProvider โดยอัตโนมัติสำหรับแต่ละคลาส Notifier ที่มีคำอธิบายประกอบ @riverpod
build_runner: การสร้างโค้ดเริ่มต้นด้วยคำสั่ง dart run build_runner build ไฟล์ที่สร้างขึ้นมีส่วนต่อท้าย .g.dart และถูกนำเข้าในซอร์สโค้ด เมื่อคำอธิบายประกอบหรือชนิด provider เปลี่ยนไป จำเป็นต้องรันการสร้างโค้ดอีกครั้ง Riverpod 2.x แนะนำให้ใช้การสร้างโค้ดสำหรับโปรเจกต์ใหม่ทั้งหมด — การสร้าง provider ด้วยตนเองกำลังล้าสมัย
ความแตกต่างหลัก ระหว่าง Riverpod และ Provider: ความเป็นอิสระจาก BuildContext ความปลอดภัยในเวลาคอมไพล์ การรองรับอะซิงโครนัสในตัว การแคชอัตโนมัติ และการทดสอบผ่าน override Provider ต้องการ BuildContext เพื่อเข้าถึงสถานะ (context.watch, context.read) ในขณะที่ Riverpod ใช้ WidgetRef และ providers ที่ประกาศในระดับสากล
| คุณลักษณะ | Provider | Riverpod |
|---|---|---|
| การพึ่งพา BuildContext | ใช่ | ไม่ |
| การตรวจสอบในเวลาคอมไพล์ | ไม่ | ใช่ (ผ่าน @riverpod) |
| ProviderNotFoundException | รันไทม์ | เป็นไปไม่ได้ |
| อะซิงโครนัส | ด้วยตนเอง | AsyncValue (ในตัว) |
| การทดสอบ | ตัวห่อหุ้มใน Provider | ProviderScope.overrideWith |
| การแคช | ไม่ | อัตโนมัติ + keepAlive |
การย้ายจาก Provider: Riverpod รองรับ ChangeNotifierProvider.adaptive สำหรับใช้ ChangeNotifier ที่มีอยู่โดยไม่ต้องเขียนใหม่ การย้ายแบบค่อยเป็นค่อยไป: ขั้นแรกเขียนคุณสมบัติใหม่ด้วย Riverpod จากนั้นแทนที่อินสแตนซ์ Provider เก่าด้วย Riverpod providers ผ่านอะแดปเตอร์ ทั้งสองแพ็คเกจสามารถอยู่ร่วมกันในโปรเจกต์เดียว ทำให้สามารถย้ายได้โดยไม่หยุดการพัฒนา
การทดสอบ Riverpod สร้างขึ้นบน ProviderScope.overrideWith แต่ละ provider จะถูกแทนที่ภายใน ProviderScope การทดสอบโดยไม่ต้องใช้ mock หรือคอนเทนเนอร์ DI ProviderContainer — สภาพแวดล้อมที่แยกออกมาสำหรับการทดสอบโดยไม่มี Flutter (Dart บริสุทธิ์) ทำให้สามารถทดสอบ providers ได้โดยไม่ต้องเรนเดอร์วิดเจ็ต
import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart';
void main() {
test('Counter increments correctly', () {
final container = ProviderContainer();
container.read(counterProvider.notifier).increment();
expect(container.read(counterProvider), 1);
});
testWidgets('UI updates on increment', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [counterProvider.overrideWithValue(5)],
child: CounterScreen(),
),
);
expect(find.text('5'), findsOneWidget);
});
}ProviderContainer — ไม่มี Flutter ใช้ ProviderContainer สำหรับการทดสอบหน่วยของ providers โดยไม่ต้องใช้วิดเจ็ต overrideWithValue — แทนที่ provider ด้วยค่าเฉพาะ overrideWith — แทนที่ด้วยโรงงาน provider (สำหรับการ mock บริการ) autodispose — ในการทดสอบ ตรวจสอบว่า provider ถูกทำลายเมื่อออกจากขอบเขตโดยใช้ container.dispose()
คำถามที่พบบ่อย
Riverpod เป็นไลบรารีการจัดการสถานะที่มี providers ระดับสากล AsyncValue และการสร้างโค้ด BLoC เป็นรูปแบบสถาปัตยกรรมที่มี Event → Stream → State Riverpod เรียนรู้ง่ายกว่าและมี DX ที่ดีกว่าผ่านคำอธิบายประกอบ @riverpod BLoC ให้การแยกตรรกะทางธุรกิจที่เข้มงวดและการติดตาม Event ผ่าน BlocObserver การเลือกขึ้นอยู่กับกระบวนทัศน์ของโปรเจกต์: Riverpod ใกล้เคียงกับ Provider มากกว่า BLoC — ใกล้เคียงกับสตรีมเชิงตอบสนอง
Autodispose เป็นกลไกในการทำลาย provider โดยอัตโนมัติเมื่อไม่มีใครติดตามอยู่ โดยค่าเริ่มต้น providers ทั้งหมดของ Riverpod จะ autodispose: เมื่อวิดเจ็ตออกจากโครงสร้าง provider จะถูกลบออกจากหน่วยความจำ keepAlive — แฟล็กที่ปิดการใช้งาน autodispose สำหรับ providers ที่ควรมีชีวิตอยู่เสมอ (API clients, repositories, การตั้งค่า) ซึ่งป้องกันการรั่วไหลของหน่วยความจำ — providers ที่ไม่ได้ใช้จะถูกทำลายโดยอัตโนมัติ
ref.invalidate — เมธอดที่บังคับรีเซ็ตแคชของ provider หลังจาก invalidate provider จะถูกสร้างใหม่ในการอ่านครั้งถัดไป: FutureProvider ดำเนินการฟังก์ชันอะซิงโครนัสอีกครั้ง StreamProvider สมัครรับสตรีมอีกครั้ง ใช้ invalidate เพื่อบังคับรีเฟรชข้อมูล (pull-to-refresh, การเปลี่ยนผู้ใช้) ref.refresh — การรวมกันของ invalidate + การอ่าน: รีเซ็ตและอ่านค่าใหม่ทันทีในการดำเนินการเดียว
ได้ Riverpod 1.x ทำงานโดยไม่ต้องสร้างโค้ดเท่านั้น — providers ถูกสร้างด้วยตนเองโดยใช้ Provider(), StateNotifierProvider(), FutureProvider() ฯลฯ Riverpod 2.x รองรับทั้งสองวิธี หากไม่มีการสร้างโค้ดจะมี boilerplate มากขึ้น แต่ไม่มีการพึ่งพา build_runner และ dart run build_runner build สำหรับโปรเจกต์ขนาดเล็ก (สูงสุด 30 providers) การสร้างด้วยตนเองเป็นสิ่งที่สมเหตุสมผล สำหรับโปรเจกต์ขนาดใหญ่ การสร้างโค้ดเป็นสิ่งจำเป็น
Family — ตัวปรับแต่ง provider ที่ยอมรับพารามิเตอร์ภายนอก ตัวอย่างเช่น userProvider(123) — provider ที่โหลดผู้ใช้ที่มี ID 123 Family providers แคชผลลัพธ์สำหรับแต่ละพารามิเตอร์ที่ไม่ซ้ำกันแยกต่างหาก ใช้ Family สำหรับรายการของรายการที่แต่ละรายการถูกโหลดโดย ID ตัวปรับแต่ง Family พร้อมใช้งานสำหรับ providers ทุกประเภท: Provider.family, FutureProvider.family, StreamProvider.family
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ