đ Daftar Isi
1. Dart Variables & Types
đĄ Tips
Gunakan final untuk variabel yang hanya diinisialisasi sekali, dan const untuk nilai yang sudah diketahui saat compile-time.
Deklarasi Variabel
// Tipe eksplisit
String nama = 'Budi';
int umur = 25;
double tinggi = 175.5;
bool aktif = true;
// Tipe dinamis
var kota = 'Jakarta'; // Inferred sebagai String
dynamic data = 'bisa apa saja'; // Bisa berubah tipe
// final & const
final waktu = DateTime.now(); // Runtime const, tidak bisa diubah
const pi = 3.14159; // Compile-time const
// Nullable (Dart null safety)
String? alamat; // Bisa null
int? nilai; // Bisa null
String nama2 = 'Wajib isi'; // Tidak boleh null
// Null safety operators
print(alamat?.length); // Null-aware access
String namaFinal = nama2 ?? 'Default'; // Null coalescing
String namaUpper = nama2.toUpperCase(); // Cascade
alamat ??= 'Belum diisi'; // Null-aware assignmentTipe Data Dart
| Tipe | Keterangan | Contoh |
|---|---|---|
int | Bilangan bulat | int x = 42; |
double | Desimal | double y = 3.14; |
num | Parent int/double | num z = 7; |
String | Teks | String s = 'hello'; |
bool | Boolean | bool b = true; |
List | Array | List<int> a = [1,2,3]; |
Map | Key-value pairs | Map<String,int> m = {'a':1}; |
Set | Unique collection | Set<int> s = {1,2,3}; |
dynamic | Tipe apapun | dynamic x = 'abc'; |
var | Inferred type | var x = 10; |
final | Sekali assign | final x = getValue(); |
const | Compile-time constant | const x = 42; |
2. Dart Functions
// Fungsi dasar
int tambah(int a, int b) {
return a + b;
}
// Arrow function (satu expression)
int kali(int a, int b) => a * b;
// Named parameters
void greet({required String nama, String salam = 'Halo'}) {
print('$salam, $nama!');
}
greet(nama: 'Budi'); // Halo, Budi!
greet(nama: 'Budi', salam: 'Hi'); // Hi, Budi!
// Optional positional parameters
void log(String message, [String? prefix]) {
print('${prefix ?? 'LOG'}: $message');
}
log('Server started'); // LOG: Server started
log('Server started', 'INFO'); // INFO: Server started
// Anonymous function / lambda
var list = [1, 2, 3, 4, 5];
var genap = list.where((n) => n % 2 == 0).toList();
// [2, 4]
// Higher-order function
List<int> apply(List<int> list, int Function(int) fn) {
return list.map(fn).toList();
}
var hasil = apply([1, 2, 3], (x) => x * 2);
// [2, 4, 6]
// Cascade notation
var buffer = StringBuffer()
..write('Hello ')
..write('World')
..write('!');
print(buffer.toString()); // Hello World!3. Dart Async/Await & Future
â ī¸ Peringatan
Selalu gunakan async/await untuk operasi async di Flutter. Hindari .then() chaining yang membuat kode sulit dibaca.
// Future - asynchronous computation
Future<String> fetchData() async {
// Simulasi network request
await Future.delayed(Duration(seconds: 2));
return 'Data loaded!';
}
// Async/Await
void loadData() async {
try {
String data = await fetchData();
print(data); // Data loaded!
} catch (e) {
print('Error: $e');
}
}
// Future chaining dengan .then()
fetchData()
.then((data) => print(data))
.catchError((e) => print('Error: $e'));
// Stream - multiple async values
Stream<int> countStream(int max) async* {
for (int i = 1; i <= max; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
// Mendengarkan Stream
void listenStream() async {
await for (int value in countStream(5)) {
print('Count: $value');
}
}
// StreamBuilder di Flutter
// StreamBuilder<int>(
// stream: countStream(10),
// builder: (context, snapshot) {
// if (snapshot.hasData) {
// return Text('Count: ${snapshot.data}');
// }
// return CircularProgressIndicator();
// },
// )
// Completer - manual future completion
import 'dart:async';
Future<String> getAuth() {
final completer = Completer<String>();
// Misalnya callback-based API
onAuthSuccess((token) {
completer.complete(token);
});
return completer.future;
}4. Dart Collections
List
// List
var buah = ['apel', 'jeruk', 'mangga'];
buah.add('pisang');
buah.addAll(['anggur', 'melon']);
buah.remove('jeruk');
buah.removeAt(0);
buah.insert(0, 'durian');
// List properties & methods
buah.length; // Jumlah elemen
buah.isEmpty; // true jika kosong
buah.isNotEmpty; // true jika ada isi
buah.first; // Elemen pertama
buah.last; // Elemen terakhir
buah.contains('apel'); // true/false
buah.sublist(0, 2); // [durian, mangga]
buah.shuffle(); // Acak urutan
// Spread operator & collection if/for
var a = [1, 2, 3];
var b = [0, ...a]; // [0, 1, 2, 3]
var c = [0, ...?a]; // null-safe spread
var showItem = true;
var items = [
'item1',
if (showItem) 'item2', // Conditional
];
var doubled = [for (var i in [1,2,3]) i * 2]; // [2, 4, 6]Map
// Map
var mahasiswa = {
'nama': 'Budi',
'nim': '2024001',
'ipk': 3.75,
};
// Operasi Map
mahasiswa['semester'] = 5;
mahasiswa.remove('ipk');
mahasiswa.containsKey('nama'); // true
mahasiswa.containsValue('Budi'); // true
mahasiswa.keys; // (nama, nim, semester)
mahasiswa.values; // (Budi, 2024001, 5)
// Iterasi
mahasiswa.forEach((key, value) {
print('$key: $value');
});
// Map dari List
var angka = [1, 2, 3];
var mapAngka = angka.asMap(); // {0: 1, 1: 2, 2: 3}5. Common Flutter Widgets
Layout Widgets
| Widget | Keterangan | Contoh Penggunaan |
|---|---|---|
Container | Box dengan padding, margin, decoration | Wrapper serbaguna |
Row | Susun anak secara horizontal | Navbar, button group |
Column | Susun anak secara vertikal | Form, daftar item |
Stack | Susun anak bertumpuk (z-axis) | Overlay, badge |
Wrap | Seperti Row tapi bisa wrap ke baris baru | Chip/tag |
ListView | Scrollable list | Daftar item panjang |
GridView | Grid scrollable | Gallery, katalog |
Expanded | Expand anak di Row/Column | Flex layout |
Flexible | Seperti Expanded tapi bisa shrink | Responsive layout |
SizedBox | Box dengan ukuran tetap | Spacer, fixed size |
Padding | Tambahkan padding | Spacing konten |
Center | Center anak | Content centering |
Align | Align anak ke posisi tertentu | Positioning |
Scaffold | Struktur halaman Material | Setiap halaman utama |
SafeArea | Hindari notch/status bar | Wrapper halaman |
Input & Display Widgets
| Widget | Keterangan |
|---|---|
Text | Tampilkan teks |
TextField | Input teks |
ElevatedButton | Tombol dengan elevation |
TextButton | Tombol flat tanpa elevation |
IconButton | Tombol berupa ikon |
Image | Tampilkan gambar (asset/network/file) |
Icon | Tampilkan ikon Material |
Card | Card Material Design |
ListTile | List item Material |
CircleAvatar | Bulat, untuk foto profil |
Chip | Label kecil compact |
Checkbox | Checkbox input |
Switch | Toggle on/off |
DropdownButton | Dropdown selection |
Slider | Input range nilai |
CircularProgressIndicator | Loading spinner |
LinearProgressIndicator | Loading bar |
6. StatelessWidget vs StatefulWidget
StatelessWidget
âšī¸ Info
Gunakan StatelessWidget untuk widget yang tidak punya state internal â tampilannya hanya bergantung pada input (constructor) dan Theme.
class GreetingCard extends StatelessWidget {
final String nama;
const GreetingCard({super.key, required this.nama});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
'Halo, $nama!',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
);
}
}StatefulWidget
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: Text('Count: $_count', style: TextStyle(fontSize: 32)),
),
floatingActionButton: FloatingActionButton(
onPressed: _increment,
child: Icon(Icons.add),
),
);
}
}Perbandingan
| Aspek | StatelessWidget | StatefulWidget |
|---|---|---|
| State | Tidak ada state internal | Punya state yang bisa berubah |
| Rebuild | Hanya dari parent | Dari parent + setState() |
| Use case | Tombol, label, card statis | Counter, form, animation |
| Performance | Lebih ringan | Sedikit lebih berat |
| Lifecycle | build() saja | initState, build, dispose, dll. |
7. Navigation & Routing
Navigator (Basic)
// Push halaman baru
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailPage(id: 42)),
);
// Push dan ganti halaman (tidak bisa kembali)
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => HomePage()),
);
// Push dan hapus semua stack
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => LoginPage()),
(route) => false,
);
// Kembali ke halaman sebelumnya
Navigator.pop(context);
// Pop dengan result
Navigator.pop(context, 'result_data');
// Push dan dapatkan result
final result = await Navigator.push<String>(
context,
MaterialPageRoute(builder: (context) => SearchPage()),
);
print('Result: $result');Named Routes
// Definisi routes di MaterialApp
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomePage(),
'/detail': (context) => DetailPage(),
'/settings': (context) => SettingsPage(),
},
);
// Navigasi dengan named route
Navigator.pushNamed(context, '/detail');
Navigator.pushNamed(context, '/detail', arguments: {'id': 42});
// Ambil arguments di halaman tujuan
final args = ModalRoute.of(context)!.settings.arguments as Map;8. setState & State Lifecycle
StatefulWidget Lifecycle
| Method | Kapan Dipanggil |
|---|---|
initState() | Sekali saat widget dibuat (inisialisasi data) |
didChangeDependencies() | Setelah initState, atau saat InheritedWidget berubah |
build() | Setiap kali UI perlu di-rebuild |
didUpdateWidget() | Saat parent widget rebuild dengan widget baru |
setState() | Trigger rebuild (dipanggil manual) |
deactivate() | Saat widget dihapus dari tree sementara |
dispose() | Saat widget dihapus permanen (cleanup) |
class TimerPage extends StatefulWidget {
@override
State<TimerPage> createState() => _TimerPageState();
}
class _TimerPageState extends State<TimerPage> {
int _seconds = 0;
late Timer _timer;
@override
void initState() {
super.initState();
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
setState(() {
_seconds++;
});
});
}
@override
void dispose() {
_timer.cancel(); // WAJIB cancel timer
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(child: Text('$_seconds detik'));
}
}9. Provider Basics
đĄ Tips
Provider adalah state management yang direkomendasikan oleh tim Flutter. Alternatif lain: Riverpod, BLoC, GetX.
Setup
// pubspec.yaml
dependencies:
provider: ^6.1.01. Buat ChangeNotifier
import 'package:flutter/foundation.dart';
class CartModel extends ChangeNotifier {
final List<Map<String, dynamic>> _items = [];
List<Map<String, dynamic>> get items => _items;
int get itemCount => _items.length;
double get totalPrice =>
_items.fold(0, (sum, item) => sum + (item['price'] * item['qty']));
void addItem(String name, double price) {
_items.add({'name': name, 'price': price, 'qty': 1});
notifyListeners(); // WAJIB untuk trigger rebuild
}
void removeItem(int index) {
_items.removeAt(index);
notifyListeners();
}
void clear() {
_items.clear();
notifyListeners();
}
}2. Provide di Root
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CartModel()),
ChangeNotifierProvider(create: (_) => AuthModel()),
],
child: MyApp(),
),
);
}3. Konsumsi di Widget
// Consumer - rebuild hanya widget di dalam builder
Consumer<CartModel>(
builder: (context, cart, child) {
return Text('${cart.itemCount} item');
},
)
// Provider.of - baca tanpa rebuild
final cart = Provider.of<CartModel>(context, listen: false);
cart.addItem('Kaos', 150000);
// context.read - sama dengan listen: false (sekali baca)
context.read<CartModel>().addItem('Kaos', 150000);
// context.watch - rebuild saat berubah
final count = context.watch<CartModel>().itemCount;
// Selector - rebuild hanya saat value tertentu berubah
Selector<CartModel, int>(
selector: (_, cart) => cart.itemCount,
builder: (_, count, __) => Text('$count items'),
)10. HTTP Requests
Setup
// pubspec.yaml
dependencies:
http: ^1.2.0
// Import
import 'package:http/http.dart' as http;
import 'dart:convert';CRUD Operations
// GET - Ambil data
Future<List<dynamic>> getUsers() async {
final response = await http.get(
Uri.parse('https://api.example.com/users'),
headers: {'Authorization': 'Bearer $token'},
);
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
throw Exception('Gagal memuat data: ${response.statusCode}');
}
}
// POST - Kirim data
Future<Map<String, dynamic>> createUser(String nama, String email) async {
final response = await http.post(
Uri.parse('https://api.example.com/users'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer $token',
},
body: jsonEncode({
'nama': nama,
'email': email,
}),
);
if (response.statusCode == 201) {
return jsonDecode(response.body);
} else {
throw Exception('Gagal membuat user');
}
}
// PUT - Update data
Future<void> updateUser(int id, Map<String, dynamic> data) async {
final response = await http.put(
Uri.parse('https://api.example.com/users/$id'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(data),
);
if (response.statusCode != 200) {
throw Exception('Gagal mengupdate user');
}
}
// DELETE - Hapus data
Future<void> deleteUser(int id) async {
final response = await http.delete(
Uri.parse('https://api.example.com/users/$id'),
);
if (response.statusCode != 200) {
throw Exception('Gagal menghapus user');
}
}11. JSON Parsing & Serialization
Manual Parsing
class User {
final int id;
final String nama;
final String email;
final String? avatar;
User({
required this.id,
required this.nama,
required this.email,
this.avatar,
});
// Factory constructor dari JSON
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
nama: json['nama'] as String,
email: json['email'] as String,
avatar: json['avatar'] as String?,
);
}
// Convert ke JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'nama': nama,
'email': email,
'avatar': avatar,
};
}
}
// Menggunakan
void parseData() async {
// Parse single object
final response = await http.get(Uri.parse('https://api.example.com/users/1'));
final jsonData = jsonDecode(response.body);
final user = User.fromJson(jsonData);
// Parse list
final listResponse = await http.get(Uri.parse('https://api.example.com/users'));
final List<dynamic> jsonList = jsonDecode(listResponse.body);
final users = jsonList.map((json) => User.fromJson(json)).toList();
// Convert ke JSON string
final jsonString = jsonEncode(user.toJson());
print(jsonString);
}JSON Serializable (Code Generation)
// pubspec.yaml
dependencies:
json_annotation: ^4.8.0
dev_dependencies:
json_serializable: ^6.7.0
build_runner: ^2.4.0
// user.dart
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
final int id;
final String nama;
@JsonKey(name: 'email_address') // Custom key mapping
final String email;
User({required this.id, required this.nama, required this.email});
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
// Generate: dart run build_runner build12. Flutter CLI Commands
| Perintah | Keterangan |
|---|---|
flutter create myapp | Buat project baru |
flutter create --org com.example myapp | Buat dengan org name |
flutter run | Jalankan di device/emulator |
flutter run -d chrome | Jalankan di Chrome |
flutter run -d <device_id> | Jalankan di device tertentu |
flutter devices | List device yang terhubung |
flutter pub get | Install dependencies |
flutter pub add http | Tambah package |
flutter pub remove http | Hapus package |
flutter pub upgrade | Update dependencies |
flutter clean | Bersihkan build cache |
flutter build apk | Build APK (Android) |
flutter build appbundle | Build AAB untuk Play Store |
flutter build ios | Build iOS (macOS only) |
flutter build web | Build untuk web |
flutter test | Jalankan semua test |
flutter analyze | Analyze kode (linting) |
flutter doctor | Cek setup environment |
flutter doctor -v | Cek setup (verbose) |
flutter upgrade | Upgrade Flutter SDK |
flutter gen-l10n | Generate localization |
dart run build_runner build | Generate code (json_serializable, dll) |
dart run build_runner watch | Auto-generate saat file berubah |
13. Layout Tips & Patterns
Responsive Layout
// MediaQuery - dapatkan ukuran layar
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
bool isMobile = width < 600;
bool isTablet = width >= 600 && width < 1200;
bool isDesktop = width >= 1200;
// LayoutBuilder - responsive berdasarkan parent constraints
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
return _buildWideLayout();
} else {
return _buildNarrowLayout();
}
},
)
// OrientationBuilder
OrientationBuilder(
builder: (context, orientation) {
return orientation == Orientation.portrait
? _buildPortrait()
: _buildLandscape();
},
)Common Layout Patterns
| Pattern | Widget | Keterangan |
|---|---|---|
| Vertikal scroll | SingleChildScrollView | Wrap Column yang panjang |
| Horizontal scroll | SingleChildScrollView(scrollDirection: Axis.horizontal) | Tab bar, carousel |
| List besar | ListView.builder | Lazy loading, performa baik |
| Grid besar | GridView.builder | Grid dengan lazy loading |
| Sticky header | CustomScrollView + SliverAppBar | Collapsing toolbar |
| Tab pages | TabBarView + TabController | Swipeable tabs |
| Bottom sheet | showModalBottomSheet() | Panel dari bawah |
| Dialog | showDialog() | Popup dialog |
| Snack bar | ScaffoldMessenger.showSnackBar() | Toast notification |