본문 바로가기
개발 관련 공부/코틀린

Unit 3-3

by 슴새 2022. 9. 14.
반응형
이 포스트는 2021.12~2022.09 기간동안 벨로그에 작성한 글을 티스토리에 옮겨 적은 것입니다.

ViewModel에 데이터 저장


Android 아키텍쳐는 UI Controller, ViewModel, LiveData, Room 으로 이루어져 있다. LiveData  Room은 이후에 알아보고, 일단은 UI Controller와 ViewModel에 대해서 먼저 알아보자.

UI Controller
액태비티와 프래그먼트를 말한다. UI 컨트롤러는 화면에 뷰를 그리고, 사용자와 상호작용하는 모든 UI를 제어한다.

🔎 UI Controller는 뷰와 데이터를 화면에 그리고 사용자 이벤트에 응답한다.

ViewModel
앱의 데이터나 데이터와 관련된 로직은 UI 컨트롤러에 포함되어서는 안된다. 이것들은 ViewModel에 포함되어야 한다. ViewModel은 뷰에 표시되는 앱 데이터의 모델이다. 액티비티/프래그먼트가 소멸될때 폐기되면 안되는 데이터를 저장한다.

🔎 ViewModel은 UI에 필요한 모든 데이터를 보유하고 처리한다. view binding 객체 등에 엑세스해서는 안된다.


예제에서 ViewModel은 다음과 같은 구조로 앱에 추가된다.
MainActivity에 GameFragment가 포함되어 있으며, GameFragment는 GameViewModel에 있는 게임 관련 정보에 액세스한다.

private val viewModel = GameViewModel()

이런식으로 초기화하면 기기를 회전하는 등 앱의 구성이 변경되면 viewModel 참조의 상태를 잃어버리게 된다.

//대리자 속성은 다음과 같이 by 절 및 대리자 클래스 인스턴스를 사용하여 정의한다.
private val viewModel: GameViewModel by viewModels()

대신 속성 위임 접근 방식을 사용해 viewModel 객체의 책임을 viewModels라는 별도의 클래스에 위임하자. (즉, viewModel 객체에 액세스하면 이 객체는 대리자 클래스 viewModels에 의해 내부적으로 처리된다.)대리자 클래스는 첫 번째 액세스 시 자동으로 viewModel 객체를 만든다. 얘는 기기 회전 등 구성 변경이 있어도 값을 유지하며, 요청이 있을때 값을 반환한다.

ViewModel의 수명 주기


ViewModel은 화면 회전 등의 구성 변경으로 인해 소멸되는 경우에도 없어지지 않는다. 사용자의 새 activity 인스턴스는 기존 ViewModel 인스턴스에 다시 연결된다. 앱을 종료하면 그제서야 onCleared()가 호출돼 ViewModel이 삭제된다.


대화상자


1. 알림 대화상자
2. 제목(선택사항)
3. 메시지
4. 텍스트 버튼
Android는 다양한 유형의 대화상자를 제공한다. MaterialAlertDialog를 사용하여 머티리얼 가이드라인을 따르는 대화상자를 사용할 수 있다.

//대화상자 만드는 함수를 만들어서 필요할때 호출하자.
private fun showFinalScoreDialog() {
   //requireContext는 프래그먼트에서 context 반환할때 쓰는 함수.
   MaterialAlertDialogBuilder(requireContext())
}

대화상자 함수를 정의해서 필요할때 적절히 쓰도록 해보자.

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() //알림상자를 표시
    }
    

MaterialAlertDialog에 각종 옵션들을 붙여준다. 마지막으로.show()를 붙여서 알림상자를 표시한다.


ViewModel과 함께 LiveData 사용하기

LiveData는 수명 주기를 인식하는 데이터 홀더 클래스이다.

LiveData의 특징들

  • 모든 유형의 데이터에 사용할 수 있다.
  • 관찰 가능하다. (= LiveData 객체에서 보유한 데이터가 변경되면 관찰자가 알 수 있음.)
  • LiveData는 수명 주기를 인식한다. LiveData는 활성 수명 주기인 활동/프레그먼트의 관찰자만 업데이트한다.

🔎 예제의 앱에서 updateNextWordOnScreen() 메서드는 아주 많은 위치에서 호출된다. Livedata를 사용하면 UI를 업데이트하기 위해 여러 위치에서 이 메서드를 호출하지 않아도 된다. 관찰자에서 한 번만 호출한다.

MutableLiveData
MutableLiveData 내부에 저장된 값을 변경할 수 있는LiveData이다.

private val _currentScrambledWord = MutableLiveData<String>()
    val currentScrambledWord: LiveData<String>
        get() = _currentScrambledWord
...
private fun getNextWord() {
 ...
   } else {
       _currentScrambledWord.value = String(tempWord)
       ...
   }
}

LiveData의 값에 접근하기 위해서는 .value를 사용한다.

//관찰자 추가
        //첫번째 매개변수 viewLifecycleOwner. 이를 통해 STARTED 또는 RESUMED일때만 관찰자에 알림.
        //두번째 매개변수 newWord (뒤섞인 글자값을 저장. 람다표현식. neworld를 매개변수로 받아 오른쪽 식을 반환)
        viewModel.currentScrambledWord.observe(viewLifecycleOwner,
            { newWord ->binding.textViewUnscrambledWord.text = newWord })

예제에서는 프레그먼트에 관찰자를 추가한다.onViewCreated 함수 안에 위와 같이 observe()를 사용한 코드를 작성하면 된다.

currentScrambledWord 외의 변수도 LiveData 타입으로 선언하고 .value를 통해 접근하고 observe()를 통해 관찰자를 붙여줄 수 있다.

데이터 결합 사용하기

이전에 배운 뷰결합을 사용하면, 코드에서 뷰를 참조할 수 있었다. 하지만 뷰에서 앱 데이터를 참조할 수는 없었다. 이 작업에는 데이터 결합을 사용한다. 데이터 결합 라이브러리는 Android Jetpack 라이브러리의 일부이다.

android:text="@{gameViewModel.currentScrambledWord}"

레이아웃 파일에서 데이터결합의 사용 예. @{} 구문을 사용한다.

사용하기위해서는 build.gradle 파일에서

buildFeatures {
   dataBinding = true
}
plugins {
   id 'com.android.application'
   id 'kotlin-android'
   id 'kotlin-kapt'
}

이렇게 수정해준다.

이후 xml파일에 가서 루트 요소(여기선 ScrollView)에 마우스 오른쪽-show context action을 누르면


data binding layout으로 바꾸는 옵션이 떠서 간편하게 바꿀 수 있다.

binding = DataBindingUtil.inflate(inflater, R.layout.game_fragment, container, false)

데이터 결합을 사용하도록 binding 변수를 선언하는 부분도 바꿔준다.

xml파일이 data binding layout으로 바뀌면 <data></data> 태그가 생긴다.

<data>
   <variable
       name="gameViewModel"
       type="com.example.android.unscramble.ui.game.GameViewModel" />
</data>

<data> 태그 내에 <variable>이라는 하위 태그를 추가하고 GameViewModel 유형의 gameViewModel이라는 속성을 선언한다. 이 속성을 사용하여 ViewModel의 데이터를 레이아웃에 결합할 수 있다.

 <data>
        <variable
            name="gameViewModel"
            type="com.example.android.unscramble.ui.game.GameViewModel" />
        <variable
            name="maxNoOfWords"
            type="int" />
    </data>

평범한 int 변수등도 줄줄이 추가할 수 있다.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   super.onViewCreated(view, savedInstanceState)

    	binding.gameViewModel = viewModel
        binding.maxNoOfWords = MAX_NO_OF_WORDS
        //레이아웃에 수명주기 전달→ 변수가 바뀌면 ui요소가 알아서 업뎃됨
        binding.lifecycleOwner = viewLifecycleOwner
...
}

레이아웃 파일에서 선언해준 애들을 onViewCreated에서 새롭게 초기화하자.

이제 결합표현식 @{} 를 사용해서 ViewModel에 있는 변수가 변경되면 그에따라 뷰를 업데이트 할 수 있게 해보자.

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

이제 LiveData의 관찰자가 변경을 감지하면 text를 업데이트 하는 코드는 필요가 없다.

viewModel.currentScrambledWord.observe(viewLifecycleOwner,
   { newWord ->
       binding.textViewUnscrambledWord.text = newWord
   })

이런 코드들 삭제해준다.

결합 표현식에 @string을 껴넣을 수도 있다.

//layout xml
android:text="@{@string/example_resource(user.lastName)}"
//string.xml
<string name="example_resource">Last Name: %s</string>

이런식으로!!

 
반응형

'개발 관련 공부 > 코틀린' 카테고리의 다른 글

Unit 4-1  (0) 2022.09.14
Unit 3-4  (0) 2022.09.14
Unit 3-2  (0) 2022.09.14
코틀린에서 ?와 !!  (0) 2022.09.14
Unit 3-1(2)  (0) 2022.09.14

댓글