Tugas 8 PPB - B

Nama : Tsabita Putri Ramadhany

NRP : 5025211130

Kelas : PPB - B


Tugas 8

ViewModel and State in Compose


Tugas : Menerapkan ViewModel and State in Compose

Petunjuk Tugas : https://kuliahppb.blogspot.com/2024/05/viewmodel-and-state-in-compose.html



Pendahuluan

Aplikasi Unscramble adalah permainan kata pemain tunggal yang mengacak kata-kata sehingga pemain harus menebak kata asli menggunakan semua huruf yang disediakan. Pemain akan mendapatkan poin jika mereka menebak kata dengan benar. Jika tebakan salah, pemain dapat mencoba lagi hingga berhasil. Aplikasi ini juga menyediakan opsi untuk melewatkan kata saat ini. Di pojok kanan atas, terdapat indikator jumlah kata yang telah dimainkan dalam permainan tersebut, dengan setiap permainan terdiri dari 10 kata acak.

Untuk menjalankan aplikasi Unscramble, langkah pertama adalah mengunduh proyek awal dari GitHub, mengekstrak file zip, kemudian membuka proyek tersebut di Android Studio dan menjalankan aplikasi. Pada percobaan pertama, Anda mungkin akan menemukan beberapa bug. Misalnya, kata yang diacak tidak ditampilkan dengan benar, dan hanya menampilkan kata "scrambleun" secara hard-coded. Tombol tidak berfungsi sebagaimana mestinya. Tugas kita adalah memodifikasi aplikasi ini agar dapat berfungsi dengan baik.

Langkah-Langkah Implementasi

1. Membuat GameViewModel dan GameUiState

Langkah pertama dalam memodifikasi aplikasi ini adalah membuat dua berkas penting: GameViewModel.kt untuk view model dan GameUiState.kt untuk menyimpan state aplikasi. Berikut adalah contoh kode untuk kedua berkas tersebut:

// GameUiState.kt data class GameUiState( val currentScrambledWord: String = "", val isGuessedWordWrong: Boolean = false, val isGameOver: Boolean = false, val score: Int = 0, val currentWordCount: Int = 1, ) // GameViewModel.kt class GameViewModel : ViewModel() { // Game UI state private val _uiState = MutableStateFlow(GameUiState()) val uiState: StateFlow<GameUiState> = _uiState.asStateFlow() var userGuess by mutableStateOf("") private set // Set of words used in the game private var usedWords: MutableSet<String> = mutableSetOf() private lateinit var currentWord: String init { resetGame() } fun resetGame() { usedWords.clear() _uiState.value = GameUiState(currentScrambledWord = pickRandomWordAndShuffle()) } fun updateUserGuess(guessedWord: String){ userGuess = guessedWord } fun checkUserGuess() { if (userGuess.equals(currentWord, ignoreCase = true)) { // User's guess is correct, increase the score // and call updateGameState() to prepare the game for next round val updatedScore = _uiState.value.score.plus(SCORE_INCREASE) updateGameState(updatedScore) } else { // User's guess is wrong, show an error _uiState.update { currentState -> currentState.copy(isGuessedWordWrong = true) } } // Reset user guess updateUserGuess("") } fun skipWord() { updateGameState(_uiState.value.score) // Reset user guess updateUserGuess("") } private fun updateGameState(updatedScore: Int) { if (usedWords.size == MAX_NO_OF_WORDS){ // Last round in the game, update isGameOver to true, don't pick a new word _uiState.update { currentState -> currentState.copy( isGuessedWordWrong = false, score = updatedScore, isGameOver = true ) } } else { // Normal round in the game _uiState.update { currentState -> currentState.copy( isGuessedWordWrong = false, currentScrambledWord = pickRandomWordAndShuffle(), currentWordCount = currentState.currentWordCount.inc(), score = updatedScore ) } } } private fun shuffleCurrentWord(word: String): String { val tempWord = word.toCharArray() tempWord.shuffle() while (String(tempWord).equals(word)) { tempWord.shuffle() } return String(tempWord) } private fun pickRandomWordAndShuffle(): String { currentWord = allWords.random() if (usedWords.contains(currentWord)) { return pickRandomWordAndShuffle() } else { usedWords.add(currentWord) return shuffleCurrentWord(currentWord) } } }

