Panduan Belajar Darurat Ujian IF3210: Pengembangan Aplikasi Piranti Bergerak
π’ 0. Prasyarat Wajib: Fundamental Kotlin
Pahami ini dulu agar kamu bisa membaca kode Android di soal ujian.
[HARD] Null Safety (?, ?., ?:, !!)
String? = Boleh null. String = Tidak boleh null.
?. (Safe call): Hanya jalankan jika tidak null. user?.name
?: (Elvis operator): Beri default jika null. val p = name ?: "Anonim"
!! (Assertion): Memaksa compiler percaya ini tidak null. Bahaya: Kalau ternyata null, aplikasi CRASH (NPE).
var name: String? = "John"val length1 = name?.length // Return 4 (karena name tidak null)name = nullval length2 = name?.length ?: 0 // Return 0 (karena name null, pakai nilai default)// val lengthCrash = name!!.length // π₯ CRASH! NullPointerException
[HARD] Coroutines (Dasar)
Fungsi dengan awalan suspend adalah fungsi yang memakan waktu (network/DB) dan bisa ditangguhkan tanpa memblokir thread.
Hanya bisa dipanggil dari dalam coroutine (launch, async) atau suspend function lainnya.
suspend fun fetchData(): String { delay(1000) // pura-puranya simulasi network 1 detik return "Data loaded"}// Panggil fungsi suspend dari dalam coroutine scope (misal di ViewModel)viewModelScope.launch { val data = fetchData() println(data)}
[MEDIUM] Trailing Lambdas
Jika parameter fungsi terakhir adalah fungsi (lambda), tulis kurung kurawalnya {} di luar tanda kurung (). Ini sangat sering dipakai di Jetpack Compose:
// Definisi Fungsifun performAction(id: Int, callback: (Boolean) -> Unit) { /* ... */ }// Pemanggilan normalperformAction(1, { success -> println(success) })// Pemanggilan dengan Trailing Lambda (Lebih sering dipakai, direkomendasikan)performAction(1) { success -> println(success) }// Contoh paling sering di ComposeButton(onClick = { /* aksi klik */ }) { Text("Klik Aku") // lambda ditaruh di luar kurung}
[EASY] Data Class & Companion Object
data class User(...): Otomatis buatkan toString(), equals(), dan setter-getter.
companion object: Pengganti static di Java. Milik kelas, bukan instance.
// Otomatis punya copy(), equals(), toString()data class User(val id: Int, val name: String)class ApiConfig { companion object { const val BASE_URL = "https://api.example.com" fun build() { /* ... */ } }}// Pemanggilan:val url = ApiConfig.BASE_URL ApiConfig.build()
[EASY] Variabel (val vs var)
val (Value) = Read-only / Tidak bisa diubah (mirip final).
var (Variable) = Bisa diubah.
val phi = 3.14// phi = 3.14159 // β Error compile: val cannot be reassignedvar count = 0count = 1 // β Boleh diubah
π΅ 0.5. Prasyarat Wajib: Fundamental Dart & Flutter
Pahami sintaks dasar ini karena Dart punya aturan unik soal null dan tipe data.
[HARD] Null-aware Operators (??, ??=, ?., !)
?? (If null): Mengembalikan nilai default jika sebelah kiri null.
??= (Assign if null): Isi variabel HANYA jika nilainya saat ini null.
! (Null assertion): Mirip !! di Kotlin. Memaksa compiler percaya tidak null (Bisa CRASH!).
String? name; // Nilai awal adalah nullprint(name?.length); // β Aman, print "null"print(name ?? "Anonim"); // β Print "Anonim" karena name nullname ??= "John"; // β name sekarang diisi "John" karena tadinya nullname ??= "Budi"; // β name TETAP "John", karena sudah tidak null!// print(name!.length); // π₯ CRASH runtime kalau name beneran null!
[MEDIUM] Arrow Functions & Anonymous Functions
Fungsi di Dart yang isinya cuma 1 baris (mengembalikan nilai) sangat sering disingkat pakai => (Arrow function).
=> expr artinya sama persis dengan { return expr; }.
// Fungsi Normalint add(int a, int b) { return a + b; }// Arrow Function (Sering dipakai di build() Flutter)int add(int a, int b) => a + b;// Anonymous function (Lambda) di dalam list methodvar doubled = [1, 2, 3].map((n) => n * 2).toList();
[EASY] Type Check & Casting (is, as)
is: Mengecek apakah variabel adalah tipe data tertentu.
as: Memaksa konversi (casting) dari parent class ke child class.
Object obj = "Hello";// Mengecek tipe dataif (obj is String) { print("Ini String, panjangnya: ${obj.length}"); }// Memaksa konversi (Casting)String text = obj as String;
[EASY] Named Parameters ({})
Parameter yang dibungkus {} sifatnya bernama. Saat dipanggil, wajib menyebutkan namanya, dan boleh dibolak-balik urutannya.
Pakai required jika wajib diisi. Jika tidak required, wajib diberi default value.
// Deklarasi fungsivoid setBox({required int width, int height = 10}) { print("W: $width, H: $height"); }// Pemanggilan fungsi (Bisa dibolak-balik urutannya!)setBox(height: 20, width: 50); setBox(width: 30); // height otomatis ikut default = 10
π 1. Bab 15: Jetpack Compose (Paradigma Baru UI)
Keywords:Declarative UI, @Composable, Recomposition, Modifier, State Hoisting, remember, Side Effects
[HARD] State & Recomposition
Recomposition: Compose otomatis merender ulang hanya fungsi yang membaca state yang berubah.
remember { mutableStateOf() }: Menyimpan state saat recomposition agar tidak ter-reset.
rememberSaveable: Seperti remember, tapi nilainya tidak hilang saat layar dirotasi (disimpan via Bundle).
State Hoisting: Memindahkan state ke atas (parent), dan passing state ke bawah sebagai parameter nilai & callback (membuat composable jadi stateless dan reusable).
@Composablefun Counter() { // State akan selamat dari recomposition & rotasi layar var count by rememberSaveable { mutableStateOf(0) } Button(onClick = { count++ }) { Text("Count: $count") }}
[HARD] Side Effects (Efek Samping)
Fungsi Compose harus bebas efek samping. Jika butuh memanggil API atau timer:
LaunchedEffect(key): Jalankan coroutine (misal fetch data) saat pertama kali Compose muncul.
DisposableEffect(key): Untuk listener/observer yang butuh dibersihkan (onDispose) saat Compose hancur.
rememberCoroutineScope(): Untuk meluncurkan aksi dari event (misal saat tombol diklik).
LaunchedEffect(Unit) { // Jalan 1x saat pertama kali muncul delay(2000) // pura-puranya load API println("Selesai")}
[MEDIUM] Urutan Modifier
Modifier.padding(8.dp).background(Color.Red)BERBEDA dengan Modifier.background(Color.Red).padding(8.dp). Efek diterapkan berurutan dari kiri ke kanan/atas ke bawah.
// Padding LUAR (margin), lalu background, lalu padding DALAMModifier.padding(16.dp).background(Color.Blue).padding(8.dp)
[MEDIUM] Deklaratif vs Imperatif
Imperatif (XML/Lama): Kita mengatur state UI secara manual (textView.setText()).
Deklaratif (Compose): UI adalah fungsi dari State. State berubah β UI otomatis ikut berubah.
[EASY] Interoperabilitas
ComposeView: Memakai Compose di dalam layout XML lama.
AndroidView: Memakai UI tradisional (seperti WebView atau MapView) di dalam Compose.
π οΈ 2. Bab 11: Android Services & Background Tasks
Keywords:Main Thread, Started vs Bound vs Foreground, IntentService, BroadcastReceiver, Notification
[HARD] Konsep Service & Main Thread
Jebakan Ujian: Service secara default berjalan di MAIN THREAD. Melakukan proses berat (download/DB) di Service tanpa bikin thread baru akan bikin aplikasi ANR (Application Not Responding).
[HARD] 3 Jenis Service
Started Service: Dijalankan (startService), jalan terus di belakang sampai dipanggil stopSelf(). (Fire-and-forget).
Bound Service: Hubungan Client-Server (bindService). Mengembalikan IBinder via onBind(). Hancur otomatis jika semua client unbind.
Foreground Service: Untuk tugas yang disadari pengguna (music player). Wajib punya persistent notification (notif yang tidak bisa di-swipe tutup). Prioritasnya dijaga agar tidak dimatikan OS.
// Cara mulai Started Service biasaval intent = Intent(this, MyService::class.java)startService(intent) // Di-stop oleh Service dengan stopSelf()
[MEDIUM] WorkManager & IntentService
IntentService (deprecated): Otomatis bikin worker thread sendiri, ngerjain task berurutan, langsung hancur kalau selesai.
WorkManager (Modern): Solusi background task masa kini. Bisa pakai constraint (contoh: βhanya jalan kalau ada Wi-Fiβ).
[EASY] Broadcast & Content Provider
Broadcast Receiver: Menangkap pesan global (baterai low, internet nyala). Bisa Static (via Manifest) atau Dynamic (via kode).
Content Provider: Cara share data antar aplikasi dengan aman via URI content://.
// Mendaftarkan receiver secara dinamis di kodeval filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)registerReceiver(receiver, filter)// Ingat: WAJIB panggil unregisterReceiver(receiver) di onDestroy()!
Dispatchers.IO: Khusus input/output seperti panggil Room Database & Retrofit (network).
Dispatchers.Default: Khusus komputasi berat CPU (sorting 10.000 data).
viewModelScope.launch { ... }: Kalau ViewModel mati (Activity pindah), coroutine otomatis dibatalkan agar tidak memory leak.
viewModelScope.launch(Dispatchers.Main) { // Mulai di Main Thread // Pindah ke thread IO sebentar buat panggil DB val data = withContext(Dispatchers.IO) { myDao.getAll() } // Kembali ke Main Thread, update UI!}
[MEDIUM] Database Singleton & Keamanan Thread
Instansiasi database itu berat, jadi hanya boleh ada satu (Singleton).
@VolatileINSTANCE: Menjamin instance yang baru dibuat langsung terlihat oleh semua thread (tidak tersangkut di cache memori CPU).
synchronized(this): Mengunci agar kalau ada 2 thread meminta bikin DB bersamaan, cuma 1 yang boleh bikin.
@Dao: Interface untuk naruh SQL Query (@Query("SELECT *..."), @Insert).
@Database: Kelas abstract penyambung DAO dan Entity.
@Entity(tableName = "users")data class User(@PrimaryKey(autoGenerate = true) val id: Int, val name: String)@Daointerface UserDao { @Query("SELECT * FROM users") suspend fun getAll(): List<User> // HARUS suspend function!}
Beda utama: onCreateView() meng-inflate layout, sedangkan onViewCreated() dipakai untuk setup logika UI agar lebih aman (view sudah pasti ada).
π¦ 5. Bab 13: Dart & Flutter
Keywords:final vs const, Canonicalization, Sound Null Safety, late, StatefulWidget Lifecycle
[HARD] final vs const & Canonicalization
final: Nilai dievaluasi saat runtime (bisa dari hasil API/DB) & tidak bisa diganti.
const: Nilai pasti saat compile-time.
Kenapa penting di Flutter? Flutter punya Canonicalization: Objek const yang sama hanya dibikin 1x di memori. Flutter tahu widget const tidak mungkin berubah, jadi di-skip saat rebuild (Performa sangat cepat!).
final time = DateTime.now(); // β OK, dievaluasi saat runtime// const time = DateTime.now(); // β ERROR, nilai tidak konstan
[HARD] StatefulWidget Lifecycle
createState()βinitState() (cuma jalan 1x buat setup) βbuild() (merender UI) βsetState() (ngasih tahu Flutter ada data berubah, panggil build() lagi) βdispose() (matiin listener/controller, cuma 1x).
void _updateUI() { setState(() { // WAJIB ditaruh dalam setState() agar trigger build() ulang _counter++; });}
[MEDIUM] Sound Null Safety & late
Di Dart 3.0, variabel secara default gak boleh null.
Keyword late: Janji sama compiler bahwa variabel ini gak boleh null, tapi aku mau inisialisasi dia nanti (misal di initState), bukan sekarang.
late String name; // Boleh declare tanpa inisialisasi awal// print(name); // π₯ CRASH! Jika diakses sebelum diisiname = "Dart"; // Diinisialisasi! β
[EASY] JIT vs AOT
JIT (Just-In-Time): Waktu Development. Kompilasi sambil jalan. Kenapa penting? Memungkinkan fitur Hot Reload (tak perlu re-compile seluruh app, cukup suntik kode baru, UI langsung terupdate).
AOT (Ahead-Of-Time): Waktu Production (build rilis). Kode dikompilasi duluan jadi bahasa mesin ARM/x86. Kenapa penting? Agar aplikasi bisa langsung jalan sangat cepat (fast startup) dan UI stabil 60fps tanpa nge-lag saat pertama dibuka.
[HARD] Penyimpanan Data Sensitif & TEE (Trusted Execution Environment)
App-Specific Internal Storage: Paling aman. Data disimpan di /data/data/<package> (terisolasi/Sandbox).
Untuk hal rahasia (token, password), simpan kuncinya di Android Keystore (mendukung TEE / Secure Element Hardware).
Kenapa TEE tidak bisa diretas? TEE adalah chip / area pemrosesan yang terisolasi secara hardware di dalam CPU (memiliki mini-OS sendiri seperti TrustZone). Walaupun OS utama (Android) sudah di-root atau kena malware parah, hacker tetap tidak bisa membaca kunci dari TEE karena terpisah secara fisik & logika.
[MEDIUM] Keamanan Jaringan & SSL Pinning
Gunakan HTTPS, hindari HTTP.
Certificate Pinning: Hardcode sertifikat server (public key-nya) ke dalam aplikasi. Kenapa? Agar aplikasi cuma mau komunikasi dengan server yang sertifikatnya persis sama. Walau hacker sukses ngakalin CA (Certificate Authority) di HP (Man-In-The-Middle attack), aplikasi tetap menolak koneksi!
[EASY] Filosofi Keamanan (Prevent, Minimize, Detect)
Prevent: Cegah dari awal! Pakai Sandbox (tiap aplikasi beda Linux UID sehingga dilarang saling intip folder), enkripsi AES 256.
Minimize: Minimalisir kerugian kalau kebobolan! (Prinsip Least Privilege = jangan minta permission lokasi kalau cuma butuh kamera).
Detect: Deteksi bahaya! Gunakan SafetyNet (ngecek device di-root atau tidak).
[HARD] Optimasi Baterai & Radio State Machine (Sering Ditanyakan!)
Kenapa kita harus βBundle Requestβ (Kirim data internet sekaligus)? Jaringan seluler memiliki Radio State Machine (Standby β Active β Tail β Standby). Menyalakan antena radio itu menyedot baterai sangat besar.
Jika aplikasi kita minta data kecil-kecil tapi terus-menerus (unbundled transfer, misalnya 1 KB per detik selama 18 detik), antena radio tidak akan pernah masuk ke mode Standby. Antena nyangkut di state Active/Tail terus, yang bikin baterai bocor/boros parah.
Solusi: Pre-fetch data sekalian banyak, dan gabungkan request (Bundle).
[MEDIUM] Retrofit Interface & Coroutines
Buat request API jadi suspend fun agar jalan di coroutine (background).
Single Source of Truth. Repository tugasnya mikir data ini harus diambil dari DB lokal (Room) atau narik API baru (Retrofit). ViewModel gak perlu tahu datanya dari mana.
π§ 9. TEORI βHEAVYβ & STUDI KASUS (Bocoran PPT)
A. Lifecycle & Komponen (Bab 9 & 11)
onRestart() Flow:onRestart() dipanggil sebelumonStart() jika Activity kembali dari state onStop. Jadi onStart() bisa dipanggil setelah onCreate() (awal mula) ATAU onRestart() (kembali).
Batas Waktu BroadcastReceiver: Method onReceive() HANYA PUNYA WAKTU 5 DETIK! Lebih dari itu = ANR/Dihancurkan sistem. Jangan lakukan operasi DB/Network yang lama di sini.
Kenapa pakai PendingIntent di Notifikasi? Karena sistem Notifikasi berjalan di proses/app yang berbeda. PendingIntent memberikan hak kepada OS untuk mengeksekusi aksi seolah-olah ia adalah aplikasi kamu (membawa permission milikmu).
Fragment Bundle: Fragment bisa membaca savedInstanceState (Bundle) di 3 tempat: onCreate(), onCreateView(), dan onViewCreated(). (Sedangkan Activity cuma di onCreate()).
B. Security & Network (Bab 12 & 14)
OWASP Mobile Top 10 (2024):
M1: Improper Credential Usage
M2: Inadequate Supply Chain Security
M3: Insecure Authentication/Authorization
M9: Insecure Data Storage
Permission Levels:
Normal: Otomatis disetujui saat install (Contoh: INTERNET, VIBRATE).
Dangerous: Harus minta izin (prompt dialog) ke user saat runtime (Contoh: CAMERA, READ_CONTACTS).
Moshi + Retrofit Setup (Sering keluar di soal kodingan):
var counters = <Function>[];for (var i = 0; i < 3; i++) { counters.add(() => i); }for (var c in counters) { print(c()); }
Output Dart:0, 1, 2. (Dart menyimpan/meng-capture copy variabel untuk tiap loop. Berbeda dengan JS yang akan nge-print 3, 3, 3).
LazyColumn vs RecyclerView:
RecyclerView (Lama): Sangat ribet! Butuh buat class Adapter, ViewHolder, file XML layout terpisah, dan di-setup di Activity.
LazyColumn (Compose): Super singkat! Cukup pakai blok items(data) { item -> Text(item.nama) }.
Compose Modifier Ordering (Margin vs Padding):
Modifier.padding(16.dp).background(Color.Red) = Layar kosong (margin) 16dp, lalu kotak merah.
Modifier.background(Color.Red).padding(16.dp) = Kotak merah yang isinya menjorok 16dp ke dalam.
Observing ViewModel di Compose:
Wajib menggunakan extension method agar UI otomatis update: val users by viewModel.allUsers.observeAsState() (Butuh dependensi lifecycle-viewmodel-compose).