TeachingMobile Technology SolutionSession 2 - Activity Lifecycle
Week 2Workshop

Session 2 - Activity Lifecycle

Sesi 2: Activity Lifecycle

Mata Kuliah: Mobile Technology Solution (COSC6103001) Learning Outcome:

  • LO2 — Describe the main features of Android Programming and Android Software Development
  • LO3 — Produce simple Mobile Application using the main features of Android

Daftar Isi

  1. Understanding an Activity
  2. Intent — Cara Activity Saling Memanggil
  3. Activity Lifecycle
  4. Tujuh Method Lifecycle
  5. Studi Kasus: Music Player App
  6. Menangani Rotasi Layar
  7. Activity Monitoring: Tiga Lifetime
  8. Ejection from Memory
  9. Android Components (Material Design 3)
  10. Rangkuman Cepat

1. Understanding an Activity

Definisi

Activity merepresentasikan satu window atau satu halaman dalam aplikasi, berisi satu hierarki View.

Empat karakteristik utama:

Karakteristik Penjelasan
Satu window/page + satu hierarki view Satu Activity = satu layar. Hierarki View-nya didefinisikan di file XML layout
Bisa fullscreen atau floating Tidak selalu menutupi seluruh layar — dialog izin dan Picture-in-Picture adalah contoh Activity floating
Menangani interaksi user Jembatan antara aksi user (klik, ketik) dengan respons yang ditampilkan
Bisa memanggil Activity lain Baik dalam app yang sama maupun app lain (lewat Intent)

Kaitan dengan project HelloBinus:

  • MainActivity.kt = satu Activity
  • activity_main.xml = hierarki View-nya
  • binding.buttonSapa.setOnClickListener { } = menangani interaksi user
  • binding.textViewGreeting.text = "..." = menampilkan respons

Konsep: Non-Deterministic User Journey

Non-deterministic = alurnya tidak bisa ditebak, bisa berbeda-beda tiap kali.

Contoh konkret:

Membuka app Email dari Home Screen → menampilkan inbox. Membuka app Email yang sama dari app Galeri (lewat tombol "Share") → langsung menampilkan halaman compose email baru, bukan inbox.

App yang sama bisa "dimasuki" dari pintu berbeda tergantung konteks.

Alur Logika Desain Android

MASALAH:  User journey di mobile TIDAK bisa ditebak (non-deterministic)
    ↓
SOLUSI:   Android memecah app menjadi Activity-Activity yang INDEPENDEN
    ↓
EFEK:     Setiap Activity berpotensi menjadi entry point
    ↓
HASIL:    Activity bisa dipanggil dari Activity/app manapun (lewat Intent)

2. Intent — Cara Activity Saling Memanggil

Intent adalah "pesan permintaan" yang dikirim ke sistem Android untuk meminta suatu aksi dilakukan.

Dua Jenis Intent

Explicit Intent — tahu persis tujuannya

Dipakai untuk berpindah antar Activity dalam app yang sama.

val intent = Intent(this, HomeActivity::class.java)
startActivity(intent)

Analogi: mengirim surat dengan alamat lengkap — sistem tidak perlu mencari.

Implicit Intent — hanya menyebut "aksi apa"

Dipakai untuk meminta aksi tanpa tahu app spesifik mana yang akan menjalankannya.

// Buka kamera — sistem yang cari app kamera mana yang tersedia
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
startActivity(intent)

// Buka halaman web
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://binus.ac.id"))
startActivity(intent)

// Bagikan teks ke app lain (WhatsApp, Email, dll)
val intent = Intent(Intent.ACTION_SEND).apply {
    type = "text/plain"
    putExtra(Intent.EXTRA_TEXT, "Halo dari app saya!")
}
startActivity(intent)

Analogi: "Tolong carikan siapa saja yang bisa fotokopi dokumen ini" — tidak peduli siapa, yang penting bisa mengerjakan.

Mengirim Data lewat Intent

// Activity pengirim
val intent = Intent(this, HomeActivity::class.java)
intent.putExtra("nama_user", "Budi")
startActivity(intent)

// Activity penerima
val nama = intent.getStringExtra("nama_user")

Topik ini akan dibahas mendalam di Topik 3 — Multiple Activities.

Membatasi Urutan Pemanggilan Activity

Pertanyaan umum: bisakah Activity A dibatasi hanya boleh dibuka setelah Activity B?

Jawaban: Android tidak menyediakan mekanisme bawaan untuk ini — sesuai filosofi non-deterministic. Developer harus menegakkannya sendiri lewat kode.

class HomeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val prefs = getSharedPreferences("app_prefs", MODE_PRIVATE)
        val isLoggedIn = prefs.getBoolean("is_logged_in", false)

        if (!isLoggedIn) {
            startActivity(Intent(this, LoginActivity::class.java))
            finish()  // tutup Activity ini agar tidak bisa di-back
            return
        }

        setContentView(R.layout.activity_home)
        // ...lanjut kode normal...
    }
}

Jangan Tertukar: android:exported

Mekanisme Mencegah apa
android:exported="false" di Manifest App lain memanggil Activity ini dari luar
Pengecekan kondisi di onCreate() Activity dipanggil tanpa melalui alur yang benar (dari dalam maupun luar)

Sejak Android 12, android:exported wajib dideklarasikan eksplisit untuk Activity yang punya <intent-filter> — kalau tidak, app gagal di-compile.


3. Activity Lifecycle

Activity Stack (Back Stack)

Activity dikelola sistem sebagai stack dengan prinsip LIFO (Last In, First Out) — seperti tumpukan piring.

Buka MainActivity      → Stack: [MainActivity]
Buka DetailActivity    → Stack: [MainActivity, DetailActivity]        ← terlihat
Buka SettingsActivity  → Stack: [MainActivity, DetailActivity, SettingsActivity]  ← terlihat

Tekan BACK             → SettingsActivity di-pop
                       → Stack: [MainActivity, DetailActivity]        ← DetailActivity terlihat lagi

Setiap startActivity(intent) = push Activity baru ke atas stack.

Bisa ada lebih dari satu stack di layar sekaligus — contohnya saat split-screen (fitur resmi sejak Android 7.0), di mana dua app berdampingan masing-masing punya stack independen.

Empat State Activity

Istilah di slide Istilah resmi Android Kondisi
Active / Running Resumed Di foreground, menerima interaksi user
Paused Paused Masih terlihat, tapi kehilangan fokus
Stopped Stopped Tidak terlihat sama sekali
— Destroyed Sudah dihancurkan

Perbedaan krusial Paused vs Stopped:

  • Paused = masih terlihat sebagian. Contoh: dialog muncul di atas Activity, atau split-screen dengan fokus di app sebelah
  • Stopped = tertutup total. Contoh: tekan tombol Home, atau Activity lain menutupi penuh

Penting: Baik Paused maupun Stopped, sistem berhak membunuh proses kapan saja tanpa pemberitahuan kalau butuh RAM.

Diagram Lifecycle Resmi

                      ┌─────────────┐
              ┌──────►│   Resumed   │◄──────┐
              │       │  (visible)  │       │ onResume()
   onResume() │       └──────┬──────┘       │
              │              │ onPause()    │
       ┌──────┴──────┐       ▼       ┌──────┴────────────┐
       │   Started   │            │      Paused          │
       │  (visible)  │            │ (partially visible)  │
       └──────▲──────┘            └──────┬───────────────┘
              │ onStart()                │ onStop()
              │                          ▼
       ┌──────┴──────┐  onRestart() ┌──────────────┐
       │   Created   │◄─────────────│   Stopped    │
       └──────▲──────┘              │   (hidden)   │
              │ onCreate()          └──────┬───────┘
              │                            │ onDestroy()
        [App diluncurkan]                  ▼
                                    ┌──────────────┐
                                    │  Destroyed   │  ← tidak ada jalan kembali
                                    └──────────────┘

Pelajaran Utama dari Diagram

Jumlah method yang dipanggil saat "kembali aktif" berbeda drastis tergantung seberapa parah gangguannya:

Skenario Jalur Method yang dipanggil
Kembali dari Paused (gangguan ringan) Paused → Resumed 1 method: onResume()
Kembali dari Stopped (gangguan berat) Stopped → Created → Started → Resumed 3 method: onRestart() → onStart() → onResume()

