Belajar Desktop Developer - Flutter Desktop
Episode 6 of 28

Belajar Desktop Developer - Flutter Desktop

Membangun CatatanKu di Flutter Desktop: satu codebase Dart untuk desktop dan mobile, sistem widget dengan hot reload, serta platform channels untuk memanggil API native lintas OS

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Kita sudah membangun CatatanKu dua kali: di Tauri (Rust + WebView) di episode 4 dan Electron (Node.js + Chromium) di episode 5. Sekarang pendekatan ketiga: Flutter Desktop. Berbeda dari keduanya, Flutter tidak memakai teknologi web sama sekali — UI digambar langsung ke kanvas oleh engine Flutter (Skia/Impeller) dari kode Dart.

Keunggulan unik Flutter: satu codebase untuk desktop dan mobile. Jika kalian juga membangun aplikasi Android/iOS, Flutter Desktop memungkinkan UI yang konsisten di lima platform sekaligus. Sama seperti Tauri dan Electron, Flutter punya konsep jembatan ke sistem — disebut platform channels — yang kita pakai untuk memanggil API native.

Arsitektur Flutter Desktop

Dart + Engine + Platform Channel

100%
  • Dart UI — semua tampilan ditulis dalam Dart sebagai pohon widget (konsep episode 3).
  • Engine Flutter — merender widget ke kanvas; konsisten di semua platform karena menggambar sendiri.
  • Embedder + platform channels — jembatan ke sistem: file picker, window management, integrasi OS. Flutter menyediakan channel default untuk hal umum; untuk hal spesifik, kita menulis sendiri.

Karena Flutter menggambar sendiri, aplikasi tidak bergantung pada WebView atau browser — hasilnya konsisten dan cepat, namun ada biaya: ukuran biner dan memori lebih besar dari Tauri.

Scaffold Project CatatanKu

Scaffold project Flutter
flutter create catatanku_flutter
cd catatanku_flutter
flutter run -d linux   # ganti: windows / macos

Struktur yang dihasilkan sudah mengelompokkan kode per platform:

Struktur project Flutter
catatanku_flutter/
├─ lib/
│  ├─ main.dart          # entry point aplikasi
│  └─ ...                # widgets, state, models
├─ linux/                # embedder Linux (GTK)
├─ macos/                # embedder macOS
├─ windows/              # embedder Windows
└─ pubspec.yaml

flutter run -d linux membuka jendela dengan counter demo. Mari kita ganti dengan UI CatatanKu yang sebenarnya.

Widget dan State: Inti Flutter

Membangun UI dengan Widget

Flutter adalah Flutter karena sistem widget-nya. Semuanya widget — bahkan layout dan padding. UI CatatanKu dibangun dengan menyusun widget:

lib/main.dart — UI CatatanKu
import 'package:flutter/material.dart';
 
void main() => runApp(const CatatanKuApp());
 
class CatatanKuApp extends StatelessWidget {
  const CatatanKuApp({super.key});
 
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'CatatanKu',
      home: HomeScreen(),
    );
  }
}
 
class HomeScreen extends StatefulWidget {
  @override
  State<HomeScreen> createState() => _HomeScreenState();
}
 
class _HomeScreenState extends State<HomeScreen> {
  final List<String> _notes = [];
  final _controller = TextEditingController();
 
  void _addNote() {
    setState(() => _notes.add(_controller.text));
    _controller.clear();
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('CatatanKu')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16),
            child: TextField(controller: _controller, hintText: 'Judul catatan'),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: _notes.length,
              itemBuilder: (context, i) => ListTile(title: Text(_notes[i])),
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addNote,
        child: const Icon(Icons.add),
      ),
    );
  }
}

Perhatikan setState(() => _notes.add(...)) — ini cara Flutter menandakan "state berubah, render ulang". Ini analog dengan setQuery di episode 3: ubah state, tampilan mengikuti. StatefulWidget memegang state yang bisa berubah; StatelessWidget murni dari input.

Hot Reload

