✦ Selamat Idul Fitri 1447 H πŸŒ™ Taqabbalallahu minna wa minkum. Mohon maaf lahir dan batin. ✦
ALFAkwt

Optimasi Aplikasi Flutter untuk HP Lama: Studi Kasus Safinatun Najah Lite

Β· 0 komentar Β· Β± 8 menit baca Β· πŸ‘ 255 dilihat
Tampilan aplikasi Safinatun Najah Lite di berbagai layar Android

Ada pertanyaan yang selalu mengganggu saya setiap kali mengerjakan proyek Flutter untuk pengguna Indonesia: seberapa kecil APK yang bisa saya hasilkan tanpa mengorbankan pengalaman pengguna?

Pertanyaan itu bukan retorika. Di Indonesia, segmen pengguna dengan HP ber-RAM 1 GB ke bawah masih sangat besar β€” khususnya di daerah, kalangan pelajar, dan komunitas pesantren. Mereka adalah pengguna yang paling membutuhkan aplikasi berkualitas, tapi justru paling sering terpinggirkan karena keterbatasan perangkat.

Dari kegelisahan itu lahirlah Safinatun Najah Lite β€” aplikasi kitab fiqih mazhab Syafi'i yang dirancang untuk berjalan mulus bahkan di HP dengan RAM 256 MB. Target saya jelas sejak awal: APK di bawah 10 MB, penggunaan RAM di bawah 60 MB saat berjalan, dan tampilan yang indah β€” bukan sekadar fungsional.

Di artikel ini saya akan membuka seluruh proses pengembangan: keputusan arsitektur, filosofi desain, optimasi performa, hingga solusi untuk masalah teknis spesifik yang saya hadapi. Ini bukan tutorial dasar. Ini catatan kerja dari pengembangan nyata.


Mengapa Flutter, Bukan Native Kotlin?

Pertanyaan ini selalu muncul. Untuk aplikasi ringan, bukankah native lebih efisien?

Tidak selalu. Flutter memang membawa overhead engine sekitar 4–5 MB, tapi keunggulannya terletak di kontrol rendering. Saya bisa menggambar ornamen kaligrafi Islam secara programatik menggunakan CustomPainter tanpa satu pun file gambar eksternal. Untuk tampilan sekompleks yang saya targetkan, ini adalah keunggulan yang sangat signifikan.

Selain itu, satu codebase untuk Android dan iOS adalah keputusan bisnis yang masuk akal untuk proyek solo. Jika suatu hari ingin menjangkau pengguna iPhone, tidak perlu menulis ulang dari awal.


Keputusan Arsitektur: Data Hardcoded, Bukan Database

Ini keputusan paling kontroversial dalam proyek ini β€” dan saya siap menjelaskan alasannya.

Pendekatan umum untuk aplikasi kitab adalah menyimpan data di SQLite atau file JSON, lalu membacanya saat runtime. Masalahnya: setiap operasi I/O di HP lama memiliki latency yang terasa. Pengguna merasakan jeda saat berpindah pasal. Pengalaman membaca jadi tidak nyaman.

Solusi saya: semua 68 pasal disimpan sebagai const List langsung di Dart. Tidak ada database, tidak ada file eksternal, tidak ada I/O sama sekali untuk konten utama.

class Pasal {
final int nomor;
final String judul;
final String arab;
final String latin;
final String terjemah;
final String penjelasan;
const Pasal({
required this.nomor,
required this.judul,
required this.arab,
required this.latin,
required this.terjemah,
required this.penjelasan,
});
}
// Di file data_pasal.dart
const List<Pasal> daftarPasal = [
Pasal(
nomor: 1,
judul: 'Pembahasan Thaharah',
arab: 'Ψ§Ω„Ψ·ΩŽΩ‘Ω‡ΩŽΨ§Ψ±ΩŽΨ©Ω...',
latin: 'At-thaharatu...',
terjemah: 'Thaharah (bersuci)...',
penjelasan: '<p>...</p>',
),
// ... 67 pasal lainnya
];

Kata kunci const bukan sekadar kebiasaan β€” ini instruksi ke Dart compiler untuk mengalokasikan objek ini di compile-time, bukan runtime. Hasilnya: navigasi antar pasal benar-benar instan, tanpa loading indicator sama sekali.

Tradeoff-nya: ukuran kode Dart membesar sekitar 800 KB. Tapi ini jauh lebih baik daripada mengorbankan responsivitas di HP lama.


Custom HTML Renderer: Membuang flutter_html

Konten penjelasan setiap pasal disimpan dalam format HTML sederhana β€” ada tag <p>, <h3>, <ul>, <li>, dan <div class="arabic"> untuk teks Arab inline.

Solusi paling mudah adalah menggunakan package flutter_html. Saya pernah menggunakannya di proyek lain β€” performanya di HP lama tidak memuaskan, dan ukuran dependency-nya terlalu besar untuk proyek ini.

Jadi saya buat renderer sendiri. Idenya sederhana: parse HTML string menggunakan regex, map setiap tag ke widget Flutter yang sesuai.

List<Widget> parseHtml(String html) {
final List<Widget> widgets = [];
// Pisahkan per blok (p, h3, ul, div.arabic)
final RegExp blockPattern = RegExp(
r'<(p|h3|h4|ul|div[^>]*)>([sS]*?)</1>',
caseSensitive: false,
);
for (final match in blockPattern.allMatches(html)) {
final tag = match.group(1)!.toLowerCase();
final content = match.group(2)!;
if (tag == 'h3') {
widgets.add(_buildHeading(content, level: 3));
} else if (tag == 'h4') {
widgets.add(_buildHeading(content, level: 4));
} else if (tag == 'ul') {
widgets.add(_buildUnorderedList(content));
} else if (tag.startsWith('div') && tag.contains('arabic')) {
widgets.add(_buildArabicBlock(content));
} else {
widgets.add(_buildParagraph(content));
}
}
return widgets;
}

Ini bukan parser HTML general-purpose β€” dan memang tidak perlu. Saya hanya perlu menangani subset tag yang ada di data saya. Hasilnya jauh lebih ringan dari flutter_html dan performa rendering-nya jauh lebih baik di HP lama.


CustomPainter: Ornamen Tanpa Aset Gambar

Bagian ini yang paling saya nikmati selama pengembangan. Saya ingin tampilan aplikasi mencerminkan nuansa manuskrip Islam klasik β€” bukan tampilan aplikasi Android generik.

Seluruh ornamen β€” bingkai halaman detail, divider antar seksi, ornamen header, bintang sudut β€” digambar menggunakan CustomPainter. Tidak ada file PNG, tidak ada SVG aset. Semua digambar ulang setiap frame oleh Flutter.

class OrnamenBintang extends CustomPainter {
final Color warna;
final double ukuran;
const OrnamenBintang({required this.warna, required this.ukuran});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = warna
..style = PaintingStyle.fill;
final center = Offset(size.width / 2, size.height / 2);
final path = Path();
// Bintang 8 sudut β€” dua kotak dirotasi 45 derajat
for (int i = 0; i < 2; i++) {
canvas.save();
canvas.translate(center.dx, center.dy);
canvas.rotate(i * 45 * (pi / 180));
final rect = Rect.fromCenter(
center: Offset.zero,
width: ukuran,
height: ukuran,
);
path.addRect(rect);
canvas.restore();
}
canvas.drawPath(path, paint);
// Lingkaran tengah
canvas.drawCircle(center, ukuran * 0.15, paint);
}
@override
bool shouldRepaint(OrnamenBintang old) =>
old.warna != warna || old.ukuran != ukuran;
}

Pendekatan ini memiliki keunggulan ganda: tidak ada overhead loading aset, dan ornamen bisa di-scale ke ukuran layar apapun dengan sempurna karena berbasis vektor.


Palet Warna: Nuansa Manuskrip Klasik

Pemilihan warna bukan sekadar estetika β€” ini komunikasi. Saya ingin pengguna merasakan bahwa ini bukan aplikasi Android biasa, tapi sebuah kitab digital yang layak dihormati.

Palet yang saya pilih:

  • Krem parchment #F5F0E8 β€” background utama, meniru warna kertas kitab tua
  • Hijau teal Islamic #2D6A4F β€” warna primer, aksen judul dan ikon aktif
  • Emas antik #C9A84C β€” ornamen, divider, border dekoratif
  • Coklat tinta #3D2B1F β€” teks utama, meniru tinta pena pada manuskrip

