[Android] Jetpack ViewModel이란

Lee Jun Hyeong·2023년 5월 7일
0

Android 톺아보기 😇

목록 보기
16/17
post-thumbnail

오늘 알아볼 ViewModel은 Android JetPack의 구성요소 중 하나이다.
ViewModel이란 이름은 소프트웨어 개발 디자인 패턴 중 하나인 MVVM(Model-View-ViewModel) 디자인 패턴으로부터 파생되었다.

MVVM의 관점에서 부르는 ViewModel과 Android Jetpack에 포함된 ViewModel클래스를 구분하기 위해 Android Jetpack에 포함된 ViewModel을 Android Architecture ViewModel의 약자인 AAC ViewModel이라고도 부른다.

ViewModel 이란?

Activity와 fragment와 같은 UI 컨트롤러의 로직에서 데이터를 다루는 로직을 분리하기 위해 등장한 Android JetPack 라이브러리이다.

왜 UI 컨트롤러와 데이터를 분리할까?

UI 컨트롤러의 목적

데이터를 표시해주거나, 사용자가 어떤 작업을 했을 때 반응을 보여주거나, 권한 요청과 같은 OS 커뮤니케이션을 처리하는 것이 UI 컨트롤러의 목적이다.
따라서, UI 컨트롤러에서 데이터를 다루는 로직을 책임지게 되면 많은 유지보수가 필요한 비동기 호출과 같은 작업들을 해야하기 때문에 UI 컨트롤러에 과도한 책임이 생기게 된다.

데이터 손실 방지

UI 컨트롤러에서는 생명주기에 따라 앱이 활동중에 예를 들어 화면을 회전했을 경우 화면을 갱신하면서 데이터가 초기화 된다.
데이터를 복원하기 위해 onSaveInstanceState()메서드를 사용하는 방법은 작은 용량의 데이터에만 적합하고 데이터가 커지게 되면 적합하지 않다.

ViewModel이 필요한 이유 정리

MVVM의 관점에서 봤을 때 ViewModel은 View로부터 독립적이며, View가 필요로 하는 데이터만을 소유한다.
안드로이드 앱 개발시에도 MVVM 디자인 패턴을 적용하면 Activity나 Fragment 같은 UI 컨트롤러의 과도한 책임을 분담하여 클래스가 거대해지는 것을 방지하고, 유지보수, 재사용성 그리고 테스트 등을 용이하게 만들어 준다.

구글에서도 앱 개발자들에게 MVVM패턴 사용을 권장하고 있다.
안드로이드 생명주기를 관리하기 쉽기 때문이다.
MVVM관점의 ViewModel을 구현할 때 AAC ViewModel을 사용하면 좋다.

ViewModel의 생명주기

Activity에서는 Activity가 완전히 종료될 때까지,
Fragment에서는 Fragment가 분리될 때까지 메모리에 남아있도록 설계되어 있다.

ViewModel의 Scope(생명주기의 범위)는 ViewModel을 가져올 때 ViewModelProvider에 의해 결정 된다.

Activity의 finish() 호출 등에 의해 Activity 생명주기가 종료됨에 따라 내부의 LifecycleEventObserver를 통해 ViewModel도 onCleared() 콜백 메서드를 호출하고 종료된다.

Activity & Fragment

ViewModel 요청 프로세스

  1. ViewModelProvider를 통해 ViewModel 인스턴스를 요청한다.
  2. ViewModelProvider 내부에서는 ViewModelStoreOwner를 참조하여 ViewModelStore를 가져온다.
  3. ViewModelStore에게 이미 생성된(저장된) ViewModel 인스턴스를 요청한다.
  4. 만약 ViewModelStore가 적합한 ViewModel 인스턴스를 가지고 있지 않다면, Factory를 통해 ViewModel인스턴스를 생성한다.
  5. 생성한 ViewModel 인스턴스를 ViewModelStore에 저장하고 만들어진 ViewModel 인스턴스를 클라이언트에게 반환한다.
  6. 똑같은 ViewModel 인스턴스 요청이 들어온다면, 1~3번의 과정을 반복하게 된다.

ViewModel 구현

ViewModel을 이용하여 + 버튼- 버튼으로 숫자를 카운트하는 앱을 만들어보겠다.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0"
        android:textSize="50dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


    <Button
        android:id="@+id/plus"
        android:text="+"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/minus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="-"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

MainViewModel.kt

class MainViewModel : ViewModel() {

    var countValue = 0

    init {
        Log.d("MainViewModel", "init")
    }

    fun plus(){
        countValue++
    }

    fun minus(){
        countValue--
    }

    fun getCount() : Int {
        return countValue
    }

}

MainActivity.kt

ViewModelProvider는 ViewModel 객체를 생성하기 위해 사용하는 클래스이다. Activity나 Fragment에서는 ViewModelProvider를 통해 자기 자신을 생성자로 전달하여 ViewModel 인스턴스를 획득해야 한다.

class MainActivity : AppCompatActivity() {

    lateinit var viewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

		// ViewModel 인스턴스 생성
        viewModel = ViewModelProvider(this).get(MainViewModel::class.java)


        val plusBtn : Button = findViewById(R.id.plus)
        val minusBtn : Button = findViewById(R.id.minus)

        val resultArea : TextView = findViewById(R.id.result)
		
        // 화면 처음 갱신 될 때 초기값 보여주기
        resultArea.text= viewModel.countValue.toString()

        plusBtn.setOnClickListener{
        	// 카운트 값 텍스트뷰에 보여주기
			viewModel.plus()
		}

        minusBtn.setOnClickListener{
        	// 숫자 감소
        	viewModel.minus()
            // 카운트 값 텍스트뷰에 보여주기
            resultArea.text= viewModel.countValue.toString()
        }
	}
}

참고
https://www.google.com/url?sa=i&url=https%3A%2F%2Fzsmb.co%2Ffragment-lifecycles-in-the-age-of-jetpack%2F&psig=AOvVaw34bHyO_U-jCcumpB4I1L-0&ust=1683537494054000&source=images&cd=vfe&ved=0CBEQjRxqFwoTCOjXl4fw4v4CFQAAAAAdAAAAABAP https://charlezz.medium.com/viewmodel%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-viewmodel-%EC%B4%88%EB%B3%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%80%EC%9D%B4%EB%93%9C-e1be5dc1ac18
https://www.google.com/url?sa=i&url=https%3A%2F%2Fvelog.io%2F%40pachuho%2FAndroid-Docs-MVVM-1%25ED%258E%25B8&psig=AOvVaw02rjwKlrQM53ik_o9lPnJN&ust=1683540094031000&source=images&cd=vfe&ved=0CBEQjRxqFwoTCNCnleL54v4CFQAAAAAdAAAAABAP
profile
"왜" 사용하며, "어떻게" 사용하는지에 대해

0개의 댓글