Flutter

Flutter #

Flutter adalah framework UI dari Google yang memungkinkan membangun aplikasi mobile (iOS & Android), web, dan desktop dari satu codebase Dart. Yang membedakan Flutter dari framework lain bukan hanya “satu kode, banyak platform” — Flutter memiliki rendering engine sendiri (Skia/Impeller) yang menggambar setiap piksel secara langsung ke layar, tanpa bergantung pada komponen UI native platform. Hasilnya adalah tampilan dan performa yang konsisten di semua platform, termasuk animasi 60/120fps yang mulus. Bagi developer Dart yang belum kenal Flutter, artikel ini membangun pemahaman tentang cara berpikir Flutter dari perspektif Dart.

Mengapa Flutter Relevan untuk Developer Dart? #

Dart dan Flutter adalah ekosistem yang saling melengkapi:

flowchart LR
    DART["Dart SDK\n(bahasa pemrograman)"] --> SERVER["Server-side\ndart:io, shelf"]
    DART --> CLI["Command-line\ndart compile"]
    DART --> FLUTTER["Flutter SDK\n(framework UI)"]
    FLUTTER --> MOBILE["Mobile\niOS & Android"]
    FLUTTER --> WEB["Web\nFlutter Web"]
    FLUTTER --> DESKTOP["Desktop\nmacOS, Windows, Linux"]

Pengetahuan Dart yang kamu miliki — null safety, async/await, class, generic, mixin — semuanya langsung applicable di Flutter. Yang perlu dipelajari adalah cara berpikir Flutter: everything is a widget.


Instalasi Flutter #

Flutter SDK sudah menyertakan Dart SDK di dalamnya:

# macOS — via Homebrew
brew install flutter

# Atau download manual dari flutter.dev
# Ekstrak dan tambahkan ke PATH

# Verifikasi instalasi
flutter doctor

# Output menunjukkan status setiap komponen:
# [✓] Flutter (Channel stable, 3.x.x)
# [✓] Android toolchain
# [✓] Xcode (untuk iOS/macOS)
# [✓] VS Code dengan Flutter extension

Everything is a Widget #

Konsep paling mendasar di Flutter: semua yang terlihat di layar adalah widget. Teks adalah widget. Tombol adalah widget. Padding adalah widget. Bahkan aplikasi itu sendiri adalah widget.

Widget di Flutter adalah immutable description dari bagian UI — bukan objek yang bisa diubah langsung, melainkan blueprint yang digunakan Flutter untuk membangun dan merender UI:

import 'package:flutter/material.dart';

// Aplikasi Flutter paling sederhana
void main() {
  runApp(
    const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Text('Halo, Flutter!'),
        ),
      ),
    ),
  );
}

Struktur ini adalah widget tree — setiap widget menjadi parent dari widget lain di bawahnya:

MaterialApp
  └── Scaffold
        └── Center
              └── Text('Halo, Flutter!')

StatelessWidget vs StatefulWidget #

Dua jenis widget fundamental yang harus dipahami sebelum apapun:

StatelessWidget — Widget Tanpa State #

Widget yang tampilannya sepenuhnya ditentukan oleh parameter yang diterimanya — tidak ada state internal yang berubah:

import 'package:flutter/material.dart';

// StatelessWidget — build() dipanggil sekali, tidak berubah kecuali parent rebuild
class KartuProduk extends StatelessWidget {
  final String nama;
  final double harga;
  final String? urlGambar;

