Episode ini menghubungkan aplikasi kalian ke dunia luar: HTTP requests dengan package http atau dio, JSON deserialization dengan dart:convert dan json_serializable, async programming dengan Future dan Stream, serta loading states, error handling, dan retry yang layak untuk produksi.

Aplikasi yang berguna hampir selalu membaca dan mengirim data ke server. Episode 8 membuka pintu itu: kalian akan belajar melakukan request HTTP, mengurai JSON menjadi model Dart, memahami async programming dengan Future dan Stream, serta menangani loading, error, dan retry seperti aplikasi produksi. Mulai dengan instalasi package:
flutter pub add httpflutter pub add http menambahkan package http ke pubspec.yaml dan langsung menjalankan pub get. Untuk kebutuhan yang lebih besar, package dio menawarkan interceptors, timeout, dan upload progress — akan kita bandingkan sebentar lagi.
Dengan package http, request sederhana hanya beberapa baris:
import 'package:http/http.dart' as http;
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),
);
if (response.statusCode == 200) {
print(response.body);
} else {
throw Exception('Gagal: ${response.statusCode}');
}await http.get(Uri.parse(...)) mengembalikan Response. Selalu periksa statusCode sebelum memproses body. Perhatikan bahwa URL selalu dibungkus Uri.parse — http.get menerima Uri, bukan string mentah.
dio menambahkan fitur di atas http: interceptors untuk logging, base options untuk URL dasar, dan penanganan timeout yang lebih rapi. Untuk aplikasi dengan banyak endpoint dan kebutuhan auth, dio menghemat banyak boilerplate:
flutter pub add diodart:convert mengurai JSON mentah, lalu kalian memetakannya ke model Dart:
class Post {
const Post({required this.id, required this.title});
final int id;
final String title;
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
id: json['id'] as int,
title: json['title'] as String,
);
}
}Post.fromJson(json) memetakan key JSON ke field model. Gunakan jsonDecode(response.body) untuk mengubah string menjadi Map sebelum memanggil fromJson.
Menulis fromJson manual membosankan saat model banyak. Package json_serializable membangkitkannya dari anotasi:
flutter pub add json_annotation
flutter pub add dev:build_runner dev:json_serializableJalankan codegen:
dart run build_runner builddart run build_runner build membangkitkan file .g.dart yang berisi implementasi fromJson dan toJson dari anotasi @JsonSerializable().
Future mewakili operasi yang menghasilkan satu nilai di kemudian hari. Kombinasi async dan await membuat kode asinkron terbaca seperti kode sinkron:
Future<Post> ambilPost(int id) async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/posts/$id'),
);
final json = jsonDecode(response.body) as Map<String, dynamic>;
return Post.fromJson(json);
}Fungsi bertipe Future<Post> memberi tahu pemanggil bahwa hasilnya belum siap. await menunggu hasil tanpa memblokir UI — kunci kenyamanan pemrograman asinkron di Flutter.
Stream mewakili aliran data yang terus menerus — misalnya notifikasi atau progress. Di UI, StreamBuilder merender ulang setiap kali stream mengirim data baru:
StreamBuilder<int>(
stream: streamKoneksi,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('Ping: ${snapshot.data} ms');
}
return const Text('Menunggu data...');
},
)StreamBuilder memanggil builder ulang setiap kali streamKoneksi mengirim nilai baru. snapshot.hasData dan snapshot.data memberi akses ke nilai terakhir.
Gunakan FutureBuilder untuk menampilkan loading, error, dan data dalam satu widget:
FutureBuilder<Post>(
future: ambilPost(1),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text('Terjadi kesalahan: ${snapshot.error}');
}
return Text(snapshot.data!.title);
},
)ConnectionState.waiting menunjukkan proses sedang berjalan — tampilkan spinner. snapshot.hasError memegang penanganan error, dan snapshot.data dipakai setelah data siap.
Jangan menyerah pada satu kegagalan jaringan. Buat helper retry dengan batas percobaan:
Future<T> cobaUlang<T>(Future<T> Function() aksi, {int maks = 3}) async {
for (var i = 0; i < maks; i++) {
try {
return await aksi();
} catch (e) {
if (i == maks - 1) rethrow;
await Future.delayed(Duration(milliseconds: 500 * (i + 1)));
}
}
throw StateError('Tidak terjangkau');
}cobaUlang memanggil aksi hingga sukses atau mencapai maks percobaan. Jeda Duration(milliseconds: 500 * (i + 1)) memberi waktu server pulih — tanpa denyut yang terlalu agresif.
Inti yang harus dibawa pulang:
http untuk project kecil; dio untuk kebutuhan interceptor dan timeout.statusCode dan bungkus URL dengan Uri.parse.fromJson manual atau codegen json_serializable.Future untuk satu hasil; Stream untuk data berkelanjutan.FutureBuilder menangani loading, error, dan data dalam satu tempat.Di episode 9 selanjutnya kita membahas persistent storage — local storage dengan shared_preferences dan file I/O, database lokal SQLite dengan sqflite dan Drift, caching strategies dan offline support, serta secure storage untuk menyimpan token dan secret. Data aplikasi kalian tidak lagi hilang saat aplikasi ditutup.