본문으로 건너뛰기

เนื้อหาภาษาไทย / 태국어 콘텐츠

시스템 가이드 검수 완료된 콘텐츠

การใช้ Riverpod สำหรับ State Management

บทความนี้แนะนำ Riverpod ซึ่งเป็น State Management Library ที่พัฒนาต่อยอดมาจาก Provider เพื่อแก้ปัญหาและเพิ่มประสิทธิภาพในการจัดการ State ใน Flutter โดยเน้นข้อดี...

การใช้ Riverpod สำหรับ State Management

ในบทความก่อนหน้า เราได้เรียนรู้เกี่ยวกับ BLoC Pattern ซึ่งเป็นสถาปัตยกรรมที่ช่วยให้เราจัดการ State ใน Flutter ได้อย่างมีประสิทธิภาพ ในบทความนี้ เราจะมาเรียนรู้เกี่ยวกับอีกหนึ่งทางเลือกที่น่าสนใจสำหรับการจัดการ State นั่นคือ Riverpod

Riverpod เป็น State Management Library ที่พัฒนาโดยผู้สร้าง Provider ซึ่งเป็น Library ที่ได้รับความนิยมอย่างมากใน Flutter Riverpod ได้รับการออกแบบมาเพื่อแก้ไขข้อจำกัดบางประการของ Provider และนำเสนอแนวทางที่ปลอดภัยและมีประสิทธิภาพยิ่งขึ้นในการจัดการ State

ทำไมต้อง Riverpod?

Riverpod มีข้อดีหลายประการเมื่อเทียบกับ Provider และ State Management Libraries อื่นๆ:

  • Type Safety: Riverpod ใช้ Type Safety อย่างเต็มที่ ซึ่งช่วยลดข้อผิดพลาดที่อาจเกิดขึ้นจากการใช้งานข้อมูลผิดประเภท
  • Compile-Time Safety: Riverpod ตรวจสอบความถูกต้องของ Provider ที่ Compile-Time ซึ่งช่วยให้เราตรวจพบข้อผิดพลาดได้ตั้งแต่เนิ่นๆ
  • Testability: Riverpod ออกแบบมาให้ง่ายต่อการทดสอบ เราสามารถ Mock Providers ได้อย่างง่ายดาย
  • No Dependency on `BuildContext`: Riverpod ไม่จำเป็นต้องใช้ `BuildContext` ในการเข้าถึง State ซึ่งทำให้โค้ดของเรามีความยืดหยุ่นมากขึ้น
  • Simplified Code: Riverpod มักจะทำให้โค้ดสั้นลงและอ่านง่ายขึ้นเมื่อเทียบกับ Provider และ BLoC

แนวคิดหลักของ Riverpod

Riverpod มีแนวคิดหลักๆ ที่เราต้องทำความเข้าใจดังนี้:

  • ProviderScope: เป็น Widget ที่ทำให้ Providers สามารถเข้าถึงได้ใน Widget Tree ทั้งหมด เราต้องใส่ ProviderScope ไว้ที่จุดเริ่มต้นของแอป
  • Provider: เป็น Object ที่เก็บ State และสามารถสร้าง, แก้ไข, และเข้าถึง State ได้ Providers สามารถเป็นได้หลายประเภท เช่น `Provider`, `StateProvider`, `FutureProvider`, `StreamProvider`
  • Consumer: เป็น Widget ที่ใช้ในการอ่าน State จาก Provider และ Rebuild Widget เมื่อ State เปลี่ยนแปลง

การเริ่มต้นใช้งาน Riverpod

ในการเริ่มต้นใช้งาน Riverpod เราต้องเพิ่ม Dependency ใน `pubspec.yaml`:

dependencies:
  flutter_riverpod: ^2.0.0 # หรือเวอร์ชันล่าสุด

หลังจากนั้นให้รัน `flutter pub get` เพื่อติดตั้ง Dependency

ProviderScope

ก่อนที่เราจะสามารถใช้ Providers ได้ เราต้อง Wrap แอปของเราด้วย `ProviderScope` Widget:

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

`ProviderScope` ทำให้ Providers ทั้งหมดที่เราจะสร้างในภายหลังสามารถเข้าถึงได้จากทุกส่วนของแอป

Provider

`Provider` เป็น Provider ที่ง่ายที่สุด ใช้สำหรับเก็บค่าที่ไม่เปลี่ยนแปลงหรือคำนวณได้ง่าย ตัวอย่างเช่น:

import 'package:flutter_riverpod/flutter_riverpod.dart';

final messageProvider = Provider((ref) => 'Hello Riverpod!');

ในตัวอย่างนี้ เราสร้าง `Provider` ที่ชื่อ `messageProvider` ซึ่งเก็บ String "Hello Riverpod!" ฟังก์ชันที่ส่งให้กับ `Provider` จะรับ `ref` เป็น Parameter ซึ่งเราสามารถใช้เพื่อเข้าถึง Providers อื่นๆ ได้ (จะกล่าวถึงในภายหลัง)

Consumer

`Consumer` เป็น Widget ที่ใช้ในการอ่านค่าจาก `Provider` และ Rebuild Widget เมื่อค่าใน `Provider` เปลี่ยนแปลง:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class MyWidget extends ConsumerWidget {
  const MyWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final message = ref.watch(messageProvider);

    return Text(message);
  }
}

