Back to IF3210 Pengembangan Aplikasi Piranti Bergerak

Flutter Widgets and App Architecture

Questions/Cues

  • Apa perbedaan utama StatelessWidget dan StatefulWidget, dan kapan menggunakan masing-masing?
  • Mengapa Flutter menggunakan composition over inheritance untuk membangun UI?
  • Apa itu state lifting dan mengapa diperlukan dalam arsitektur Flutter?
  • Bagaimana switch expression, spread, dan if/for dalam collection literal menyederhanakan kode?
  • Apa peran Flutter Inspector dalam proses debugging UI?

Reference Points

  • IF3210 Pengembangan Aplikasi Piranti Bergerak (Slides 13a: 30-32, 13b: 24-39)

Struktur Proyek Flutter dan Entry Point

Proyek Flutter memiliki struktur direktori yang terorganisir: folder lib/ berisi seluruh kode Dart, dengan lib/main.dart sebagai entry point aplikasi. Fungsi main() memanggil runApp() yang menerima widget root. Widget root biasanya adalah MaterialApp yang menyediakan tema Material Design, routing, dan konfigurasi aplikasi secara global.

Setiap widget mendefinisikan UI-nya melalui method build() yang mengembalikan widget tree. Method ini dipanggil oleh framework Flutter setiap kali widget perlu dirender ulang. Prinsip utamanya: UI adalah fungsi dari state — tampilan ditentukan sepenuhnya oleh data, bukan oleh mutasi langsung elemen DOM seperti di web tradisional.

StatelessWidget vs StatefulWidget

Flutter membagi widget menjadi dua kategori berdasarkan kemampuan mengelola state:

flowchart TD
    A["Widget baru dibutuhkan"] --> B{"Apakah UI perlu</br>berubah berdasarkan</br>data internal?"}
    B -->|"Tidak"| C["StatelessWidget"]
    B -->|"Ya"| D["StatefulWidget"]
    C --> E["build() dipanggil</br>hanya saat parent</br>membangun ulang"]
    D --> F["State object</br>terpisah dari Widget"]
    F --> G["setState() memicu</br>build() ulang"]
    C --> H["Contoh: ikon,</br>label, kartu statis"]
    D --> I["Contoh: form,</br>animasi, counter"]

StatelessWidget tidak memiliki state internal yang berubah — sekali dibuat, tampilan hanya bergantung pada properti yang diteruskan dari parent. Method build() hanya dipanggil ulang jika parent membangun ulang.

StatefulWidget terdiri dari dua objek: widget itu sendiri (immutable) dan objek State terpisah yang dapat berubah. Memanggil setState() memberitahu framework bahwa state telah berubah dan build() perlu dipanggil ulang untuk merefleksikan perubahan tersebut di UI.

Control Flow Lanjutan: switch Expression, Spread, if, dan for

Dart 3 memperkenalkan switch expression (berbeda dari switch statement): ekspresi ini dievaluasi menjadi nilai dan dapat digunakan langsung di dalam ekspresi lain. Sintaksnya menggunakan => dan tidak memerlukan break. Kompiler memastikan semua kemungkinan nilai tertangani (exhaustive).

Di dalam collection literals ([], {}, {:}), Dart mendukung tiga operator khusus yang sangat berguna untuk membangun UI Flutter secara deklaratif:

  • Spread operator ...: menyisipkan seluruh elemen koleksi lain ke dalam koleksi saat ini. Juga tersedia ...? untuk spread yang null-safe.
  • if dalam collection: menyertakan elemen secara kondisional tanpa perlu membuat list sementara. Contoh: [Widget1(), if (kondisi) Widget2()].
  • for dalam collection: menghasilkan elemen secara iteratif. Contoh: [for (var item in items) ItemWidget(item)].

Ketiga fitur ini menjadi tulang punggung cara Flutter membangun widget tree secara dinamis dan deklaratif di dalam method build().

Konvensi Kode Dart

Tim Dart mendefinisikan konvensi penamaan yang konsisten:

KonteksKonvensiContoh
Variabel & fungsilowerCamelCaseuserName, fetchData()
Class & enumUpperCamelCaseUserProfile, AppStatus
Package & filesnake_caseuser_profile.dart, my_app
Parameter tidak dipakai_(_, index) => ...
Konstanta globalAwalan kkDefaultPadding
String literalSingle quote '...''Halo'

Dokumentasi menggunakan /// (doc comment) untuk komentar yang muncul di hover tooltip IDE, dan /** */ untuk komentar blok multi-baris.

Data Models: PODO (Plain Old Dart Objects)

Model data di Dart biasanya diimplementasikan sebagai PODO (Plain Old Dart Objects) — class sederhana yang berisi data dengan getter dan setter. Konvensi Flutter merekomendasikan field privat dengan _ prefix dan akses via getter/setter publik untuk enkapsulasi yang baik. Method copyWith(), toString(), ==, dan hashCode sering ditambahkan untuk kemudahan penggunaan (value semantics).

State Lifting: Arsitektur State Antar Widget

Ketika dua atau lebih widget perlu berbagi atau saling mempengaruhi state yang sama, solusinya adalah state lifting: pindahkan state ke widget leluhur terdekat yang menjadi parent bersama kedua widget tersebut.

Widget parent meneruskan data ke child via constructor parameters (props). Untuk memungkinkan child mengubah state di parent, parent meneruskan callback function sebagai parameter — ini adalah closure yang menangkap referensi ke setState() dari parent. Ketika child memanggil callback tersebut, state di parent berubah dan seluruh subtree dirender ulang.

Alur state lifting secara sederhana:

  • State disimpan di widget parent (misal _selectedIndex)
  • Data diteruskan ke bawah ke child yang membutuhkan tampilan (child1.selectedIndex = _selectedIndex)
  • Callback diteruskan ke child yang perlu memicu perubahan (child2.onSelected = (i) => setState(() => _selectedIndex = i))

Styling dan List di Flutter

Flutter mendukung Google Fonts melalui package google_fonts yang mengunduh dan men-cache font dari Google Fonts API. Rounded corners dibuat menggunakan BorderRadius.circular() pada dekorasi widget.

Untuk efek transparansi gambar, terdapat dua pendekatan utama:

  1. Widget Opacity: Menerapkan transparansi pada seluruh subtree di bawahnya. Subtree dirender ke offscreen buffer terlebih dahulu sebelum transformasi diterapkan, yang lebih berat secara performa.
  2. Properti color pada Image: Menerapkan warna sebagai tint (biasanya digabung dengan colorBlendMode). Ini merupakan bagian dari proses rendering gambar standar dan dapat diakselerasi oleh GPU.

Untuk filter data pada list, gunakan method .where() dari Iterable yang menerima fungsi predikat dan mengembalikan Iterable baru berisi elemen yang memenuhi kondisi. Kombinasikan dengan .toList() untuk mengkonversi hasilnya.

Dalam Flutter, widget yang melebihi batas layar (overflow) tidak otomatis terbungkus atau dapat discroll. Pengembang harus menyediakan widget pendukung secara eksplisit:

  • SingleChildScrollView: membungkus satu child yang mungkin lebih besar dari layar — cocok untuk form atau konten panjang yang tidak diketahui jumlahnya.
  • Expanded: mengisi ruang yang tersedia dalam Row atau Column — penting agar ListView di dalam Column bisa scrollable tanpa overflow.

Flutter Inspector

Flutter Inspector adalah tool developer yang terintegrasi di Android Studio dan VS Code. Fungsinya meliputi visualisasi widget tree secara interaktif, pemeriksaan properti setiap widget, highlight batas widget di layar, dan debug masalah layout seperti overflow. Inspector membantu memahami bagaimana widget tree yang kompleks disusun dan di mana masalah rendering terjadi.

Summary

Flutter membangun UI secara deklaratif menggunakan widget tree dengan build() sebagai fungsi dari state. StatelessWidget digunakan untuk UI statis; StatefulWidget untuk UI yang berubah, dengan setState() memicu rebuild. Composition over inheritance adalah prinsip utama — widget dibuat dengan menggabungkan widget yang lebih kecil, bukan mewarisi. State lifting memindahkan state ke parent bersama dan meneruskan callback closure ke bawah untuk mutasi. Collection literal mendukung spread ..., if, dan for untuk membangun widget tree dinamis secara deklaratif. Model data diimplementasikan sebagai PODO, scrolling dikelola via SingleChildScrollView/Expanded, dan Flutter Inspector adalah alat debugging layout utama.