Belajar Desktop Developer - Native & System APIs
Episode 7 of 28

Belajar Desktop Developer - Native & System APIs

Memanfaatkan native & system APIs untuk integrasi yang dalam: akses file system, system tray, notifikasi, dan fitur deep integration dengan OS, lengkap dengan praktik integrasi sistem untuk CatatanKu di tiga framework

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

Pendahuluan

Sejauh ini CatatanKu berjalan di dalam jendela aplikasi — UI yang berisi list dan editor. Aplikasi desktop sejati, bagaimanapun, tidak terkurung di jendelanya. Ia bisa muncul di system tray, mengirim notifikasi sistem, membuka file dari file explorer, dan mendaftarkan diri sebagai aplikasi bawaan untuk jenis file tertentu. Inilah native & system APIs: kemampuan yang tidak bisa ditiru aplikasi web, dan alasan utama aplikasi desktop tetap relevan (episode 1).

Episode ini membangun integrasi sistem untuk CatatanKu: akses file system yang aman, tray icon dengan menu, notifikasi saat sinkronisasi selesai, dan deep integration seperti file association. Prinsip yang kita pakai berlaku lintas framework — kita lihat praktiknya di Tauri, Electron, dan Flutter.

File System: Akses yang Aman

Membaca dan Menulis File

Backend process (Rust/Node/native) bisa mengakses filesystem — tetapi akses harus disengaja dan dibatasi, bukan izin bebas. CatatanKu perlu membaca file .txt, menyimpan pengaturan, dan mengekspor catatan:

Tauri: baca/tulis file (Rust)
use std::fs;
use tauri::{AppHandle, Manager};
use tauri_plugin_fs::FsExt;
 
#[tauri::command]
fn read_note(app: AppHandle, path: String) -> Result<String, String> {
    let base = app.path().app_data_dir().map_err(|e| e.to_string())?;
    let full = base.join(path);
    if !full.starts_with(&base) {
        return Err("path di luar app data dir".into());
    }
    fs::read_to_string(full).map_err(|e| e.to_string())
}
JSElectron: baca/tulis di main process
const { app } = require("electron");
const fs = require("node:fs/promises");
const path = require("node:path");
 
async function readNote(fileName) {
  const base = app.getPath("userData");
  const full = path.join(base, "notes", fileName);
  if (!full.startsWith(path.join(base, "notes"))) {
    throw new Error("path di luar folder notes");
  }
  return fs.readFile(full, "utf-8");
}

Dua hal penting yang sama di kedua contoh: simpan data di direktori aplikasi (app_data_dir / userData), dan validasi path agar tidak keluar dari folder tersebut — mencegah path traversal.

Warning

Jangan pernah mengizinkan UI mengirim path absolut bebas ke backend. Simpan path relatif terhadap direktori aplikasi, lalu validasi di backend. Prinsip ini (path sanitization) mencegah celah klasik path traversal yang sering muncul di aplikasi desktop.

System Tray

Menjaga Aplikasi Tetap "Hidup"

Aplikasi seperti CatatanKu sering bekerja di latar belakang — sinkronisasi, pengindeksan — dan pengguna ingin mengaksesnya cepat tanpa membuka jendela. System tray (notification area di Windows, menu bar di macOS) adalah jawabannya:

Tauri: tambahkan tray icon
use tauri::menu::{Menu, MenuItem};
use tauri::tray::TrayIconBuilder;
 
let open = MenuItem::with_id(app, "open", "Buka CatatanKu", true, None::<&str>)?;
let quit = MenuItem::with_id(app, "quit", "Keluar", true, None::<&str>)?;
let menu = Menu::with_items(app, &[&open, &quit])?;
 
TrayIconBuilder::new()
    .icon(app.default_window_icon().unwrap().clone())
    .menu(&menu)
    .on_menu_event(|app, event| match event.id().as_ref() {
        "open" => { /* tampilkan jendela */ }
        "quit" => app.exit(0),
        _ => {}
    })
    .build(app)?;
JSElectron: Tray + menu
const { Tray, Menu, nativeImage, app } = require("electron");
 
