1. Pengenalan Flutter
Flutter adalah framework UI open-source yang dikembangkan oleh Google untuk membangun aplikasi mobile, web, dan desktop dari satu basis kode. Menggunakan bahasa pemrograman Dart, Flutter memungkinkan developer membangun aplikasi yang berjalan native di Android, iOS, Windows, macOS, Linux, dan web — hanya dengan satu codebase.
Flutter pertama kali diperkenalkan pada tahun 2017 dan sejak itu telah digunakan oleh perusahaan besar seperti Google, BMW, Alibaba, dan banyak lainnya. Keunggulan utama Flutter adalah hot reload yang memungkinkan perubahan kode langsung terlihat tanpa restart aplikasi, serta performa tinggi yang mendekati native berkat rendering engine sendiri.
Mengapa Memilih Flutter?
| Keunggulan | Penjelasan |
|---|---|
| Satu Codebase | Tulis sekali, jalankan di Android, iOS, Web, Desktop — menghemat waktu dan biaya pengembangan |
| Hot Reload | Perubahan kode langsung terlihat dalam hitungan milidetik tanpa kehilangan state |
| Performa Native | Dikompilasi ke ARM machine code, menggunakan rendering engine Skia — performa mendekati native |
| Widget Kaya | Ratusan widget Material Design dan Cupertino yang sudah siap pakai |
| Komunitas Besar | Ekosistem package pub.dev yang sangat kaya, dokumentasi lengkap |
| Gratis & Open Source | BSD license, tidak ada biaya lisensi, bebas digunakan untuk proyek komersial |
| Google Support | Dikembangkan dan dipelihara oleh Google, digunakan di produk-produk Google |
Flutter vs Alternatif Lain
| Aspek | Flutter | React Native | Native (Swift/Kotlin) |
|---|---|---|---|
| Bahasa | Dart | JavaScript/TypeScript | Swift / Kotlin |
| Rendering | Skia (custom engine) | Native bridge | Native |
| Performa | 🟢 Sangat baik | 🟡 Baik | 🟢 Terbaik |
| Hot Reload | ✅ Sangat cepat | ✅ Fast Refresh | ⚠️ Terbatas |
| UI Consistency | Sama di semua platform | Native feel per platform | Native per platform |
| Kurva Belajar | Mudah-Sedang | Mudah (jika tahu React) | Sedang-Sulit |
2. Dasar Pemrograman Dart
Sebelum mulai dengan Flutter, kita perlu memahami dasar-dasar Dart — bahasa pemrograman yang digunakan oleh Flutter. Dart dikembangkan oleh Google dan memiliki sintaks yang familiar bagi yang pernah menggunakan Java, JavaScript, atau C#.
Instalasi Flutter SDK
Row & Column
// Row — Layout horizontal
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.star, color: Colors.yellow, size: 40),
Icon(Icons.star, color: Colors.yellow, size: 40),
Icon(Icons.star, color: Colors.yellow, size: 40),
],
)
// Column — Layout vertikal
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('Judul', style: TextStyle(fontSize: 24)),
SizedBox(height: 8),
Text('Subjudul', style: TextStyle(fontSize: 16)),
SizedBox(height: 16),
ElevatedButton(onPressed: () {}, child: Text('Aksi')),
],
)
// Expanded & Flexible — Mengisi ruang yang tersisa
Row(
children: [
Expanded(
flex: 2,
child: Container(color: Colors.blue, height: 100),
),
SizedBox(width: 8),
Expanded(
flex: 1,
child: Container(color: Colors.red, height: 100),
),
],
)
ListView & GridView
// ListView — Scrollable list
ListView(
children: [
ListTile(title: Text('Item 1'), leading: Icon(Icons.star)),
ListTile(title: Text('Item 2'), leading: Icon(Icons.book)),
ListTile(title: Text('Item 3'), leading: Icon(Icons.music)),
],
)
// ListView.builder — Efficient untuk list panjang
ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item ${index + 1}'),
leading: CircleAvatar(child: Text('${index + 1}')),
);
},
)
// GridView — Grid layout
GridView.count(
crossAxisCount: 2,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
padding: EdgeInsets.all(8),
children: List.generate(6, (index) {
return Card(
child: Center(
child: Text('Grid ${index + 1}',
style: TextStyle(fontSize: 18)),
),
);
}),
)
MainAxisAlignment mengatur posisi sepanjang sumbu utama (Row → horizontal, Column → vertikal). CrossAxisAlignment mengatur posisi sepanjang sumbu silang. Nilai umum: start, center, end, spaceBetween, spaceEvenly, spaceAround.
6. Navigasi & Routing
Flutter menggunakan sistem Navigator berbasis stack untuk berpindah antar halaman (route). Setiap halaman di-push ke stack dan di-pop saat kembali.
Navigator Dasar
import 'package:flutter/material.dart';
// ===== HALAMAN UTAMA =====
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Beranda')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
// Push halaman baru ke stack
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(
itemId: 42,
itemName: 'Produk Flutter',
),
),
);
},
child: Text('Buka Detail'),
),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
// Push dan ganti halaman (tidak bisa kembali)
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => LoginPage()),
);
},
child: Text('Login'),
),
],
),
),
);
}
}
// ===== HALAMAN DETAIL (menerima data) =====
class DetailPage extends StatelessWidget {
final int itemId;
final String itemName;
const DetailPage({required this.itemId, required this.itemName});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(itemName)),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text('ID: $itemId', style: TextStyle(fontSize: 20)),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
// Kembali dengan mengirim data
Navigator.pop(context, 'Data dari detail');
},
child: Text('Kembali'),
),
],
),
),
);
}
}
Named Routes
// Definisi named routes di MaterialApp
MaterialApp(
title: 'Aplikasi Flutter',
initialRoute: '/',
routes: {
'/': (context) => HomePage(),
'/detail': (context) => DetailPage(itemId: 0, itemName: ''),
'/login': (context) => LoginPage(),
'/profile': (context) => ProfilePage(),
},
)
// Navigasi menggunakan named route
Navigator.pushNamed(context, '/profile');
// Mengirim arguments
Navigator.pushNamed(
context,
'/detail',
arguments: {'id': 42, 'name': 'Produk A'},
);
// Menerima arguments di halaman tujuan
final args = ModalRoute.of(context)!.settings.arguments as Map;
7. Formulir & Input Handling
Flutter menyediakan widget Form dan TextFormField untuk menangani input pengguna dengan validasi built-in.
import 'package:flutter/material.dart';
class RegistrationForm extends StatefulWidget {
@override
State<RegistrationForm> createState() => _RegistrationFormState();
}
class _RegistrationFormState extends State<RegistrationForm> {
final _formKey = GlobalKey<FormState>();
final _namaController = TextEditingController();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _obscurePassword = true;
String? _selectedGender;
@override
void dispose() {
_namaController.dispose();
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
void _submitForm() {
if (_formKey.currentState!.validate()) {
// Form valid — proses data
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Registrasi berhasil!')),
);
print('Nama: ${_namaController.text}');
print('Email: ${_emailController.text}');
print('Gender: $_selectedGender');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Registrasi')),
body: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Nama
TextFormField(
controller: _namaController,
decoration: InputDecoration(
labelText: 'Nama Lengkap',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Nama tidak boleh kosong';
}
if (value.length < 3) {
return 'Nama minimal 3 karakter';
}
return null;
},
),
SizedBox(height: 16),
// Email
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Email',
prefixIcon: Icon(Icons.email),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Email tidak boleh kosong';
}
if (!value.contains('@')) {
return 'Email tidak valid';
}
return null;
},
),
SizedBox(height: 16),
// Password
TextFormField(
controller: _passwordController,
obscureText: _obscurePassword,
decoration: InputDecoration(
labelText: 'Password',
prefixIcon: Icon(Icons.lock),
suffixIcon: IconButton(
icon: Icon(_obscurePassword
? Icons.visibility_off
: Icons.visibility),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Password tidak boleh kosong';
}
if (value.length < 8) {
return 'Password minimal 8 karakter';
}
return null;
},
),
SizedBox(height: 16),
// Dropdown
DropdownButtonFormField<String>(
value: _selectedGender,
decoration: InputDecoration(
labelText: 'Jenis Kelamin',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
items: ['Laki-laki', 'Perempuan'].map((g) {
return DropdownMenuItem(value: g, child: Text(g));
}).toList(),
onChanged: (value) {
setState(() => _selectedGender = value);
},
validator: (value) =>
value == null ? 'Pilih jenis kelamin' : null,
),
SizedBox(height: 24),
// Submit Button
ElevatedButton(
onPressed: _submitForm,
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text('Daftar', style: TextStyle(fontSize: 16)),
),
],
),
),
),
);
}
}
8. HTTP Requests & API
Untuk berkomunikasi dengan REST API, Flutter menggunakan package http dari pub.dev. Kita akan memanggil API publik dan menampilkan data dalam bentuk list.
Instalasi Package HTTP
# Tambahkan dependency http
flutter pub add http
# Atau manual di pubspec.yaml:
# dependencies:
# http: ^1.2.0
# Lalu jalankan:
flutter pub get
Model & Service API
import 'dart:convert';
import 'package:http/http.dart' as http;
// ===== MODEL =====
class Post {
final int id;
final String title;
final String body;
Post({required this.id, required this.title, required this.body});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
id: json['id'],
title: json['title'],
body: json['body'],
);
}
}
// ===== API SERVICE =====
class ApiService {
static const String baseUrl = 'https://jsonplaceholder.typicode.com';
// GET — Ambil semua post
static Future<List<Post>> getPosts() async {
final response = await http.get(Uri.parse('$baseUrl/posts'));
if (response.statusCode == 200) {
List data = json.decode(response.body);
return data.map((json) => Post.fromJson(json)).toList();
} else {
throw Exception('Gagal memuat data');
}
}
// GET — Ambil satu post
static Future<Post> getPost(int id) async {
final response = await http.get(Uri.parse('$baseUrl/posts/$id'));
if (response.statusCode == 200) {
return Post.fromJson(json.decode(response.body));
} else {
throw Exception('Post tidak ditemukan');
}
}
// POST — Buat post baru
static Future<Post> createPost(String title, String body) async {
final response = await http.post(
Uri.parse('$baseUrl/posts'),
headers: {'Content-Type': 'application/json'},
body: json.encode({
'title': title,
'body': body,
'userId': 1,
}),
);
if (response.statusCode == 201) {
return Post.fromJson(json.decode(response.body));
} else {
throw Exception('Gagal membuat post');
}
}
}
Menampilkan Data dari API
class PostsPage extends StatefulWidget {
@override
State<PostsPage> createState() => _PostsPageState();
}
class _PostsPageState extends State<PostsPage> {
late Future<List<Post>> _futurePosts;
@override
void initState() {
super.initState();
_futurePosts = ApiService.getPosts();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Posts dari API')),
body: FutureBuilder<List<Post>>(
future: _futurePosts,
builder: (context, snapshot) {
// Loading state
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
// Error state
if (snapshot.hasError) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 64, color: Colors.red),
SizedBox(height: 16),
Text('Error: ${snapshot.error}'),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
setState(() {
_futurePosts = ApiService.getPosts();
});
},
child: Text('Coba Lagi'),
),
],
),
);
}
// Success state
final posts = snapshot.data!;
return RefreshIndicator(
onRefresh: () async {
setState(() {
_futurePosts = ApiService.getPosts();
});
},
child: ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
final post = posts[index];
return Card(
margin: EdgeInsets.symmetric(
horizontal: 12, vertical: 4),
child: ListTile(
leading: CircleAvatar(
child: Text('${post.id}'),
),
title: Text(
post.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
post.body,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
onTap: () {
// Buka detail post
},
),
);
},
),
);
},
),
);
}
}
Untuk proyek yang lebih besar, pertimbangkan menggunakan Dio — HTTP client yang lebih powerful dengan fitur interceptor, request cancellation, file upload/download, dan transformasi data. Cukup jalankan flutter pub add dio untuk menginstalnya.
9. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Flutter: