Dasar-Dasar Android Kotlin 05.1: ViewModel dan ViewModelFactory

Codelab ini adalah bagian dari kursus Dasar-Dasar Android Kotlin. Anda akan mendapatkan manfaat maksimal dari kursus ini jika menyelesaikan codelab secara berurutan. Semua codelab kursus tercantum di halaman landing codelab Dasar-Dasar Android Kotlin.

Layar judul

Layar game

Layar skor

Pengantar

Dalam codelab ini, Anda akan mempelajari salah satu Komponen Arsitektur Android, ViewModel:

  • Anda menggunakan class ViewModel untuk menyimpan dan mengelola data terkait UI dengan cara yang berbasis siklus proses. Class ViewModel memungkinkan data bertahan saat terjadi perubahan konfigurasi perangkat seperti rotasi layar dan perubahan ketersediaan keyboard.
  • Anda menggunakan class ViewModelFactory untuk membuat instance dan menampilkan objek ViewModel yang bertahan dari perubahan konfigurasi.

Yang harus sudah Anda ketahui

  • Cara membuat aplikasi Android dasar di Kotlin.
  • Cara menggunakan grafik navigasi untuk menerapkan navigasi di aplikasi Anda.
  • Cara menambahkan kode untuk menavigasi antar-tujuan aplikasi Anda dan meneruskan data antar-tujuan navigasi.
  • Cara kerja siklus proses aktivitas dan fragmen.
  • Cara menambahkan informasi logging ke aplikasi dan membaca log menggunakan Logcat di Android Studio.

Yang akan Anda pelajari

  • Cara menggunakan arsitektur aplikasi Android yang direkomendasikan.
  • Cara menggunakan class Lifecycle, ViewModel, dan ViewModelFactory di aplikasi Anda.
  • Cara mempertahankan data UI melalui perubahan konfigurasi perangkat.
  • Pengertian pola desain metode factory dan cara menggunakannya.
  • Cara membuat objek ViewModel menggunakan antarmuka ViewModelProvider.Factory.

Yang akan Anda lakukan

  • Tambahkan ViewModel ke aplikasi, untuk menyimpan data aplikasi sehingga data tersebut bertahan saat terjadi perubahan konfigurasi.
  • Gunakan ViewModelFactory dan pola desain metode factory untuk membuat instance objek ViewModel dengan parameter konstruktor.

Dalam codelab Pelajaran 5, Anda akan mengembangkan aplikasi GuessTheWord, dimulai dengan kode awal. GuessTheWord adalah game gaya tebak gaya dua pemain, tempat pemain berkolaborasi untuk mencapai skor tertinggi.

Pemain pertama melihat kata-kata di aplikasi dan memeragakan setiap kata secara bergiliran, memastikan untuk tidak menunjukkan kata tersebut kepada pemain kedua. Pemain kedua mencoba menebak kata tersebut.

Untuk bermain game, pemain pertama membuka aplikasi di perangkat dan melihat sebuah kata, misalnya "gitar", seperti yang ditunjukkan pada screenshot di bawah.

Pemain pertama memperagakan kata, dengan berhati-hati agar tidak mengucapkan kata itu sendiri.

  • Saat pemain kedua menebak kata dengan benar, pemain pertama menekan tombol Paham, yang akan menambah jumlah kata sebanyak satu dan menampilkan kata berikutnya.
  • Jika pemain kedua tidak dapat menebak kata, pemain pertama menekan tombol Lewati, yang akan mengurangi jumlah kata sebanyak satu dan beralih ke kata berikutnya.
  • Untuk mengakhiri game, tekan tombol Akhiri Game. (Fungsi ini tidak ada dalam kode awal untuk codelab pertama dalam seri ini.)

Dalam tugas ini, Anda akan mendownload dan menjalankan aplikasi starter serta memeriksa kodenya.

Langkah 1: Mulai

  1. Download kode awal GuessTheWord dan buka project di Android Studio.
  2. Jalankan aplikasi di perangkat yang didukung Android, atau di emulator.
  3. Ketuk tombol. Perhatikan bahwa tombol Lewati menampilkan kata berikutnya dan mengurangi skor sebanyak satu, dan tombol Oke menampilkan kata berikutnya dan menambah skor sebanyak satu. Tombol Akhiri Game belum diterapkan, jadi tidak ada yang terjadi saat Anda mengetuknya.

