(State Management)
Pemrograman Mobile
Septian Cahyadi, M.Kom. & Muhamad Saad Nurul Ishlah, M.Comp.
Dept. Sistem Informasi & Dept. Ilmu Komputer, Universitas Pakuan
Flutter membangun antarmuka pengguna dengan merefleksikan state saat ini
Flutter membangun antarmuka pengguna dengan merefleksikan state saat ini
Paradigma pemrograman Declarative
Widget
Container
ButtonA
ButtonB
Text
// onClicked Callback
onClicked () {
// Imperative: Mengubah
Text text = getObjectText();
text.setColor("blue");
// Declarative: Mengganti
return TextWidget(
color: blue,
);
}
Text
Widget
Container
ButtonA
ButtonB
Text
// onClicked Callback
onClicked () {
// Imperative: Mengubah
Text text = getObjectText();
text.setColor("blue");
// Declarative: Mengganti
return TextWidget(
color: blue,
);
}
Text
Widget
Container
ButtonA
ButtonB
// onClicked Callback
onClicked () {
// Imperative: Mengubah
Text text = getObjectText();
text.setColor("blue");
// Declarative: Mengganti
return TextWidget(
color: blue,
);
}
Text
Data yang berhubungan dengan Domain aplikasi, seperti TODO List, Cart Items, etc..
int age;
String title;
int counter;
List<Todo> todoList;
bool isActivated;
Contoh
Contoh
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
Membuat subclass InheritedWidget
Akses data dari widget yang hanya membutuhkan
Begitupun untuk mengupdate
https://docs.flutter.dev/development/data-and-backend/state-mgmt/options
InheritedWidget
, artinya bergantung pada pohon widgetProviderNotFoundException
flutter_riverpod
pada pubspec.yaml
ProviderScope
Provider
(opsi 8 jenis providers)
// 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 sebagai objek yang membungkus state dan memungkinkan mendengarkan state tsb.
Deklarasi ini terdiri dari 3 bagian:
// provider that returns a string value
final helloWorldProvider = Provider<String>((ref) {
return 'Hello world';
});
BuildContext
yang dapat digunakan untuk mengakses konteks dalam pohon widget, seperti Theme.of(context)
.ref
untuk membaca stateTerdapat 3 opsi yang dapat digunakan:
ConsumerWidget
Consumer
ConsumerStatefulWidget
& ConsumerState
// Widget Stateless
class HelloWorldWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
/* how to read the provider value here? */,
);
}
}
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
);
}
}
// Widget Stateless
class HelloWorldWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
/* how to read the provider value here? */,
);
}
}
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);
},
);
}
}
Scaffold
├─ AppBar
└─ Consumer
└─ Text
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: Objek yang memungkinkan widget berinteraksi dengan provider di manapun dalam pohon widget maupun di luar dikarenakan sifatnya yang global
BuildContext
memungkinkan kita mengakses widget di atasnya di pohon widget (seperti Theme.of(context)
dan MediaQuery.of(context)
)WidgetRef
memungkinkan mengakses provider apa pun di dalam aplikasi
Terdapat 8 jenis provider yang dapat digunakan:
ref.watch(provider)
untuk mengobservasi state dari provider dalam metode build. Dan membangun ulang ketika state berubahref.read(provider)
untuk membaca state dari provider sekali saja. Dapat berguna dalam initState
atau dalam metode lifecycle lainnyafinal 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++,
);
}
}
Sintaks .notifier
hanya tersedia untuk StateProvider
dan StateNotifierProvider
saja dan berfungsi sebagai berikut:
ref.read(provider.notifier)
pada StateProvider<T>
untuk mengembalikan objek StateController<T>
yang dapat kita gunakan untuk mengubah statusref.read(provider.notifier)
pada StateNotifierProvider<T>
untuk mengembalikan objek StateNotifier<T>
sehingga dapat memanggil metode di atasnyafinal 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++,
);
}
}
Pemrograman Mobile 2
Muhamad Saad Nurul Ishlah, M.Comp.
Dept. Sistem Informasi & Dept. Ilmu Komputer, Universitas Pakuan
Layer:
AsyncNotifier
Kelas model adalah kelas data sederhana dan memiliki persyaratan berikut:
fromJson
dan toJson
).hashCode
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.)
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.
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
Struktur proyek disusun berdasarkan Layer
Arsitektur Riverpod
Presentation
Application
Domain
Data
‣ lib
‣ src
‣ presentation
‣ feature1
‣ feature2
‣ application
‣ feature1
‣ feature2
‣ domain
‣ feature1
‣ feature2
‣ data
‣ feature1
‣ feature2
Struktur proyek disusun berdasarkan Layer
Arsitektur Riverpod
Presentation
Application
Domain
Data
‣ lib
‣ src
‣ presentation
‣ feature1
‣ feature2
‣ feature3 <--
‣ application
‣ feature1
‣ feature2
‣ feature3 <-- only create this when needed
‣ domain
‣ feature1
‣ feature2
‣ feature3 <--
‣ data
‣ feature1
‣ feature2
‣ feature3 <--
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
Struktur proyek disusun berdasarkan Fitur
Arsitektur Riverpod
Feature
Presentation
Application
Domain
Data
‣ lib
‣ src
‣ features
‣ feature1
‣ presentation
‣ application
‣ domain
‣ data
‣ feature2
‣ presentation
‣ application
‣ domain
‣ data
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
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 kebutuhan data
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