TIL) 0902

Hanseul Lee·2022년 9월 2일
0

TIL

목록 보기
4/23

ViewModel을 안전하게 사용하기

ViewModel을 안전하게 사용하자!

Dialogs

사용자에게 결정하거나 추가 정보를 입력하라는 메시지를 표시하는 작은 화면. 전체 화면을 채우지 않고 사용자가 액션을 해야 계속 진행할 수 있다. 일반적으로 다음과 같이 생겼다.

  1. Alert Dialog
  2. Title (optional)
  3. Message
  4. Text buttons
private fun showFinalScoreDialog() {
   MaterialAlertDialogBuilder(requireContext())
       .setTitle(getString(R.string.congratulations))
       .setMessage(getString(R.string.you_scored, viewModel.score))
       .setCancelable(false) // 사용자가 뒤로 가기를 눌러서 알림창을 취소할 수 없게 한다
       .setNegativeButton(getString(R.string.exit)) { _, _ ->
           exitGame()
       }
       .setPositiveButton(getString(R.string.play_again)) { _, _ ->
           restartGame()
       }
       .show()
}

LiveData

기존에 알고 있기 때문에 코드만 정리

  • MutabeLiveData
    // GameViewModel.kt
    
    private lateinit var _currentScrambledWord: String
    val currentScrambledWord: String get() = _currentScrambledWord
    
    ---------------------------
    
    private val _currentScrambledWord = MutableLiveData<String>()
    val currentScrambledWord: LiveData<String> get() = _currentScrambledWord
    // GameViewModel.kt
    
    private fun getNextWord() {
     ...
       } else {
           _currentScrambledWord = String(tempWord)
           ...
       }
    }
    
    -----------------------------
    
    private fun getNextWord() {
     ...
       } else {
           _currentScrambledWord.value = String(tempWord)
           ...
       }
    }
  • observer 연결 기존 업데이트와 출력을 담당하던 메서드 부분 삭제
    // GameFragment.kt
    
    private fun onSubmitWord() {
            val playerWord = binding.textInputEditText.text.toString()
    
            if (viewModel.isUserWordCorrect(playerWord)) {
                setErrorTextField(false)
                if (viewModel.nextWord()) {
                    updateNextWordOnScreen()
                } else {
                    showFinalScoreDialog()
                }
            } else {
                setErrorTextField(true)
            }
        }
    
    ------------------------
    
    private fun onSubmitWord() {
        val playerWord = binding.textInputEditText.text.toString()
    
        if (viewModel.isUserWordCorrect(playerWord)) {
            setErrorTextField(false)
            if (!viewModel.nextWord()) {
                showFinalScoreDialog()
            }
        } else {
            setErrorTextField(true)
        }
    }
    // GameFragment.kt
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            ...
    
            viewModel.currentScrambledWord.observe(viewLifecycleOwner) { newWord ->
                binding.textViewUnscrambledWord.text = newWord
            }
        }
    • viewLifecycleOwner → Fragment에 그려져 있으니 Fragment의 생명 주기를 나타낸다. 이 매개변수를 통해 LiveData가 GameFragment의 생명 주기를 인식한다. 그래서 LiveData가 GameFragment의 STATED 혹은 RESUMED일 때를 인식해 Observer에게 알릴 수 있다.
    • newWord → 기본값은 it이고, 아까 MutabeLiveData로 변환한 currentScrambledWord다.

cf) LiveData 수 연산 처리

  • 덧셈 → plus()
    private fun increaseScore() {
        _score.value = (_score.value)?.plus(SCORE_INCREASE)
    }
  • 증분 → inc()
    private fun getNextWord() {
       ...
        } else {
            _currentScrambledWord.value = String(tempWord)
            _currentWordCount.value = (_currentWordCount.value)?.inc()
            wordsList.add(currentWord)
           }
       }

DataBinding

DataBinding의 이점을 정리해보자.

  1. Activity에서 UI 프레임워크 호출을 많이 삭제할 수 있어 코드가 단순해진다.
  2. 유지관리가 쉬워진다.
  3. 앱 성능이 향상된다.
  4. 메모리 누수와 null pointer exception을 방지할 수 있다.

활용 예를 ViewBinding과 비교하여 알아보자.

  • UI Controller(Activity or Fragment)에서 ViewBinding
    binding.textViewUnscrambledWord.text = viewModel.currentScrambledWord
  • 레이아웃 파일에서 DataBinding
    android:text="@{gameViewModel.currentScrambledWord}"
  1. 모듈 단 build.gradle 세팅

    plugins {
       id 'com.android.application'
       id 'kotlin-android'
       id 'kotlin-kapt' -> !!필요!!
    }
    
    buildFeatures {
       dataBinding true
    }
  2. xml 세팅

    루트 태그가 <layout>으로 시작하고, <data>가 선택적으로 들어간다.

    cf) 기존 루트 태그를 클릭하고 우클릭해 Show Context Actions > Convert to data binding layout으로 쉽게 매핑할 수 있다.

  3. DataBinding 변수 추가

    <layout 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">
    
        <data>
            <variable
                name="gameViewModel"
                type="com.example.android.unscramble.ui.game.GameViewModel" />
            <variable
                name="maxNoOfWords"
                type="int" />
        </data>
    </layout>
    // GameFragment.kt
    
    private val viewModel: GameViewModel by viewModels()
    
    private var _binding: GameFragmentBinding? = null
    private val binding get() = _binding!!
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
    
            binding.gameViewModel = viewModel
            binding.maxNoOfWords = MAX_NO_OF_WORDS
    
    				binding.lifecycleOwner = viewLifecycleOwner
    
            ...
        }

    LiveData는 생명주기를 인식한다. 그래서 viewLifecycleOwner를 Observer에 매개변수로 줬다. 그것처럼 지금은 DataBinding을 했으니 레이아웃에 소유자를 전달해야 하니까 초기화하는 것 잊지 말자.

  4. 결합 표현식(binding expressions) 사용하기

binding expression은 @로 시작하고 {}로 래핑된다.

<TextView
		...
		android:text="@{gameViewModel.currentScrambledWord}" />

람다식 적용도 가능하다.

<RadioButton
       android:id="@+id/coffee"
       ...
       android:checked="@{viewModel.flavor.equals(@string/coffee)}"
       .../>

------------

<RadioButton
       android:id="@+id/coffee"
       ...
       android:onClick="@{() -> viewModel.setFlavor(@string/coffee)}"
       .../>

이렇게 DataBinding을 하면 GameFragment.kt에서 currentScrambledWord의 LiveData Observer를 삭제해도 된다.

앱 resource에 접근하려면 다음과 같이 사용하자.

<TextView
   android:id="@+id/word_count"
   ...
   android:text="@{@string/word_count(gameViewModel.currentWordCount, maxNoOfWords)}"
   .../>

범위함수 apply

객체의 컨텍스트 내에서 코드 블록을 실행하고 임시 범위를 만든다. 그러면 범위 내에서 객체에 액세스가 가능하다. 대개 객체를 구성하는 데 쓰인다. apply가 호출되면 객체에 다음 assignment 적용이라고 보면 되겠다.

쉽게 보면 아래 코드를

clark.firstName = "Clark"
clark.lastName = "James"
clark.age = 18

이렇게 간편하게 바꿀 수 있다는 거다.

clark.apply {
    firstName = "Clark"
    lastName = "James"
    age = 18
}

그러니 viewBinding할 때 아주 유용하겠다!

0개의 댓글