사용자에게 결정하거나 추가 정보를 입력하라는 메시지를 표시하는 작은 화면. 전체 화면을 채우지 않고 사용자가 액션을 해야 계속 진행할 수 있다. 일반적으로 다음과 같이 생겼다.
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()
}
기존에 알고 있기 때문에 코드만 정리
// 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)
...
}
}
// 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
}
}
cf) LiveData 수 연산 처리
private fun increaseScore() {
_score.value = (_score.value)?.plus(SCORE_INCREASE)
}
private fun getNextWord() {
...
} else {
_currentScrambledWord.value = String(tempWord)
_currentWordCount.value = (_currentWordCount.value)?.inc()
wordsList.add(currentWord)
}
}
DataBinding의 이점을 정리해보자.
활용 예를 ViewBinding과 비교하여 알아보자.
binding.textViewUnscrambledWord.text = viewModel.currentScrambledWord
android:text="@{gameViewModel.currentScrambledWord}"
모듈 단 build.gradle 세팅
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt' -> !!필요!!
}
buildFeatures {
dataBinding true
}
xml 세팅
루트 태그가 <layout>
으로 시작하고, <data>
가 선택적으로 들어간다.
cf) 기존 루트 태그를 클릭하고 우클릭해 Show Context Actions > Convert to data binding layout으로 쉽게 매핑할 수 있다.
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을 했으니 레이아웃에 소유자를 전달해야 하니까 초기화하는 것 잊지 말자.
결합 표현식(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가 호출되면 객체에 다음 assignment 적용이라고 보면 되겠다.
쉽게 보면 아래 코드를
clark.firstName = "Clark"
clark.lastName = "James"
clark.age = 18
이렇게 간편하게 바꿀 수 있다는 거다.
clark.apply {
firstName = "Clark"
lastName = "James"
age = 18
}
그러니 viewBinding할 때 아주 유용하겠다!