  const KartuProduk({
    super.key,
    required this.nama,
    required this.harga,
    this.urlGambar,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            if (urlGambar != null)
              Image.network(urlGambar!),
            Text(
              nama,
              style: Theme.of(context).textTheme.titleLarge,
            ),
            const SizedBox(height: 8),
            Text(
              'Rp ${harga.toStringAsFixed(0)}',
              style: const TextStyle(
                color: Colors.green,
                fontWeight: FontWeight.bold,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// Penggunaan
KartuProduk(
  nama: 'Laptop Gaming',
  harga: 15000000,
  urlGambar: 'https://example.com/laptop.jpg',
)

StatefulWidget — Widget dengan State #

Widget yang tampilannya bisa berubah berdasarkan interaksi pengguna atau data yang berubah seiring waktu:

import 'package:flutter/material.dart';

// StatefulWidget selalu berpasangan dengan State-nya
class KeranjangBelanja extends StatefulWidget {
  const KeranjangBelanja({super.key});

  @override
  State<KeranjangBelanja> createState() => _KeranjangBelanjaState();
}

class _KeranjangBelanjaState extends State<KeranjangBelanja> {
  // State — data yang bisa berubah
  final List<String> _items = [];
  int _totalItem = 0;

  // Lifecycle methods
  @override
  void initState() {
    super.initState();
    // Dipanggil sekali saat widget pertama dibuat
    // Cocok untuk inisialisasi: load data, setup controller, subscribe stream
    _muatDataAwal();
  }

  @override
  void dispose() {
    // Dipanggil saat widget dihapus dari tree
    // Wajib: tutup controller, cancel subscription, lepas resource
    super.dispose();
  }

  Future<void> _muatDataAwal() async {
    final items = await ambilKeranjangDariStorage();
    setState(() {
      _items.addAll(items);
      _totalItem = items.length;
    });
  }

  void _tambahItem(String item) {
    // setState() memberi tahu Flutter bahwa state berubah → rebuild UI
    setState(() {
      _items.add(item);
      _totalItem = _items.length;
    });
  }

  void _hapusItem(int index) {
    setState(() {
      _items.removeAt(index);
      _totalItem = _items.length;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Keranjang ($_totalItem item)'),
      ),
      body: _items.isEmpty
          ? const Center(child: Text('Keranjang kosong'))
          : ListView.builder(
              itemCount: _items.length,
              itemBuilder: (context, index) => ListTile(
                title: Text(_items[index]),
                trailing: IconButton(
                  icon: const Icon(Icons.delete),
                  onPressed: () => _hapusItem(index),
                ),
              ),
            ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _tambahItem('Produk ${_items.length + 1}'),
        child: const Icon(Icons.add),
      ),
    );
  }
}

Widget Lifecycle #

stateDiagram-v2
    [*] --> createState: new StatefulWidget()
    createState --> initState: State dibuat
    initState --> build: Widget siap di-render
    build --> didUpdateWidget: Parent rebuild dengan param baru
    didUpdateWidget --> build: Rebuild dengan data baru
    build --> setState: State berubah
    setState --> build: Rebuild UI
    build --> deactivate: Widget dilepas sementara
    deactivate --> dispose: Widget dihapus permanen
    dispose --> [*]

Layout Dasar #

Flutter menggunakan sistem layout berbasis widget — tidak ada XML atau CSS:

// Column — susun widget secara vertikal
Column(
  mainAxisAlignment: MainAxisAlignment.center,  // posisi di sumbu utama (vertikal)
  crossAxisAlignment: CrossAxisAlignment.start, // posisi di sumbu silang (horizontal)
  children: [
    Text('Baris 1'),
    Text('Baris 2'),
    Text('Baris 3'),
  ],
)

// Row — susun widget secara horizontal
Row(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  children: [
    Icon(Icons.star),
    Text('Judul'),
    Icon(Icons.more_vert),
  ],
)

// Stack — tumpuk widget di atas satu sama lain
Stack(
  children: [
    Image.network('https://example.com/bg.jpg'),
    const Positioned(
      bottom: 16,
      left: 16,
      child: Text('Overlay text', style: TextStyle(color: Colors.white)),
    ),
  ],
)

// Expanded dan Flexible — alokasi ruang dalam Row/Column
Row(
  children: [
    Expanded(flex: 2, child: Container(color: Colors.red)),   // 2/3 lebar
    Expanded(flex: 1, child: Container(color: Colors.blue)),  // 1/3 lebar
  ],
)

// Container — widget serba guna dengan dekorasi
Container(
  width: 200,
  height: 100,
  margin: const EdgeInsets.all(8),
  padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
  decoration: BoxDecoration(
    color: Colors.blue,
    borderRadius: BorderRadius.circular(12),
    boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 8)],
  ),
  child: const Text('Hello', style: TextStyle(color: Colors.white)),
)

Widget yang Sering Digunakan #

// Teks dengan styling
Text(
  'Halo Flutter',
  style: TextStyle(
    fontSize: 24,
    fontWeight: FontWeight.bold,
    color: Colors.deepPurple,
    letterSpacing: 1.5,
  ),
  textAlign: TextAlign.center,
  maxLines: 2,
  overflow: TextOverflow.ellipsis,
)

// Tombol
ElevatedButton(
  onPressed: () => print('Ditekan!'),
  child: const Text('Klik Saya'),
)

TextButton(onPressed: () {}, child: const Text('Text Button'))
OutlinedButton(onPressed: () {}, child: const Text('Outlined Button'))
IconButton(icon: const Icon(Icons.favorite), onPressed: () {})

// Input teks
TextField(
  decoration: const InputDecoration(
    labelText: 'Email',
    hintText: '[email protected]',
    prefixIcon: Icon(Icons.email),
    border: OutlineInputBorder(),
  ),
  onChanged: (nilai) => print('Input: $nilai'),
)

// Gambar
Image.network('https://example.com/gambar.jpg')
Image.asset('assets/images/logo.png')

// Ikon
Icon(Icons.home, size: 32, color: Colors.blue)

// ListView — daftar yang bisa discroll
ListView.builder(
  itemCount: 50,
  itemBuilder: (context, index) => ListTile(
    leading: CircleAvatar(child: Text('$index')),
    title: Text('Item $index'),
    subtitle: Text('Deskripsi item $index'),
    trailing: const Icon(Icons.chevron_right),
    onTap: () => print('Tap item $index'),
  ),
)

// GridView
GridView.builder(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    crossAxisSpacing: 8,
    mainAxisSpacing: 8,
  ),
  itemCount: 20,
  itemBuilder: (context, index) => Card(
    child: Center(child: Text('Grid $index')),
  ),
)

// Navigator.push — navigasi ke halaman baru
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => HalamanDetail(id: '123'),
  ),
);

