Claude Code untuk Flutter/Dart: peta proyek, state, test, dan build
Alur praktis memakai Claude Code di Flutter/Dart: pubspec, widget, state, test, build, dan verifikasi perangkat.
Claude Code paling berguna dalam Flutter/Dart saat dipakai sebagai partner yang memahami proyek, bukan generator widget sekali pakai. Perubahan nyata sering menyentuh lib/, pubspec.yaml, assets, test, konfigurasi Android dan iOS, batasan Web, emulator, serta perintah build. Widget adalah bagian UI, state adalah data yang mengubah tampilan, dan pubspec adalah file untuk dependency, assets, serta batasan SDK.
Contoh di artikel ini adalah layar cart kecil. Kodenya cukup sederhana untuk ditempel ke proyek flutter create, tetapi tetap memperlihatkan kesalahan produksi: membuat state di dalam build, menambah package tanpa alasan, lupa widget test, atau menganggap Android, iOS, dan Web punya aturan runtime yang sama.
Saat menyesuaikan contoh, pakai dokumentasi resmi: Flutter CLI, Flutter testing, Widget testing, Flutter pubspec options, Dart pubspec, Platform channels, dan Claude Code common workflows. Bacaan terkait: React Native, strategi testing, dan praktik CLAUDE.md.
Buat peta proyek dulu
Jangan mulai dengan “buat layar cart”. Minta Claude Code membaca repo tanpa mengedit. Harness, atau kerangka kerja aman, berisi file yang boleh disentuh, larangan, perintah verifikasi, dan kriteria review.
Baca proyek Flutter ini terlebih dahulu.
Jangan edit file dulu.
Laporkan:
1. SDK constraint, dependency, dan assets di pubspec.yaml
2. Struktur UI, state, dan data layer di lib/
3. Gaya test yang sudah ada di test/ dan integration_test/
4. Target aktif di android/, ios/, web/, macos/, windows/, dan linux/
5. Apakah flutter analyze, flutter test, dan build command terdokumentasi
Akhiri dengan daftar file yang aman diedit dan file yang tidak boleh disentuh.
| Area | Claude Code bisa mengerjakan | Keputusan manusia |
|---|---|---|
| Peta proyek | Konvensi folder, dependency, gaya test | Batas edit |
| Widget | Komponen UI, aksesibilitas, responsive | Copy produk dan desain |
| State | Mengikuti setState, ChangeNotifier, Riverpod, Bloc, atau pola lama | Strategi state aplikasi |
| pubspec | Usulan dependency dan assets | Apakah package baru diterima |
| Platform | Perbedaan Android/iOS/Web/Desktop | Target yang didukung |
| Test/build | Widget test, integration test, command | Merge dan release |
Perlakukan pubspec sebagai kontrak
pubspec.yaml mengatur dependency, assets, SDK, dan package test. Sebelum diedit, Claude Code harus menjelaskan alasan, alternatif, file terdampak, dan command verifikasi.
name: cart_ai_demo
description: A small Flutter cart screen used to verify Claude Code prompts.
publish_to: "none"
environment:
sdk: ">=3.4.0 <4.0.0"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
flutter_lints: ^6.0.0
flutter:
uses-material-design: true
assets:
- assets/images/
Untuk sandbox baru, buat baseline berikut. Untuk repo lama, minta Claude Code menyesuaikan dengan konfigurasi saat ini.
flutter create cart_ai_demo
cd cart_ai_demo
flutter pub get
flutter analyze
flutter test
Kesalahan umum: indentasi assets rusak, pubspec.lock berubah tanpa kebutuhan, dan package state-management ditambahkan untuk interaksi kecil.
Periksa pubspec.yaml dan putuskan apakah perubahan cart benar-benar butuh dependency baru.
Sebelum mengedit, tampilkan alasan, alternatif, file terdampak, dan command verifikasi.
Jangan ubah pubspec.yaml atau pubspec.lock sebelum disetujui.
Ubah widget dan state secara kecil
Kode ini dapat mengganti lib/main.dart di proyek flutter create cart_ai_demo. Hanya memakai Flutter SDK. CartController memegang perubahan state, sedangkan widget merender UI.
import 'package:flutter/material.dart';
void main() => runApp(const CartDemoApp());
class CartDemoApp extends StatelessWidget {
const CartDemoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Cart AI Demo',
theme: ThemeData(colorSchemeSeed: Colors.teal, useMaterial3: true),
home: const CartSummaryPage(),
);
}
}
class CartLine {
const CartLine({required this.name, required this.price, required this.quantity});
final String name;
final int price;
final int quantity;
CartLine copyWith({int? quantity}) =>
CartLine(name: name, price: price, quantity: quantity ?? this.quantity);
}
class CartController extends ChangeNotifier {
final List<CartLine> _lines = const [
CartLine(name: 'Dart notebook', price: 1800, quantity: 1),
CartLine(name: 'Flutter sticker', price: 500, quantity: 2),
].toList();
List<CartLine> get lines => List.unmodifiable(_lines);
int get total => _lines.fold(0, (sum, line) => sum + line.price * line.quantity);
void increment(int index) {
final line = _lines[index];
_lines[index] = line.copyWith(quantity: line.quantity + 1);
notifyListeners();
}
void decrement(int index) {
final line = _lines[index];
if (line.quantity == 1) return;
_lines[index] = line.copyWith(quantity: line.quantity - 1);
notifyListeners();
}
}
class CartSummaryPage extends StatefulWidget {
const CartSummaryPage({super.key});
@override
State<CartSummaryPage> createState() => _CartSummaryPageState();
}
class _CartSummaryPageState extends State<CartSummaryPage> {
late final CartController controller;
@override
void initState() {
super.initState();
controller = CartController();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Cart summary')),
body: AnimatedBuilder(
animation: controller,
builder: (context, _) => ListView(
padding: const EdgeInsets.all(16),
children: [
for (final entry in controller.lines.indexed)
ListTile(
title: Text(entry.$2.name),
subtitle: Text('JPY ${entry.$2.price} x ${entry.$2.quantity}'),
trailing: Wrap(
spacing: 8,
children: [
IconButton(
tooltip: 'Decrease ${entry.$2.name}',
onPressed: () => controller.decrement(entry.$1),
icon: const Icon(Icons.remove_circle_outline),
),
IconButton(
tooltip: 'Increase ${entry.$2.name}',
onPressed: () => controller.increment(entry.$1),
icon: const Icon(Icons.add_circle_outline),
),
],
),
),
const Divider(),
Text('Total: JPY ${controller.total}',
style: Theme.of(context).textTheme.headlineSmall),
],
),
),
);
}
}
Jaga batasnya dengan prompt berikut:
Update UI cart, tetapi semua perubahan state tetap di CartController.
Jangan membuat CartController di dalam build. Jangan tambah package.
Tambahkan widget test yang membuktikan total berubah setelah tap plus dan minus.
Tambahkan test dan verifikasi perangkat
Jika nama proyek cart_ai_demo, letakkan ini di test/cart_summary_test.dart.
import 'package:cart_ai_demo/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('updates the cart total when quantity changes', (tester) async {
await tester.pumpWidget(const CartDemoApp());
expect(find.text('Total: JPY 2800'), findsOneWidget);
await tester.tap(find.byIcon(Icons.add_circle_outline).first);
await tester.pump();
expect(find.text('Total: JPY 4600'), findsOneWidget);
await tester.tap(find.byIcon(Icons.remove_circle_outline).first);
await tester.pump();
expect(find.text('Total: JPY 2800'), findsOneWidget);
});
}
flutter analyze
flutter test
Untuk emulator atau device nyata, tambahkan integration test.
import 'package:cart_ai_demo/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('cart can be changed on a real device', (tester) async {
await tester.pumpWidget(const CartDemoApp());
await tester.tap(find.byIcon(Icons.add_circle_outline).last);
await tester.pumpAndSettle();
expect(find.text('Total: JPY 3300'), findsOneWidget);
});
}
flutter devices
flutter test integration_test -d <device_id>
Use case dan jebakan platform
Use case 1: menambah quantity, filter, atau sort pada list lama. Use case 2: merapikan pubspec.yaml dan assets. Use case 3: menelusuri bug yang hanya muncul di Android, iOS, atau Web, misalnya permission, notification, location, atau external link. Use case 4: menambahkan regression test untuk layar lama yang belum punya coverage. Setiap prompt perlu menyebut target file, command yang diharapkan, dan format laporan gagal.
Flutter berbagi layer Dart, tetapi aturan deploy tetap berbeda. Android mungkin butuh permission manifest, iOS butuh Info.plist, CocoaPods, dan signing, Web bisa kena CORS atau API browser, Desktop punya permission file dan perilaku plugin native.
flutter analyze
flutter test
flutter build apk --debug
flutter build web --release
Build release iOS membutuhkan macOS dan Xcode. Tulis di prompt mana yang bisa dijalankan di mesin ini dan mana yang masih pending.
Prompt aman dan catatan lapangan
Investigasi: baca lib/, test/, pubspec.yaml, dan folder platform. Jangan edit. Beri peta dan risiko.
Implementasi: ubah hanya lib/features/cart. Ikuti pola state lama. Jangan tambah dependency.
pubspec: jika dependency dibutuhkan, berhenti dan beri alasan, alternatif, impact, dan command.
Verifikasi: tambahkan widget test. Jalankan flutter analyze dan flutter test. Laporkan gagal dengan nama file.
Review: cek dispose, async, side effect di build, config platform, aksesibilitas, package churn, dan test yang hilang.
Catatan praktik Masa: hasil Flutter terbaik muncul dari urutan “peta dulu, diff kecil, lalu test dan build”. Prompt lemah adalah “buat layar Flutter yang bagus”; UI terlihat masuk akal, tetapi state dibuat di build, package bertambah, dan platform tidak direview. Tim bisa mulai dari cheatsheet gratis lalu merapikan adopsi lewat training dan konsultasi Claude Code. Untuk guardrail berulang, baca harness engineering.
Snippet ini menargetkan flutter create cart_ai_demo. Lingkungan penulisan ini tidak memiliki Flutter SDK, jadi verifikasi final harus dijalankan di environment Flutter nyata dengan flutter analyze, flutter test, build target platform, dan test emulator atau device.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Workflow Obsidian ke CLAUDE.md untuk Claude Code
Ubah catatan kerja Obsidian menjadi operating note CLAUDE.md agar konteks tidak dijelaskan ulang.
Claude Code Revenue CTA Routing: dari artikel ke PDF, Gumroad, dan konsultasi
Workflow Claude Code untuk mengarahkan pembaca ke PDF gratis, Gumroad, atau konsultasi sesuai intent.
Aturan handoff tim Claude Code: bukti review, permission, rollback, dan jalur revenue
Format handoff Claude Code untuk tim: bukti, permission rule, rollback, PDF gratis, Gumroad, dan konsultasi.