Android View Model
69582 ワード
ActivityはonCreateポイントでdevice configurationを参照します.デバイス構成が変更された場合はonCreateを再度呼び出し、これらの理由で他の操作がない場合は携帯電話を横モードとしてviewを初期化します.
このときBundleを使用することができますが、ActivityとPregmentのコードはアプリケーションの複雑さが増すにつれて複雑になりますので、Bundleを処理すると非常に面倒になります.
view model
このときBundleを使用することができますが、ActivityとPregmentのコードはアプリケーションの複雑さが増すにつれて複雑になりますので、Bundleを処理すると非常に面倒になります.
view model
ビューモデルは、画面上のすべてのデータとビジネスロジックを担当するオブジェクトです.画面を更新する必要がある場合は、アクティビティとプレゼンテーションでビューモデルが尋ねられ、ビジネスロジック関数もビューモデルから呼び出されます.
これらのビューモデルを使用すると、アクティブおよびレンダリングはレイアウト処理に集中できます.また、デバイスconfigurationが変更されても、ビューモデルオブジェクトは安全であるため、Bundleの追加処理は必要ありません.
前の例
単語推測ゲームをして、ビューモデルの適用前後を比較します.
メインアクティブデバイスは、ナビゲータのフロントエンドとしてのみ機能し、GameFragmentとResultFragmentを作成します.
framgent_game
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp"
tools:context=".GameFragment">
<TextView
android:id="@+id/word"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="36sp"
android:letterSpacing="0.1"/>
<TextView
android:id="@+id/lives"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"/>
<TextView
android:id="@+id/incorrect_guesses"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"/>
<EditText
android:id="@+id/guess"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:hint="Guess a letter"
android:inputType="text"
android:maxLength="1"/>
<Button
android:id="@+id/guessButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Guess!"/>
</LinearLayout>
framgent_result
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:gravity="center"
tools:context=".ResultFragment">
<TextView
android:id="@+id/won_lost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="28sp"
android:layout_margin="18dp"/>
<Button
android:id="@+id/new_game_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Start new game"/>
</LinearLayout>
nav_graph
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/gameFragment">
<fragment
android:id="@+id/gameFragment"
android:name="com.example.guessinggame.GameFragment"
android:label="Game"
tools:layout="@layout/fragment_game">
<action
android:id="@+id/toResultFragment"
app:destination="@id/resultFragment"
app:popUpTo="@id/gameFragment"
app:popUpToInclusive="true"/>
</fragment>
<fragment
android:id="@+id/resultFragment"
android:name="com.example.guessinggame.ResultFragment"
android:label="Result"
tools:layout="@layout/fragment_game">
<argument
android:name="result"
app:argType="string" />
<action
android:id="@+id/toGameFragment"
app:destination="@id/gameFragment" />
</fragment>
</navigation>
activity_main
<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/navHost"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true"
tools:context=".MainActivity">
</androidx.fragment.app.FragmentContainerView>
GameFragment.kt
class GameFragment: Fragment() {
private var _binding: FragmentGameBinding? = null
private val binding get() = _binding!!
val words = listOf("Android", "Activity", "Fragment")
val secretWord = words.random().uppercase()
var secretWordDisplay = ""
var correctGuess = ""
var incorrectGuess = ""
var livesLeft = 8
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentGameBinding.inflate(inflater, container, false)
val view = binding.root
secretWordDisplay = deriveSecretWordDisplay()
updateScreen()
binding.guessButton.setOnClickListener {
makeGuess(binding.guess.text.toString().uppercase())
binding.guess.text = null
updateScreen()
if (isWon() || isLost()) {
val action = GameFragmentDirections
.toResultFragment(wonListMessage())
view.findNavController().navigate(action)
}
}
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
fun updateScreen() {
binding.word.text = secretWordDisplay
binding.lives.text = "You have $livesLeft lives left"
binding.incorrectGuesses.text = "Incorrect guesses: $incorrectGuess"
}
fun deriveSecretWordDisplay() : String {
var display = ""
secretWord.forEach {
display += checkLetter(it.toString())
}
return display
}
fun checkLetter(str: String) = when (correctGuess.contains(str)) {
true -> str
false -> "_"
}
fun makeGuess(guess: String) {
if (guess.length == 1) {
if (secretWord.contains(guess)) {
correctGuess += guess
secretWordDisplay = deriveSecretWordDisplay()
} else {
incorrectGuess += "$guess"
livesLeft--
}
}
}
fun isWon() = secretWord.equals(secretWordDisplay, true)
fun isLost() = livesLeft <= 0
fun wonListMessage() : String {
var message = ""
if (isWon()) message = "You Won!"
else if (isLost()) message = "You Lost!"
message += " The word wat $secretWord"
return message
}
}
ResultFragment.kt
class ResultFragment: Fragment() {
private var _binding: FragmentResultBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentResultBinding.inflate(inflater, container, false)
val view = binding.root
binding.wonLost.text = ResultFragmentArgs.fromBundle(requireArguments()).result
binding.newGameButton.setOnClickListener {
view.findNavController().navigate(R.id.toGameFragment)
}
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
まず、ゲーム宣伝のコードが長いです.しかし、この状態では、携帯電話が横画面モードになると、既存のデータはすべて消えてしまいます.Bundleを処理するコードを再加入するのは負担です.次に、ビューモデルを適用します.
ビューモデルの適用
ビューモデルは、画面から出力されるデータとビジネスロジックを担当するオブジェクトです.従って、game fragmentのprefertieおよび関数は、ビューモデルに移行されるべきである.残りのコードはUIを制御するコードだけです.
ビューモデルを適用することで、注目点分離の概念を適用できます.Activity、pregment、viewmodelの仕事は完全に分かれています.
ビューモデル依存性の追加
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
ビューモデルオブジェクトの追加
ゲームプレゼンテーションからビジネスロジックとビューに関するデータを取得します.そしてAndroid xlifecycle.ViewModelを拡張する必要があります.class GameViewModel: ViewModel() {
val words = listOf("Android", "Activity", "Fragment")
val secretWord = words.random().uppercase()
var secretWordDisplay = ""
var correctGuess = ""
var incorrectGuess = ""
var livesLeft = 8
init {
secretWordDisplay = deriveSecretWordDisplay()
}
fun deriveSecretWordDisplay() : String {
var display = ""
secretWord.forEach {
display += checkLetter(it.toString())
}
return display
}
fun checkLetter(str: String) = when (correctGuess.contains(str)) {
true -> str
false -> "_"
}
fun makeGuess(guess: String) {
if (guess.length == 1) {
if (secretWord.contains(guess)) {
correctGuess += guess
secretWordDisplay = deriveSecretWordDisplay()
} else {
incorrectGuess += "$guess"
livesLeft--
}
}
}
fun isWon() = secretWord.equals(secretWordDisplay, true)
fun isLost() = livesLeft <= 0
fun wonListMessage() : String {
var message = ""
if (isWon()) message = "You Won!"
else if (isLost()) message = "You Lost!"
message += " The word wat $secretWord"
return message
}
}
Link ViewModel & Fragment
ゲームプレゼンテーションから直接オブジェクトを作成し、ViewModelProviderオブジェクトから作成します.このオブジェクトから新しいインスタンスを作成し、ビューモデルオブジェクトが以前に作成されていない場合にのみ新しいインスタンスを作成します.デバイス構成が変更されても、すでに生成されたビューモデルがあるため、再生成されません.
レンダリングのライフサイクルによってレンダリングが破壊されると、ビューモデルも消えます.次に、レイヤーを再作成すると、ビューモデルが再作成されます.class GameFragment: Fragment() {
private var _binding: FragmentGameBinding? = null
private val binding get() = _binding!!
lateinit var viewModel: GameViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentGameBinding.inflate(inflater, container, false)
val view = binding.root
viewModel = ViewModelProvider(this).get(GameViewModel::class.java)
updateScreen()
binding.guessButton.setOnClickListener {
viewModel.makeGuess(binding.guess.text.toString().uppercase())
binding.guess.text = null
updateScreen()
if (viewModel.isWon() || viewModel.isLost()) {
val action = GameFragmentDirections
.toResultFragment(viewModel.wonListMessage())
view.findNavController().navigate(action)
}
}
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
fun updateScreen() {
binding.word.text = viewModel.secretWordDisplay
binding.lives.text = "You have ${viewModel.livesLeft} lives left"
binding.incorrectGuesses.text = "Incorrect guesses: ${viewModel.incorrectGuess}"
}
}
ResultViewModel
結果を出せばStringでいいclass ResultViewModel(finalResult: String): ViewModel() {
val result = finalResult
}
しかし、ジェネレータでパラメータを受信するビューモデルをどのように作成しますか?上記では、GameViewModelはジェネレータを単独で考慮していません.
ViewModelFactoryを使用できます.このオブジェクトの目的は、ビューモデルの作成と初期化です.
以下に作成します.// 이 인터페이스를 구현함으로써 클래스를 뷰모델 팩토리로 바꿔놓음.
class ResultViewModelFactory(private val finalResult: String)
:ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
// 제대로된 타입으로 만들려고 하는지 체크
if (modelClass.isAssignableFrom(ResultViewModel::class.java))
return ResultViewModel(finalResult) as T
// 그렇지 않다면 예외 처리
throw IllegalArgumentException("Unknown ViewModel")
}
}
ResultFragmentコードを変更するにはclass ResultFragment: Fragment() {
private var _binding: FragmentResultBinding? = null
private val binding get() = _binding!!
lateinit var viewModelFactory: ResultViewModelFactory
lateinit var viewModel: ResultViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentResultBinding.inflate(inflater, container, false)
val view = binding.root
val result = ResultFragmentArgs.fromBundle(requireArguments()).result
viewModelFactory = ResultViewModelFactory(result)
viewModel = ViewModelProvider(this, viewModelFactory).get(ResultViewModel::class.java)
binding.wonLost.text = viewModel.result
binding.newGameButton.setOnClickListener {
view.findNavController().navigate(R.id.toGameFragment)
}
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
onCleared()
onCleared()は、ビューモデルが消去される前に呼び出されます.ビューモデルに関連するアクティブまたはバージョンのライフサイクルでビューモデルを使用して、ビューモデルが消える前にリソースをクリーンアップする場合に便利です.ビューモデルのライフサイクルと考えられますか?
Reference
この問題について(Android View Model), 我々は、より多くの情報をここで見つけました
https://velog.io/@tjeong/Android-View-Model
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp"
tools:context=".GameFragment">
<TextView
android:id="@+id/word"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textSize="36sp"
android:letterSpacing="0.1"/>
<TextView
android:id="@+id/lives"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"/>
<TextView
android:id="@+id/incorrect_guesses"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"/>
<EditText
android:id="@+id/guess"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:hint="Guess a letter"
android:inputType="text"
android:maxLength="1"/>
<Button
android:id="@+id/guessButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Guess!"/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:gravity="center"
tools:context=".ResultFragment">
<TextView
android:id="@+id/won_lost"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="28sp"
android:layout_margin="18dp"/>
<Button
android:id="@+id/new_game_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Start new game"/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/gameFragment">
<fragment
android:id="@+id/gameFragment"
android:name="com.example.guessinggame.GameFragment"
android:label="Game"
tools:layout="@layout/fragment_game">
<action
android:id="@+id/toResultFragment"
app:destination="@id/resultFragment"
app:popUpTo="@id/gameFragment"
app:popUpToInclusive="true"/>
</fragment>
<fragment
android:id="@+id/resultFragment"
android:name="com.example.guessinggame.ResultFragment"
android:label="Result"
tools:layout="@layout/fragment_game">
<argument
android:name="result"
app:argType="string" />
<action
android:id="@+id/toGameFragment"
app:destination="@id/gameFragment" />
</fragment>
</navigation>
<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/navHost"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true"
tools:context=".MainActivity">
</androidx.fragment.app.FragmentContainerView>
class GameFragment: Fragment() {
private var _binding: FragmentGameBinding? = null
private val binding get() = _binding!!
val words = listOf("Android", "Activity", "Fragment")
val secretWord = words.random().uppercase()
var secretWordDisplay = ""
var correctGuess = ""
var incorrectGuess = ""
var livesLeft = 8
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentGameBinding.inflate(inflater, container, false)
val view = binding.root
secretWordDisplay = deriveSecretWordDisplay()
updateScreen()
binding.guessButton.setOnClickListener {
makeGuess(binding.guess.text.toString().uppercase())
binding.guess.text = null
updateScreen()
if (isWon() || isLost()) {
val action = GameFragmentDirections
.toResultFragment(wonListMessage())
view.findNavController().navigate(action)
}
}
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
fun updateScreen() {
binding.word.text = secretWordDisplay
binding.lives.text = "You have $livesLeft lives left"
binding.incorrectGuesses.text = "Incorrect guesses: $incorrectGuess"
}
fun deriveSecretWordDisplay() : String {
var display = ""
secretWord.forEach {
display += checkLetter(it.toString())
}
return display
}
fun checkLetter(str: String) = when (correctGuess.contains(str)) {
true -> str
false -> "_"
}
fun makeGuess(guess: String) {
if (guess.length == 1) {
if (secretWord.contains(guess)) {
correctGuess += guess
secretWordDisplay = deriveSecretWordDisplay()
} else {
incorrectGuess += "$guess"
livesLeft--
}
}
}
fun isWon() = secretWord.equals(secretWordDisplay, true)
fun isLost() = livesLeft <= 0
fun wonListMessage() : String {
var message = ""
if (isWon()) message = "You Won!"
else if (isLost()) message = "You Lost!"
message += " The word wat $secretWord"
return message
}
}
class ResultFragment: Fragment() {
private var _binding: FragmentResultBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentResultBinding.inflate(inflater, container, false)
val view = binding.root
binding.wonLost.text = ResultFragmentArgs.fromBundle(requireArguments()).result
binding.newGameButton.setOnClickListener {
view.findNavController().navigate(R.id.toGameFragment)
}
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
class GameViewModel: ViewModel() {
val words = listOf("Android", "Activity", "Fragment")
val secretWord = words.random().uppercase()
var secretWordDisplay = ""
var correctGuess = ""
var incorrectGuess = ""
var livesLeft = 8
init {
secretWordDisplay = deriveSecretWordDisplay()
}
fun deriveSecretWordDisplay() : String {
var display = ""
secretWord.forEach {
display += checkLetter(it.toString())
}
return display
}
fun checkLetter(str: String) = when (correctGuess.contains(str)) {
true -> str
false -> "_"
}
fun makeGuess(guess: String) {
if (guess.length == 1) {
if (secretWord.contains(guess)) {
correctGuess += guess
secretWordDisplay = deriveSecretWordDisplay()
} else {
incorrectGuess += "$guess"
livesLeft--
}
}
}
fun isWon() = secretWord.equals(secretWordDisplay, true)
fun isLost() = livesLeft <= 0
fun wonListMessage() : String {
var message = ""
if (isWon()) message = "You Won!"
else if (isLost()) message = "You Lost!"
message += " The word wat $secretWord"
return message
}
}
class GameFragment: Fragment() {
private var _binding: FragmentGameBinding? = null
private val binding get() = _binding!!
lateinit var viewModel: GameViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentGameBinding.inflate(inflater, container, false)
val view = binding.root
viewModel = ViewModelProvider(this).get(GameViewModel::class.java)
updateScreen()
binding.guessButton.setOnClickListener {
viewModel.makeGuess(binding.guess.text.toString().uppercase())
binding.guess.text = null
updateScreen()
if (viewModel.isWon() || viewModel.isLost()) {
val action = GameFragmentDirections
.toResultFragment(viewModel.wonListMessage())
view.findNavController().navigate(action)
}
}
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
fun updateScreen() {
binding.word.text = viewModel.secretWordDisplay
binding.lives.text = "You have ${viewModel.livesLeft} lives left"
binding.incorrectGuesses.text = "Incorrect guesses: ${viewModel.incorrectGuess}"
}
}
class ResultViewModel(finalResult: String): ViewModel() {
val result = finalResult
}
// 이 인터페이스를 구현함으로써 클래스를 뷰모델 팩토리로 바꿔놓음.
class ResultViewModelFactory(private val finalResult: String)
:ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
// 제대로된 타입으로 만들려고 하는지 체크
if (modelClass.isAssignableFrom(ResultViewModel::class.java))
return ResultViewModel(finalResult) as T
// 그렇지 않다면 예외 처리
throw IllegalArgumentException("Unknown ViewModel")
}
}
class ResultFragment: Fragment() {
private var _binding: FragmentResultBinding? = null
private val binding get() = _binding!!
lateinit var viewModelFactory: ResultViewModelFactory
lateinit var viewModel: ResultViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentResultBinding.inflate(inflater, container, false)
val view = binding.root
val result = ResultFragmentArgs.fromBundle(requireArguments()).result
viewModelFactory = ResultViewModelFactory(result)
viewModel = ViewModelProvider(this, viewModelFactory).get(ResultViewModel::class.java)
binding.wonLost.text = viewModel.result
binding.newGameButton.setOnClickListener {
view.findNavController().navigate(R.id.toGameFragment)
}
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Reference
この問題について(Android View Model), 我々は、より多くの情報をここで見つけました https://velog.io/@tjeong/Android-View-Modelテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol