Studi kasus rekayasa
Membangun Kemampuan Screen Time di Flutter
Pemantauan penggunaan dan pembatasan aplikasi Android, dengan jembatan izin iOS — studi kasus plugin Flutter yang memperjelas batas dukungan platform.
- Peran
- Penulis & Pemelihara Plugin
- Perusahaan
- Solusi Bejo
- Periode
- April 2025 — Sekarang
- Diperbarui
- 7 September 2026
- Flutter
- Dart
- Kotlin
- Swift
- Android
- iOS
- Platform Channels
Screen Time adalah plugin Flutter untuk membangun aplikasi yang membantu orang mengelola penggunaan perangkat Android: membaca statistik pemakaian, memantau aplikasi aktif, serta menerapkan atau menjadwalkan pembatasan aplikasi. Implementasi iOS terbatas pada permintaan izin dan pemeriksaan status otorisasi.
Ini contoh paling jelas dari apa yang terjadi ketika kebutuhan produk jatuh sepenuhnya di luar framework. Tidak ada API Dart untuk satu pun hal di atas, dan kedua platform bahkan tidak sepakat soal apa arti "screen time".
Lihat handler metode Android dan handler metode iOS untuk implementasi yang ditinjau.
Situasinya
Kebutuhannya sederhana untuk dinyatakan: biarkan pengguna melihat ke mana waktunya pergi, dan biarkan mereka membatasinya.
Setiap bagian dari kalimat itu adalah kemampuan sistem operasi yang dijaga izin istimewa — karena aplikasi yang bisa melihat aplikasi lain yang Anda pakai, dan menghentikan Anda memakainya, persis berbentuk seperti aplikasi yang rawan disalahgunakan.
Masalahnya
Android dan iOS memilih model yang berlawanan.
Android menyediakan bahan mentahnya dan menyuruh kita merakit sendiri. UsageStatsManager
memberi data pemakaian historis, tapi hanya setelah pengguna memberikan PACKAGE_USAGE_STATS
— izin yang tidak bisa diminta lewat dialog runtime biasa, dan diperiksa lewat AppOpsManager
alih-alih API izin standar. Deteksi aplikasi aktif secara real time sama sekali tidak tersedia
lewat jalur itu; butuh AccessibilityService, yang harus diaktifkan pengguna secara manual di
pengaturan sistem. Memblokir aplikasi berarti menggambar di atasnya, yang butuh
SYSTEM_ALERT_WINDOW.
Kode iOS memakai FamilyControls.AuthorizationCenter untuk otorisasi. Jembatan izin ini
belum mengimplementasikan laporan penggunaan atau pembatasan aplikasi. Fitur-fitur itu
membutuhkan pekerjaan native tambahan; otorisasi saja belum menyediakannya.
Batas implementasi ini penting bagi pemakai plugin: antarmuka Dart yang sama belum berarti Android dan iOS menyediakan kemampuan yang sama.
Batasannya
Tiga batasan membentuk setiap keputusan:
Izin tidak bisa diminta secara normal. PACKAGE_USAGE_STATS, aksesibilitas, dan overlay
masing-masing menuntut kita mengirim pengguna ke layar pengaturan sistem yang berbeda, lalu
mendeteksi saat mereka kembali apakah izinnya benar-benar diberikan.
Layanan aksesibilitas sensitif secara kebijakan. Memakai API aksesibilitas untuk hal selain aksesibilitas mengundang pemeriksaan ketat dari Play Store. Ini risiko distribusi yang nyata, bukan teoretis.
Pekerjaan latar harus bertahan dari sistem operasi. Fitur pemblokiran yang berhenti bekerja setelah perangkat di-reboot, atau saat prosesnya dimatikan sistem, lebih buruk daripada tidak ada fitur sama sekali — pengguna merasa terlindungi padahal tidak.
Pendekatan Rekayasa
Plugin ini dibangun dalam potongan kecil yang bisa dirilis, selama kurang lebih delapan bulan, dan riwayat versinya adalah catatan jujur tentang bagaimana pemahamannya berkembang:
0.1.0 Mengambil daftar aplikasi terpasang
0.2.0 UsageStatsManager · pemeriksaan izin AppOpsManager · AccessibilityService
0.3.0 Menyaring aplikasi yang dipasang pengguna; pemakaian per paket
0.4.0 Permintaan izin dan status izin yang tersentralisasi
0.5.0 Izin draw overlay
0.6.0 Pemblokiran aplikasi
0.7.0 Blokir dan buka blokir aplikasi tertentu
0.10.0 Otorisasi iOS lewat FamilyControlsDua keputusan dalam daftar itu menanggung sebagian besar bebannya.
Menyentralkan model izin
Pada versi 0.3.0 sudah ada tiga alur izin terpisah — akses pemakaian, aksesibilitas, dan overlay — masing-masing dengan jalur permintaan sendiri, layar pengaturan sendiri, dan cara pemeriksaan sendiri. Pemanggil harus menulis urutan kikuk yang sama tiga kali.
Versi 0.4.0 meringkasnya menjadi sepasang metode di atas satu enum:
// Satu bentuk untuk setiap izin, seberapa pun berbedanya implementasi platform.
final granted = await screenTime.requestPermission(
permissionType: ScreenTimePermissionType.appUsage,
);
final status = await screenTime.permissionStatus(
permissionType: ScreenTimePermissionType.accessibilitySettings,
);Enum berisi appUsage, accessibilitySettings, drawOverlay, dan notification. Maknanya
bergantung pada platform. Di iOS, tiga nilai pertama memetakan permintaan dan status ke
otorisasi Family Controls yang sama; nilai tersebut tidak memberikan akses aksesibilitas
atau overlay seperti di Android. Notifikasi memakai UNUserNotificationCenter secara terpisah.
Pemetaan ini terlihat dalam implementasi izin iOS.
Memperlakukan pemblokiran sebagai pekerjaan latar terjadwal, bukan loop yang berjalan
Pemblokiran tidak boleh bergantung pada aplikasi yang masih hidup. Implementasi Android-nya
dibangun dari worker WorkManager dan receiver sistem, bukan foreground loop yang berumur
panjang — worker untuk menerapkan, melanjutkan, dan mencabut blokir; boot receiver agar jadwal
bertahan setelah restart; alarm receiver untuk aturan berbasis waktu; dan worker pemantau yang
menyalakan ulang layanan jika sistem mematikannya.
Itu jauh lebih banyak mesin daripada sekadar "jalankan service". Itu juga yang membedakan fitur yang bekerja dari fitur yang bekerja sampai ponselnya di-reboot.
Tantangan
Bug yang layak dilaporkan adalah yang hanya muncul dengan data nyata.
Overflow bilangan yang senyap. appUsageData menerima waktu mulai dan selesai sebagai int
Dart. Menanyakan rentang tanggal lama mendorong milidetik epoch melewati batas yang bisa
dikonversi platform channel menjadi int 32-bit, sehingga menghasilkan type error, bukan angka
yang salah. Perbaikannya: menerima Number di sisi native — perubahan satu baris yang hanya
muncul karena ada yang menanyakan data cukup jauh ke belakang.
Kueri pemakaian yang mengembalikan hari yang salah. Menanyakan tanggal tanpa catatan screen time mengembalikan data dari hari sebelumnya yang ada datanya, bukan nol. Itu jawaban yang salah parah tapi berkostum masuk akal: dasbor pemakaian akan diam-diam menampilkan angka kemarin di bawah tanggal hari ini. Perbaikannya: menyaring hasil kueri ke tanggal spesifik, bukan memercayai agregasinya.
Keduanya diperbaiki di 0.10.3. Keduanya jenis cacat yang tidak akan ditemukan unit test dengan data sintetis.
Trade-off
Kemampuan yang diimplementasikan plugin saat ini berbeda pada tiap platform. Kueri
pemakaian, pemantauan, pemblokiran, dan penjadwalan tersedia di handler Android. Handler iOS
mengembalikan FlutterMethodNotImplemented untuk panggilan tersebut. Aplikasi pemakai perlu
memeriksa platform sebelum menawarkan fitur; nama metode yang sama belum membuktikan dukungan iOS.
Layanan aksesibilitas butuh penyiapan manual oleh pengguna. Tidak ada jalan memutar, jadi
plugin ini mengekspos openAccessibilitySettings() dan isAppMonitoringServiceEnabled() lalu
membiarkan aplikasi induk merancang proses onboarding-nya secara jujur.
Pemakai plugin mewarisi pekerjaan integrasi nyata. Layanannya harus dideklarasikan di manifest aplikasi induk beserta berkas konfigurasinya sendiri. Itu lebih banyak dari yang biasanya diminta sebuah plugin, dan itu adalah harga dari kemampuannya, bukan kelalaian.
Pekerjaan plugin terkait
Batas yang sama muncul di paket-paket lebih kecil yang dibangun dengan cara serupa: Screenshot Callback Plus — notification observer di iOS, pengamatan konten di Android — dan Flutter Dynamic Icon Plus, di mana ikon alternatif didukung secara tidak merata antar OEM Android dan angka badge iOS sama sekali tidak punya padanan di Android.
Hasil
Screen Time dipublikasikan di pub.dev. Versi 0.10.3 adalah rilis terpublikasi yang ditinjau pada 7 September 2026. Changelog mencatat pengembangan fitur Android sejak April 2025 dan penambahan metode permintaan serta status izin iOS pada Mei 2025.
Hasilnya adalah implementasi Android untuk pemantauan dan pembatasan penggunaan, disertai jembatan izin iOS yang lebih terbatas. Tinjauan sumber ini membuktikan keberadaan jalur kode; bukan jaminan keandalan pada semua perangkat atau kesetaraan fitur antarplatform.
Yang Saya Pelajari
Cross-platform adalah strategi pengiriman, bukan strategi rekayasa. Begitu sebuah produk meraih sistem operasi, kita sedang melakukan rekayasa platform — dan kualitas hasilnya bergantung pada pemahaman kita atas Android dan iOS secara terpisah, bukan pada seberapa mahir kita dengan Flutter.
Pelajaran kedua soal kejujuran API. Dokumentasi dan UI produk perlu menunjukkan kemampuan yang benar-benar diimplementasikan pada tiap platform. Izin mengakses framework native adalah titik awal, bukan fitur lintas platform yang sudah selesai.