Memahami Cara Kerja State Management di Flutter
Author: Sucipto Elnanda
Flutter adalah salah satu framework yang saat ini sangat popular untuk pengembangan aplikasi lintas platform, tidak hanya lintas platform mobile IOS dan Android, framework ini juga mendukung untuk web development bahkan desktop application development, walaupun memang untuk saat ini flutter lebih popular untuk pengembangan cross platform mobile development.
Saya berkenalan dengan framework ini beberapa bulan yang lalu ketika internal tim kami berencana untuk membangun ulang aplikasi mobile salah satu produk dari kantor tempat saya bekerja, hal ini dilakukan untuk mempermudah pengembangan aplikasi kedepannya karena baik IOS dan android akan menggunakan base code yang sama.
Selama mempelajari flutter ini , ada satu hal yang menurut saya agak susah dipahami adalah “State Management”, apalagi untuk tipikal backend programmer, konsep state management ini bukan istilah yang biasa kita gunakan dibackend development, kalau front end developer mungkin sudah biasa, tapi bagi programmer UI pemula atau yang baru belajar front end developer istilah state management agak membingungkan, apalagi ditambah state management ini tidak hanya menggunakan bawaan flutter saja tapi ada banyak eksternal State Management library yang bisa dipakai di Flutter dengan cara kerja yang berbeda2, hal ini menimbulkan pertanyaan kenapa harus pakai external library ? , kenapa tidak hanya cukup dengan State Management bawaan flutter saja ? , hal ini cukup membingungkan.
Menurut website resmi flutter pengertian “State” seperti berikut :
“State, in Flutter, refers to any data that can change during the lifetime of a widget. Unlike properties, the state is not passed down from parent widgets to child widgets. Instead, the state is managed either by the widget itself or its parent widget.”
Sederhananya menurut saya adalah proses perubahan data atau tampilan selama masa aktif widget tanpa harus merebuild ulang satu halaman dan semua komponent pada halaman tersebut, sebagai contoh, satu halaman terdiri dari header, footer , dan content, kalau kita mau merubah background salah satu component header ketika tombol ditekan, tanpa harus merebuild ulang content, footer dan headernya itu sendiri, hal ini disebut dengan “State”. Sementara State Management adalah mekanisme dalam flutter untuk mengatur perubahan2 state yang kita jelaskan tadi.
Selain State Management bawaan Flutter, untuk mengelola state ini juga banyak external library yang popular saat ini seperti Provider, Bloc, Cubit, Redux dan lain lain, tapi pada artikel kali ini kita hanya akan fokus membahas State Management Flutter saja, bagimana cara kerjanya dan best practise penggunaannya. Untuk external library kita akan bahas khuusu di artikel berikutnya setelah kita memahami betul basic state yang ada, karena kalau kita mengimplementasikan state secara serampangan , dalam skala aplikasi besar dan banyak interaksi UI akan mempengaruhi performa aplikasi karena akan sangat boros sumber daya atau resources.
Untuk menjelaskan best practise implementasi State Management pada pada pembahasan kali ini, kita akan membuat sebuah single page aplikasi sederhana yang terdiri dari 3 widget berikut :
1. Widget pertama untuk menampilkan data daftar nama employee, ketika tombol “Add Employee” ditekan akan ditambahkan satu baris baru employee.
2. Widget kedua adalah Absensi Data, ketika tombol update data ditekan, maka data absensi akan diubah.
3. Widget ketiga adalah Data cuti, ketika tombol refresh cuti ditekan, maka juga akan mentrigger perubahan data cuti.
Agar contoh aplikasi kita kali ini terasa mirip real world aplikasi , tidak hanya simple increament counter, untuk masing masing widget diatas saya akan buatkan masing masing satu model dan satu repository untuk ketiganya yang akan kita sebut home repository sebagai data sourcenya untuk semua data.
Employee Data Model
class EmployeeDataModel {
String name;
String alamat;
EmployeeDataModel({required this.name, required this.alamat});
}
Absensi Data Model
class AbsensiDataModel {
String checkin;
String checkout;
AbsensiDataModel({required this.checkin, required this.checkout});
}
Leave Data Model
class LeaveDataModel {
String usedLeave;
String onProgressLeave;
String remainingLeave;
LeaveDataModel(
{required this.usedLeave,
required this.onProgressLeave,
required this.remainingLeave});
}
Home Repository
import 'package:state_sample_project/model/absen_data_model.dart';
import 'package:state_sample_project/model/leave_data_model.dart';
import 'package:state_sample_project/model/employee_data_model.dart';
class HomeRepository {
List<EmployeeDataModel> getDataEmployeeData() {
List<EmployeeDataModel> results = [];
results
.add(EmployeeDataModel(name: "Mr Ahmad Dani", alamat: "Jakarta Pusat"));
results.add(EmployeeDataModel(name: "Mr Once", alamat: "Jakarta Selatam"));
return results;
}
LeaveDataModel getDataLeave() {
LeaveDataModel leave = LeaveDataModel(
usedLeave: "2", onProgressLeave: "2", remainingLeave: "8");
return leave;
}
AbsensiDataModel getDataAbsensi() {
AbsensiDataModel absensi = AbsensiDataModel(checkin: "08:00", checkout: "");
return absensi;
}
}
Percobaan Pertama
OK mari kita lanjut ke bagian implementasi State Managementnya, untuk percobaan pertama kita hanya akan membuat satu widget build saja yang kita beri nama classnya “HomeSingleWidget.dart”. Semua komponen UI yang kita gambarkan diatas akan dibangun dalam satu widget ini, kita hanya akan menambahkan komponen komponen yang akan kita sesuaikan dengan rancangan aplikasi kita diatas, berikut codenya :
import 'package:flutter/material.dart';
import 'package:state_sample_project/model/absen_data_model.dart';
import 'package:state_sample_project/model/leave_data_model.dart';
import 'package:state_sample_project/model/employee_data_model.dart';
import 'package:state_sample_project/repositories/home_repository.dart';
class HomeSingleWidget extends StatefulWidget {
@override
_HomeSingleWidgetState createState() => _HomeSingleWidgetState();
}
class _HomeSingleWidgetState extends State<HomeSingleWidget> {
late List<EmployeeDataModel> employeeList = [];
late AbsensiDataModel? absensiData;
late LeaveDataModel leaveData;
late HomeRepository homeRepository = HomeRepository();
var itemCountEmployeeList = 0;
@override
void initState() {
super.initState();
employeeList = homeRepository.getDataEmployeeData();
absensiData = homeRepository.getDataAbsensi();
leaveData = homeRepository.getDataLeave();
itemCountEmployeeList = employeeList.length;
}
@override
Widget build(BuildContext context) {
print("Build ulang dijalankan");
return Scaffold(
appBar: AppBar(
title: const Text('Home Page Example 1'),
),
body: SingleChildScrollView(
child: Column(
children: [
Card(
elevation: 5, // Controls the shadow depth
margin:
EdgeInsets.all(10), // Controls the spacing around the card
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: Icon(Icons.album),
title: Text('Employee Data'),
),
Container(
height: 150,
child: ListView.builder(
itemCount: itemCountEmployeeList,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: const Icon(Icons.list),
title: Text(employeeList[index].name));
}),
),
ButtonBar(
children: <Widget>[
TextButton(
onPressed: () {
setState(() {
itemCountEmployeeList++;
employeeList.add(EmployeeDataModel(
name: "Employee " +
itemCountEmployeeList.toString(),
alamat: "Bogor"));
});
},
child: Text('Add Employee'),
),
],
),
],
),
),
Card(
elevation: 5, // Controls the shadow depth
margin:
EdgeInsets.all(10), // Controls the spacing around the card
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: Icon(Icons.album),
title: Text('Data Absensi'),
),
Padding(
padding: EdgeInsets.all(5.0),
child: Text(
'Checkin : ' + absensiData!.checkin + ' ',
style: TextStyle(fontSize: 16),
),
),
Padding(
padding: EdgeInsets.all(5.0),
child: Text(
'Checkout : ' + absensiData!.checkout + ' ',
style: TextStyle(fontSize: 16),
),
),
ButtonBar(
children: <Widget>[
TextButton(
onPressed: () {
setState(() {
absensiData = absensiData = AbsensiDataModel(
checkin: "(09:00)", checkout: "Missing");
});
// Add your action here
},
child: Text('Update Data'),
),
],
),
],
),
),
Card(
elevation: 5, // Controls the shadow depth
margin:
EdgeInsets.all(10), // Controls the spacing around the card
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: Icon(Icons.album),
title: Text('Data Cuti'),
),
Padding(
padding: EdgeInsets.all(5.0),
child: Text(
'Used Leave : ' + leaveData!.usedLeave + ' ',
style: TextStyle(fontSize: 16),
),
),
Padding(
padding: EdgeInsets.all(5.0),
child: Text(
'On Progress Leave : ' + leaveData!.onProgressLeave + ' ',
style: TextStyle(fontSize: 16),
),
),
Padding(
padding: EdgeInsets.all(5.0),
child: Text(
'Remaining Leave :' + leaveData!.remainingLeave + ' ',
style: TextStyle(fontSize: 16),
),
),
ButtonBar(
children: <Widget>[
TextButton(
onPressed: () {
setState(() {
leaveData = LeaveDataModel(
usedLeave: "4",
onProgressLeave: "4",
remainingLeave: "4");
});
},
child: Text('Refresh Cuti'),
),
],
),
],
),
),
],
),
),
);
}
}
Kalau diperhatikan , pada code diatas ada beberapa setState :
Ketika initState, tahap ini data2 dari Home Repository data source akan dipanggil untuk ditampilkan ke UI yang sudah kita siapkan. Kedua ketika tombol Add Employee ditekan. Ketiga ketika update Data absensi dan yang terakhir ketika refresh cuti ditekan.
Untuk mengetahui behaviour setstate ini saya sudah menyiapkan simple print code dibagian build widget seperti kode dibawah ini.
Analisis Percobaan Pertama
Berikut console ketika tombol Add employee diklik satu kali.
Berikut console ketika tombol Update data absensi diklik satu kali, terlihat semua komponen dibangun ulang.
Berikut console ketika tombol Refresh cuti diklik satu kali, hal sama juga terlihat sama dengan penjelasan diatas, terlihat semua komponen widget dibangun ulang keseluruhan.
Berikut console ketika add employee diklik berkali2.
Dari hasil Analisa diatas, kita bisa simpulkan kalau pendekatan penggunaan setState dengan cara diatas bisa dianggap kurang tepat karena menyebabkan semua data pada komponen employee, absensi dan leave akan diload berulang2 setiap kali user mengklik tombol2 yang ada di aplikasi kita, tentu jika aplikasi dengan skala besar, implementasi seperti ini sebaiknya dihindari karena akan berdampak ke penurunan performa dari aplikasi.
Implementasi Tipe Kedua
Selanjutnya untuk pendekatan kedua, kita akan memisahkan komponen diatas tadi menjadi 3 widget yang terpisah dari widget parent atau widget utamanya. Kita akan beri nama absensi_widget.dart, employee_widget.dart and leave_widget.dart. setStatenya akan dilakukan dimasing masing widget tidak lagi didalam widget induknya. Untuk detail kodenya bisa didowload dibagian bawah artikel, berikut capture dari widget yang kita jelaskan tadi.
Selanjutnya kita akan panggil masing-masing dari widget diatas ke dalam widget utamanya yang kita beri nama HomeMultipleWidget.dart. Berikut sample code pemanggilan masing2 widget pada page utama :
OK, Mari kita Analisa pendekatan kedua. Berikut tampilan console ketika aplikasi dijalankan pertama kali :
Hal ini sesuai ekpektasi yang kita inginkan, karena ketika pertama kali aplikasi dijalankan semua widget akan dibentuk dan diload datanya dari home repository data source yang sudah kita siapkan.
Selanjutnya kita akan mengklik tombol Add employee.
Terlihat dari tampilan console diatas hanya widget employee yang dibuild ulang, tanpa harus mereload ulang semua widget yang ada
Berikutnya kita akan klik tombol Update data absensi
Juga terlihat hanya widget absensi yang dibuild ulang.
Terakhir kita akan klik tombol Refresh Cuti
Sama seperti 2 capture diatas, hal yang sama juga terjadi di data cuti, ketika tombol refresh cuti diklik, maka state management hanya akan merebuild ulang widget leave.
Kesimpulan:
State Management adalah salah satu bagian krusial dalam membangun aplikasi dengan Flutter, sangat penting memahami cara mengimplementasikan state management ini dengan benar sehingga akan didapat performa aplikasi yang lebih baik dan efesien. Berdasarkan Analisa diatas kita dapat memahami bahwa cara terbaik menerapkan state management adalah menjadikan setiap komponen yang akan dimanipulasi atau yang akan diubah datanya setelah widget terbentuk adalah dengan memisahkan komponen tersebut menjadi widget yang berdiri sendiri. Hal ini sesuai dengan performance best practice seperti yang dijelaskan di website dokumentasi resmi flutter.
https://docs.flutter.dev/perf/best-practices
“When setState() is called on a State object, all descendent widgets rebuild. Therefore, localize the setState() call to the part of the subtree whose UI actually needs to change. Avoid calling setState() high up in the tree if the change is contained to a small part of the tree.”.
Detail code dapat didownload disini :