Manajemen State Menggunakan Riverpod
(State Management)
Pemrograman Mobile
Septian Cahyadi, M.Kom. & Muhamad Saad Nurul Ishlah, M.Comp.
Dept. Sistem Informasi & Dept. Ilmu Komputer, Universitas Pakuan
Agenda Kuliah
- Review Manajemen State
- Manajemen State dengan Riverpod
- Menggunakan Riverpod
- Riverpod Provider
- Membuat dan Membaca Provider
- WidgetRef
- Jenis Providers di Riverpod
- rev.watch vs ref.read
State Management
(Review)
Flutter membangun antarmuka pengguna dengan merefleksikan state saat ini
Flutter membangun antarmuka pengguna dengan merefleksikan state saat ini
Paradigma pemrograman Declarative
Declarative
Widget
Container
ButtonA
ButtonB
Text
// onClicked Callback
onClicked () {
// Imperative: Mengubah
Text text = getObjectText();
text.setColor("blue");
// Declarative: Mengganti
return TextWidget(
color: blue,
);
}
Text
Declarative
Widget
Container
ButtonA
ButtonB
Text
// onClicked Callback
onClicked () {
// Imperative: Mengubah
Text text = getObjectText();
text.setColor("blue");
// Declarative: Mengganti
return TextWidget(
color: blue,
);
}
Text
Declarative
Widget
Container
ButtonA
ButtonB
// onClicked Callback
onClicked () {
// Imperative: Mengubah
Text text = getObjectText();
text.setColor("blue");
// Declarative: Mengganti
return TextWidget(
color: blue,
);
}
Text
State?
State == Data
Data yang berhubungan dengan Domain aplikasi, seperti TODO List, Cart Items, etc..
int age;
String title;
int counter;
List<Todo> todoList;
bool isActivated;
State
- Arti luas: state adalah segala sesuatu yang ada di memori saat aplikasi berjalan.
- aset aplikasi,
- semua variabel yang disimpan framework Flutter tentang UI,
- status animasi, tekstur, font
- dll
- Tidak semua kita kelola
- Data apa pun yang dibutuhkan untuk membangun kembali UI kapanpun.
State Management
==
Data Management
Ephemeral vs App State
Ephemeral State
- UI state atau locale state
- Widget lain jarang membutuhkan state ini
- Tidak berubah secara kompleks
- Dapat dikelola dengan kelas State & metode setState dalam StatefulWidget
App State
- Shared state
- State level aplikasi
- Biasanya digunakan oleh banyak widget
- Banyak opsi untuk mengelola, contoh: InheritedWidget, provider, riverpod, mobx, redux, dll
Ephemeral State
Contoh
- Halaman saat ini pada PageView
- Progress Animasi
- Tab yang terpilih
App State
Contoh
- User preferences
- Login info
- Notification
- Shopping cart
- Read/unread state dari artikel di aplikasi News
Membedakan State?
Manajemen State
(Application State)
Mengelola Data melalui Widget Tree
-
Memberikan Data dari Widget atas ke widget bawah (Lifting State Up)
-
Widget yang tidak menggunakan Data, mendapat data
-
Bisa sangat berantakan, update data. callback hell
Mengelola Data melalui InheritedWidget
-
Membuat subclass InheritedWidget
-
Akses data dari widget yang hanya membutuhkan
-
Begitupun untuk mengupdate
Opsi Pengelolaan State Lain
- Provider
- Riverpod
- Redux
- Fish-REdux
- BLoC / Rx
- GetIt
- MobX
- dll
https://docs.flutter.dev/development/data-and-backend/state-mgmt/options
Manajemen State
Menggunakan Riverpod
Riverpod
- Framework caching dan data-binding yang reaktif
- Versi evolusi dari Provider (Complete Rewrite)
Kelebihan Riverpod
- Dapat menangkap kesalahan pemrograman pada waktu kompilasi, bukan saat runtime
- Dapat dengan mudah mengambil , menyimpan , dan memperbarui data dari sumber jarak jauh
- Dapat melakukan caching reaktif dan memperbarui UI dengan mudah
- Bergantung pada keadaan asinkron atau terkomputasi
- Dapat membuat, gunakan, dan gabungkan provider dengan kode boilerplate secara minimum
- Membuang state dari provider saat tidak lagi digunakan
- Membantu menulis kode yang dapat diuji dan memisahkan logika di luar pohon widget
Mengapa Riverpod?
- Provider memanfaatkan
InheritedWidget
, artinya bergantung pada pohon widget - Dapat mengarah pada isu
ProviderNotFoundException
- Riverpod compile-safe, provider didefinisikan secara global, dapat diakses di manapun
- Provider untuk menyimpan state dan logika bisnis di luar pohon widget
- Bangun ulang provider dan widget ketika diperlukan secara reaktif
Menggunakan Riverpod
Instalasi Riverpod
- Tambahkan dependecy
flutter_riverpod
padapubspec.yaml
- Bungkus widget utama dengan
ProviderScope
- Buat
Provider
(opsi 8 jenis providers) - Konsumsi Provider pada widget untuk melakukan baca, tulis, update, dll
// 1. Tambah dependency
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.0.2
// 2. Bungkus widget utama dengan ProviderScope
void main() {
// wrap the entire app with a ProviderScope so that widgets
// will be able to read providers
runApp(ProviderScope(
child: MyApp(),
));
}
// 3. Buat Provider
// provider that returns a string value
final helloWorldProvider = Provider<String>((ref) {
return 'Hello world';
});
// 4. Konsumsi/gunakan provider pada widget
class HelloWorldWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final helloWorld = ref.watch(helloWorldProvider);
return Text(helloWorld);
}
}
Provider pada Riverpod
Provider sebagai objek yang membungkus state dan memungkinkan mendengarkan state tsb.
- Sepenuhnya menggantikan pola desain seperti singleton, service locators, dependency injection, dan InheritedWidgets .
- Memungkinkan untuk menyimpan beberapa state dan dengan mudah mengaksesnya di beberapa lokasi.
- Memungkinkan untuk mengoptimalkan kinerja dengan memfilter pembuatan ulang widget atau men-cache state dengan komputasi yg mahal.
- Membuat kode lebih dapat diuji, karena setiap provide dapat diganti untuk berperilaku berbeda selama pengujian.
Membuat Provider
Deklarasi ini terdiri dari 3 bagian:
- Deklarasi variable global, digunakan untuk membaca state dari provider
- Isi variable dengan tipe Provider yang digunakan
- Fungsi yang membuat state. menyediakan parameter ref untuk membaca provider lain, menambahkan logic, dll.
// provider that returns a string value
final helloWorldProvider = Provider<String>((ref) {
return 'Hello world';
});
Mengkonsumsi Provider
- Widget di Flutter memiliki objek
BuildContext
yang dapat digunakan untuk mengakses konteks dalam pohon widget, sepertiTheme.of(context)
. - Namun Provider di Riverpod bertempat di luar pohon widget, gunakan objek
ref
untuk membaca state
Terdapat 3 opsi yang dapat digunakan:
- Menggunakan
ConsumerWidget
- Menggunakan
Consumer
- Menggunakan
ConsumerStatefulWidget
&ConsumerState
ConsumerWidget
// Widget Stateless
class HelloWorldWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
/* how to read the provider value here? */,
);
}
}
ConsumerWidget
final helloWorldProvider = Provider<String>((_) => 'Hello world');
// 1. Widget Stateless diganti ConsumerWidget
class HelloWorldWidget extends ConsumerWidget {
@override
// 2. build method has an extra [WidgetRef] argument
Widget build(BuildContext context, WidgetRef ref) {
// 3. use ref.watch() to get the value of the provider
final helloWorld = ref.watch(helloWorldProvider);
return Text(
/* gunakan helloWorld di sini */
helloWorld
);
}
}
Consumer
// Widget Stateless
class HelloWorldWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
/* how to read the provider value here? */,
);
}
}
Consumer
final helloWorldProvider = Provider<String>((_) => 'Hello world');
class HelloWorldWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 1. Add a Consumer
return Consumer(
// 2. specify the builder and obtain a WidgetRef
builder: (_, WidgetRef ref, __) {
// 3. use ref.watch() to get the value of the provider
final helloWorld = ref.watch(helloWorldProvider);
return Text(helloWorld);
},
);
}
}
Consumer
Scaffold
├─ AppBar
└─ Consumer
└─ Text
ConsumerStatefulWidget & ConsumerState
final helloWorldProvider = Provider<String>((_) => 'Hello world');
// 1. extend [ConsumerStatefulWidget]
class HelloWorldWidget extends ConsumerStatefulWidget {
@override
ConsumerState<HelloWorldWidget> createState() => _HelloWorldWidgetState();
}
// 2. extend [ConsumerState]
class _HelloWorldWidgetState extends ConsumerState<HelloWorldWidget> {
@override
void initState() {
super.initState();
// 3. if needed, we can read the provider inside initState
final helloWorld = ref.read(helloWorldProvider);
print(helloWorld); // "Hello world"
}
@override
Widget build(BuildContext context) {
// 4. use ref.watch() to get the value of the provider
final helloWorld = ref.watch(helloWorldProvider);
return Text(helloWorld);
}
}
WidgetRef
WidgetRef: Objek yang memungkinkan widget berinteraksi dengan provider di manapun dalam pohon widget maupun di luar dikarenakan sifatnya yang global
- Sebagai argumen yang dapat dipakai ketika menggunakan Consumer, ConsumerWidget.
- Sebagai property ketika membuat subclass dari ConsumerState
BuildContext dan WidgetRef
-
BuildContext
memungkinkan kita mengakses widget di atasnya di pohon widget (sepertiTheme.of(context)
danMediaQuery.of(context)
) -
WidgetRef
memungkinkan mengakses provider apa pun di dalam aplikasi
Jenis Provider pada Riverpod
Terdapat 8 jenis provider yang dapat digunakan:
- Provider
- StateProvider (legacy)
- StateNotifierProvider (legacy)
- FutureProvider
- StreamProvider
- ChangeNotifierProvider (legacy)
- NotifierProvider (baru)
- AsyncNotifierProvider (baru)
ref.watch vs ref.read
- Gunakan
ref.watch(provider)
untuk mengobservasi state dari provider dalam metode build. Dan membangun ulang ketika state berubah - Gunakan
ref.read(provider)
untuk membaca state dari provider sekali saja. Dapat berguna dalaminitState
atau dalam metode lifecycle lainnya
ref.watch vs ref.read
final counterStateProvider = StateProvider<int>((_) => 0);
class CounterWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 1. watch the provider and rebuild when the value changes
final counter = ref.watch(counterStateProvider);
return ElevatedButton(
// 2. use the value
child: Text('Value: $counter'),
// 3. change the state inside a button callback
onPressed: () => ref.read(counterStateProvider.notifier).state++,
);
}
}
ref.watch vs ref.read
Sintaks .notifier
hanya tersedia untuk StateProvider
dan StateNotifierProvider
saja dan berfungsi sebagai berikut:
- panggil
ref.read(provider.notifier)
padaStateProvider<T>
untuk mengembalikan objekStateController<T>
yang dapat kita gunakan untuk mengubah status - panggil
ref.read(provider.notifier)
padaStateNotifierProvider<T>
untuk mengembalikan objekStateNotifier<T>
sehingga dapat memanggil metode di atasnya
ref.listen
final counterStateProvider = StateProvider<int>((_) => 0);
class CounterWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// if we use a StateProvider<T>, the type of the previous and current
// values is StateController<T>
ref.listen<StateController<int>>(counterStateProvider.state, (previous, current) {
// note: this callback executes when the provider value changes,
// not when the build method is called
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Value is ${current.state}')),
);
});
// watch the provider and rebuild when the value changes
final counter = ref.watch(counterStateProvider);
return ElevatedButton(
// use the value
child: Text('Value: $counter'),
// change the state inside a button callback
onPressed: () => ref.read(counterStateProvider.notifier).state++,
);
}
}
Diskusi
Ada pertanyaan?
Referensi
- State Management. https://docs.flutter.dev/development/data-and-backend/state-mgmt/intro. diakses: 25 Mei 2022
- Inherited Widgets Explained - Flutter Widgets 101 Ep. 3. Nov 2018. Google Developers. https://www.youtube.com/watch?v=Zbm3hjPjQMk
- Tutorial Flutter – State Management Menggunakan Provider (Bagian 1). Des 2021. Muhamad Ishlah. https://www.youtube.com/watch?v=dFJK3NN-L0k
- Tutorial Flutter – State Management Menggunakan Provider (Bagian 2). Des 2021. Muhamad Ishlah. https://www.youtube.com/watch?v=_XCSeHozJjM
- Flutter Riverpod 2.0: The Ultimate Guide. Oct 2022. Andrea. https://codewithandrea.com/articles/flutter-state-management-riverpod/
Terima Kasih
Arsitektur Aplikasi Flutter
Pemrograman Mobile 2
Muhamad Saad Nurul Ishlah, M.Comp.
Dept. Sistem Informasi & Dept. Ilmu Komputer, Universitas Pakuan
Agenda Kuliah
- Arsitektur Aplikasi Flutter
- Perbandingan Arsitektur Aplikasi
- Penyusunan Struktur Proyek
Arsitektur Aplikasi Flutter
Arsitektur Aplikasi
- Sekumpulun aturan dan prinsip yang membantu merancang sistem perangkat lunak yang kompleks
- Pemilihan arsitektur aplikasi yang tepat sangatlah penting
- Memungkinkan untuk menyusun struktur kode dan mendukung pengembangan selanjutnya secara terorganisir
- Menangani kerumitan proses development
- Tanpa arsitektur menciptakan kurangnya organisi kode
- Penggunaan yang berlebih mengarah pada rekayasa berlebih, membuatnya sulit untuk melakukan perubahan
Arsitektur Aplikasi yang Ada
- Beberapa arsitektur populer:
- MVC,
- MVP,
- MVVM,
- Clean Architecture
- Tidak dibuat khusus untuk Flutter
- Percobaan dilakukan untuk digunakan pada Flutter
Arsitektur Aplikasi Flutter
Arsitektur Aplikasi Riverpod
Layer:
- Presentation Layer
- Widget > State > Controller
- Application Layer
- Service
- Domain Layer
- Models
- Data Layer
- Repositories > DTOs > Data Sources
The Presentation Layer
- Sering disebut lapisan UI
- Peran UI adalah untuk menampilkan data aplikasi di layar dan juga berfungsi sebagai titik utama interaksi pengguna. Setiap kali data berubah, baik karena interaksi pengguna (seperti menekan tombol) atau masukan eksternal (seperti respons jaringan), UI harus diperbarui untuk mencerminkan perubahan tersebut. Secara efektif, UI adalah representasi visual dari status aplikasi yang diambil dari lapisan data.
The Presentation Layer
- Widget, yaitu representasi data yang akan ditampilkan di layar.
-
Pengontrol, yang melakukan mutasi data asinkron dan mengelola status widget.
- biasanya direpresentasikan sebagai subkelas
AsyncNotifier
- biasanya direpresentasikan sebagai subkelas
The Domain Layer
- Peran utama lapisan domain adalah untuk menentukan kelas model khusus aplikasi yang mewakili data yang berasal dari lapisan data.
-
Kelas model adalah kelas data sederhana dan memiliki persyaratan berikut:
- Mereka selalu tidak dapat diubah.
- Mereka berisi logika serialisasi (seperti metode
fromJson
dantoJson
). - Mereka mengimplementasikan operator == dan metode
hashCode
- kelas model dapat diimpor dan digunakan di mana pun di aplikasi Anda (widget, pengontrol, layanan, repositori).
The Domain Layer
The Data Layer
- Sumber Data , yaitu API pihak ketiga yang digunakan untuk berkomunikasi dengan dunia luar (misalnya database jarak jauh, klien REST API, sistem pemberitahuan push, antarmuka Bluetooth).
- Objek Transfer Data (atau DTO), yang dikembalikan oleh sumber data. DTO sering kali direpresentasikan sebagai data tidak terstruktur (seperti JSON) saat mengirim data melalui jaringan
- Repositori , yang digunakan untuk mengakses DTO dari berbagai sumber, seperti API backend, dan menjadikannya tersedia sebagai kelas model yang aman untuk tipe (alias entitas) ke seluruh aplikasi.
The Application Layer
Perbandingan
Arsitektur Umum
Clean Architecture
- Dikenalkan oleh Robert C. Martin
- Independen thd UI
- Tidak memperhitungkan aliran data searah (unidirectional data flow) dari sumber data ke UI
Rivepod vs Clean Architecture
MVC (Model View Controller)
- Design Pattern
- Model: mengelola data, logika, dan aturan aplikasi
- View: komponen UI
- Controller: menerima input dan mengubahnya menjadi perintah untuk model atau view
Rivepod vs MVC
MVVM (Model View ViewModel)
- Memisahkan logika bisnis dan presentasi dari UI
- View tidak dapat mengakses Model secara langsung
- ViewModel menangani input dan mengubah data dari Model
- ViewModel terhubung ke View melalui data binding (pengikat data) yang menggunakan pola observer
Rivepod vs MVC
Riverpod vs Arsitektur Bloc
- Presentasi (UI) → Widget
- Logika Bisnis (Blok) → Pengontrol & Layanan
- Data (Repositori) → Repositori
- Data (Penyedia Data) → Sumber Data
Riverpod vs Arsitektur Bloc
-
Blok dapat bergantung pada blok lain untuk bereaksi terhadap perubahan statusnya,
-
Riverpod memiliki perbedaan yang lebih jelas antara pengontrol (yang mengelola status widget ) dan layanan (yang mengelola status aplikasi ).
-
Saat menggunakan Bloc, perubahan status selalu direpresentasikan sebagai aliran data. Tapi Riverpod memungkinkan untuk mengobservasi berbagai jenis data (Streams, Futures, StateNotifiers, ChangeNotifiers, dll.)
Rivepod vs Arsitektur Aplikasi Android
Arsitektur Populer Lainnya
-
Arsitektur aplikasi didasarkan pada abstraksi yang diimplementasikan menggunakan design pattern, bukan API. Hasilnya, kita dapat membuat arsitektur aplikasi kita sendiri di atas library state management populer lainnya seperti redux , MobX , dll.
-
Kita bahkan dapat menggunakan apa yang langsung diberikan Flutter ( InheritedWidget, ChangeNotifier, ValueListenableBuilder, dll).
-
Pada akhirnya, yang paling penting adalah menentukan kontrak dan batasan yang jelas pada aplikasi.
Struktur Proyek
Struktur Proyek
-
Saat membangun aplikasi Flutter besar, setelah menentukan arsitektur aplikasi yang digunakan adalah bagaimana menyusun proyek kita.
-
Hal ini untuk memastikan bahwa seluruh tim dapat mengikuti konvensi yang jelas dan menambahkan fitur secara konsisten.
-
Tidak berlaku untuk aplikasi yang sederhana. Misal 1 halaman
-
Pendekatar penyusunan proyek:
-
Layer-first
-
Feature-first
-
Pendekatan Layer-first
-
Struktur proyek disusun berdasarkan Layer
-
Arsitektur Riverpod
-
Presentation
-
Application
-
Domain
-
Data
-
- Fitur ditempatkan ke dalam direktori layer
‣ lib
‣ src
‣ presentation
‣ feature1
‣ feature2
‣ application
‣ feature1
‣ feature2
‣ domain
‣ feature1
‣ feature2
‣ data
‣ feature1
‣ feature2
Pendekatan Layer-first
-
Struktur proyek disusun berdasarkan Layer
-
Arsitektur Riverpod
-
Presentation
-
Application
-
Domain
-
Data
-
- Fitur ditempatkan ke dalam direktori layer
‣ lib
‣ src
‣ presentation
‣ feature1
‣ feature2
‣ feature3 <--
‣ application
‣ feature1
‣ feature2
‣ feature3 <-- only create this when needed
‣ domain
‣ feature1
‣ feature2
‣ feature3 <--
‣ data
‣ feature1
‣ feature2
‣ feature3 <--
Kekurangan Pendekatan Layer-first
-
Mudah digunakan, tapi tidak scalable ketika aplikasi berkembang
-
Untuk sebuah fitur, penempatan file berbeda jauh sesuai layernya.
-
sulit untuk mengerjakan fitur individu, harus loncat direktori antar-layer
-
jika fitur dihapus, kemungkinan terdapat file yang lupa terhapus
-
-
Baik untuk aplikasi sedang/besar
Pendekatan Feature-first
-
Struktur proyek disusun berdasarkan Fitur
-
Arsitektur Riverpod
-
Feature
-
Presentation
-
Application
-
Domain
-
Data
-
-
- Layer ditempatkan ke dalam direktori Fitur
‣ lib
‣ src
‣ features
‣ feature1
‣ presentation
‣ application
‣ domain
‣ data
‣ feature2
‣ presentation
‣ application
‣ domain
‣ data
Pendekatan Feature-first
-
Untuk mengembangkan fitur baru, bisa fokus ke 1 direktori
-
Ketika ingin menghapus fitur, cukup hapus direktorinya (jika ada test, termasuk folder di tests)
‣ lib
‣ src
‣ features
‣ feature1
‣ presentation
‣ application
‣ domain
‣ data
‣ feature2
‣ presentation
‣ application
‣ domain
‣ data
Pendekatan Feature-first
Menyusun struktur berdasarkan fitur
-
Mulai dari lapisan domain dan identifikasi kelas model dan logika bisnis untuk memanipulasinya
-
buat folder untuk setiap model (atau grup model) yang dimiliki bersama
-
di dalam folder itu, buat sub-folder
presentation
,application
,domain
, sesuai kebutuhandata
-
di dalam setiap sub-folder, tambahkan semua file yang Anda butuhkan
‣ lib
‣ src
‣ common_widgets
‣ constants
‣ exceptions
‣ features
‣ address
‣ authentication
‣ cart
‣ checkout
‣ orders
‣ products
‣ reviews
‣ localization
‣ routing
‣ utils
Penyusunan Share Code
- Share code: kode yang dipakai bersama kode lain
- share, common, utils
- Simpan pada direktori di level atas
Diskusi
Ada pertanyaan?
Referensi
- Flutter App Architecture with Riverpod: An Introduction. https://codewithandrea.com/articles/flutter-app-architecture-riverpod-introduction/
- Flutter Project Structure: Feature-first or Layer-first?. https://codewithandrea.com/articles/flutter-project-structure/
Terima Kasih
Manajemen State Menggunakan Riverpod
By M. Saad Nurul Ishlah
Manajemen State Menggunakan Riverpod
- 464