Overview #
Memahami arsitektur Flutter bukan hanya tentang memahami cara Flutter bekerja secara internal — tapi juga tentang memahami bagaimana seharusnya kamu menyusun kode aplikasimu. Artikel ini memberikan gambaran besar arsitektur Flutter dari dua sudut pandang: sistem internal Flutter dan panduan arsitektur aplikasi yang direkomendasikan Google.
Dua Perspektif Arsitektur Flutter #
Ketika berbicara tentang “arsitektur Flutter”, ada dua hal yang sering tercampur:
PERSPEKTIF 1: Arsitektur Internal Flutter
(Bagaimana Flutter itu sendiri dibangun)
Framework (Dart)
Engine (C++)
Embedder (Native)
---------------------------------------
PERSPEKTIF 2: Arsitektur Aplikasi Flutter
(Bagaimana kamu seharusnya menyusun kode aplikasimu)
UI Layer
Logic/Domain Layer (opsional)
Data Layer
Artikel ini membahas keduanya secara berurutan — dari internal ke praktis.
Arsitektur Internal: Layered System #
Seperti yang sudah dibahas di artikel sebelumnya, Flutter dibangun sebagai sistem berlapis. Prinsip utamanya adalah setiap lapisan hanya berkomunikasi dengan lapisan di atas atau di bawahnya secara langsung — tidak ada lapisan yang bisa “melompati” lapisan lain.
+------------------------------------------+
| Aplikasi Kamu |
+------------------------------------------+
| Flutter Framework |
| Material / Cupertino |
| Widgets |
| Rendering |
| Foundation, Animation, Gestures |
+------------------------------------------+
| Flutter Engine (C++) |
| Dart Runtime, Impeller/Skia, I/O |
+------------------------------------------+
| Platform Embedder |
| Android, iOS, Web, Desktop |
+------------------------------------------+
| Sistem Operasi |
+------------------------------------------+
Yang perlu diingat dari arsitektur internal ini:
- Setiap lapisan bersifat opsional dan dapat diganti — kamu bisa membangun design system sendiri tanpa menggunakan Material atau Cupertino
- Framework sepenuhnya ditulis dalam Dart — artinya kamu bisa membaca, memahami, bahkan memodifikasi source code-nya
- Engine ditulis dalam C++ dan bersifat platform-agnostic — ia tidak peduli sedang berjalan di platform apa
Arsitektur Aplikasi: Panduan Resmi Google #
Sejak 2023, Google merilis panduan resmi arsitektur aplikasi Flutter. Rekomendasinya adalah membagi aplikasi menjadi dua hingga tiga lapisan berdasarkan prinsip Separation of Concerns:
+------------------------------------------+
| UI Layer |
| Views (Widgets/Screens) |
| ViewModels (State + Logic UI) |
+------------------------------------------+
| Logic / Domain Layer |
| (opsional, untuk bisnis logic kompleks)|
+------------------------------------------+
| Data Layer |
| Repositories |
| Data Sources (API, DB, Local) |
+------------------------------------------+
UI Layer #
UI Layer bertanggung jawab untuk menampilkan data ke pengguna dan menangani interaksi pengguna. Layer ini terdiri dari dua komponen utama berdasarkan pola MVVM (Model-View-ViewModel):
View — Komposisi widget yang membentuk sebuah fitur atau halaman:
// View: hanya bertanggung jawab untuk tampilan
// Tidak ada business logic di sini
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
final viewModel = context.watch<ProfileViewModel>();
return Scaffold(
appBar: AppBar(title: const Text('Profil')),
body: switch (viewModel.state) {
ProfileLoading() => const CircularProgressIndicator(),
ProfileLoaded(:final user) => ProfileContent(user: user),
ProfileError(:final message) => ErrorView(message: message),
},
);
}
}
ViewModel — Menyimpan state UI dan mengekspos command (callback) ke View:
// ViewModel: menjembatani UI dan Data layer
class ProfileViewModel extends ChangeNotifier {
ProfileState _state = const ProfileLoading();
ProfileState get state => _state;
final UserRepository _repository;
ProfileViewModel(this._repository);
// Command yang dipanggil View
Future<void> loadProfile(String userId) async {
_state = const ProfileLoading();
notifyListeners();
try {
final user = await _repository.getUser(userId);
_state = ProfileLoaded(user: user);
} catch (e) {
_state = ProfileError(message: e.toString());
}
notifyListeners();
}
}
Logic / Domain Layer (Opsional) #
Layer ini hanya diperlukan jika aplikasimu memiliki business logic yang kompleks di sisi client — misalnya kalkulasi, transformasi data, atau aturan bisnis yang melibatkan banyak sumber data.
// Use case / interactor di domain layer
class GetRecommendedProductsUseCase {
final ProductRepository _productRepo;
final UserRepository _userRepo;
GetRecommendedProductsUseCase(this._productRepo, this._userRepo);
Future<List<Product>> execute(String userId) async {
final user = await _userRepo.getUser(userId);
final allProducts = await _productRepo.getProducts();
// Business logic: filter berdasarkan preferensi user
return allProducts
.where((p) => user.preferences.contains(p.category))
.take(10)
.toList();
}
}
Kapan perlu Domain Layer?
Jika aplikasimu hanya menampilkan data dari API dan memperbolehkan pengguna mengubahnya (CRUD app), kamu mungkin tidak perlu domain layer. Langsung hubungkan ViewModel ke Repository. Tambahkan domain layer hanya ketika business logic mulai kompleks dan melibatkan banyak repository sekaligus.
Data Layer #
Data Layer bertanggung jawab mengelola semua akses data — baik dari API, database lokal, maupun sumber lainnya. Komponen utamanya adalah Repository:
// Repository: single source of truth untuk tipe data tertentu
class UserRepository {
final UserApiDataSource _apiDataSource;
final UserLocalDataSource _localDataSource;
UserRepository({
required UserApiDataSource apiDataSource,
required UserLocalDataSource localDataSource,
}) : _apiDataSource = apiDataSource,
_localDataSource = localDataSource;
Future<User> getUser(String id) async {
// Cek cache lokal dulu
final cached = await _localDataSource.getUser(id);
if (cached != null) return cached;
// Jika tidak ada, ambil dari API
final user = await _apiDataSource.getUser(id);
// Simpan ke cache lokal
await _localDataSource.saveUser(user);
return user;
}
}
Unidirectional Data Flow (UDF) #
Prinsip arsitektur yang sangat penting dalam Flutter modern adalah Unidirectional Data Flow — data mengalir satu arah, dari Data Layer ke UI Layer. Events dari pengguna mengalir ke arah sebaliknya.
STATE mengalir ke bawah
|
v
+-------------------------------------------+
| Data Layer |
| Repository mengupdate state |
+-------------------------------------------+
|
v
+-------------------------------------------+
| Logic / Domain Layer |
| Use case memproses dan meneruskan state |
+-------------------------------------------+
|
v
+-------------------------------------------+
| UI Layer |
| ViewModel menyimpan state |
| View me-render berdasarkan state |
+-------------------------------------------+
|
EVENT mengalir ke atas
(tap, input, gesture)
|
v
+-------------------------------------------+
| View memanggil command di ViewModel |
| ViewModel memanggil method di Repository |
| Repository mengupdate data |
| State baru mengalir kembali ke UI |
+-------------------------------------------+
UDF membuat aplikasi lebih mudah diprediksi karena:
- State hanya diubah dari satu arah — tidak ada perubahan state yang “mengejutkan”
- Lebih mudah di-debug — kamu bisa melacak perubahan state dengan jelas
- Lebih mudah diuji — setiap layer bisa diuji secara independen
Single Source of Truth (SSOT) #
Prinsip penting lainnya adalah Single Source of Truth — setiap tipe data hanya boleh memiliki satu sumber kebenaran di seluruh aplikasi.
// SALAH: state yang sama disimpan di dua tempat
class CartScreen extends StatefulWidget {
// ...
}
class _CartScreenState extends State<CartScreen> {
List<CartItem> items = []; // state lokal duplikat!
// ...
}
// BENAR: satu sumber kebenaran — di Repository
class CartRepository {
final _items = ValueNotifier<List<CartItem>>([]);
ValueListenable<List<CartItem>> get items => _items;
void addItem(CartItem item) {
_items.value = [..._items.value, item];
}
void removeItem(String itemId) {
_items.value = _items.value.where((i) => i.id != itemId).toList();
}
}
Dengan SSOT, tidak akan ada kondisi di mana data di satu bagian aplikasi berbeda dengan data di bagian lain.
Separation of Concerns dalam Praktik #
Prinsip terpenting dalam arsitektur Flutter adalah Separation of Concerns — setiap class hanya bertanggung jawab atas satu hal:
| Komponen | Tanggung Jawab | Yang Tidak Boleh Ada |
|---|---|---|
| View (Widget) | Render UI, tangani input | Business logic, API call |
| ViewModel | State UI, command | Langsung akses database/API |
| Repository | Koordinasi data sources | Logika UI, state presentasi |
| Data Source | Akses satu sumber data | Logika bisnis, koordinasi |
// SALAH: business logic di dalam Widget
class ProductCard extends StatelessWidget {
final Product product;
Widget build(BuildContext context) {
// JANGAN lakukan ini di Widget!
final discountedPrice = product.price * (1 - product.discountRate);
final formattedPrice = NumberFormat.currency().format(discountedPrice);
final isAffordable = discountedPrice < userBudget; // dari mana userBudget?
return Card(child: Text(formattedPrice));
}
}
// BENAR: business logic di ViewModel atau Domain Layer
class ProductViewModel extends ChangeNotifier {
String get formattedPrice =>
NumberFormat.currency().format(product.discountedPrice);
bool get isAffordable => product.discountedPrice < _userBudget;
}
Struktur Folder yang Direkomendasikan #
Berdasarkan panduan resmi Google dan praktik industri, berikut struktur folder yang umum digunakan:
lib/
main.dart
app.dart # Root widget & routing
ui/ # UI Layer
home/
home_screen.dart
home_view_model.dart
profile/
profile_screen.dart
profile_view_model.dart
shared/
widgets/ # Reusable widgets
domain/ # Domain Layer (opsional)
use_cases/
get_recommended_products.dart
models/
user.dart
product.dart
data/ # Data Layer
repositories/
user_repository.dart
product_repository.dart
data_sources/
remote/
user_api_data_source.dart
local/
user_local_data_source.dart
Feature-based vs Layer-based Structure
Struktur di atas adalah layer-based — dikelompokkan berdasarkan lapisan arsitektur. Alternatifnya adalah feature-based — dikelompokkan berdasarkan fitur (
features/auth/,features/cart/,features/profile/). Untuk aplikasi besar dengan banyak tim, feature-based lebih scalable. Untuk aplikasi kecil-menengah, layer-based lebih mudah dipahami. Keduanya valid — yang terpenting adalah konsistensi.
Ringkasan #
- Arsitektur Flutter memiliki dua perspektif: internal (Framework, Engine, Embedder) dan aplikasi (UI Layer, Domain Layer, Data Layer).
- Panduan resmi Google merekomendasikan arsitektur berbasis MVVM dengan dua hingga tiga lapisan yang jelas.
- UI Layer terdiri dari View (widget/screen) dan ViewModel (state + command).
- Data Layer dikelola oleh Repository sebagai Single Source of Truth — satu sumber kebenaran untuk setiap tipe data.
- Domain Layer bersifat opsional — hanya diperlukan untuk business logic yang kompleks di sisi client.
- Unidirectional Data Flow memastikan state mengalir satu arah, membuat aplikasi lebih mudah diprediksi dan diuji.
- Separation of Concerns adalah prinsip terpenting — setiap class hanya boleh bertanggung jawab atas satu hal.
← Sebelumnya: Engine, Framework & Embedder Berikutnya: Framework Layer →