Belajar Desktop Developer - GUI Fundamentals
Episode 3 of 28

Belajar Desktop Developer - GUI Fundamentals

Menguasai fondasi GUI: event loop yang menjalankan aplikasi, konsep widget dan pohon widget, serta pengelolaan UI state, lalu mempraktikkannya dengan membangun aplikasi GUI dasar yang responsif dan tidak membeku

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

Pendahuluan

Setelah di episode 2 kita memahami arsitektur aplikasi desktop — pemisahan UI process dan backend process, threading model, dan IPC — kini saatnya memahami apa yang membuat antarmuka itu sendiri hidup. Setiap aplikasi desktop, sebesar apa pun, berjalan di atas tiga konsep yang sama: event loop, widget, dan UI state.

Menguasai ketiganya adalah pembeda antara aplikasi yang terasa licin dan aplikasi yang terasa kaku. Ketika kalian paham bagaimana event loop memproses klik, bagaimana widget dibangun dan dibangun ulang, dan bagaimana state mengalir, kalian bisa menulis GUI yang responsif tanpa menebak-nebak. Episode ini menjadi fondasi untuk tiga episode framework berikutnya (Tauri, Electron, Flutter) — konsep yang sama muncul di ketiganya dengan nama berbeda.

Event Loop: Jantung Aplikasi

Aplikasi adalah Antrean Peristiwa

Aplikasi GUI tidak berjalan seperti script biasa (langkah 1 → 2 → 3 → selesai). Ia berjalan sebagai loop tak berujung yang menunggu peristiwa: klik mouse, tekanan keyboard, resize window, timer, atau data masuk dari IPC. Setiap peristiwa masuk ke antrean dan diproses satu per satu.

100%

Konsekuensi penting: karena diproses satu per satu, satu handler yang lambat akan memblokir semua peristiwa berikutnya — itulah "freeze" yang kita bahas di episode 2. Handler harus secepat mungkin, atau pekerjaan berat dipindahkan ke thread lain.

Kerangka Konseptual Event Loop

Semua framework desktop memakai pola yang sama, meski dengan istilah berbeda:

Pola dasar event loop
while (aplikasi berjalan):
    event = antrean.tunggu_peristiwa()
    handler = cari_handler(event)
    handler(event)          # cepat!
    render_ulang_jika_perlu()

Di Electron/Tauri, loop ini milik webview (Blink/WebView). Di Flutter, milik engine Flutter. Di toolkit native seperti WinUI atau GTK, milik framework masing-masing. Yang penting: kalian tidak menulis loop-nya, tetapi kalian wajib memahami aturan mainnya — jangan pernah memblokirnya.

Widget: Blok Penyusun UI

Pohon Widget

UI desktop disusun dari widget — komponen kecil yang bisa disusun seperti balok. Sebuah button, text field, list, dan layout adalah widget. Susunan widget membentuk pohon widget (widget tree):

Pohon widget editor CatatanKu
Window
└─ Column (arah vertikal)
   ├─ Toolbar (Row: tombol baru, tombol simpan, pencarian)
   ├─ Expanded (daftar catatan)
   └─ Editor (text area untuk isi catatan)

Ketika state berubah, framework tidak menggambar ulang semuanya. Ia membandingkan pohon widget lama dan baru, lalu hanya memperbarui bagian yang berubah — inilah yang membuat UI tetap cepat meski punya ribuan widget.

Widget di Tiga Framework

Widget React-style (Tauri/Electron)
function NoteList({ notes, onSelect }) {
  return (
    <ul>
      {notes.map(n => <li key={n.id} onClick={() => onSelect(n)}>{n.title}</li>)}
    </ul>
  );
}
Widget Flutter
ListView.builder(
  itemCount: notes.length,
  itemBuilder: (context, i) => ListTile(
    title: Text(notes[i].title),
    onTap: () => onSelect(notes[i]),
  ),
);

Perhatikan persamaannya: keduanya adalah fungsi dari state — diberi data yang sama, menghasilkan tampilan yang sama. Ini prinsip UI modern yang akan terus kita pakai.

UI State: Data yang Menggerakkan Tampilan

Satu Sumber Kebenaran

UI state adalah semua data yang menentukan tampilan: catatan yang terbuka, teks yang diketik, hasil pencarian, status sinkronisasi. Prinsip utamanya: satu sumber kebenaran (single source of truth) — state disimpan di satu tempat, dan tampilan selalu merupakan proyeksi dari state itu.

Contoh state CatatanKu (TypeScript)
type AppState = {
  notes: Note[];
  activeNoteId: string | null;
  searchQuery: string;
  syncStatus: "idle" | "syncing" | "error";
};

Alur Satu Arah

Perubahan UI mengikuti alur satu arah: peristiwa → ubah state → render ulang:

Alur state satu arah
Event (klik simpan) → dispatch action → reducer mengubah state → render baru
Reducer murni CatatanKu
function appReducer(state: AppState, action: Action): AppState {
  switch (action.type) {
    case "note/save":
      return { ...state, notes: upsert(state.notes, action.note) };
    case "search/set":
      return { ...state, searchQuery: action.query };
    default:
      return state;
  }
}

Reducer yang murni (input sama → output sama, tanpa efek samping) membuat state mudah diprediksi dan diuji — fondasi yang akan kita pakai saat testing di episode 15.

Note

Istilah state di desktop dan web menunjuk hal yang sama, tetapi jangan tertukar dengan state di backend. State UI hidup di frontend dan hanya menyimpan "bentuk tampilan". Data persisten (catatan, settings) adalah urusan backend process dan storage — dibedah di episode 8.

Praktik: Aplikasi GUI Dasar

Sebagai latihan pertama, kita bangun jendela CatatanKu yang menampilkan daftar catatan dan kotak pencarian — belum ada backend, cukup UI dan state. Kita pakai pendekatan framework-agnostik dengan TypeScript agar kalian bisa langsung menerjemahkannya ke Tauri (React) di episode 4 atau Electron di episode 5:

App.tsx — GUI dasar CatatanKu
import { useReducer, useState } from "react";
 
const initialNotes = [
  { id: 1, title: "Belajar Tauri" },
  { id: 2, title: "Catatan rapat" },
];
 
function App() {
  const [notes] = useState(initialNotes);
  const [query, setQuery] = useState("");
  const filtered = notes.filter(n =>
    n.title.toLowerCase().includes(query.toLowerCase())
  );
 
  return (
    <main style={{ padding: 16 }}>
      <input
        placeholder="Cari catatan..."
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <ul>
        {filtered.map((n) => <li key={n.id}>{n.title}</li>)}
      </ul>
    </main>
  );
}
 
export default App;

Perhatikan alurnya: input → setQuery → state berubah → filtered dihitung ulang → daftar dirender. Tidak ada manipulasi DOM manual — cukup ubah state, tampilan mengikuti. Inilah cara kerja GUI modern.

Penutup

Inti yang harus dibawa pulang:

  • Event loop memproses peristiwa satu per satu; jangan pernah memblokirnya dengan pekerjaan berat.
  • UI disusun dari widget membentuk pohon; framework hanya menggambar ulang bagian yang berubah.
  • UI state adalah sumber kebenaran; tampilan adalah proyeksi dari state.
  • Gunakan alur satu arah (event → reducer → render) agar state mudah diprediksi dan diuji.

Di episode 4 selanjutnya kita akan membangun CatatanKu pertama yang nyata di Tauri 2 (Rust + Web) — dari scaffold project, commands, IPC, hingga plugin ecosystem. Pastikan tooling Rust sudah terinstall, karena kita mulai menulis kode framework pertama!