Catatan tentang onRestart()

onRestart() bukan pengganti onCreate(). Dia hanya "jembatan" yang dipanggil sebelum onStart(), khusus untuk Activity yang sebelumnya sempat Stopped.

Analogi:

  • onCreate() = menyalakan TV dari kondisi mati total (kabel dicabut)
  • onRestart() = menyalakan TV dari mode standby

4. Tujuh Method Lifecycle

Tabel Lengkap

Method Kapan dipanggil Contoh penggunaan
onCreate() Activity dibuat Inflate layout, setup View Binding, pasang listener
onStart() Activity mulai terlihat Mulai animasi, daftarkan BroadcastReceiver
onResume() Activity mulai berinteraksi dengan user Mulai GPS, buka kamera, putar musik
onPause() Activity kehilangan fokus Hentikan GPS, jeda musik, simpan data
onStop() Activity tidak terlihat lagi Simpan ke database, tutup koneksi
onDestroy() Activity dihancurkan Lepas resource berat (release())
onRestart() Activity bangkit dari Stopped Jarang diisi kode khusus

Pola Pasangan "Buka–Tutup"

Ini cara paling efektif memahami lifecycle — jangan hafal 7 item terpisah, tapi ingat sebagai 3 pasang + 1 jembatan:

  onCreate()  ←────────────────→  onDestroy()     (Entire Lifetime)
     onStart()  ←────────────→  onStop()          (Visible Lifetime)
        onResume()  ←──────→  onPause()           (Foreground Lifetime)

                  onRestart()  (jembatan, berdiri sendiri)

Prinsip: apapun yang "dinyalakan" di satu method, harus "dimatikan" di method pasangannya.

Contoh Implementasi Lengkap

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        Log.d("LifecycleDemo", "onCreate() dipanggil")
    }

    override fun onStart() {
        super.onStart()
        Log.d("LifecycleDemo", "onStart() dipanggil")
    }

    override fun onResume() {
        super.onResume()
        Log.d("LifecycleDemo", "onResume() dipanggil")
    }

    override fun onPause() {
        super.onPause()
        Log.d("LifecycleDemo", "onPause() dipanggil")
    }

    override fun onStop() {
        super.onStop()
        Log.d("LifecycleDemo", "onStop() dipanggil")
    }

    override fun onRestart() {
        super.onRestart()
        Log.d("LifecycleDemo", "onRestart() dipanggil")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("LifecycleDemo", "onDestroy() dipanggil")
    }
}

Jangan lupa import:

import android.util.Log

Aturan Penting per Method

onPause() harus CEPAT. Activity berikutnya (yang baru dibuka) menunggu onPause() selesai sebelum bisa masuk onResume(). Kalau isinya proses berat, transisi akan terasa lag.

onDestroy() TIDAK terjamin dipanggil. Kalau sistem membunuh proses paksa karena kekurangan RAM, onDestroy() bisa dilewati sepenuhnya.

Tabel Keandalan

Method Seberapa terjamin dipanggil?
onCreate(), onStart(), onResume() Selalu, dalam alur normal
onPause() Sangat terjamin
onStop() Cukup terjamin
onDestroy() TIDAK terjamin — bisa dilewati saat kill paksa

Aturan emas: simpan data penting di onPause() atau onStop(), jangan menunggu onDestroy().


5. Studi Kasus: Music Player App

Versi Awal (Bermasalah)

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val musicUrl = "https://...../music.mp3"

    mediaPlayer = MediaPlayer().apply {
        setAudioAttributes(
            AudioAttributes.Builder()
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .build()
        )
        setDataSource(musicUrl)
        prepare()
        start()
    }
}

Penjelasan kode:

Bagian Fungsi
MediaPlayer() Class bawaan Android untuk memutar audio/video
.apply { } Scope function Kotlin — this merujuk ke objek MediaPlayer, jadi tidak perlu mengulang nama variabel
AudioAttributes.Builder() Pola Builder untuk konfigurasi: CONTENT_TYPE_MUSIC (ini musik, bukan notifikasi), USAGE_MEDIA (volume media)
setDataSource → prepare → start Alur baku MediaPlayer: tentukan sumber, siapkan, putar

Empat Masalah pada Versi Awal

1. prepare() untuk sumber internet — berisiko ANR

prepare() bersifat blocking — menahan UI thread sampai buffering selesai. Untuk URL internet dengan koneksi lambat, ini bisa memicu ANR (Application Not Responding).

Solusi:

mediaPlayer.prepareAsync()                       // non-blocking
mediaPlayer.setOnPreparedListener { it.start() }  // start setelah siap

2. Tidak ada release() — resource leak

MediaPlayer memakai buffer native dan koneksi jaringan. Tanpa release(), resource tetap menggantung di memori.

3. Musik restart setiap onCreate() dipanggil ulang

Karena start() ada di dalam onCreate(), setiap Activity dibuat ulang (misal karena rotasi), musik mengulang dari awal.

4. Tidak menangani perpindahan app

Tidak ada onPause()/onResume() — musik terus main (atau berhenti mendadak kalau proses di-kill) tanpa kontrol.

Versi Perbaikan

override fun onResume() {
    super.onResume()
    mediaPlayer?.start()
}

override fun onPause() {
    super.onPause()
    mediaPlayer?.pause()
}

override fun onDestroy() {
    super.onDestroy()
    mediaPlayer?.release()
}

Mengapa desain ini tepat:

Keputusan Alasan
Pakai onResume()/onPause(), bukan onStart()/onStop() Musik wajar dijeda begitu user kehilangan fokus sedikit saja
Pakai pause(), bukan stop() atau release() pause() cepat (sesuai aturan onPause()) dan posisi lagu tersimpan
Pakai release() di onDestroy() Pasangan dari pembuatan objek di onCreate()
Operator ?. (safe call) Mencegah crash kalau method lifecycle terpanggil sebelum mediaPlayer diinisialisasi

Alur Cerita Lengkap App Ini

1. User buka app
   → onCreate() : layout disiapkan, musik disiapkan dan mulai main
   → onStart() → onResume() : mediaPlayer.start() (musik sudah main, ini memastikan saja)

2. User dengarkan musik
   → State: Resumed. Musik main normal.

3. User tekan Home / pindah app
   → onPause() : mediaPlayer.pause() → musik berhenti, posisi TERSIMPAN (misal detik 45)
   → onStop()  : Activity tidak terlihat

4A. User kembali ke app (proses masih hidup)
   → onRestart() → onStart() → onResume() : mediaPlayer.start()
   → Musik LANJUT dari detik 45

4B. Sistem membunuh proses (butuh RAM) sebelum user kembali
   → Semua objek di memori HILANG, termasuk mediaPlayer dan posisi lagunya
   → Saat app dibuka lagi: onCreate() jalan dari nol
   → Musik MULAI ULANG dari detik 0

5. User menutup app sepenuhnya
   → onPause() → onStop() → onDestroy() : mediaPlayer.release()
   → Semua resource dilepas bersih

Keterbatasan yang Masih Ada

Perbaikan di atas belum menyelesaikan masalah rotasi layar — karena mediaPlayer masih dibuat dari nol di onCreate().

Untuk musik yang benar-benar independen dari lifecycle Activity (seperti Spotify yang tetap main saat app ditutup), solusi sesungguhnya adalah Service — dibahas di Topik 9.


6. Menangani Rotasi Layar

Mengapa Musik Restart Saat Rotasi?

Rotasi layar memicu Activity benar-benar di-destroy lalu dibuat ulang (onDestroy() → onCreate()), karena rotasi dianggap perubahan konfigurasi. Objek mediaPlayer yang lama ikut hilang.

Solusi 1: Cegah Recreate (Sederhana)

AndroidManifest.xml:

<activity
    android:name=".MainActivity"
    android:configChanges="orientation|screenSize"
    android:exported="true">
    ...
</activity>

Dengan ini, onCreate() tidak dipanggil ulang saat rotasi — musik lanjut mulus tanpa putus.

Kalau perlu tahu kapan rotasi terjadi:

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    // sesuaikan tampilan manual di sini kalau perlu
}

Trade-off: Google tidak merekomendasikan ini sebagai solusi umum, karena kamu jadi harus urus sendiri penyesuaian layout tiap orientasi. Tapi untuk media player, ini solusi yang wajar dan umum dipakai di industri.

Solusi 2: Simpan Posisi (Lebih Formal)

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putInt("posisi_musik", mediaPlayer?.currentPosition ?: 0)
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    mediaPlayer = MediaPlayer().apply {
        setDataSource(musicUrl)
        prepare()

        val posisiTersimpan = savedInstanceState?.getInt("posisi_musik") ?: 0
        seekTo(posisiTersimpan)

        start()
    }
}

Cara kerja: onSaveInstanceState() dipanggil sistem sebelum Activity dihancurkan. Kita "titipkan" posisi lagu ke Bundle. Saat onCreate() jalan lagi, savedInstanceState berisi titipan itu.

Kelemahan: musik tetap putus sepersekian detik karena Activity memang dibuat ulang.

Perbandingan Dua Solusi

Solusi 1 (configChanges) Solusi 2 (onSaveInstanceState)
Kompleksitas Rendah Sedang (perlu paham Bundle)
Musik putus? Tidak Ya, sedikit
Sesuai rekomendasi Google? Kurang, untuk kasus umum Ya, pola resmi
Cocok untuk sesi pengantar? ✅ Materi pengayaan

Layout Berbeda untuk Portrait dan Landscape

Kalau app perlu tampilan berbeda tiap orientasi, cukup buat folder terpisah:

res/
├── layout/                    ← PORTRAIT (default)
│   └── activity_main.xml
└── layout-land/                ← LANDSCAPE
    └── activity_main.xml

Cara membuat di Android Studio: klik kanan res → New → Android Resource Directory → Resource type: layout → qualifier: Orientation → Landscape.

Aturan penting: nama file dan nama id harus sama persis di kedua folder.

<!-- res/layout/activity_main.xml -->
<Button android:id="@+id/buttonSapa" ... />

<!-- res/layout-land/activity_main.xml -->
<Button android:id="@+id/buttonSapa" ... />   ← id SAMA, posisi/susunan boleh beda

Kode Kotlin tidak perlu diubah — setContentView(R.layout.activity_main) otomatis memilih file yang sesuai orientasi.

Konflik: configChanges vs Layout Otomatis

Kebutuhan Solusi Efek samping
Musik tidak putus saat rotasi android:configChanges Layout tidak otomatis berganti
Layout otomatis berganti Biarkan default (destroy-recreate) Musik restart dari 0

Solusi gabungan — pakai configChanges tapi ganti layout manual:

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)

    // Android tetap otomatis ambil dari layout-land kalau orientasi landscape
    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)

    // PENTING: pasang ulang listener, karena View lama sudah diganti
    binding.buttonSapa.setOnClickListener {
        mediaPlayer?.let { if (it.isPlaying) it.pause() else it.start() }
    }
}

mediaPlayer tetap aman karena dia objek terpisah, bukan bagian dari View.


7. Activity Monitoring: Tiga Lifetime

Hubungan Bersarang (Nested)

Entire Lifetime      [onCreate() ................................ onDestroy()]
Visible Lifetime          [onStart()...onStop()]    [onStart()...onStop()]
Foreground Lifetime          [onResume()..onPause()]    [onResume()..onPause()]

Foreground selalu di dalam Visible, dan Visible selalu di dalam Entire.

Tabel Perbandingan

Lifetime Rentang Frekuensi Aturan isi kode
Entire onCreate() – onDestroy() 1 kali per generasi Activity Setup/bongkar resource besar (boleh agak berat)
Visible onStart() – onStop() Beberapa kali Resource yang perlu aktif selama terlihat
Foreground onResume() – onPause() Paling sering, bisa berkali-kali dalam detik HARUS ringan dan cepat

Prinsip Umum

Semakin ke dalam (Entire → Visible → Foreground), semakin sering method itu dipanggil, jadi semakin ringan kode di dalamnya harus dibuat.

Panduan Memilih Lifetime

Kalau resource ini... Setup di Bersihkan di
Perlu ada sekali selama Activity hidup (koneksi database, View Binding) onCreate() onDestroy()
Perlu aktif selama terlihat, walau tidak fokus (animasi background) onStart() onStop()
Hanya boleh aktif saat benar-benar dipakai (musik, GPS, kamera) onResume() onPause()

Contoh Perbandingan Nyata

Animasi awan di app cuaca → cocok di Visible Lifetime:

override fun onStart() {
    super.onStart()
    animasiAwan.mulai()   // boleh jalan walau tidak fokus penuh
}

override fun onStop() {
    super.onStop()
    animasiAwan.berhenti()  // berhenti saat tidak terlihat sama sekali
}

Musik → cocok di Foreground Lifetime (seperti studi kasus kita), karena wajar berhenti begitu user teralihkan sedikit saja.

Contoh Kode Ringan vs Berat

✅ RINGAN — cocok untuk onResume()/onPause():

override fun onResume() {
    super.onResume()
    mediaPlayer?.start()   // operasi cepat
}

❌ BERAT — hindari di onResume()/onPause():

override fun onResume() {
    super.onResume()
    val semuaData = database.query("SELECT * FROM riwayat_besar")   // lambat
    mediaPlayer = MediaPlayer().apply { setDataSource(url); prepare(); start() }  // bikin objek baru terus
}

8. Ejection from Memory

Rantai Sebab-Akibat

Sistem butuh RAM
      ↓
Sistem harus MEMBUNUH beberapa proses
      ↓
Kemungkinan proses X dibunuh, tergantung STATE PROSES itu
      ↓
State proses itu sendiri, tergantung STATE ACTIVITY di dalamnya

Tabel Prioritas Kill

Likelihood of being killed Process state Final activity state
Lowest Foreground (focus) Resumed
Low Visible (no focus) Started / Paused
Higher Background (invisible) Stopped
Highest Empty Destroyed

Catatan Penting

"Lowest" ≠ "Never". Google memakai kata likelihood (kemungkinan), bukan jaminan mutlak. Bahkan Activity Resumed tetap bisa hilang dalam skenario:

Skenario Penjelasan
Tekanan memori ekstrem HP RAM terbatas + banyak app berat berjalan
User force-stop manual Settings → Apps → Force Stop
App crash Unhandled exception (misal NullPointerException)
HP restart / baterai habis Semua proses mati bersamaan
Perintah developer adb shell am force-stop

Satu proses bisa punya banyak Activity. State proses mengikuti Activity paling penting di dalamnya — kalau ada satu saja Activity yang Resumed, seluruh proses dianggap foreground (aman).

Method vs State

Perlu dibedakan:

  • onCreate(), onStart(), onResume() = method, titik transisi sesaat
  • Created, Started, Resumed = state, kondisi yang bertahan
onCreate() dipanggil → sesaat di state "Created" → langsung lanjut ke onStart()
onStart() dipanggil  → sesaat di state "Started" → langsung lanjut ke onResume()
onResume() dipanggil → masuk state "Resumed" → BERTAHAN sampai ada gangguan

Tabel prioritas kill mengacu ke state yang sedang bertahan, bukan method-nya.


9. Android Components (Material Design 3)

Enam Kategori

Components adalah building block interaktif untuk membuat user interface, dikelompokkan berdasarkan tujuan:

Kategori Definisi Contoh komponen
Action Membantu user mencapai suatu tujuan Button, FAB, Icon Button, Segmented Button
Containment Menampung informasi dan aksi lain Card, Dialog, List
Communication Menyediakan informasi berguna Snackbar, Badge, Progress indicator
Navigation Membantu user berpindah di dalam UI Bottom Nav, Tabs, Navigation Drawer
Selection Membiarkan user menentukan pilihan Checkbox, Radio Button, Switch, Chip
Text Input User memasukkan teks Text Field

Status di Project HelloBinus

Kategori Sudah dipraktikkan? Komponen
Action ✅ MaterialButton
Containment ✅ MaterialCardView
Communication ⚠️ Sebagian error message di TextInputLayout
Navigation ❌ Belum —
Selection ❌ Belum —
Text Input ✅ TextInputLayout

Action Components — Empat Varian

1. Common Button — sudah dipakai di HelloBinus

<com.google.android.material.button.MaterialButton
    android:id="@+id/buttonSapa"
    android:text="@string/btn_sapa" />

Tiga level penekanan visual:

Level Tampilan Kapan dipakai
Filled Solid berwarna penuh Aksi paling penting (contoh: "Sapa Saya")
Outlined Hanya garis tepi Aksi sekunder (contoh: "Reset")
Text Tanpa background/border Aksi paling ringan ("Batal")
<!-- Outlined -->
<com.google.android.material.button.MaterialButton
    style="?attr/materialButtonOutlinedStyle"
    android:id="@+id/buttonReset"
    android:text="@string/btn_reset" />

2. Floating Action Button (FAB) — tombol bulat mengambang untuk satu aksi paling penting di layar

<com.google.android.material.floatingactionbutton.FloatingActionButton
    android:id="@+id/fabEdit"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@android:drawable/ic_menu_edit"
    app:layout_gravity="bottom|end" />

Contoh nyata: FAB "+" di Gmail untuk compose email baru.

3. Icon Button — kecil, hanya ikon, untuk aksi yang simbolnya universal (gear = settings)

4. Segmented Button — beberapa tombol menyatu, untuk switch tampilan

<com.google.android.material.button.MaterialButtonToggleGroup
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:singleSelection="true">

    <com.google.android.material.button.MaterialButton android:text="Songs" />
    <com.google.android.material.button.MaterialButton android:text="Albums" />
    <com.google.android.material.button.MaterialButton android:text="Podcasts" />

</com.google.android.material.button.MaterialButtonToggleGroup>

Catatan klasifikasi: Segmented Button masuk Action, bukan Selection — karena mengkliknya memicu aksi (mengganti konten yang ditampilkan), bukan sekadar menyimpan pilihan.

Containment Components — Tiga Contoh

1. Card — sudah dipakai di HelloBinus

<com.google.android.material.card.MaterialCardView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardCornerRadius="16dp"
    app:cardElevation="6dp"
    app:cardBackgroundColor="@color/white">

    <!-- TextView, EditText, Button ditampung di sini -->

</com.google.android.material.card.MaterialCardView>

2. Dialog — contoh nyata Activity/komponen yang floating (tidak fullscreen)

binding.buttonReset.setOnClickListener {
    MaterialAlertDialogBuilder(this)
        .setTitle("Konfirmasi Reset")
        .setMessage("Yakin mau reset sapaan?")
        .setPositiveButton("Ya") { _, _ ->
            // logic reset
        }
        .setNegativeButton("Batal", null)
        .show()
}

Dialog bisa diimplementasikan dua cara:

Cara Kapan dipakai
AlertDialog (komponen biasa) Paling umum — konfirmasi sederhana
Activity dengan tema Dialog Kasus kompleks yang butuh lifecycle sendiri

3. List — item berulang dengan struktur sama tapi data berbeda

List membutuhkan RecyclerView + Adapter karena datanya dinamis — dibahas mendalam di Topik 5 — RecyclerView and Adapter.


10. Rangkuman

Tujuh Method

onCreate()   → Activity dibuat            (sekali saja)
onStart()    → mulai terlihat             (bisa berkali-kali)
onResume()   → mulai berinteraksi         (paling sering)
onPause()    → kehilangan fokus           (harus CEPAT)
onStop()     → tidak terlihat lagi
onRestart()  → bangkit dari Stopped       (jembatan)
onDestroy()  → dihancurkan                (TIDAK terjamin dipanggil)

Empat State

Resumed   → terlihat + fokus       → paling AMAN dari kill
Paused    → terlihat, tanpa fokus  → agak berisiko
Stopped   → tidak terlihat         → berisiko tinggi
Destroyed → sudah hancur           → tidak ada jalan kembali

Tiga Lifetime

Entire     [onCreate ──────────────────── onDestroy]   1x, kode boleh berat
Visible      [onStart ───── onStop]                     beberapa kali
Foreground     [onResume ─ onPause]                     paling sering, HARUS ringan

Referensi