ช่วงนี้กำลังจะกลับมาปัดฝุ่น เขียน flutter เลยมองหา state management อื่นๆ ที่ไม่ใช่ Provider, BLoC, GETX มาลองเล่นดูบ้าง เลยไปเจอกับ Riverpod ตัวนี้

What is Riverpod?

Riverpod เป็น state management ตัวหนึ่งที่มีรากฐานการพัฒนามาจาก Provider โดยใช้วิธีการ Dependency Injection มาใช้เพื่อการจัดการกับ State ต่าง ๆ

เช่น จัดการ Authentication Data , Network Request รวมไปถึง Local Storage

จุดเด่นของ Riverpod คือ สามารถ auto dispose ตัวของมันเองได้ โดยการใช้ Riverpod นั้น จะทำให้เรา maintain หรือ ทำการ scale ได้สะดวกยิ่งขึ้น\


Install

ทำการ add dependency ลงใน pubspec.yaml สำหรับการใช้งาน riverpod โดยเราสามารถไปทำตามลิงค์นี้ได้เลย Getting started

flutter pub add flutter_riverpod
flutter pub add riverpod_annotation
flutter pub add dev:riverpod_generator
flutter pub add dev:build_runner
flutter pub add dev:custom_lint
flutter pub add dev:riverpod_lint

add flutter riverpod

เมื่อติดตั้งเสร็จแล้ว เราจะได้

name: riverpod_app
environment:
  sdk: '>=3.2.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  riverpod: ^2.5.1
  flutter_riverpod: ^2.5.1
  riverpod_annotation: ^2.3.5

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^4.0.0
  riverpod_generator: ^2.4.0
  build_runner: ^2.4.9
  custom_lint: ^0.6.4

pubspec.yaml

ถ้าหากมี package แล้วแต่ยังไม่ได้ติดตั้ง ก็สั่งติดตั้งได้เลย

flutter pub get

Install Package

เท่านี้เราก็สามารถใช้งาน riverpod และ สามารถรันสร้างโค้ดด้วย

dart run build_runner watch

run code-generator


Enabling riverpod_lint/custom_lint

ต่อมาเรามาเปิดการใช้งาน riverpod_lint/custom_lint ซึ่งตัวนี้เป็น optional แต่ถ้าหากเราต้องการที่จะเปิดใช้งานมัน ก็สามารถเข้าไปเพิ่มใน analysis_options.yaml

analyzer:
  plugins:
    - custom_lint

analysis_options.yaml

ตัว riverpod_lint จะไม่ได้แสดงบน dart analyze หากเราต้องการตรวจสอบให้สั่งผ่าน terminal ด้วยคำสั่งนี้เลย

dart run custom_lint

run riverpod_lint/custom_lint


Getting Started: Hello world

เริ่มต้นที่แอปไหว้ครู.... hello world นั้นเอง เริ่มต้นมาสร้างหน้า Hello, World! แบบธรรมดากันก่อนเลย

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Example')),
        body: const Center(
          child: Text('Hello, World!'),
        ),
      ),
    );
  }
}

main.dart

เสร็จแล้วก็ลอง run แอพดู

flutter run

run application

ทีนี้เราจะเปลี่ยนจากการแสดงผล Hello, World! แบบ text เป็นการไปเรียกจาก state ผ่าน riverpod

เริ่มต้นด้วยการเพิ่ม method helloWorld ในไฟล์ main.dart กันก่อนเลย

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'main.g.dart';

@riverpod
String helloWorld(HelloWorldRef ref) {
  return 'Hello world';
}

main.dart

ตอนนี้เรามี code แล้ว แต่เรายังไม่มีไฟล์ main.g.dart เพื่อที่จะสร้างไฟล์นี่เราสามารถให้มันสร้างผ่าน build_runner ได้เลย

dart run build_runner watch

run build_runner

ถ้าเราสั่งคำสั่ง build_runner นี่แล้ว มันจะสร้างไฟล์ main.g.dart ขึ้นมาใช้เราแล้ว

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'main.dart';

