Image dari Asset & Network
Menampilkan gambar lokal dan dari internet dengan benar.
Analogi: Album Foto vs Foto Pinjaman
Bayangkan dua cara mendapatkan foto untuk dipajang di dinding. Cara pertama: foto dari album pribadimu, selalu ada di rumah, bisa dilihat kapan saja bahkan saat listrik padam. Cara kedua: meminjam foto dari teman, kamu harus menghubunginya dulu, menunggu ia mengirim, dan kalau ia sedang sibuk kamu tidak dapat apa-apa. Di Flutter, Image.asset adalah foto dari album sendiri: file gambar dibundel ke dalam aplikasi dan selalu tersedia offline. Image.network adalah foto pinjaman: diambil dari internet saat dibutuhkan, butuh loading, dan bisa gagal.
Kenapa dibedakan? Karena keduanya butuh perlakuan yang sangat berbeda: yang satu butuh didaftarkan sebelum aplikasi dibuild, yang satu butuh indikator loading dan penanganan error.
Kenapa Asset Harus Didaftarkan di pubspec.yaml?
File Dart otomatis ikut terkompilasi, tapi file gambar tidak. Flutter tidak bisa menebak gambar mana saja yang akan kamu pakai, jadi kamu harus mendaftarkannya secara eksplisit di pubspec.yaml. Saat build, Flutter hanya membundel file yang terdaftar, sehingga ukuran aplikasi tidak membengkak oleh gambar yang tidak terpakai.
Satu pendaftaran folder (assets/images/) mencakup semua file di dalamnya, jadi kamu tidak perlu mendaftarkan satu per satu. Tapi ingat: setelah menambah asset baru, lakukan hot restart (bukan hot reload), karena daftar asset dibaca saat aplikasi dimulai.
Contoh 1: Logo Aplikasi dari Asset
# pubspec.yaml
flutter:
assets:
- assets/images/Image.asset(
'assets/images/logo.png',
width: 120,
height: 120,
)Untuk layar dengan kepadatan piksel berbeda, Flutter mendukung varian resolusi otomatis. Simpan logo.png (1x), 2.0x/logo.png, dan 3.0x/logo.png, lalu cukup panggil Image.asset('assets/images/logo.png'). Flutter otomatis memilih varian yang tajam untuk tiap perangkat, tanpa kamu ubah kode.
Contoh 2: Foto Profil yang Realistis
Contoh yang lebih realistis: foto profil berbentuk lingkaran yang diambil dari internet, dengan loading indicator dan gambar cadangan kalau gagal dimuat.
class FotoProfil extends StatelessWidget {
final String urlFoto;
final String nama;
const FotoProfil({super.key, required this.urlFoto, required this.nama});
@override
Widget build(BuildContext context) {
return Row(
children: [
ClipOval(
child: Image.network(
urlFoto,
width: 56,
height: 56,
fit: BoxFit.cover,
loadingBuilder: (context, child, progress) {
if (progress == null) return child;
return const SizedBox(
width: 56,
height: 56,
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
);
},
errorBuilder: (context, error, stackTrace) {
return Container(
width: 56,
height: 56,
color: Colors.teal.shade100,
child: Center(
child: Text(
nama.isNotEmpty ? nama[0].toUpperCase() : '?',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.teal,
),
),
),
);
},
),
),
const SizedBox(width: 12),
Text(nama, style: const TextStyle(fontWeight: FontWeight.bold)),
],
);
}
}Pola ini (loadingBuilder + errorBuilder + fallback inisial nama) adalah standar industri untuk avatar di aplikasi nyata: cepat, tidak pernah menampilkan kotak merah error, dan tetap informatif saat foto gagal dimuat.
Catatan teknis: Selalu sediakan
errorBuilderuntuk gambar network. Tanpa itu, gambar yang gagal dimuat tampil sebagai kotak merah error yang merusak tampilan. Untuk aplikasi produksi dengan banyak gambar network, pertimbangkan packagecached_network_imagesupaya gambar di-cache dan tidak diunduh ulang setiap dibuka.
BoxFit: Mengatur Cara Gambar Mengisi Kotak
Gambar jarang berukuran pas dengan kotaknya, jadi kamu harus memilih strateginya lewat fit:
BoxFit.cover: memenuhi kotak sepenuhnya, bagian yang berlebih dipotong. Cocok untuk foto profil dan banner.BoxFit.contain: mengecilkan gambar supaya muat utuh tanpa terpotong, menyisakan ruang kosong. Cocok untuk logo.BoxFit.fill: memaksa mengisi kotak dengan mengorbankan proporsi, gambar bisa gepeng. Jarang dipakai.BoxFit.fitWidth/fitHeight: memenuhi lebar atau tinggi saja.
Pasangan wajibnya: ClipRRect untuk sudut membulat dan ClipOval untuk lingkaran, karena Image sendiri selalu berbentuk kotak.
Kesalahan umum pemula
1. Lupa mendaftarkan asset di pubspec.yaml.
# SALAH: file ada di folder, tapi tidak terdaftar: error "Unable to load asset"
# (tidak ada baris assets di pubspec.yaml)
# BENAR:
flutter:
assets:
- assets/images/logo.pngErrornya baru muncul saat aplikasi dijalankan, bukan saat coding, jadi mudah terlewat. Selalu cek pubspec setiap menambah gambar baru.
2. Memakai hot reload setelah menambah asset baru. Hot reload hanya memperbarui kode Dart, sedangkan daftar asset dibaca saat aplikasi start. Gejalanya: gambar baru tetap error "unable to load asset" padahal pubspec sudah benar. Solusinya: hot restart (atau stop lalu run ulang).
3. Image.network tanpa batas ukuran di dalam layout fleksibel.
// SALAH: di dalam Column/Row tanpa batas, gambar network yang belum
// selesai dimuat tidak punya ukuran, layout bisa berantakan
Column(children: [Image.network(url)])
// BENAR: beri width/height atau bungkus dengan AspectRatio/SizedBox
SizedBox(
width: double.infinity,
height: 200,
child: Image.network(url, fit: BoxFit.cover),
)Sebelum gambar selesai diunduh, Flutter belum tahu dimensinya. Tanpa batas eksplisit, layout bisa "melompat" saat gambar tiba-tiba muncul.
4. Salah indentasi di pubspec.yaml. YAML sangat sensitif spasi. assets: harus sejajar di bawah flutter: dengan dua spasi, dan tiap path memakai strip - dengan spasi setelahnya. Satu spasi meleset saja membuat seluruh pendaftaran asset diabaikan tanpa error yang jelas.
Tantangan
Galeri dengan fallback
Tampilkan Image.network dengan loadingBuilder (CircularProgressIndicator) dan errorBuilder (Icon broken_image). Bungkus dalam ClipRRect radius 16.