Langkah 2: Lakukan penelusuran kode

  1. Di Android Studio, jelajahi kode untuk memahami cara kerja aplikasi.
  2. Pastikan untuk melihat file yang dijelaskan di bawah, yang sangat penting.

MainActivity.kt

File ini hanya berisi kode default yang dihasilkan oleh template.

res/layout/main_activity.xml

File ini berisi tata letak utama aplikasi. NavHostFragment menghosting fragmen lain saat pengguna menavigasi aplikasi.

Fragmen UI

Kode awal memiliki tiga fragmen dalam tiga paket berbeda di bawah paket com.example.android.guesstheword.screens:

  • title/TitleFragment untuk layar judul
  • game/GameFragment untuk layar game
  • score/ScoreFragment untuk layar skor

screens/title/TitleFragment.kt

Fragmen judul adalah layar pertama yang ditampilkan saat aplikasi diluncurkan. Penanganan klik ditetapkan ke tombol Play, untuk membuka layar game.

screens/game/GameFragment.kt

Ini adalah fragmen utama, tempat sebagian besar tindakan game berlangsung:

  • Variabel ditetapkan untuk kata saat ini dan skor saat ini.
  • wordList yang ditentukan di dalam metode resetList() adalah contoh daftar kata yang akan digunakan dalam game.
  • Metode onSkip() adalah handler klik untuk tombol Skip. Fungsi ini mengurangi skor sebesar 1, lalu menampilkan kata berikutnya menggunakan metode nextWord().
  • Metode onCorrect() adalah handler klik untuk tombol Got It. Metode ini diimplementasikan mirip dengan metode onSkip(). Satu-satunya perbedaan adalah metode ini menambahkan 1 ke skor, bukan menguranginya.

screens/score/ScoreFragment.kt

ScoreFragment adalah layar terakhir dalam game, dan menampilkan skor akhir pemain. Dalam codelab ini, Anda akan menambahkan implementasi untuk menampilkan layar ini dan menunjukkan skor akhir.

res/navigation/main_navigation.xml

Grafik navigasi menunjukkan cara fragmen terhubung melalui navigasi:

  • Dari fragmen judul, pengguna dapat membuka fragmen game.
  • Dari fragmen game, pengguna dapat membuka fragmen skor.
  • Dari fragmen skor, pengguna dapat kembali ke fragmen game.

Dalam tugas ini, Anda akan menemukan masalah pada aplikasi starter GuessTheWord.

  1. Jalankan kode awal dan mainkan beberapa kata di dalam game, dengan mengetuk Lewati atau Oke setelah setiap kata.
  2. Layar game kini menampilkan kata dan skor saat ini. Ubah orientasi layar dengan memutar perangkat atau emulator. Perhatikan bahwa skor saat ini hilang.
  3. Mainkan game dengan beberapa kata lagi. Saat layar game ditampilkan dengan skor tertentu, tutup dan buka kembali aplikasi. Perhatikan bahwa game dimulai ulang dari awal, karena status aplikasi tidak disimpan.
  4. Mainkan beberapa kata di dalam game, lalu ketuk tombol End Game. Perhatikan bahwa tidak ada yang terjadi.

Masalah di aplikasi:

  • Aplikasi awal tidak menyimpan dan memulihkan status aplikasi selama perubahan konfigurasi, seperti saat orientasi perangkat berubah, atau saat aplikasi ditutup dan dimulai ulang.
    Anda dapat menyelesaikan masalah ini menggunakan callback onSaveInstanceState(). Namun, penggunaan metode onSaveInstanceState() mengharuskan Anda menulis kode tambahan untuk menyimpan status dalam paket, dan menerapkan logika untuk mengambil status tersebut. Selain itu, jumlah data yang dapat disimpan sangat sedikit.
  • Layar game tidak menavigasi ke layar skor saat pengguna mengetuk tombol Akhiri Game.

Anda dapat mengatasi masalah ini menggunakan komponen arsitektur aplikasi yang Anda pelajari dalam codelab ini.

Arsitektur aplikasi

Arsitektur aplikasi adalah cara mendesain class aplikasi Anda, dan hubungan di antara class tersebut, sehingga kode teratur, berperforma baik dalam skenario tertentu, dan mudah digunakan. Dalam empat set codelab ini, peningkatan yang Anda lakukan pada aplikasi GuessTheWord mengikuti pedoman arsitektur aplikasi Android, dan Anda menggunakan Komponen Arsitektur Android. Arsitektur aplikasi Android mirip dengan pola arsitektur MVVM (model-view-viewmodel).

Aplikasi GuessTheWord mengikuti prinsip desain pemisahan fokus dan dibagi ke dalam beberapa class, dengan setiap class menangani fokus yang terpisah. Dalam codelab pertama dari materi ini, class yang Anda gunakan adalah pengontrol UI, ViewModel, dan ViewModelFactory.

Pengontrol UI

Pengontrol UI adalah class berbasis UI seperti Activity atau Fragment. Pengontrol UI hanya boleh berisi logika yang menangani UI dan interaksi sistem operasi seperti menampilkan tampilan dan merekam input pengguna. Jangan masukkan logika pengambilan keputusan, seperti logika yang menentukan teks yang akan ditampilkan, ke dalam pengontrol UI.

Dalam kode awal GuessTheWord, pengontrol UI adalah tiga fragmen: GameFragment, ScoreFragment,, dan TitleFragment. Mengikuti prinsip desain "pemisahan tugas", GameFragment hanya bertanggung jawab untuk menggambar elemen game ke layar dan mengetahui kapan pengguna mengetuk tombol, dan tidak lebih. Saat pengguna mengetuk tombol, informasi ini akan diteruskan ke GameViewModel.

ViewModel

ViewModel menyimpan data untuk ditampilkan dalam fragmen atau aktivitas yang terkait dengan ViewModel. ViewModel dapat melakukan penghitungan dan transformasi sederhana pada data untuk mempersiapkan data yang akan ditampilkan oleh pengontrol UI. Dalam arsitektur ini, ViewModel melakukan pengambilan keputusan.

GameViewModel menyimpan data seperti nilai skor, daftar kata, dan kata saat ini, karena ini adalah data yang akan ditampilkan di layar. GameViewModel juga berisi logika bisnis untuk melakukan perhitungan sederhana guna menentukan status data saat ini.

ViewModelFactory

ViewModelFactory membuat instance objek ViewModel, dengan atau tanpa parameter konstruktor.

Dalam codelab berikutnya, Anda akan mempelajari Komponen Arsitektur Android lainnya yang terkait dengan pengontrol UI dan ViewModel.

Class ViewModel dirancang untuk menyimpan dan mengelola data terkait UI. Di aplikasi ini, setiap ViewModel dikaitkan dengan satu fragmen.

Dalam tugas ini, Anda akan menambahkan ViewModel pertama ke aplikasi, yaitu GameViewModel untuk GameFragment. Anda juga akan mempelajari apa artinya ViewModel mendukung siklus proses.

Langkah 1: Tambahkan class GameViewModel

  1. Buka file build.gradle(module:app). Di dalam blok dependencies, tambahkan dependensi Gradle untuk ViewModel.

    Jika Anda menggunakan versi terbaru library, aplikasi solusi akan dikompilasi seperti yang diharapkan. Jika tidak, coba selesaikan masalahnya, atau kembalikan ke versi yang ditampilkan di bawah.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
  1. Di folder paket screens/game/, buat class Kotlin baru bernama GameViewModel.
  2. Buat class GameViewModel memperluas class abstrak ViewModel.
  3. Untuk membantu Anda lebih memahami cara ViewModel mendukung siklus proses, tambahkan blok init dengan pernyataan log.
class GameViewModel : ViewModel() {
   init {
       Log.i("GameViewModel", "GameViewModel created!")
   }
}

Langkah 2: Ganti onCleared() dan tambahkan logging

ViewModel akan dihancurkan saat fragmen yang terkait dilepas, atau saat aktivitas selesai. Tepat sebelum ViewModel dihancurkan, callback onCleared() dipanggil untuk membersihkan resource.

  1. Di class GameViewModel, ganti metode onCleared().
  2. Tambahkan laporan log di dalam onCleared() untuk melacak siklus proses GameViewModel.
override fun onCleared() {
   super.onCleared()
   Log.i("GameViewModel", "GameViewModel destroyed!")
}

Langkah 3: Kaitkan GameViewModel dengan fragmen game

ViewModel harus dikaitkan dengan pengontrol UI. Untuk mengaitkan keduanya, Anda membuat referensi ke ViewModel di dalam pengontrol UI.

Pada langkah ini, Anda membuat referensi GameViewModel di dalam pengontrol UI yang sesuai, yaitu GameFragment.

  1. Di class GameFragment, tambahkan kolom jenis GameViewModel di tingkat teratas sebagai variabel class.
private lateinit var viewModel: GameViewModel

Langkah 4: Inisialisasi ViewModel

Selama perubahan konfigurasi seperti rotasi layar, pengontrol UI seperti fragmen dibuat ulang. Namun, instance ViewModel tetap berjalan. Jika Anda membuat instance ViewModel menggunakan class ViewModel, objek baru akan dibuat setiap kali fragmen dibuat ulang. Sebagai gantinya, buat instance ViewModel menggunakan ViewModelProvider.

Cara kerja ViewModelProvider:

  • ViewModelProvider menampilkan ViewModel yang sudah ada jika ada, atau membuat yang baru jika belum ada.
  • ViewModelProvider membuat instance ViewModel yang terkait dengan cakupan tertentu (aktivitas atau fragmen).
  • ViewModel yang dibuat dipertahankan selama cakupan masih aktif. Misalnya, jika cakupannya adalah fragmen, ViewModel dipertahankan hingga fragmen terlepas.

Lakukan inisialisasi ViewModel, menggunakan metode ViewModelProviders.of() untuk membuat ViewModelProvider:

  1. Di class GameFragment, lakukan inisialisasi variabel viewModel. Masukkan kode ini di dalam onCreateView(), setelah definisi variabel binding. Gunakan metode ViewModelProviders.of(), dan teruskan konteks GameFragment terkait dan class GameViewModel.
  2. Di atas inisialisasi objek ViewModel, tambahkan pernyataan log untuk mencatat panggilan metode ViewModelProviders.of().
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
  1. Jalankan aplikasi. Di Android Studio, buka panel Logcat dan filter di Game. Ketuk tombol Putar di perangkat atau emulator. Layar game terbuka.

    Seperti yang ditunjukkan di Logcat, metode onCreateView() dari GameFragment memanggil metode ViewModelProviders.of() untuk membuat GameViewModel. Pernyataan logging yang Anda tambahkan ke GameFragment dan GameViewModel akan muncul di Logcat.

  1. Aktifkan setelan putar otomatis di perangkat atau emulator dan ubah orientasi layar beberapa kali. GameFragment dihancurkan dan dibuat ulang setiap kali, sehingga ViewModelProviders.of() dipanggil setiap kali. Namun, GameViewModel dibuat hanya sekali, dan tidak dibuat ulang atau dihancurkan untuk setiap panggilan.
I/GameFragment: Called ViewModelProviders.of
I/GameViewModel: GameViewModel created!
I/GameFragment: Called ViewModelProviders.of
I/GameFragment: Called ViewModelProviders.of
I/GameFragment: Called ViewModelProviders.of
  1. Keluar dari game atau keluar dari fragmen game. GameFragment telah dihancurkan. GameViewModel terkait juga dihancurkan, dan callback onCleared() dipanggil.
I/GameFragment: Called ViewModelProviders.of
I/GameViewModel: GameViewModel created!
I/GameFragment: Called ViewModelProviders.of
I/GameFragment: Called ViewModelProviders.of
I/GameFragment: Called ViewModelProviders.of
I/GameViewModel: GameViewModel destroyed!

ViewModel bertahan saat terjadi perubahan konfigurasi, sehingga merupakan tempat yang tepat untuk data yang perlu dipertahankan saat terjadi perubahan konfigurasi:

  • Masukkan data yang akan ditampilkan di layar, dan kode untuk memproses data tersebut, di ViewModel.
  • ViewModel tidak boleh berisi referensi ke fragmen, aktivitas, atau tampilan, karena aktivitas, fragmen, dan tampilan tidak bertahan saat terjadi perubahan konfigurasi.

