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 |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β YOUR DART CODE β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β β Widgets (Material / Cupertino / Custom) β β β βββββββββββββββββββββββββ¬ββββββββββββββββββββββββββ β β βΌ β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β β Flutter Framework β β β β ββββββββββ ββββββββββββ ββββββββββ ββββββββββ β β β β βMaterialβ βRendering β β Anim β βGesture β β β β β β Lib β β Engine β β Engine β β Detect β β β β β ββββββββββ ββββββ¬ββββββ ββββββββββ ββββββββββ β β β βββββββββββββββββββββΌββββββββββββββββββββββββββββββ β β βΌ β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β β Flutter Engine (C++) β Skia + Dart VM β β β βββββββββββββββββββββββββ¬ββββββββββββββββββββββββββ β β βΌ β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β β β Platform Embedder (Android / iOS / Web / Desk) β β β βββββββββββββββββββββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
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
# 1. Download Flutter SDK dari https://flutter.dev
# 2. Extract dan tambahkan ke PATH
# Linux / macOS:
export PATH="$PATH:/path/to/flutter/bin"
# Windows (PowerShell):
# [Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\flutter\bin", "User")
# Verifikasi instalasi
flutter doctor
# Output:
# Doctor summary (to see all details, run flutter doctor -v):
# [β] Flutter (Channel stable, 3.22.0)
# [β] Android toolchain
# [β] Chrome (web)
# [β] Android Studio
# [β] VS Code
Instalasi Flutter SDK & Buat Proyek Baru
# Buat proyek Flutter baru flutter create my_flutter_app # Masuk ke direktori proyek cd my_flutter_app # Jalankan di emulator / device flutter run # Jalankan di Chrome (web) flutter run -d chrome # Jalankan di device Android yang terhubung flutter run -d <device_id> # Cek device yang tersedia flutter devices
Variabel & Tipe Data Dart
// ===== VARIABEL & TIPE DATA =====
// Deklarasi variabel
String nama = 'BeebaneLabs';
int umur = 25;
double tinggi = 175.5;
bool isActive = true;
// var β tipe di-inferensi otomatis
var kota = 'Jakarta'; // String
var populasi = 10000000; // int
// final β nilai tidak bisa diubah setelah di-set (runtime)
final tanggalSekarang = DateTime.now();
// const β nilai compile-time constant
const pi = 3.14159;
const appName = 'BeebaneLabs App';
// Nullable (Dart null safety)
String? alamatOpsional = null; // Bisa null
int? nomorRumah; // Default null
// Collection
List<String> buah = ['Apel', 'Mangga', 'Jeruk'];
Map<String, int> harga = {'Apel': 5000, 'Mangga': 8000};
Set<int> angkaUnik = {1, 2, 3, 4, 5};
// String interpolation
print('Halo, saya $nama berumur $umur tahun');
print('Total: ${buah.length} buah');
Function & Arrow Syntax
// Function biasa
int tambah(int a, int b) {
return a + b;
}
// Arrow function (untuk satu expression)
int kali(int a, int b) => a * b;
// Named parameters
String sapa({required String nama, String pesan = 'Halo'}) {
return '$pesan, $nama!';
}
print(sapa(nama: 'Flutter')); // "Halo, Flutter!"
// Optional positional parameters
double hitungLuas(double panjang, [double lebar = 0]) {
return panjang * lebar;
}
// Async function
Future<String> fetchData() async {
final response = await http.get(Uri.parse('https://api.example.com'));
return response.body;
}
// Lambda / Anonymous function
var list = [1, 2, 3, 4, 5];
var genap = list.where((angka) => angka % 2 == 0).toList();
print(genap); // [2, 4]
Class & Object-Oriented Programming
// Class dasar
class Mahasiswa {
String nama;
int umur;
String jurusan;
// Constructor
Mahasiswa({
required this.nama,
required this.umur,
this.jurusan = 'Teknik Informatika',
});
// Named constructor
Mahasiswa.angkatanBaru(this.nama)
: umur = 18,
jurusan = 'Belum ditentukan';
// Method
void perkenalkan() {
print('Halo, saya $nama, umur $umur, jurusan $jurusan');
}
// Getter
String get info => '$nama ($umur tahun)';
// Override toString
@override
String toString() => 'Mahasiswa($nama, $umur)';
}
// Inheritance
class MahasiswaS2 extends Mahasiswa {
String topikRiset;
MahasiswaS2({
required String nama,
required this.topikRiset,
}) : super(nama: nama, umur: 24, jurusan: 'S2');
}
// Penggunaan
void main() {
var mhs = Mahasiswa(nama: 'Budi', umur: 20);
mhs.perkenalkan();
var mhs2 = Mahasiswa.angkatanBaru('Ani');
print(mhs2.info);
}
Dart menggunakan sound null safety sejak versi 2.12+. Semua variabel secara default tidak bisa null. Untuk membuat variabel yang bisa null, gunakan tanda ? (misalnya String?). Gunakan operator ?? (default value), ?. (safe access), dan ! (force unwrap) untuk menangani nullable values.
3. Sistem Widget Flutter
Di Flutter, semuanya adalah widget. Widget adalah blok pembangun UI yang mendeskripsikan bagaimana tampilan seharusnya terlihat berdasarkan konfigurasi dan state saat ini. Widget bisa berupa tombol, teks, gambar, layout, atau bahkan seluruh halaman.
Flutter menggunakan pendekatan declarative UI β Anda mendeskripsikan UI berdasarkan state saat ini, dan framework yang akan membangun dan memperbarui UI secara efisien. Ini berbeda dengan pendekatan imperative di mana Anda secara manual mengubah properti widget.
Widget Dasar yang Sering Digunakan
import 'package:flutter/material.dart';
// ===== WIDGET-WIDGET DASAR FLUTTER =====
// Text β Menampilkan teks
Text(
'Halo, Flutter!',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
)
// Container β Box model dengan dekorasi
Container(
width: 200,
height: 100,
padding: EdgeInsets.all(16),
margin: EdgeInsets.symmetric(vertical: 8),
decoration: BoxDecoration(
color: Colors.blue.shade100,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(color: Colors.black26, blurRadius: 8),
],
),
child: Text('Ini Container'),
)
// Icon β Menampilkan ikon Material
Icon(Icons.home, size: 32, color: Colors.green)
// Image β Menampilkan gambar
Image.network('https://picsum.photos/200')
Image.asset('assets/images/logo.png')
// ElevatedButton β Tombol dengan elevasi
ElevatedButton(
onPressed: () => print('Ditekan!'),
child: Text('Tekan Saya'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
)
Composite Widget
// Card β Material Design card
Card(
elevation: 4,
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text('Judul Card', style: TextStyle(fontSize: 18)),
SizedBox(height: 8),
Text('Deskripsi card dengan detail informasi...'),
],
),
),
)
// ListTile β Item list standar Material
ListTile(
leading: CircleAvatar(child: Icon(Icons.person)),
title: Text('Nama Pengguna'),
subtitle: Text('email@example.com'),
trailing: Icon(Icons.arrow_forward_ios),
onTap: () => print('Item diketuk'),
)
// Scaffold β Struktur dasar halaman Material
Scaffold(
appBar: AppBar(title: Text('Halaman Utama')),
body: Center(child: Text('Konten di sini')),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Cari'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profil'),
],
),
)
Flutter membangun UI sebagai tree of widgets. Setiap widget bisa memiliki child atau children. Flutter memiliki 3 fase rendering: Build (membuat widget tree), Layout (menghitung posisi dan ukuran), dan Paint (menggambar ke layar). Optimasi dilakukan dengan hanya me-render ulang widget yang berubah.
4. Stateless vs Stateful Widget
Flutter memiliki dua jenis widget utama: StatelessWidget dan StatefulWidget. Pemahaman kapan menggunakan masing-masing sangat krusial untuk membangun aplikasi Flutter yang efisien.
StatelessWidget
Widget yang tidak memiliki internal state. Setelah dibuat, propertinya tidak berubah. Cocok untuk elemen UI yang statis seperti label teks, ikon, atau kartu informasi.
import 'package:flutter/material.dart';
class ProfileCard extends StatelessWidget {
final String nama;
final String email;
final String avatarUrl;
// Constructor dengan named parameters
const ProfileCard({
Key? key,
required this.nama,
required this.email,
required this.avatarUrl,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(16),
elevation: 4,
child: Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
CircleAvatar(
radius: 30,
backgroundImage: NetworkImage(avatarUrl),
),
SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
nama,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 4),
Text(
email,
style: TextStyle(color: Colors.grey.shade600),
),
],
),
),
],
),
),
);
}
}
// Penggunaan:
// ProfileCard(
// nama: 'Budi Santoso',
// email: 'budi@email.com',
// avatarUrl: 'https://i.pravatar.cc/150',
// )
StatefulWidget
Widget yang memiliki state yang bisa berubah selama lifecycle-nya. Cocok untuk counter, form input, animasi, atau data yang berubah berdasarkan interaksi user.
import 'package:flutter/material.dart';
class CounterWidget extends StatefulWidget {
const CounterWidget({Key? key}) : super(key: key);
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
bool _isLoading = false;
void _increment() {
setState(() {
_count++;
});
}
void _decrement() {
setState(() {
if (_count > 0) _count--;
});
}
void _reset() {
setState(() {
_count = 0;
});
}
@override
Widget build(BuildContext context) {
return Card(
margin: EdgeInsets.all(16),
child: Padding(
padding: EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Counter',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
SizedBox(height: 16),
Text(
'$_count',
style: TextStyle(fontSize: 48, fontWeight: FontWeight.bold,
color: Colors.blue),
),
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: _decrement,
icon: Icon(Icons.remove_circle_outline),
iconSize: 36,
),
SizedBox(width: 16),
ElevatedButton(
onPressed: _reset,
child: Text('Reset'),
),
SizedBox(width: 16),
IconButton(
onPressed: _increment,
icon: Icon(Icons.add_circle_outline),
iconSize: 36,
),
],
),
],
),
),
);
}
}
setState() cocok untuk state lokal yang sederhana. Untuk state yang kompleks atau perlu dibagikan antar banyak widget, pertimbangkan state management solution seperti Provider, Riverpod, atau Bloc. Jangan panggil setState() setelah widget di-dispose β cek dengan mounted property.
Lifecycle StatefulWidget
| Method | Kapan Dipanggil | Kegunaan |
|---|---|---|
createState() | Pertama kali widget dibuat | Membuat State object |
initState() | Sekali setelah State dibuat | Inisialisasi data, listener, controller |
build() | Setiap setState() dipanggil | Mem-build widget tree |
didUpdateWidget() | Saat parent rebuild dengan config baru | Merespon perubahan properti |
dispose() | Saat widget dihapus dari tree | Cleanup: controller, stream, listener |
5. Layouts & Responsive Design
Flutter menyediakan berbagai widget layout untuk menyusun widget-child secara fleksibel. Tiga layout utama adalah Row (horizontal), Column (vertikal), dan Stack (tumpukan).
Row (β) Column (β) Stack (β)
ββββββββββββββββββββ ββββββββββ ββββββββββ
β Child1 Child2 β βChild1 β β Child3 β β paling atas
β β βChild2 β β Child2 β
β β βChild3 β β Child1 β β paling bawah
ββββββββββββββββββββ ββββββββββ ββββββββββ
ListView (β scroll) GridView (β grid)
ββββββββββββββββββββ ββββββββββββββββββββ
β Item 1 β β ββββ ββββ β
β Item 2 β β β β β β β
β Item 3 β β ββββ ββββ β
β ... β β ββββ ββββ β
ββββββββββββββββββββ β β β β β β
β ββββ ββββ β
ββββββββββββββββββββ
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: