Tugas 10: Membuat Aplikasi Unscramble

Blog ini akan memberikan penjelasan komprehensif dari seluruh kode aplikasi "Unscramble". Kita akan melihat bagaimana setiap file—mulai dari data, state, hingga logika di ViewModel—saling terhubung untuk menciptakan sebuah game yang fungsional dan terstruktur dengan baik dalam arsitektur Android modern.

Alur Kerja Aplikasi: Dari UI ke Logika dan Kembali Lagi

Secara garis besar, aplikasi ini bekerja dengan alur sebagai berikut:

  1. Inisialisasi: MainActivity memuat GameScreen (UI). GameScreen kemudian mendapatkan instance dari GameViewModel.
  2. Pengamatan State: GameScreen "mengamati" atau "mendengarkan" perubahan pada uiState dari GameViewModel.
  3. Tampilan UI: Berdasarkan data dari uiState (misalnya, kata yang diacak, skor), GameScreen menampilkan elemen-elemen yang sesuai.
  4. Interaksi Pengguna: Pengguna mengetik tebakan di TextField atau menekan tombol "Submit".
  5. Pemicu Aksi: Aksi pengguna tersebut memanggil fungsi yang sesuai di GameViewModel (misalnya, checkUserGuess()).
  6. Proses Logika: GameViewModel menjalankan logika permainan (memeriksa jawaban, memperbarui skor, dll).
  7. Pembaruan State: GameViewModel memperbarui _uiState internalnya dengan data baru.
  8. Recomposition: Karena GameScreen mengamati uiState, perubahan ini secara otomatis memicu penggambaran ulang (recomposition) UI untuk menampilkan data terbaru.

1. Sumber Data dan Aturan Permainan (`WordsData.kt`)


const val MAX_NO_OF_WORDS = 10
const val SCORE_INCREASE = 20

val allWords: Set<String> =
    setOf(
        "animal",
        "auto",
        "anecdote",
        // ...and many more words
    )
        

File ini adalah fondasi dari permainan. Ia mendefinisikan aturan dasar seperti jumlah maksimum kata per permainan (MAX_NO_OF_WORDS) dan penambahan skor (SCORE_INCREASE). Variabel allWords yang bertipe Set adalah bank kata yang akan digunakan. Penggunaan Set efisien untuk memastikan tidak ada kata duplikat.

2. Cetak Biru State Tampilan (`GameUiState.kt`)


data class GameUiState(
    val currentScrambledWord: String = "",
    val currentWordCount: Int = 1,
    val score: Int = 0,
    val isGuessedWordWrong: Boolean = false,
    val isGameOver: Boolean = false
)
        

Ini adalah sebuah data class yang berfungsi sebagai "cetak biru" atau "blueprint" untuk semua data yang perlu diketahui oleh UI pada satu waktu. Dengan menyatukan semua state relevan dalam satu objek, kita menciptakan "sumber kebenaran tunggal" (single source of truth). UI hanya perlu menerima objek ini untuk menampilkan seluruh keadaan permainan.

3. Otak Permainan (`GameViewModel.kt`)

Ini adalah komponen paling penting yang berisi seluruh logika dan state management.

a. Manajemen State dengan StateFlow

private val _uiState = MutableStateFlow(GameUiState())
val uiState: StateFlow<GameUiState> = _uiState.asStateFlow()
        

ViewModel menggunakan MutableStateFlow untuk menyimpan state game saat ini. _uiState bersifat private, artinya hanya ViewModel sendiri yang bisa mengubahnya. Kemudian, ia diekspos sebagai StateFlow yang bersifat read-only ke UI. Pola ini memastikan bahwa UI tidak dapat secara tidak sengaja mengubah state, menjaga alur data tetap teratur.

b. Logika Permainan (Memeriksa dan Memperbarui)

fun checkUserGuess() {
    if (userGuess.equals(currentWord, ignoreCase = true)) {
        val updatedScore = _uiState.value.score.plus(SCORE_INCREASE)
        updateGameState(updatedScore)
    } else {
        _uiState.update { currentState ->
            currentState.copy(isGuessedWordWrong = true)
        }
    }
    updateUserGuess("") // Reset tebakan setelah diperiksa
}