Sebagai perbandingan, berikut cara data UI GameFragment ditangani di aplikasi starter sebelum Anda menambahkan ViewModel, dan setelah Anda menambahkan ViewModel:

  • Sebelum Anda menambahkan ViewModel:
    Saat aplikasi mengalami perubahan konfigurasi seperti rotasi layar, fragmen game akan dihancurkan dan dibuat ulang. Data hilang.
  • Setelah menambahkan ViewModel dan memindahkan data UI fragmen game ke ViewModel:
    Semua data yang perlu ditampilkan fragmen kini menjadi ViewModel. Saat aplikasi mengalami perubahan konfigurasi, ViewModel akan tetap ada, dan data akan dipertahankan.

Dalam tugas ini, Anda memindahkan data UI aplikasi ke class GameViewModel, beserta metode untuk memproses data. Anda melakukan ini agar data dipertahankan selama perubahan konfigurasi.

Langkah 1: Pindahkan kolom data dan pemrosesan data ke ViewModel

Pindahkan kolom dan metode data berikut dari GameFragment ke GameViewModel:

  1. Pindahkan kolom data word, score, dan wordList. Pastikan word dan score bukan private.

    Jangan pindahkan variabel pengikatan, GameFragmentBinding, karena berisi referensi ke tampilan. Variabel ini digunakan untuk memperluas tata letak, menyiapkan pemroses klik, dan menampilkan data di layar—tanggung jawab fragmen.
  2. Pindahkan metode resetList() dan nextWord(). Metode ini memutuskan kata apa yang akan ditampilkan di layar.
  3. Dari dalam metode onCreateView(), pindahkan panggilan metode ke resetList() dan nextWord() ke blok init pada GameViewModel.

    Metode ini harus berada di blok init, karena Anda harus mereset daftar kata saat ViewModel dibuat, bukan setiap kali fragmen dibuat. Anda dapat menghapus pernyataan log di blok init dari GameFragment.

Handler klik onSkip() dan onCorrect() di GameFragment berisi kode untuk memproses data dan memperbarui UI. Kode untuk memperbarui UI harus tetap berada di fragmen, tetapi kode untuk memproses data harus dipindahkan ke ViewModel.

Untuk saat ini, letakkan metode yang identik di kedua tempat:

  1. Salin metode onSkip() dan onCorrect() dari GameFragment ke GameViewModel.
  2. Di GameViewModel, pastikan metode onSkip() dan onCorrect() tidak private, karena Anda akan mereferensikan metode ini dari fragmen.

Berikut adalah kode untuk class GameViewModel, setelah pemfaktoran ulang:

class GameViewModel : ViewModel() {
   // The current word
   var word = ""
   // The current score
   var score = 0
   // The list of words - the front of the list is the next word to guess
   private lateinit var wordList: MutableList<String>

   /**
    * Resets the list of words and randomizes the order
    */
   private fun resetList() {
       wordList = mutableListOf(
               "queen",
               "hospital",
               "basketball",
               "cat",
               "change",
               "snail",
               "soup",
               "calendar",
               "sad",
               "desk",
               "guitar",
               "home",
               "railway",
               "zebra",
               "jelly",
               "car",
               "crow",
               "trade",
               "bag",
               "roll",
               "bubble"
       )
       wordList.shuffle()
   }

   init {
       resetList()
       nextWord()
       Log.i("GameViewModel", "GameViewModel created!")
   }
   /**
    * Moves to the next word in the list
    */
   private fun nextWord() {
       if (!wordList.isEmpty()) {
           //Select and remove a word from the list
           word = wordList.removeAt(0)
       }
       updateWordText()
       updateScoreText()
   }
 /** Methods for buttons presses **/
   fun onSkip() {
       if (!wordList.isEmpty()) {
           score--
       }
       nextWord()
   }

   fun onCorrect() {
       if (!wordList.isEmpty()) {
           score++
       }
       nextWord()
   }

   override fun onCleared() {
       super.onCleared()
       Log.i("GameViewModel", "GameViewModel destroyed!")
   }
}

Berikut adalah kode untuk class GameFragment, setelah pemfaktoran ulang:

/**
* Fragment where the game is played
*/
class GameFragment : Fragment() {


   private lateinit var binding: GameFragmentBinding


   private lateinit var viewModel: GameViewModel


   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                             savedInstanceState: Bundle?): View? {

       // Inflate view and obtain an instance of the binding class
       binding = DataBindingUtil.inflate(
               inflater,
               R.layout.game_fragment,
               container,
               false
       )

       Log.i("GameFragment", "Called ViewModelProviders.of")
       viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)

       binding.correctButton.setOnClickListener { onCorrect() }
       binding.skipButton.setOnClickListener { onSkip() }
       updateScoreText()
       updateWordText()
       return binding.root

   }


   /** Methods for button click handlers **/

   private fun onSkip() {
       if (!wordList.isEmpty()) {
           score--
       }
       nextWord()
   }

   private fun onCorrect() {
       if (!wordList.isEmpty()) {
           score++
       }
       nextWord()
   }


   /** Methods for updating the UI **/

   private fun updateWordText() {
       binding.wordText.text = word
   }

   private fun updateScoreText() {
       binding.scoreText.text = score.toString()
   }
}

Langkah 2: Perbarui referensi ke pengendali klik dan kolom data di GameFragment

  1. Di GameFragment, perbarui metode onSkip() dan onCorrect(). Hapus kode untuk memperbarui skor dan sebagai gantinya, panggil metode onSkip() dan onCorrect() yang sesuai di viewModel.
  2. Karena Anda memindahkan metode nextWord() ke ViewModel, fragmen game tidak dapat mengaksesnya lagi.

    Di GameFragment, dalam metode onSkip() dan onCorrect(), ganti panggilan ke nextWord() dengan updateScoreText() dan updateWordText(). Metode ini menampilkan data di layar.
private fun onSkip() {
   viewModel.onSkip()
   updateWordText()
   updateScoreText()
}
private fun onCorrect() {
   viewModel.onCorrect()
   updateScoreText()
   updateWordText()
}
  1. Di GameFragment, perbarui variabel score dan word untuk menggunakan variabel GameViewModel, karena variabel ini sekarang ada di GameViewModel.
private fun updateWordText() {
   binding.wordText.text = viewModel.word
}

private fun updateScoreText() {
   binding.scoreText.text = viewModel.score.toString()
}
  1. Di GameViewModel, di dalam metode nextWord(), hapus panggilan ke metode updateWordText() dan updateScoreText(). Metode ini kini dipanggil dari GameFragment.
  2. Build aplikasi dan pastikan tidak ada error. Jika ada error, bersihkan dan bangun ulang project.
  3. Jalankan aplikasi dan mainkan game dengan beberapa kata. Saat Anda berada di layar game, putar perangkat. Perhatikan bahwa skor saat ini dan kata saat ini dipertahankan setelah perubahan orientasi.

Bagus! Sekarang semua data aplikasi Anda disimpan di ViewModel, sehingga dipertahankan selama perubahan konfigurasi.

Dalam tugas ini, Anda akan mengimplementasikan pemroses klik untuk tombol End Game.

  1. Di GameFragment, tambahkan metode bernama onEndGame(). Metode onEndGame() akan dipanggil saat pengguna mengetuk tombol Akhiri Game.
private fun onEndGame() {
   }
  1. Di GameFragment, di dalam metode onCreateView(), temukan kode yang menetapkan pemroses klik untuk tombol Got It dan Skip. Tepat di bawah dua baris ini, tetapkan pemroses klik untuk tombol End Game. Gunakan variabel binding, binding. Di dalam pemroses klik, panggil metode onEndGame().
binding.endGameButton.setOnClickListener { onEndGame() }
  1. Di GameFragment, tambahkan metode yang disebut gameFinished() untuk mengarahkan aplikasi ke layar skor. Teruskan skor sebagai argumen, menggunakan Safe Args.
/**
* Called when the game is finished
*/
private fun gameFinished() {
   Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
   val action = GameFragmentDirections.actionGameToScore()
   action.score = viewModel.score
   NavHostFragment.findNavController(this).navigate(action)
}
  1. Dalam metode onEndGame(), panggil metode gameFinished().
private fun onEndGame() {
   gameFinished()
}
  1. Jalankan aplikasi, mainkan game, dan lihat beberapa kata. Ketuk tombol Akhiri Game . Perhatikan bahwa aplikasi membuka layar skor, tetapi skor akhir tidak ditampilkan. Anda akan memperbaikinya di tugas berikutnya.