Salah satu fitur favorit Flutter: hot reload. Saat aplikasi berjalan, tekan r di terminal untuk memuat ulang UI tanpa kehilangan state, atau R untuk hot restart. Proses iterasi desain jadi jauh lebih cepat dibanding membangun ulang biner native setiap perubahan.

Tip

Flutter memiliki sistem theming terintegrasi (ThemeData, ColorScheme.fromSeed). Kita akan memakai kekuatannya penuh di episode 10 saat membahas UI & theming desktop — untuk sekarang, cukup kenali bahwa MaterialApp menerima theme: dengan satu objek konfigurasi.

Platform Channels: Jembatan ke Native

Mengapa Dibutuhkan

Widget Flutter tidak bisa langsung berbicara dengan sistem — untuk memanggil API native (file dialog, keychain, window management), kita pakai platform channels. Ini analog dengan invoke Tauri dan ipcRenderer.invoke Electron.

lib/services/native_bridge.dart
import 'package:flutter/services.dart';
 
class NativeBridge {
  static const _channel = MethodChannel('catatanku/native');
 
  static Future<String> pickFile() async {
    final path = await _channel.invokeMethod<String>('pickFile');
    return path ?? '';
  }
 
  static Future<bool> writeFile(String path, String content) async {
    return await _channel.invokeMethod<bool>('writeFile', {
      'path': path,
      'content': content,
    }) ?? false;
  }
}

Implementasi di Sisi Native

Sisi native ditulis per platform dalam bahasa masing-masing: C++ untuk Windows/Linux, Swift untuk macOS. Contoh implementasi Windows:

windows/runner/flutter_window.cpp (potongan)
// Set MethodChannel untuk "catatanku/native"
const auto channel = flutter::MethodChannel<flutter::EncodableValue>(
    flutter_controller()->engine().messenger(), "catatanku/native");
 
channel.SetMethodCallHandler(
    [](const auto& call, auto result) {
      if (call.method_name() == "pickFile") {
        // panggil Win32 API GetOpenFileName
        result->Success(open_file_dialog());
      } else if (call.method_name() == "writeFile") {
        result->Success(write_file(call.arguments()));
      } else {
        result->NotImplemented();
      }
    });

Pola yang sama berlaku di Linux (GTK/C++) dan macOS (Swift/AppKit). Kode per platform ini adalah bagian "native" yang membedakan Flutter dari Tauri/Electron — di mana logika native ditulis dalam satu bahasa (Rust atau Node), di Flutter kalian menulisnya dalam bahasa platform masing-masing.

Common Pitfalls

  • Lupa menangani channel di sisi native: memanggil channel yang tidak diimplementasikan → MissingPluginException. Selalu implementasikan di semua platform yang kalian target.
  • Thread UI dibekukan: pekerjaan berat di main isolate membekukan UI. Gunakan Isolate.run (episode 9).
  • State di luar widget: jangan simpan state penting di StatelessWidget; gunakan state management (setState, atau library seperti provider/riverpod untuk skala besar).
  • Ukuran biner: Flutter desktop lebih besar dari Tauri; pertimbangkan trade-off saat memilih framework (episode 1 dan 26).

Penutup

Inti yang harus dibawa pulang:

  • Flutter menggambar UI ke kanvas dengan engine sendiri — konsisten di desktop dan mobile, tanpa WebView.
  • UI disusun dari widget; state berubah lewat setState atau state management.
  • Platform channels (MethodChannel) adalah jembatan ke API native, diimplementasikan per platform.
  • Hot reload mempercepat iterasi UI secara signifikan.
  • Trade-off utama: ukuran biner vs konsistensi lintas platform dan mobile.

Di episode 7 selanjutnya kita akan keluar dari kotak framework dan membahas native & system APIs — file system, system tray, notifikasi, dan deep integration dengan OS — yang membuat aplikasi desktop terasa benar-benar native. Sampai jumpa di episode 7!

Belajar Desktop Developer - Flutter Desktop | Belajar Desktop Developer