// Navigator.pop — kembali ke halaman sebelumnya
Navigator.pop(context);

// Dengan named routes (konfigurasi di MaterialApp)
MaterialApp(
  routes: {
    '/': (context) => const HalamanUtama(),
    '/detail': (context) => const HalamanDetail(),
    '/profil': (context) => const HalamanProfil(),
  },
)

Navigator.pushNamed(context, '/detail', arguments: {'id': '123'});

State Management Overview #

Untuk aplikasi sederhana, setState di StatefulWidget sudah cukup. Untuk aplikasi yang lebih besar, ada beberapa solusi state management:

Solusi Kompleksitas Cocok untuk
setState Rendah Widget lokal, state sederhana
InheritedWidget Sedang Sharing state ke widget child
Provider Rendah-sedang Aplikasi menengah, mudah dipelajari
Riverpod Sedang Aplikasi menengah-besar, type-safe
Bloc/Cubit Sedang-tinggi Aplikasi enterprise, testable
GetX Rendah Pengembangan cepat
// Provider — salah satu yang paling populer
// pubspec.yaml: dependencies: provider: ^6.1.0

class KeranjangModel extends ChangeNotifier {
  final List<Produk> _items = [];

  List<Produk> get items => List.unmodifiable(_items);
  int get jumlah => _items.length;
  double get total => _items.fold(0, (sum, p) => sum + p.harga);

  void tambah(Produk produk) {
    _items.add(produk);
    notifyListeners(); // beritahu semua listener untuk rebuild
  }

  void hapus(Produk produk) {
    _items.remove(produk);
    notifyListeners();
  }
}

// Setup di main.dart
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => KeranjangModel(),
      child: const MyApp(),
    ),
  );
}

// Consume di widget manapun
class TombolKeranjang extends StatelessWidget {
  const TombolKeranjang({super.key});

  @override
  Widget build(BuildContext context) {
    // watch — rebuild saat model berubah
    final keranjang = context.watch<KeranjangModel>();

    return Badge(
      label: Text('${keranjang.jumlah}'),
      child: IconButton(
        icon: const Icon(Icons.shopping_cart),
        onPressed: () => Navigator.pushNamed(context, '/keranjang'),
      ),
    );
  }
}

Hot Reload dan Hot Restart #

Fitur yang membuat pengembangan Flutter sangat produktif:

Hot Reload (r):
  • Injeksi kode baru ke VM yang berjalan
  • State dipertahankan
  • Sangat cepat (~300ms)
  • Cocok untuk perubahan UI: warna, teks, layout

Hot Restart (R):
  • Restart aplikasi dari awal
  • State di-reset
  • Sedikit lebih lambat (~3-5 detik)
  • Diperlukan saat: tambah dependency, ubah main(), perubahan logika awal

Full Restart:
  • Stop dan start ulang dari cold start
  • Diperlukan saat: ubah native code, ubah AndroidManifest/Info.plist

Membuat Project Flutter Baru #

# Buat project baru
flutter create nama_aplikasi

# Dengan template yang spesifik
flutter create --template=app --org=com.perusahaan nama_aplikasi

# Struktur project
nama_aplikasi/
  ├── lib/
  │   └── main.dart          ← entry point aplikasi
  ├── test/
  │   └── widget_test.dart   ← test UI
  ├── assets/                ← gambar, font, dll.
  ├── android/               ← konfigurasi Android
  ├── ios/                   ← konfigurasi iOS
  ├── web/                   ← konfigurasi web
  ├── macos/                 ← konfigurasi macOS
  ├── linux/                 ← konfigurasi Linux
  ├── windows/               ← konfigurasi Windows
  └── pubspec.yaml           ← dependencies

# Jalankan di emulator/device
flutter run

# Build untuk production
flutter build apk --release        # Android APK
flutter build appbundle --release  # Android App Bundle (Google Play)
flutter build ios --release        # iOS (butuh Xcode di macOS)
flutter build web                  # Web
flutter build macos                # macOS desktop

Ringkasan #

  • Everything is a widget — teks, padding, tombol, layout, bahkan aplikasi itu sendiri adalah widget. Berpikir dalam tree widget adalah cara berpikir Flutter.
  • StatelessWidget untuk UI yang tidak berubah — tampilannya ditentukan sepenuhnya oleh parameter yang diterima. Selalu gunakan const jika tidak ada parameter yang bisa berubah.
  • StatefulWidget untuk UI yang perlu berubah — pasangkan dengan State class yang menyimpan state. Gunakan setState() untuk memberi tahu Flutter bahwa UI perlu diupdate.
  • initState() untuk inisialisasi (load data, setup controller), dispose() untuk pembersihan resource (tutup controller, cancel subscription). Selalu panggil super di keduanya.
  • Layout Flutter sepenuhnya berbasis widget — Column/Row untuk susunan linear, Stack untuk tumpukan, Expanded/Flexible untuk alokasi ruang, Container untuk dekorasi.
  • const widget tidak di-rebuild saat parent rebuild — selalu tambahkan const pada widget yang tidak bergantung pada state atau parameter yang berubah untuk performa optimal.
  • Hot reload (r) untuk melihat perubahan UI secara instan tanpa kehilangan state — salah satu fitur pengembangan paling produktif di Flutter.
  • State management: gunakan setState untuk state lokal sederhana, Provider atau Riverpod untuk state yang perlu dishare antar widget.
  • Satu codebase, semua platform — Flutter mendukung iOS, Android, Web, macOS, Windows, dan Linux dari satu project Dart yang sama.
  • Flutter SDK menyertakan Dart SDK — jika menggunakan Flutter, kamu sudah punya Dart. Semua konsep Dart yang dipelajari di seri ini langsung applicable di Flutter.

← Sebelumnya: Memcached   Berikutnya: Conduit →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact