새소식

android studio

[android] ViewModel란?

  • -

ViewModel은 대부분 MVVM 패턴에서 사용되는 Model, View, ViewModel 용어에서 들어봤을 것이다. 

 

구글이 개발자들을 위해  Clean Architecture를 쉽게 구현할 수 있도록 라이브러리들을 만들었는데

이를 Android Architecture Components (AAC)라고 부르며 그중 하나가 바로 ViewModel이다.

 

Android Jepack의 구성요소 중 하나로, 본래 ViewModel이란 이름은 소프트웨어 개발 디자인 패턴중 하나인 MVVM(Model — View — ViewModel) 디자인 패턴으로부터 파생되었다.

 

뷰모델 클래스 ❓

1. 수명 주기를 고려해 UI 관련 데이터를 저장하고 관리하도록 설계됐다. 

2. 뷰모델 클래스를 사용하면 화면 회전 같이 구성을 변경할 때도 데이터를 유지할 수 있다.

3. 뷰모델의 목적은 UI 컨트롤러의 데이터를 캡슐화해서 구성이 변경돼도 데이터를 유지하는 것이다.

 

ViewModel을 사용 예)

ViewModel을 사용하지 않고 구현을 하였을 때, 키 증가 버튼을 누르고 회전을 시키면 값이 초기값으로 돌아가는 것볼 수 있다. 

이런 현상이 발생하는 이유는 바로 생명 주기 때문이다.  

화면 회전이 이루어지면 액티비티가 Destroy 됐다가 다시 Create 되기 때문에 기존의 데이터가 날라가는 것이다.

회전 뿐만 아니라 다양한 경우에서 이런 문제가 발생한다. 

 

 

saveInstanceState

이런 문제를 해결하기 위해 saveInstanceState를 통해 해결할 수 있다.

액티비티가 destory되기 전 세이브하고 싶은 데이터를 saveInstanceState 통해 onCreate로 넘겨주면 데이터를 날리지 않고 계속 이용할 수 있는 것이다. 

class MainActivity : AppCompatActivity() {
 
    override fun onCreate(savedInstanceState: Bundle?) { 
        super.onCreate(savedInstanceState) 
        setContentView(R.layout.activity_main)
    }
}

하지만 이 방법에는 다음과 문제가 있다.

  • 담을 수 있는 데이터가 적다. 공식문서 - Parcelables and Bundles에서는 50k 미만의 데이터를 권장하고 있다.
  • 담을 수 있는 데이터의 형태가 제한된다.
  • onCreate에서 작업을 처리해야 하므로 UI 컨트롤러가 해야 할 일이 늘어나며 화면을 띄우는데 시간이 오래 걸린다.

 

- ViewModel

ViewModel을 상속받는 클래스를 만들어 데이터를 저장하고 관리하는 로직을 간단하게 구현하였다.

class MyViewModel : ViewModel() {
    private val users: MutableLiveData<List<User>> by lazy {
        MutableLiveData().also {
            loadUsers()
        }
    }
 
    fun getUsers(): LiveData<List<User>> {
        return users
    }
 
    private fun loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

 

이렇게 생성된 ViewModel은 액티비티 혹은 프래그먼트와 다른 생명주기를 가지게 된다.

뷰모델은 범위가 지정된 LifeCycle이 영구적으로 경과될 때까지, 즉 액티비티에선 액티비티가 끝날 때까지, 그리고 프래그먼트에선 프래그먼트가 분리될 때까지 메모리에 남아 있다.

즉, 생명 주기가 더 길다는 뜻이다. 

finish 메서드가 호출됐을 때, 혹은 사용자가 직접 뒤로 가기 버튼을 눌러 액티비티를 종료했을 때, onCleared 메서드를 통해 ViewModel은 비로소 소멸이 된다. 

이렇게 뷰 모델을 분리하면 다음과 같은 장점이 있다.

  • 생명 주기에 영향을 받지 않고 데이터를 유지할 수 있다.
  • UI 컨트롤러와 데이터가 분리된다. 그로 인해 오는 장점은 여기에 자세히 적어두었다.
  • 프래그먼트 간의 데이터 공유가 쉬워진다.

 

🖥️ ViewModel 구현

 

ViewModel을 상속하는 서브 클래스를 생성한다.

보통 ViewModel은 LiveData와 같이 쓰인다.

ViewModel안에는 height이라는 데이터와 관리하는 로직이 담겨있다.

class UserViewModel(): ViewModel() {
    private var _height = MutableLiveData<Int>()
 
    val height: LiveData<Int>
        get() = _height
 
    init {
        _height.value = 170
    }
 
    fun increase() {
        _height.value = _height.value?.plus(1)
    }
}

그 후에 다음과 같이 ViewModel 인스턴스에 접근할 수 있다.

class MainActivity : AppCompatActivity() {
 
    private lateinit var binding: ActivityMainBinding
    private lateinit var userViewModel: UserViewModel
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        userViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
        binding.user = userViewModel
 
        userViewModel.height.observe(this, Observer {
            binding.textViewHeight.text = it.toString()
        })
    }
}

ViewModel을 생성하기 위해서는 ViewModel Provider 객체가 필요하다.

 

위 코드에서 

ViewModelProvider(this).get(UserViewModel::class.java)

이 부분은 ViewModelProvider를 사용할 때 this를 넘겨주는 데 이는 owner를 의미한다.

ViewModelStore를 누가 소유하고 있느냐? -> this가 소유하고 있다 = MainActivity가 소유하고 있다.

 

그렇다면 ViewModelStore은 무엇일까?

ViewModelStore은 ViewModel 객체가 HashMap 구조로 저장되는 곳이다.

즉, get() 안에 있는 'UserViewModel..'은 객체를 찾아오기 위한 Key값으로 쓰이는 것이다.

 

그림으로 설명하자면 이렇다.

뷰 모델을 HashMap 구조로 저장하니까 get() 메서드에 Key값을 넣어준 거고.

(만약 Key에 해당하는 Value가 없으면 생성하고 가져온다. 그래서 처음 뷰 모델 객체를 처음 만드는데도 set 따위가 아니라 get을 쓰는 것)

저 ViewModelStore를 소유하고 있는 주체가 MainActivity라는 것을 알려주는 것이다.

 

이를 통해 우리는 2가지 사실을 알 수 있다

  • 뷰 모델을 각각 다른 소유자가 생성하면 이는 별개의 메모리 공간을 사용하는 다른 객체가 된다.
  • 하나의 액티비티를 소유자로 지정해 사용하면 같은 ViewModel을 공유할 수 있다. = 데이터 공유 가능

 

 

feat) ❗❗

#위에 코드 두줄과 아래 코드 한줄이 같은 의미 
private lateinit var userViewModel: UserViewModel 
userViewModel = ViewModelProvider(this).get(UserViewModel::class.java) 

private val model: UserViewModel by viewModels()

공식문서를 보니 뷰모델 객체를 생성하는 위에 두 줄을 아래 한 줄처럼 사용해도 된다고 한다. 

 

 

 

 

 


 

(추가로 ViewModel과 Livedata에 관한 구현 예시입니다.) 

https://www.youtube.com/watch?v=-b0VNKw_niY 

 


 

reference:  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://todaycode.tistory.com/33

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.