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

  1. Tambahkan dependecy flutter_riverpod pada pubspec.yaml
  2. Bungkus widget utama dengan ProviderScope
  3. Buat Provider (opsi 8 jenis providers)
  4. 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, seperti Theme.of(context).
  • Namun Provider di Riverpod bertempat di luar pohon widget, gunakan objek ref untuk membaca state

Terdapat 3 opsi yang dapat digunakan:

  1. Menggunakan ConsumerWidget
  2. Menggunakan Consumer
  3. 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 (seperti Theme.of(context)dan MediaQuery.of(context))
  • WidgetRef memungkinkan mengakses provider apa pun di dalam aplikasi

Jenis Provider pada Riverpod

Terdapat 8 jenis provider yang dapat digunakan:

  1. Provider
  2. StateProvider (legacy)
  3. StateNotifierProvider (legacy)
  4. FutureProvider
  5. StreamProvider
  6. ChangeNotifierProvider (legacy)
  7. NotifierProvider (baru)
  8. 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 dalam initState 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) pada StateProvider<T>untuk mengembalikan objek StateController<T> yang dapat kita gunakan untuk mengubah status
  • panggil ref.read(provider.notifier) pada StateNotifierProvider<T> untuk mengembalikan objek StateNotifier<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

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

Beberapa arsitektur populer untuk Flutter:

  • Bloc - Bloc library
    • Opionated - pakem khusus
    • Aturan yang ketat untuk menyusun aplikasi Flutter
  • Stacked - Stacked
    • terinspirasi MVVM
  • Keduanya bergantung pada Provider
  • Riverpod - library Riverpod

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.

 

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 dan toJson).
    • 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 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

Penyusunan Share Code

  • Share code: kode yang dipakai bersama kode lain
    • share, common, utils
  • Simpan pada direktori di level atas

Diskusi

Ada pertanyaan?

Referensi

Terima Kasih

Manajemen State Menggunakan Riverpod

By M. Saad Nurul Ishlah

Manajemen State Menggunakan Riverpod

  • 464