Kombinasi ini jarang digunakan di aplikasi Android Indonesia β€” dan itu disengaja. Diferensiasi visual adalah bagian dari strategi produk.


ListView.builder: Satu-satunya Pilihan untuk Daftar Panjang

Ini bukan tips β€” ini keharusan. Jika Anda menggunakan ListView biasa (bukan builder) untuk menampilkan 68 item, Flutter akan membuat semua 68 widget sekaligus saat halaman dibuka. Di HP 256 MB RAM, itu bisa langsung force close.

ListView.builder hanya membuat widget yang terlihat di layar, plus beberapa di luar layar sebagai buffer. Untuk daftar 68 item, perbedaan konsumsi memorinya bisa mencapai 70–80%.

ListView.builder(
itemCount: daftarPasal.length,
// Pastikan extent tetap jika memungkinkan
itemExtent: 88.0,
itemBuilder: (context, index) {
final pasal = daftarPasal[index];
return PasalCard(pasal: pasal);
},
)

Parameter itemExtent adalah optimasi tambahan yang sering dilewatkan. Jika semua item memiliki tinggi yang sama, Flutter tidak perlu menghitung layout setiap item satu per satu β€” ini mengurangi beban CPU secara signifikan di scroll cepat.


Gradle Migration: Pitfall yang Sering Menjatuhkan Developer

Ini bagian yang paling sering membuat developer Flutter pemula frustasi, dan saya ingin membahasnya secara eksplisit.

Flutter versi terbaru (3.x ke atas) sudah tidak mendukung cara lama konfigurasi Gradle:

// CARA LAMA β€” sudah deprecated, akan gagal di Flutter 3.x terbaru
apply from: "$flutterRoot/packages/flutter_tools/gradle/app_plugin_loader.gradle"

Migrasi yang benar adalah ke declarative plugins block:

// android/settings.gradle β€” cara baru
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.1.0" apply false
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
}

Ini bukan hal yang intuitif jika tidak membaca release notes Flutter dengan teliti. Saya menghabiskan waktu lebih dari yang seharusnya untuk masalah ini di awal pengembangan.


Hasil Akhir: Angka yang Berbicara

Setelah semua optimasi di atas diterapkan, berikut hasil yang terukur:

  • Ukuran APK release: 6.8 MB (dengan ProGuard aktif)
  • RAM usage saat idle: 32–38 MB
  • RAM usage saat scroll aktif: 45–55 MB
  • Cold start time (Redmi 4A, Android 6): 1.2 detik
  • Target minimum Android: API 21 (Android 5.0 Lollipop)
  • Coverage perangkat aktif Indonesia: ~97%

Angka-angka ini bisa lebih baik dengan native Kotlin β€” tapi tradeoff antara performa dan maintainability sudah saya pertimbangkan dari awal. Untuk proyek solo dengan target lintas platform, ini adalah titik keseimbangan yang tepat.


Kesimpulan

Mengembangkan aplikasi untuk HP lama adalah disiplin tersendiri. Setiap keputusan arsitektur memiliki konsekuensi performa yang nyata. Tidak ada yang bisa dikorbankan "nanti saja dioptimasi" β€” karena di HP lama, masalah performa langsung terasa oleh pengguna.

Tapi di situlah tantangannya menjadi menarik. Ketika semua kemudahan diabaikan dan kita dipaksa berpikir tentang setiap byte yang digunakan, hasilnya adalah kode yang lebih bersih, lebih efisien, dan β€” paradoksnya β€” lebih mudah dimaintain.

Safinatun Najah Lite adalah bukti bahwa aplikasi yang ringan dan indah bukan pilihan yang saling bertentangan. Keduanya bisa dicapai bersamaan, asal kita mau duduk, berpikir, dan membuat keputusan yang tepat sejak awal.

Aplikasi tersedia di Google Play Store. Gratis, tanpa biaya konten, selamanya.


Komentar

Belum ada komentar. Jadilah yang pertama menulis.

Tulis Komentar

↑