// **************************************************************************
// RiverpodGenerator
// **************************************************************************

String _$helloWorldHash() => r'8bbe6cff2b7b1f4e1f7be3d1820da793259f7bfc';

/// See also [helloWorld].
@ProviderFor(helloWorld)
final helloWorldProvider = AutoDisposeProvider<String>.internal(
  helloWorld,
  name: r'helloWorldProvider',
  debugGetCreateSourceHash:
      const bool.fromEnvironment('dart.vm.product') ? null : _$helloWorldHash,
  dependencies: null,
  allTransitiveDependencies: null,
);

typedef HelloWorldRef = AutoDisposeProviderRef<String>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

main.g.dart

ถ้าเราเข้าไปดูในไฟล์ main.g.dart เราจะเห็นว่ามีการสร้าง Provider ของ helloWorld มาให้เราแล้ว

Provider Scope

ต่อไปเราจะมากำหนด provider scope ของ riverpod โดยที่เราจะเพิ่มเข้าไปที่ฟังก์ชั่น main()

import 'package:flutter_riverpod/flutter_riverpod.dart';

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

main.dart

Extend ConsumerWidget

ต่อมาเราจะทำการเพิ่มโค๊ดเข้าไปใส่ส่วนของ widget ที่เราต้องการใช้งาน provider กันหน่อย

ConsumerWidget
การที่เราจะทำให้ Widget ของเราสามารถเข้าถึง provider ได้เราจะเป็นจะต้อง extends ส่วนของ ConsumerWidget เข้ามายัง Widget ที่เราต้องการก่อน

สิ่งที่แตกต่างออกไปจาก Stateless และ Stateful คือ function build ของ ConsumerWidget จะมี argument อีกตัวนึงที่ชื่อว่า WidgetRef ref ชึ่งเป็นตัวที่จะใช้สำหรับการอ้างอิง Widget นั้น ๆ ไปยัง provider

ว่าแล้วก็เพิ่ม argument WedgetRef ref เข้าไปในฟังก์ชั่นเลย Widget build(BuildContext context, WidgetRef ref)

Watch & Read
เมื่อเราทำการการสร้าง provider ขึ้นมาแล้ว ถึงเวลาที่เราจะต้องทำการเข้าถึงข้อมูลที่ถูกเก็บอยู่บน provider ซึ่งสามารถเข้าถึงด้วย method read และ watch

โดย...

.watch คือ การสังเกตการเปลี่ยนแปลงของ state อยู่ตลอดเวลาเมื่อ value เกิดการเปลี่ยนแปลงจะทำการ rebuild widget นั้นใหม่ทันที

.read คือ การ access ไปยัง attribute หรือ method ใด ๆ ที่ถูกเก็บอยู่บน provider แต่จะเป็นการเข้าถือเมื่อ .read ถูก action ขึ้นมาเท่านั้น โดยจะไม่มีการ rebuild เมื่อ value มีการเปลี่ยนแปลง

ในตัวอย่าง hello world เราจะใช้แค่ watch อย่ารอช้า... เราจะเข้าไปดึงข้อมูลมาได้โดยผ่านตัวแปร ref.watch และระบุตัวฟังก์ชั่นที่ใช้ helloWorldProvider (ระบุไว้ใน main.g.dart)

class MyApp extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final String value = ref.watch(helloWorldProvider);

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Example')),
        body: Center(
          child: Text(value),
        ),
      ),
    );
  }
}

main.dart

เท่านี้เราก็ได้ code hello word ที่เรียกข้อมูลจาก provider ได้แล้ว

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

part 'main.g.dart';

@riverpod
String helloWorld(HelloWorldRef ref) {
  return 'Hello world2';
}

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

class MyApp extends ConsumerWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final String value = ref.watch(helloWorldProvider);

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Example')),
        body: Center(
          child: Text(value),
        ),
      ),
    );
  }
}

main.dart

เรียบร้อย ลอง run แอพดู

flutter run

run application

แค่นี้... สำหรับการเริ่มต้น ลองใช้งาน riverpod


References:

Getting started | Riverpod
Try Riverpod online