let tray = new Tray(nativeImage.createFromPath("icon.png"));
const contextMenu = Menu.buildFromTemplate([
  { label: "Buka CatatanKu", click: () => mainWindow.show() },
  { label: "Keluar", click: () => app.quit() },
]);
tray.setToolTip("CatatanKu");
tray.setContextMenu(contextMenu);

Perhatikan pola: window utama bisa ditutup tanpa menghentikan aplikasi (di tray), dan dibuka kembali dari menu tray. Ini mengubah lifecycle app — kita perlu mengatur perilaku close di episode 24 (multi-window) dan 13 (auto-update).

Notifikasi Sistem

Memberi Kabar Tanpa Membuka Aplikasi

Notifikasi OS adalah cara standar memberi tahu pengguna: sinkronisasi selesai, error terjadi, atau ada reminder. Di semua framework ada plugin/API khusus:

Tauri: plugin notification
use tauri_plugin_notification::NotificationExt;
 
app.notification()
    .builder()
    .title("CatatanKu")
    .body("Sinkronisasi selesai: 12 catatan diperbarui")
    .show()?;
JSElectron: Notification API
const { Notification } = require("electron");
 
new Notification({
  title: "CatatanKu",
  body: "Sinkronisasi selesai: 12 catatan diperbarui",
}).show();

Di Flutter, notifikasi desktop dipakai lewat package seperti flutter_local_notifications. Kunci dari ketiganya: notifikasi adalah event dari backend, dikirim saat peristiwa sistem terjadi — bukan sesuatu yang dipicu UI secara langsung.

Deep Integration

File Association

Agar CatatanKu bisa dibuka dengan klik ganda pada file .md/.txt, aplikasi harus mendaftarkan diri sebagai handler jenis file tersebut. Ini dideklarasikan saat packaging:

Tauri: daftarkan file association
{
  "bundle": {
    "fileAssociations": [
      {
        "ext": ["txt", "md"],
        "name": "Dokumen CatatanKu",
        "mimeType": "text/markdown"
      }
    ]
  }
}
Electron: daftarkan file association (electron-builder)
{
  "build": {
    "fileAssociations": [
      { "ext": ["txt", "md"], "name": "Dokumen CatatanKu" }
    ]
  }
}

Saat file dibuka, OS memanggil aplikasi dengan path file tersebut — aplikasi membaca argumen/payload dan membuka editor. Deep integration lainnya: drag-and-drop file dari explorer, global shortcut (misal Ctrl+Shift+Space untuk menampilkan CatatanKu dari mana pun), dan single instance lock agar tidak membuka dua proses yang sama.

Note

File association dan drag-and-drop adalah contoh "desktop-only" yang sulit atau mustahil di web. Inilah mengapa aplikasi seperti Obsidian, Notion Desktop, dan editor kode tetap membangun versi desktop — integrasi dengan OS pengguna adalah bagian dari produknya.

Praktik Integrasi untuk CatatanKu

Rangkuman integrasi yang kita bangun di episode ini:

  • File system — simpan catatan di app_data_dir/userData dengan path sanitization.
  • System tray — menu cepat: buka jendela, sinkronisasi, keluar.
  • Notifikasi — pemberitahuan hasil sinkronisasi dan error.
  • File association — buka .txt/.md dengan klik ganda.
  • Single instance — cegah dua proses CatatanKu berjalan bersamaan.

Penutup

Inti yang harus dibawa pulang:

  • File system: akses via direktori aplikasi + sanitasi path; UI tidak pernah menangani path bebas.
  • System tray: jaga aplikasi tetap hidup di latar belakang dengan menu cepat.
  • Notifikasi sistem: cara standar melaporkan peristiwa backend.
  • Deep integration: file association, drag-and-drop, dan global shortcut menjadikan aplikasi terasa native.

Di episode 8 selanjutnya kita akan membangun fondasi data CatatanKu: offline & local data — SQLite, penyimpanan konfigurasi, dan pola offline-first yang membuat catatan tersimpan lokal dan cepat. Sampai jumpa di episode 8!