Menerjemahkan wireframe menjadi kode dilakukan bertahap, bukan sekaligus, sehingga setiap keputusannya dapat direncanakan. Topik ini menjabarkan tiga wireframe aplikasi catatan menjadi rencana pohon widget, menetapkan alur navigasi antarlayar dengan go_router beserta guard sederhananya, lalu memetakan data dan menentukan letak setiap state. Hasilnya adalah cetak biru yang digunakan topik implementasi apa adanya, sehingga penulisan kodenya tinggal mengikuti keputusan yang sudah dibuat.
Membaca wireframe menjadi rencana pohon widget#
Wireframe adalah sketsa kasar satu layar yang menunjukkan penempatan elemennya tanpa detail visual seperti warna dan gaya huruf. Sebelum kode ditulis, setiap wireframe diterjemahkan menjadi rencana pohon widget, yaitu daftar widget utama beserta sumber datanya. Kebiasaan ini memperlihatkan biaya setiap layar sejak awal, karena layar yang membutuhkan terlalu banyak state memaksa desainnya disederhanakan, bukan diperbaiki saat kode setengah jadi.
Rencana untuk ketiga layar aplikasi catatan adalah sebagai berikut.
- Layar masuk berupa
ScaffoldberisiColumndenganTextFormFieldnama pengguna,TextFormFieldsandi yang menyembunyikan ketikannya, tombol Masuk, sertaTextpesan yang tampil saat autentikasi gagal. - Layar daftar berupa
ScaffolddenganAppBar, body berupaFutureBuilderyang menunggu pemuatan data lalu menampilkanListView.builderberisiListTileuntuk setiap catatan, danFloatingActionButtonyang membuka layar ubah untuk catatan baru. - Layar ubah berupa
ScaffolddenganFormberisiTextFormFieldjudul dan isi, sedangkan aksi Batal dan Simpan ditempatkan padaAppBar.
Struktur di atas kecil, tetapi dua kesimpulan penting sudah dapat diambil darinya. Ketiga layar hanya membutuhkan masukan teks melalui TextFormField, sehingga satu pola validasi dari pembahasan form mencakup semuanya. Tidak ada layar yang menyimpan daftar objek sebagai state lokal, dan hal itu konsisten dengan keputusan di bagian akhir topik ini bahwa daftar catatan disimpan pada kelas state tersendiri.
Alur navigasi dan path#
Alur navigasi dinyatakan deklaratif dengan go_router sebagaimana pembahasan navigasi. Ketiga layar mendapat tiga path, yaitu /masuk untuk layar masuk, / untuk layar daftar, dan /ubah/:id untuk layar ubah. Layar daftar sengaja diletakkan di path akar karena layar itu paling lama ditampilkan, sedangkan parameter :id menampung id catatan yang dibuka dengan nilai khusus baru untuk catatan yang belum ada. Dengan nilai khusus itu, satu layar ubah melayani tambah dan ubah sekaligus.
// tidak-dijalankan
final _router = GoRouter(
routes: [
GoRoute(
path: '/masuk',
builder: (context, state) => const LayarMasuk(),
),
GoRoute(
path: '/',
builder: (context, state) => const LayarDaftar(),
),
GoRoute(
path: '/ubah/:id',
builder: (context, state) =>
LayarUbah(id: state.pathParameters['id']!),
),
],
);Diagram berikut merangkum alur antarlayar beserta cabang keluarnya, mulai dari layar masuk yang diarahkan ke daftar apabila sandi yang dimasukkan cocok, layar daftar yang membuka layar ubah ketika satu catatan disentuh, hingga dua jalan kembali ke daftar melalui Simpan atau Batal.
Cabang Simpan dan Batal sama-sama berakhir di layar daftar, tetapi pekerjaan yang dilakukan sebelum perpindahan berbeda. Simpan menulis data melalui kelas state terlebih dahulu, kemudian berpindah dengan context.go('/') agar tumpukan navigasi kembali bersih. Batal tidak mengubah data sama sekali dan cukup menutup layar ubah, sebab layar itu dibuka dengan context.push dari layar daftar.
Guard sederhana saat belum masuk#
Guard adalah pemeriksaan yang dijalankan sebelum layar tujuan dibangun, dan go_router menyediakannya melalui callback redirect. Fungsi ini menerima keadaan navigasi, lalu mengembalikan path pengganti apabila akses harus dialihkan, atau null apabila navigasi boleh dilanjutkan. Untuk aplikasi catatan, dua aturan sudah cukup, yaitu pengguna yang belum masuk dialihkan ke /masuk, dan pengguna yang sudah masuk tidak pernah kembali ke /masuk.
// tidak-dijalankan
redirect: (context, state) {
final sesi = context.read<SesiStore>();
final menujuMasuk = state.matchedLocation == '/masuk';
if (!sesi.sudahMasuk && !menujuMasuk) {
return '/masuk';
}
if (sesi.sudahMasuk && menujuMasuk) {
return '/';
}
return null;
},Callback redirect dievaluasi ulang pada setiap perpindahan, sehingga keberhasilan masuk yang diikuti context.go('/') langsung melewati guard dengan status sesi yang sudah diperbarui. Perlu dicatat bahwa guard semacam ini mengatur tampilan layar, bukan keamanan data. API tetap wajib menolak permintaan yang membawa token tidak sah, sebab aplikasi dapat dimodifikasi pihak lain dan guard di dalamnya bukan penghalang yang serius.
Peta data dan letak state#
Model Catatan dengan tiga kolom, yaitu id, judul, dan isi, menjadi bentuk data yang sama bagi seluruh aplikasi. Datanya datang dari dua sumber, yaitu penyimpanan lokal shared_preferences yang selalu tersedia dan API yang menjadi sumber kebenaran lintas perangkat. Urutan pemuatannya ditetapkan sekarang, yaitu data lokal ditampilkan terlebih dahulu agar layar tidak pernah kosong tanpa alasan, kemudian data dari API menggantikannya apabila jaringan tersedia.
Setiap lingkup state mendapat satu kelas ChangeNotifier sendiri, dan aplikasi ini membutuhkan dua lingkup. SesiStore menangani identitas, yaitu token atau kunci sesi yang diberikan server setelah autentikasi berhasil, beserta status sudah masuk atau belum. CatatanStore menangani isi, yaitu daftar catatan beserta operasi tambah dan ubahnya. Keduanya dipisah karena widget pelanggannya berbeda. Layar masuk hanya bergantung pada sesi, sedangkan layar daftar hanya bergantung pada catatan, sehingga perubahan pada satu lingkup tidak memicu rebuild pada widget milik lingkup yang lain.
Token tidak disimpan di dalam CatatanStore. Kelas itu menerimanya sebagai argumen pada setiap operasi jaringan, sehingga daftar catatan tidak terikat pada identitas pemiliknya dan pengujian store tidak perlu menyiapkan sesi. Pemetaan data dengan pembagian state ini menjadi kontrak yang diturunkan kepada topik implementasi, dan seluruh namanya tetap sama saat kodenya ditulis.
Kesalahan umum#
- Merinci pohon widget terlalu jauh sebelum alur disepakati. Rencana yang memuat properti kecil setiap widget menjadi kaku dan cepat usang, sedangkan yang dibutuhkan hanya struktur utama beserta sumber datanya. Perbaikannya, berhentilah pada widget penentu seperti
Scaffold,Form, danListView. - Melupakan kondisi kosong pada wireframe layar daftar. Sketsa yang hanya menggambar daftar terisi membuat keadaan tanpa catatan terlambat dipikirkan, padahal tampilannya berbeda dari keadaan gagal memuat. Perbaikannya, tambahkan pesan seperti
Belum ada catatansejak rencana dibuat. - Menumpuk dua lingkup state menjadi satu kelas besar. Kelas yang memegang sesi sekaligus daftar catatan memicu rebuild meluas dan sulit diuji terpisah. Perbaikannya, pisahkan menjadi satu kelas
ChangeNotifieruntuk setiap lingkup sebagaimana rancangan di atas.
Ringkasan#
- Tiga wireframe diterjemahkan menjadi rencana pohon widget yang menetapkan widget utama dan sumber data setiap layar sebelum kode ditulis.
- Navigasi dinyatakan deklaratif dengan tiga path, yaitu
/masuk,/, dan/ubah/:id, dan nilaibarupada parameter:idmembuat satu layar ubah melayani tambah dan ubah. - Callback
redirectpada go_router berperan sebagai guard yang mengalihkan pengguna yang belum masuk, sedangkan keamanan data tetap menjadi tanggung jawab API. - Data mengalir dari dua sumber dengan urutan lokal lebih dahulu, dan setiap lingkup state memiliki satu kelas
ChangeNotifier, yaituSesiStoreuntuk identitas danCatatanStoreuntuk isi.
Referensi#
- Navigation and routing — Flutter — Panduan resmi navigasi Flutter yang mencakup routing deklaratif dan deep link.
- Simple app state management — Flutter — Panduan resmi menempatkan state aplikasi pada kelas terpisah dengan ChangeNotifier.
- go_router — pub.dev — Halaman package go_router yang memuat contoh konfigurasi GoRoute dan redirect.