Saat pengguna mengakhiri game, ScoreFragment tidak menampilkan skor. Anda menginginkan ViewModel untuk menyimpan skor yang akan ditampilkan oleh ScoreFragment. Anda akan meneruskan nilai skor selama inisialisasi ViewModel menggunakan pola metode factory.

Pola metode factory adalah pola desain pembuatan yang menggunakan metode factory untuk membuat objek. Metode factory adalah metode yang menampilkan instance dari class yang sama.

Dalam tugas ini, Anda akan membuat ViewModel dengan konstruktor berparameter untuk fragmen skor dan metode factory untuk membuat instance ViewModel.

  1. Di bawah paket score, buat class Kotlin baru bernama ScoreViewModel. Kelas ini akan menjadi ViewModel untuk fragmen skor.
  2. Perluas class ScoreViewModel dari ViewModel. Tambahkan parameter konstruktor untuk skor akhir. Tambahkan blok init dengan pernyataan log.
  3. Di class ScoreViewModel, tambahkan variabel bernama score untuk menyimpan skor akhir.
class ScoreViewModel(finalScore: Int) : ViewModel() {
   // The final score
   var score = finalScore
   init {
       Log.i("ScoreViewModel", "Final score is $finalScore")
   }
}
  1. Di bagian paket score, buat class Kotlin lain bernama ScoreViewModelFactory. Class ini akan bertanggung jawab untuk membuat instance objek ScoreViewModel.
  2. Perluas class ScoreViewModelFactory dari ViewModelProvider.Factory. Tambahkan parameter konstruktor untuk skor akhir.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}
  1. Di ScoreViewModelFactory, Android Studio menampilkan error tentang anggota abstrak yang belum diimplementasikan. Untuk mengatasi error, ganti metode create(). Dalam metode create(), tampilkan objek ScoreViewModel yang baru dibuat.
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
   if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
       return ScoreViewModel(finalScore) as T
   }
   throw IllegalArgumentException("Unknown ViewModel class")
}
  1. Di ScoreFragment, buat variabel class untuk ScoreViewModel dan ScoreViewModelFactory.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory
  1. Di ScoreFragment, di dalam onCreateView(), setelah menginisialisasi variabel binding, inisialisasi viewModelFactory. Gunakan ScoreViewModelFactory. Teruskan skor akhir dari paket argumen, sebagai parameter konstruktor ke ScoreViewModelFactory().
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)
  1. Di onCreateView(), setelah menginisialisasi viewModelFactory, inisialisasi objek viewModel. Panggil metode ViewModelProviders.of(), teruskan konteks fragmen skor terkait dan viewModelFactory. Tindakan ini akan membuat objek ScoreViewModel menggunakan metode factory yang ditentukan dalam class viewModelFactory.
viewModel = ViewModelProviders.of(this, viewModelFactory)
       .get(ScoreViewModel::class.java)
  1. Dalam metode onCreateView(), setelah melakukan inisialisasi viewModel, tetapkan teks tampilan scoreText ke skor akhir yang ditentukan dalam ScoreViewModel.
binding.scoreText.text = viewModel.score.toString()
  1. Jalankan aplikasi Anda dan mainkan game-nya. Ulangi beberapa atau semua kata, lalu ketuk Akhiri Game. Perhatikan bahwa fragmen skor kini menampilkan skor akhir.

  1. Opsional: Periksa log ScoreViewModel di Logcat dengan memfilter ScoreViewModel. Nilai skor harus ditampilkan.
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15

Dalam tugas ini, Anda telah mengimplementasikan ScoreFragment untuk menggunakan ViewModel. Anda juga mempelajari cara membuat konstruktor berparameter untuk ViewModel menggunakan antarmuka ViewModelFactory.

Selamat! Anda mengubah arsitektur aplikasi untuk menggunakan salah satu Komponen Arsitektur Android, ViewModel. Anda telah menyelesaikan masalah siklus proses aplikasi, dan kini data game dapat bertahan saat terjadi perubahan konfigurasi. Anda juga mempelajari cara membuat konstruktor berparameter untuk membuat ViewModel, menggunakan antarmuka ViewModelFactory.

