Flutter, Laravel, GoLang, dan AI sebagai Pair Programmer
Studi Kasus : Pembuatan Maintenance Management SystemMembangun aplikasi dari nol bisa menjadi proses yang panjang dan melelahkan. Kami memilih pendekatan berbeda: menggunakan Claude Code sebagai AI pair-programming partner untuk mempercepat siklus development tanpa mengorbankan kualitas arsitektur.
Mengapa Flutter dan BLoC?
Pilihan teknologi bukan keputusan yang diambil secara sembarangan. Flutter dipilih karena beberapa alasan pragmatis:
- Cross-platform — satu codebase untuk Android dan iOS. Tim teknisi di lapangan menggunakan berbagai macam perangkat, dan kami tidak ingin membangun dua aplikasi terpisah.
- Performa native — Flutter mengompilasi ke kode native, bukan web view. Ini penting untuk pengalaman yang responsif, terutama saat membuka kamera dan mengupload foto di area dengan sinyal terbatas.
- Widget system yang kaya — Material Design out-of-the-box, membuat kami bisa fokus ke logika bisnis alih-alih membangun komponen UI dari nol.
Untuk state management, kami memilih BLoC (Business Logic Component) karena memberikan pemisahan yang jelas antara UI dan logika bisnis, mudah di-test secara unit, dan menggunakan pattern event-driven yang predictable.
| Layer | Teknologi | Fungsi |
|---|---|---|
| Framework | Flutter | UI cross-platform |
| State Management | flutter_bloc | BLoC pattern |
| HTTP Client | Dio | API calls + interceptors |
| Routing | go_router | Declarative navigation |
| DI | get_it | Service locator |
| Image | image_picker | Kamera & galeri |
| Testing | bloc_test + mocktail | Unit & widget testing |
Clean Architecture: Fondasi yang Tidak Bisa Ditawar
Sejak awal, kami menerapkan Clean Architecture dengan tiga layer yang tegas. Ini bukan over-engineering — ini adalah investasi jangka panjang. Ketika API berubah, hanya data layer yang perlu dimodifikasi. Ketika UI di-redesign, domain layer tidak tersentuh. Ketika bisnis rule bertambah, presentation layer tetap bersih.
Setiap feature module mengikuti pola yang sama: data → domain ← presentation. Domain layer menjadi pusat — murni Dart, tanpa dependency ke Flutter atau package pihak ketiga. Ini membuatnya sangat mudah di-test.
Claude Code sebagai Partner Development
Inilah bagian yang membedakan project ini dari pendekatan konvensional. Kami tidak hanya menggunakan AI untuk generate code potongan demi potongan. Kami merancang seluruh workflow development agar bisa dieksekusi oleh Claude Code secara sistematis.
Langkah 1: Menyiapkan Konteks Project
Sebelum satu baris kode Flutter pun ditulis, kami membuat file CLAUDE.md — sebuah dokumen yang berisi seluruh konteks project: tech stack, arsitektur, design system, konvensi bahasa (kode dalam bahasa Inggris, UI dalam bahasa Indonesia), dan business rules. File ini menjadi “otak” yang dibaca Claude Code sebelum mengeksekusi apapun.
maintenance-mobile/
├── CLAUDE.md # Project context
└── .claude/
├── context/
│ └── api-spec.md # API specification
├── rules/
│ ├── 01-code-style.md # Dart/Flutter conventions
│ ├── 02-testing-conventions.md # Test patterns & coverage
│ └── 03-security-requirements.md # Token, network, upload
└── tasks/
├── 00-task-index.md # Execution order
├── 01-project-setup.md # Foundation
├── 02-shared-widgets.md # Reusable components
├── 03-auth-feature.md # Login flow
├── 04-dashboard-feature.md # Home page
├── 05-task-list-feature.md # Search, filter, sort
├── 06-task-detail-feature.md # Detail + actions
├── 07-report-form-feature.md # Report + photo upload
└── 08-integration-polish.md # Wiring & polish
Langkah 2: Rules sebagai Guardrails
Tiga file rules berfungsi sebagai guardrails — memastikan Claude Code menghasilkan output yang konsisten, aman, dan bisa di-maintain:
📐 Code Style Guidelines
Naming conventions (PascalCase untuk BLoC events, camelCase untuk variabel), import ordering, dependency direction, BLoC patterns (event past-tense, state sealed class), dan aturan string externalization — semua label UI harus dari AppStrings, tidak boleh hardcoded.
🧪 Testing Conventions
Minimum coverage per layer (domain: 100%, BLoC: 95%, widget: 80%). Pattern AAA (Arrange-Act-Assert). Mocking dengan mocktail. Setiap BLoC harus di-test menggunakan blocTest untuk setiap event × state transition — termasuk edge cases seperti empty list dan network failure.
🔒 Security Requirements
Token JWT disimpan dengan flutter_secure_storage. Interceptor otomatis menangani 401 (redirect ke login). Validasi input di client-side sebelum dikirim ke API. Foto divalidasi MIME type dan ukuran (maks 5MB). Tidak ada data sensitif yang pernah muncul di log.
Langkah 3: Task Files — Blueprint untuk Eksekusi
Setiap task file bukan sekadar todo list. Ini adalah blueprint lengkap yang berisi: objective, API endpoints yang digunakan, entity dan model yang harus dibuat (termasuk contoh kode), BLoC events dan states, mockup UI dalam ASCII art, daftar unit test yang harus ditulis, dan acceptance criteria yang harus dipenuhi.
Dengan struktur ini, menjalankan satu task dengan Claude Code menjadi sangat straightforward:
claude "Read and execute .claude/tasks/03-auth-feature.md.
Follow rules in .claude/rules/ and reference CLAUDE.md.
After completing, run flutter analyze and flutter test."
Claude Code membaca task, memahami konteks dari CLAUDE.md, mengikuti aturan dari rules, dan menghasilkan seluruh feature — lengkap dengan test. Ini bukan magic — ini hasil dari persiapan konteks yang detail.
Anatomi Satu Feature: Task Detail
Mari kita bedah satu feature untuk melihat bagaimana semua elemen bekerja bersama. Task Detail adalah halaman yang menampilkan informasi lengkap sebuah tugas dengan action buttons yang berubah berdasarkan status.
API yang Digunakan
| Action | Method | Endpoint |
|---|---|---|
| Lihat detail | GET | /tasks/:id |
| Ambil tugas | POST | /tasks/:id/take |
| Update status | PATCH | /tasks/:id/status |
| Kirim laporan | POST | /reports |
| Upload foto | POST | /reports/:id/photos |
Tiga Kondisi, Tiga Tampilan
Halaman detail tugas memiliki action button yang berubah sesuai status:
Tombol “Ambil Tugas”
“Tambah Laporan” + “Selesaikan”
Read-only, tanpa tombol
Setiap transisi status memiliki confirmation dialog — mencegah teknisi tidak sengaja menyelesaikan tugas yang belum benar-benar selesai. Dan setiap aksi memberi feedback berupa SnackBar dalam bahasa Indonesia: “Tugas berhasil diambil”, “Tugas berhasil diselesaikan”.
BLoC yang Mengorkestrasi
BLoC untuk fitur ini mengelola state yang cukup kompleks. Selain state standar (loading, success, error), ada state khusus untuk action loading — ketika teknisi menekan “Ambil Tugas”, UI tetap menampilkan detail task sambil menunjukkan loading indicator di tombol. Ini memberikan pengalaman yang mulus tanpa flash putih yang mengganggu.
Event: TaskTaken(taskId)
→ emit TaskActionLoading(detail, "Mengambil tugas...")
→ call POST /tasks/:id/take
→ re-fetch detail
→ emit TaskActionSuccess(updatedDetail, "Tugas berhasil diambil")
Event: TaskCompleted(taskId)
→ emit TaskActionLoading(detail, "Menyelesaikan tugas...")
→ call PATCH /tasks/:id/status {"status": "done"}
→ re-fetch detail
→ emit TaskActionSuccess(updatedDetail, "Tugas berhasil diselesaikan")
Reusable Widget Library
Sebelum membangun satu halaman pun, kami membangun library widget yang reusable. Ini investasi waktu di awal yang menghemat sangat banyak waktu di kemudian hari. Sebelas widget yang menjadi building blocks seluruh aplikasi:
Setiap widget menerima data dan konfigurasi melalui constructor — tidak ada hardcoded value. Warna dari AppColors, teks dari AppStrings. Ini membuat widget bisa digunakan di konteks manapun tanpa modifikasi.
Dual-Language Convention
Salah satu keputusan desain yang menarik: kode ditulis dalam bahasa Inggris, tetapi semua yang dilihat pengguna dalam bahasa Indonesia. Ini bukan sekadar preferensi — ini best practice. Kode dalam bahasa Inggris membuatnya universal dan bisa di-maintain oleh developer manapun. UI dalam bahasa Indonesia membuatnya aksesibel bagi teknisi di lapangan.
// Code in English
class LoginSubmitted extends AuthEvent {
final String email;
final String password;
}
// Labels in Indonesian (from AppStrings)
class AppStrings {
static const loginTitle = 'Masuk';
static const emailRequired = 'Email wajib diisi';
static const passwordMinLength = 'Kata sandi minimal 6 karakter';
static const taskTakenSuccess = 'Tugas berhasil diambil';
}
Testing: Bukan Afterthought
Testing bukan sesuatu yang kami tambahkan di akhir. Setiap task file sudah mendefinisikan test apa saja yang harus ditulis — dari model serialization, repository success/failure paths, BLoC state transitions, hingga widget rendering.
Setiap BLoC di-test untuk setiap kombinasi event dan outcome: sukses, gagal, network error, empty data, dan edge cases. Menggunakan blocTest dari package bloc_test dan mocktail untuk mocking — tanpa exception.
Apa yang Kami Pelajari
Menggunakan AI sebagai pair-programming partner mengubah cara kami berpikir tentang development. Beberapa insight:
- Investasi terbesar ada di persiapan, bukan eksekusi. Membuat CLAUDE.md, rules, dan task files memakan waktu. Tapi begitu semua konteks siap, eksekusi menjadi jauh lebih cepat dan konsisten.
- AI bekerja paling baik dengan batasan yang jelas. Rules file bukan formalitas — ini yang membedakan output berkualitas dari spaghetti code. Tanpa guardrails, AI bisa menghasilkan kode yang “bekerja” tapi tidak bisa di-maintain.
- Task decomposition adalah skill yang underrated. Memecah project menjadi 8 task yang sequential dengan dependency yang jelas membuat seluruh pembangunan bisa dilakukan secara incremental — setiap task menghasilkan output yang bisa diverifikasi secara independen.
- API spec adalah kontrak. Ketika API spec berubah (dan itu terjadi di project ini), kami hanya perlu mengupdate context file dan task file yang terdampak — bukan menyusuri seluruh codebase.