private fun updateGameState(updatedScore: Int) {
    if (usedWords.size == MAX_NO_OF_WORDS) {
        _uiState.update { currentState ->
            currentState.copy(isGameOver = true, score = updatedScore)
        }
    } else {
        _uiState.update { currentState ->
            currentState.copy(
                isGuessedWordWrong = false,
                currentScrambledWord = pickRandomWordAndShuffle(),
                currentWordCount = currentState.currentWordCount.inc(),
                score = updatedScore
            )
        }
    }
}
        

Fungsi-fungsi publik seperti checkUserGuess() dan skipWord() adalah antarmuka yang akan dipanggil oleh UI. Mereka kemudian memanggil fungsi privat updateGameState() yang mengatur alur permainan, apakah lanjut ke ronde berikutnya atau mengakhiri permainan. Semua logika kompleks ini terisolasi di dalam ViewModel.

4. Menghubungkan ke UI (`GameScreen.kt` - Sebuah Inferensi)

Catatan: Kode UI (GameScreen.kt) tidak disertakan dalam prompt Anda, namun berdasarkan ViewModel yang ada, kita dapat menyimpulkan (inferensi) bagaimana kode tersebut akan terlihat dan bekerja.


import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun GameScreen(
    gameViewModel: GameViewModel = viewModel() // 1. Mendapatkan instance ViewModel
) {
    // 2. Mengumpulkan state dari ViewModel menjadi State Compose
    val gameUiState by gameViewModel.uiState.collectAsState()

    Column(...) {
        // 3. Menampilkan UI berdasarkan state
        Text(text = gameUiState.currentScrambledWord, style = MaterialTheme.typography.headlineMedium)
        Text(text = "Skor: ${gameUiState.score}")
        Text(text = "Kata ke-${gameUiState.currentWordCount}")

        OutlinedTextField(
            value = gameViewModel.userGuess,
            onValueChange = { gameViewModel.updateUserGuess(it) }, // 4. Mengirim event ke ViewModel
            label = { Text("Masukkan tebakan Anda") },
            isError = gameUiState.isGuessedWordWrong // Mengubah tampilan jika jawaban salah
        )

        Button(onClick = { gameViewModel.checkUserGuess() }) { // 5. Memanggil aksi di ViewModel
            Text("Submit")
        }

        // 6. UI Kondisional untuk Game Over
        if (gameUiState.isGameOver) {
            FinalScoreDialog(
                score = gameUiState.score,
                onPlayAgain = { gameViewModel.resetGame() }
            )
        }
    }
}
        

Berikut adalah penjelasan dari kode inferensi di atas:

  1. Mendapatkan ViewModel: Composable menggunakan `viewModel()` untuk mendapatkan instance dari GameViewModel yang terikat pada siklus hidup yang benar.
  2. Mengumpulkan State: collectAsState() adalah fungsi ekstensi yang mengubah StateFlow dari ViewModel menjadi State yang dapat dibaca oleh Compose. Setiap kali StateFlow mengeluarkan nilai baru, composable ini akan di-recompose.
  3. Menampilkan UI: Elemen-elemen seperti Text membaca data langsung dari objek gameUiState untuk menampilkan kata acak, skor, dll.
  4. Mengirim Event: Saat pengguna mengetik, onValueChange pada OutlinedTextField memanggil `gameViewModel.updateUserGuess(it)`. Ini adalah contoh "event yang mengalir ke atas".
  5. Memanggil Aksi: Saat tombol "Submit" ditekan, onClick memanggil `gameViewModel.checkUserGuess()`, mendelegasikan logika pemeriksaan ke ViewModel.
  6. UI Kondisional: Blok `if (gameUiState.isGameOver)` menunjukkan bagaimana UI dapat secara dinamis menampilkan komponen yang berbeda (seperti dialog skor akhir) berdasarkan sebuah flag boolean di dalam state.

Dokumentasi & Referensi

  • Github
  • https://developer.android.com/codelabs/basic-android-kotlin-compose-viewmodel-and-state
  • https://kuliahppb.blogspot.com/2024/05/viewmodel-and-state-in-compose.html

Komentar

Postingan populer dari blog ini

ETS Proyek 5: Aplikasi Galeri Foto Pribadi

Evaluasi Akhir Semester