Project Android Studio: GuessTheWord

  • Panduan arsitektur aplikasi Android merekomendasikan pemisahan class yang memiliki tanggung jawab berbeda.
  • Pengontrol UI adalah class berbasis UI seperti Activity atau Fragment. Pengontrol UI hanya boleh berisi logika yang menangani UI dan interaksi sistem operasi; pengontrol UI tersebut tidak boleh berisi data yang akan ditampilkan di UI. Masukkan data tersebut ke dalam ViewModel.
  • Class ViewModel menyimpan dan mengelola data terkait UI. Class ViewModel memungkinkan data bertahan saat terjadi perubahan konfigurasi seperti pada saat rotasi layar.
  • ViewModel adalah salah satu Komponen Arsitektur Android yang direkomendasikan.
  • ViewModelProvider.Factory adalah antarmuka yang dapat Anda gunakan untuk membuat objek ViewModel.

Tabel di bawah membandingkan pengontrol UI dengan instance ViewModel yang menyimpan data untuknya:

Pengontrol UI

ViewModel

Contoh pengontrol UI adalah ScoreFragment yang Anda buat di codelab ini.

Contoh ViewModel adalah ScoreViewModel yang Anda buat dalam codelab ini.

Tidak berisi data apa pun untuk ditampilkan di UI.

Berisi data yang ditampilkan pengontrol UI di UI.

Berisi kode untuk menampilkan data, dan kode peristiwa pengguna seperti pemroses klik.

Berisi kode untuk pemrosesan data.

Dimusnahkan dan dibuat ulang selama setiap perubahan konfigurasi.

Dihancurkan hanya saat pengontrol UI terkait hilang secara permanen—untuk aktivitas, saat aktivitas selesai, atau untuk fragmen, saat fragmen dilepas.

Berisi tampilan.

Tidak boleh berisi referensi ke aktivitas, fragmen, atau tampilan, karena tidak bertahan saat terjadi perubahan konfigurasi, tetapi ViewModel bertahan.

Berisi referensi ke ViewModel terkait.

Tidak berisi referensi ke pengontrol UI terkait.

Kursus Udacity:

Dokumentasi developer Android:

Lainnya:

Bagian ini mencantumkan kemungkinan tugas pekerjaan rumah untuk siswa yang mengerjakan codelab ini sebagai bagian dari kursus yang dipimpin oleh instruktur. Instruktur menentukan hal berikut:

  • Memberikan pekerjaan rumah jika diperlukan.
  • Memberi tahu siswa cara mengirimkan tugas pekerjaan rumah.
  • Memberi nilai tugas pekerjaan rumah.

Instruktur bisa menggunakan saran ini sesuai kebutuhan, dan bebas menugaskan pekerjaan rumah lain yang dirasa cocok.

Jika Anda menyelesaikan codelab ini sendiri, gunakan tugas pekerjaan rumah ini untuk menguji pengetahuan Anda.

Jawab pertanyaan-pertanyaan berikut

Pertanyaan 1

Untuk menghindari kehilangan data selama perubahan konfigurasi perangkat, di class mana data aplikasi harus disimpan?

  • ViewModel
  • LiveData
  • Fragment
  • Activity

Pertanyaan 2

ViewModel tidak boleh berisi referensi apa pun untuk fragmen, aktivitas, atau tampilan. Benar atau salah?

  • Benar
  • Salah

Pertanyaan 3

Kapan ViewModel dihancurkan?

  • Saat pengontrol UI terkait dihancurkan dan dibuat ulang selama perubahan orientasi perangkat.
  • Dalam perubahan orientasi.
  • Saat pengontrol UI terkait selesai (jika berupa aktivitas) atau dilepas (jika berupa fragmen).
  • Saat pengguna menekan tombol Kembali.

Pertanyaan 4

Untuk apa antarmuka ViewModelFactory?

  • Membuat instance objek ViewModel.
  • Menyimpan data selama perubahan orientasi.
  • Memuat ulang data yang ditampilkan di layar.
  • Menerima notifikasi saat data aplikasi diubah.

Mulai pelajaran berikutnya: 5.2: LiveData dan observer LiveData

Untuk link ke codelab lain dalam kursus ini, lihat halaman landing codelab Dasar-Dasar Android Kotlin.