ในตัวอย่างนี้ เราสร้าง `ConsumerWidget` ที่ชื่อ `MyWidget` ใน Method `build` เราใช้ `ref.watch(messageProvider)` เพื่ออ่านค่าจาก `messageProvider` และเก็บไว้ในตัวแปร `message` หลังจากนั้นเราก็ใช้ `message` ในการแสดงผลข้อความ

ข้อควรจำ: เราต้องใช้ `ConsumerWidget` หรือ `ConsumerStatefulWidget` เพื่อให้สามารถใช้ `ref.watch` ได้ หากเราต้องการใช้ `ref.watch` ใน Widget ปกติ เราสามารถ Wrap Widget นั้นด้วย `Consumer` Widget ได้

Widget build(BuildContext context) {
  return Consumer(
    builder: (context, ref, child) {
      final message = ref.watch(messageProvider);
      return Text(message);
    },
  );
}

StateProvider

`StateProvider` ใช้สำหรับเก็บ State ที่เปลี่ยนแปลงได้ ตัวอย่างเช่น:

import 'package:flutter_riverpod/flutter_riverpod.dart';

final counterProvider = StateProvider((ref) => 0);

ในตัวอย่างนี้ เราสร้าง `StateProvider` ที่ชื่อ `counterProvider` ซึ่งเก็บ Integer เริ่มต้นที่ 0

ในการแก้ไขค่าใน `StateProvider` เราสามารถใช้ `ref.read(counterProvider.notifier).state = newValue` หรือ `ref.read(counterProvider.notifier).update((state) => newState)`:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class MyCounterWidget extends ConsumerWidget {
  const MyCounterWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final counter = ref.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('Counter App')),
      body: Center(child: Text('Counter: $counter')),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // วิธีที่ 1: เปลี่ยนค่าโดยตรง
          // ref.read(counterProvider.notifier).state = counter + 1;

          // วิธีที่ 2: ใช้ update เพื่อสร้าง state ใหม่
          ref.read(counterProvider.notifier).update((state) => state + 1);
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

ในตัวอย่างนี้ เมื่อกด FloatingActionButton ค่าใน `counterProvider` จะเพิ่มขึ้น 1 และ Widget จะ Rebuild เพื่อแสดงค่าใหม่

FutureProvider และ StreamProvider

Riverpod ยังมี `FutureProvider` และ `StreamProvider` สำหรับจัดการ Asynchronous Data:

  • FutureProvider: ใช้สำหรับจัดการ Future
  • StreamProvider: ใช้สำหรับจัดการ Stream

ตัวอย่างการใช้ `FutureProvider`:

import 'package:flutter_riverpod/flutter_riverpod.dart';

final dataProvider = FutureProvider<String>((ref) async {
  await Future.delayed(const Duration(seconds: 2)); // Simulate API call
  return 'Data from API';
});

ในการใช้งาน `FutureProvider` เราสามารถใช้ `ref.watch` และตรวจสอบสถานะของ Future:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

class MyDataWidget extends ConsumerWidget {
  const MyDataWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final data = ref.watch(dataProvider);

    return data.when(
      data: (value) => Text('Data: $value'),
      loading: () => const CircularProgressIndicator(),
      error: (error, stackTrace) => Text('Error: $error'),
    );
  }
}

`data.when` ช่วยให้เราจัดการสถานะต่างๆ ของ Future ได้อย่างง่ายดาย: `data` (เมื่อ Future สำเร็จ), `loading` (ระหว่างที่ Future กำลังทำงาน), และ `error` (เมื่อ Future เกิดข้อผิดพลาด)

ข้อผิดพลาดที่พบบ่อย

  • ลืมใส่ ProviderScope: หากเราลืม Wrap แอปด้วย `ProviderScope` เราจะไม่สามารถใช้ Providers ได้
  • ใช้ ref.read ใน Widget Build: `ref.read` ใช้สำหรับอ่านค่า Providers เพียงครั้งเดียวเท่านั้น ไม่ควรใช้ใน Widget Build เพราะจะทำให้ Widget ไม่ Rebuild เมื่อค่าใน Provider เปลี่ยนแปลง ควรใช้ `ref.watch` แทน
  • การแก้ไข State โดยตรง: หากเราแก้ไข State โดยตรงโดยไม่ใช้ `ref.read(provider.notifier).state = newValue` หรือ `ref.read(provider.notifier).update((state) => newState)` Widget จะไม่ Rebuild

สรุป

Riverpod เป็น State Management Library ที่มีประสิทธิภาพและใช้งานง่าย ช่วยให้เราจัดการ State ใน Flutter ได้อย่างปลอดภัยและมีประสิทธิภาพมากขึ้น ในบทความนี้ เราได้เรียนรู้เกี่ยวกับแนวคิดหลักของ Riverpod เช่น `ProviderScope`, `Provider`, `StateProvider`, `FutureProvider`, และ `Consumer` รวมถึงข้อผิดพลาดที่พบบ่อยในการใช้งาน Riverpod

ในบทความถัดไป เราจะมาเรียนรู้เกี่ยวกับการใช้ Freezed Package เพื่อ Generate Data Classes ซึ่งจะช่วยให้เราจัดการ Data Model ได้อย่างมีประสิทธิภาพยิ่งขึ้น

Office.in.th Custom Service

이 구조를 바탕으로 시스템을 개발하거나 맞춤 변경하고 싶으신가요?

Office.in.th는 엔터프라이즈 웹 애플리케이션, API 연동, 공공/민간 업무 관리 시스템을 설계·개발하며 장기 지원을 제공합니다.

소프트웨어 개발 서비스 보기