GameUiState digunakan untuk menyimpan dan mengelola status permainan, seperti kata yang sedang dimainkan, apakah tebakan pemain benar atau salah, apakah permainan sudah berakhir, skor pemain saat ini, dan jumlah kata yang telah ditebak. GameViewModel adalah turunan dari kelas ViewModel dalam arsitektur MVVM (Model-View-ViewModel) di Android. Kelas ini mengelola logika dan status permainan.

Penjelasan Detail GameViewModel dan GameUiState

  • GameUiState: Data class ini menyimpan informasi status dari permainan, termasuk kata acak saat ini, apakah tebakan pemain salah, apakah permainan sudah berakhir, skor pemain, dan jumlah kata yang telah ditebak.
  • GameViewModel: Kelas ini mengelola logika permainan dan status. Menggunakan MutableStateFlow untuk menyimpan status permainan yang bisa diperbarui dan diamati. Kelas ini juga menyimpan daftar kata yang sudah digunakan untuk memastikan kata tidak terulang.

2. Menghubungkan GameViewModel dengan UI di GameScreen.kt

Langkah selanjutnya adalah menghubungkan view model dan state yang telah dibuat agar fungsionalitas aplikasi Unscramble dapat berjalan. Berikut adalah detail bagaimana screen, view model, dan state dihubungkan:

Menghubungkan ViewModel ke UI

Dalam GameScreen.kt, kita perlu membuat instance GameViewModel dan menggunakannya untuk mengakses uiState menggunakan collectorAsState(). Fungsi collectAsState() mengumpulkan nilai dari StateFlow ini dan mewakili nilai terbarunya melalui State. Berikut adalah contoh implementasinya:

@Composable fun GameScreen( gameViewModel: GameViewModel = viewModel() ) { val gameUiState by gameViewModel.uiState.collectAsState() GameLayout( currentScrambledWord = gameUiState.currentScrambledWord, isGuessedWordWrong = gameUiState.isGuessedWordWrong, score = gameUiState.score, currentWordCount = gameUiState.currentWordCount, onUserGuessChanged = { gameViewModel.updateUserGuess(it) }, onKeyboardDone = { gameViewModel.checkUserGuess() }, skipWord = { gameViewModel.skipWord() } ) } @Composable fun GameLayout( currentScrambledWord: String, isGuessedWordWrong: Boolean, score: Int, currentWordCount: Int, onUserGuessChanged: (String) -> Unit, onKeyboardDone: () -> Unit, skipWord: () -> Unit ) { // Implementasi layout permainan } @Composable fun GameStatus(score: Int, wordCount: Int) { // Implementasi status permainan } @Composable fun FinalScoreDialog(score: Int, onPlayAgain: () -> Unit) { // Implementasi dialog skor akhir }

3. Implementasi Lengkap GameScreen

Implementasi lengkap dari GameScreen menghubungkan UI dengan logika permainan yang dikelola oleh GameViewModel. Berikut adalah penjelasan singkat tentang bagaimana ini bekerja:

  1. ViewModel: GameViewModel dideklarasikan sebagai parameter default dalam fungsi GameScreen. GameViewModel ini mengelola status permainan dan menyediakan berbagai fungsi untuk mengubah status tersebut, seperti resetGame(), updateUserGuess(), checkUserGuess(), dan skipWord().

  2. State Management: gameUiState adalah aliran data yang berasal dari GameViewModel dan dikumpulkan sebagai state menggunakan fungsi collectAsState(). Ini memungkinkan UI untuk bereaksi secara otomatis terhadap perubahan status permainan yang dikelola oleh GameViewModel.

  3. Composable Functions: Beberapa fungsi composable digunakan untuk membangun UI permainan:

    • GameScreen: Fungsi utama yang menampilkan seluruh layar permainan, mengatur tata letak, dan elemen-elemen UI seperti judul, status permainan, dan tombol.
    • GameLayout: Mengatur tata letak utama untuk menampilkan kata yang diacak, input pengguna, dan instruksi.
    • GameStatus: Menampilkan skor pemain.
    • FinalScoreDialog: Menampilkan dialog skor akhir ketika permainan selesai.
  4. Interaksi Pengguna: Interaksi pengguna seperti mengubah tebakan (onUserGuessChanged), menekan tombol submit (onKeyboardDone), dan melewati kata (skipWord()) dihubungkan dengan metode yang sesuai di GameViewModel. Ini memastikan bahwa setiap interaksi pengguna diperbarui dalam status permainan dan UI secara bersamaan.

4. Detail Tambahan Implementasi di GameScreen.kt

Membuat Layout dan Mengatur Interaksi

Dalam GameScreen.kt, kita akan mengatur layout untuk menampilkan kata yang diacak, input pengguna, dan instruksi. Berikut adalah contoh implementasinya:

@Composable fun GameScreen( gameViewModel: GameViewModel = viewModel() ) { val gameUiState by gameViewModel.uiState.collectAsState() Scaffold( topBar = { TopAppBar( title = { Text("Unscramble Game") } ) } ) { GameLayout( currentScrambledWord = gameUiState.currentScrambledWord, isGuessedWordWrong = gameUiState.isGuessedWordWrong, score = gameUiState.score, currentWordCount = gameUiState.currentWordCount, onUserGuessChanged = { gameViewModel.updateUserGuess(it) }, onKeyboardDone = { gameViewModel.checkUserGuess() }, skipWord = { gameViewModel.skipWord() } ) } } @Composable fun GameLayout( currentScrambledWord: String, isGuessedWordWrong: Boolean, score: Int, currentWordCount: Int, onUserGuessChanged: (String) -> Unit, onKeyboardDone: () -> Unit, skipWord: () -> Unit ) { Column( modifier = Modifier .fillMaxSize() .padding(16.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text( text = "Word #$currentWordCount", style = MaterialTheme.typography.h5 ) Spacer(modifier = Modifier.height(16.dp)) Text( text = currentScrambledWord, style = MaterialTheme.typography.h3 ) Spacer(modifier = Modifier.height(16.dp)) OutlinedTextField( value = "", onValueChange = onUserGuessChanged, label = { Text("Your Guess") } ) Spacer(modifier = Modifier.height(16.dp)) if (isGuessedWordWrong) { Text( text = "Wrong guess! Try again.", color = MaterialTheme.colors.error ) } Spacer(modifier = Modifier.height(16.dp)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { Button(onClick = skipWord) { Text("Skip") } Button(onClick = onKeyboardDone) { Text("Submit") } } Spacer(modifier = Modifier.height(16.dp)) GameStatus(score = score, wordCount = currentWordCount) } } @Composable fun GameStatus(score: Int, wordCount: Int) { Column { Text("Score: $score") Text("Word Count: $wordCount") } } @Composable fun FinalScoreDialog(score: Int, onPlayAgain: () -> Unit) { AlertDialog( onDismissRequest = {}, title = { Text("Game Over") }, text = { Text("Your final score is $score") }, confirmButton = { Button(onClick = onPlayAgain) { Text("Play Again") } } ) }

5. Menjalankan dan Menguji Aplikasi

Setelah semua modifikasi dilakukan, kita dapat menjalankan aplikasi dan menguji apakah semua fungsionalitas bekerja dengan baik. Berikut adalah langkah-langkah untuk menjalankan aplikasi:

  1. Kompilasi dan Jalankan Aplikasi: Pastikan semua dependensi dan konfigurasi sudah benar. Klik tombol "Run" di Android Studio untuk menjalankan aplikasi di emulator atau perangkat fisik.

  2. Uji Fungsionalitas: Uji apakah aplikasi dapat menampilkan kata yang diacak dengan benar, menerima tebakan dari pengguna, menghitung skor, dan mengakhiri permainan setelah 10 kata.

  3. Perbaiki Bug Jika Ditemukan: Jika ada bug atau fungsionalitas yang tidak bekerja sebagaimana mestinya, gunakan logcat dan debugging tools di Android Studio untuk menemukan dan memperbaiki masalah tersebut.

Kesimpulan

Mengembangkan aplikasi Unscramble melibatkan pemahaman mendalam tentang siklus hidup Activity dan penggunaan ViewModel serta StateFlow untuk mengelola status aplikasi. Dengan memodifikasi aplikasi untuk menghubungkan UI dengan logika permainan melalui ViewModel, kita dapat membuat aplikasi yang reaktif dan responsif. Implementasi logging dan state management membantu memastikan bahwa aplikasi bekerja dengan baik bahkan saat terjadi perubahan status perangkat. Proyek ini memberikan pengalaman praktis dalam mengelola status aplikasi dan memastikan interaksi pengguna berjalan lancar.


Hasil Aplikasi:


Source code : GitHub





Komentar

Postingan populer dari blog ini

Tugas 1 PPL - A

Tugas 10 PPL - A

Tugas 10 PPB - B