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:
ViewModel:
GameViewModel
dideklarasikan sebagai parameter default dalam fungsiGameScreen
.GameViewModel
ini mengelola status permainan dan menyediakan berbagai fungsi untuk mengubah status tersebut, sepertiresetGame()
,updateUserGuess()
,checkUserGuess()
, danskipWord()
.State Management:
gameUiState
adalah aliran data yang berasal dariGameViewModel
dan dikumpulkan sebagai state menggunakan fungsicollectAsState()
. Ini memungkinkan UI untuk bereaksi secara otomatis terhadap perubahan status permainan yang dikelola olehGameViewModel
.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.
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:
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.
Uji Fungsionalitas: Uji apakah aplikasi dapat menampilkan kata yang diacak dengan benar, menerima tebakan dari pengguna, menghitung skor, dan mengakhiri permainan setelah 10 kata.
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.
Komentar
Posting Komentar