ช่วงนี้กำลังจะกลับมาปัดฝุ่น เขียน 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_lintadd 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.4pubspec.yaml
ถ้าหากมี package แล้วแต่ยังไม่ได้ติดตั้ง ก็สั่งติดตั้งได้เลย
flutter pub getInstall Package
เท่านี้เราก็สามารถใช้งาน riverpod และ สามารถรันสร้างโค้ดด้วย
dart run build_runner watchrun code-generator
Enabling riverpod_lint/custom_lint
ต่อมาเรามาเปิดการใช้งาน riverpod_lint/custom_lint ซึ่งตัวนี้เป็น optional แต่ถ้าหากเราต้องการที่จะเปิดใช้งานมัน ก็สามารถเข้าไปเพิ่มใน analysis_options.yaml
analyzer:
plugins:
- custom_lintanalysis_options.yaml
ตัว riverpod_lint จะไม่ได้แสดงบน dart analyze หากเราต้องการตรวจสอบให้สั่งผ่าน terminal ด้วยคำสั่งนี้เลย
dart run custom_lintrun 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 runrun 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 watchrun 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 runrun application
แค่นี้... สำหรับการเริ่มต้น ลองใช้งาน riverpod
References:
