안드로이드 ViewModel

KWANWOO·2022년 2월 9일
0
post-thumbnail

Android ViewModel

ViewModel 클래스는 수명 주기를 고려하여 UI 관련 데이터를 저장하고 관리하도록 설계되었다. ViewModel 클래스를 사용하면 화면 회전과 같이 구성을 변경할 때도 데이터를 유지할 수 있다. 이러한 ViewModel에 대하여 정리하고자 한다.

ViewModel 종속 항목 선언 - 링크 참고

1. ViewModel 이란?

시스템에서 UI 컨트롤러를 제거하거나 다시 만드는 경우, 컨트롤러에 저장된 모든 일시적인 UI 관련 데이터가 삭제된다.

  • 예를 들면 앱 Activity 중 하나에 사용자 목록이 포함되어 있는데, 구성이 변경되어 Activity가 다시 생성되면 새 Activity가 사용자 목록을 다시 가져와야 한다.

데이터가 단순한 경우 Activity는 onSaveInstanceState() 메서드를 사용하여 onCreate()의 번들에서 데이터를 복원할 수 있다. (SIS - 나중에 따로 정리할게요..ㅠ)

하지만 이 접근 방법은 사용자 목록이나 비트맵과 같은 대용량일 가능성이 높은 데이터가 아니라, 직렬화했다가 다시 역직렬화할 수 있는 소량의 데이터에만 적합하다.

다른 문제는 UI 컨트롤러가 반환하는 데 시간이 걸릴 수 있는 비동기 호출을 자주 해야 한다는 점이다. UI 컨트롤러는 비동기 호출을 관리해야 하며, 메모리 누수 가능성을 방지하기 위해 호출이 제거된 후 시스템에서 호출을 정리하는지 확인해야 한다.

관리에는 많은 유지관리가 필요하며, 구성 변경 시 개체가 다시 생성되는 경우 개체가 이미 수행된 호출을 다시 호출해야 할 수 있으므로 리소스가 낭비된다.

따라서 ViewModel을 사용하여 UI 컨트롤러 로직에서 뷰 데이터 소유권을 분리하는 방법을 사용하면 훨씬 쉽고 효율적인 앱을 구현할 수 있다.

2. ViewModel의 구현

아키텍처 구성요소는 UI의 데이터 준비를 담당하는 UI 컨트롤러에 ViewModel 도우미 클래스를 제공한다. ViewModel 객체는 구성이 변경되는 동안 자동으로 보관되므로, 이러한 객체가 보유한 데이터는 다음 Activity 또는 프래그먼트 인스턴스에서 즉시 사용할 수 있다.

  • 예를 들어 아래 코드와 같이 앱에서 사용자 목록을 표시해야 할 때 사용자 목록을 확보하여 Activity나 프래그먼트 대신 ViewModel에 보관하도록 책임을 할당해야 한다.(LiveData도 나중에 따로 정리해야겠다!!)
class MyViewModel : ViewModel() {
    private val users: MutableLiveData<List<User>> by lazy {
        MutableLiveData<List<User>>().also {
            loadUsers()
        }
    }

    fun getUsers(): LiveData<List<User>> {
        return users
    }

    private fun loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}
  • 위와 같이 ViewModel을 생성하고 Activity에서 아래 코드처럼 액세스 할 수 있다. (아래는 LiveData가 사용되었기 때문에 observer가 사용되었다. 일단은 그렇게만 알고 넘어가자)
class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val model: MyViewModel by viewModels()
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            // update UI
        })
    }
}

Activity가 다시 생성되면 첫 번째 Activity에서 생성된 동일한 MyViewModel 인스턴스를 받는다. 소유자 Activity가 완료되면 프레임워크는 리소스를 정리할 수 있도록 ViewModel 객체의 onCleared() 메서드를 호출한다.

3. ViewModel의 생명 주기

ViewModel 객체의 범위는 ViewModel을 가져올 때 ViewModelProvider에 전달되는 Lifecycle로 지정된다.

ViewModel은 범위가 지정된 Lifecycle이 영구적으로 경과될 때까지, 즉 Activity에서는 Activity가 끝날 때까지 그리고 프래그먼트에서는 프래그먼트가 분리될 때까지 메모리에 남아 있다.

  • 아래 그림은 Activity가 회전을 거친 다음 끝날 때까지 Activity의 다양한 생명 주기 상태를 보여준다. 또한 관련 Activity 생명 주기 옆에 ViewModel의 전체 기간도 보여준다.

일반적으로 시스템에서 Activity 객체의 onCreate() 메서드를 처음 호출할 때 ViewModel을 요청한다. 시스템은 Activity 기간 내내 onCreate() 메서드를 여러 번 호출할 수 있다. ViewModel이 처음 요청되었을 때부터 Activity가 끝나고 폐기될 때까지 ViewModel은 존재한다!!!!

4. 프래그먼트 간 데이터 공유

Activity에 포함된 둘 이상의 프래그먼트는 흔히 서로 커뮤니케이션해야 한다고 알려져 있다. 두 프래그먼트가 모두 인터페이스 설명을 정의해야 하고 소유자 Activity가 두 프래그먼트를 함께 결합해야 할 때 ViewModel 객체를 사용할 수 있다.

  • 아래의 두 프래그먼트는 서로 ViewModel을 공유한다.
class SharedViewModel : ViewModel() {
    val selected = MutableLiveData<Item>()

    fun select(item: Item) {
        selected.value = item
    }
}

class ListFragment : Fragment() {

    private lateinit var itemSelector: Selector

    // Use the 'by activityViewModels()' Kotlin property delegate
    // from the fragment-ktx artifact
    private val model: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        itemSelector.setOnClickListener { item ->
            // Update the UI
        }
    }
}

class DetailFragment : Fragment() {

    // Use the 'by activityViewModels()' Kotlin property delegate
    // from the fragment-ktx artifact
    private val model: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
            // Update the UI
        })
    }
}

두 프래그먼트는 모두 자신이 포함된 Activity를 검색한다. 그러면 각 프래그먼트는 ViewModelProvider를 가져올 때 이 Activity로 범위가 지정된 동일한 SharedViewModel 인스턴스를 받는다.

위와 같이 ViewModel을 공유하는 경우 아래와 같은 장점이 있다.

  • Activity는 아무것도 할 필요가 없거나 이 커뮤니케이션에 관해 어떤 것도 알 필요가 없다.
  • 프래그먼트는 SharedViewModel 외에 서로 알 필요가 없다. 프래그먼트 중 하나가 사라져도 다른 프래그먼트는 계속 평소대로 작동한다.
  • 각 프래그먼트는 자체 생명 주기가 있으며, 다른 프래그먼트 생명 주기의 영향을 받지 않는다. 한 프래그먼트가 다른 프래그먼트를 대체해도, UI는 아무 문제 없이 계속 작동한다.

5. ViewModel로 로더 대체

CursorLoader와 같은 로더 클래스는 앱 UI의 데이터와 데이터베이스 간의 동기화를 유지하는 데 자주 사용된다. ViewModel을 몇 가지 클래스와 함께 사용하여 로더를 대체할 수 있다. ViewModel을 사용하면 UI 컨트롤러가 데이터 로드 작업에서 분리된다.

  • 일반적인 로더 사용 방법 중 하나로, 앱이 CursorLoader를 사용하여 데이터베이스의 내용을 관찰할 수 있다. (아직 CursorLoader는 정리하지 않았다..ㅠ)

ViewModel은 Room 및 LiveData와 함께 작업하여 로더를 대체한다. ViewModel은 기기 설정이 변경되어도 데이터가 유지되도록 보장하는데 데이터베이스가 변경되면 Room에서 LiveData에 변경을 알리고, 알림을 받은 LiveData는 수정된 데이터로 UI를 업데이트한다.

  • ViewModel로 데이터를 로드하는 방식(LiveData와 함께 사용하는데 자세한 내용은 나중에 다시 공부해봐야 겠다.)

ViewModel에 대한 내용은 아직 많이 남은듯...ㅠ

ViewModel에 대한 기본적인 내용을 정리해 보았다. 공식 문서를 보고 정리했는데 예시에 LiveData와 같은 아직 정리하지 않은 내용도 나와서 자세히 정리하진 못한거 같다...ㅠㅠ 나중에 LiveData나 간단히 넘어간 ViewModel을 로더로 사용하기, CursorLoader 등은 따로 정리해야겠다. 추가로 ViewModel에는 Kotlin 코루틴 지원이 포함된다고 하는데 아직 코루틴에 대한 내용도 공부하지 못해서 포함하지 않았다. 즉, 이 글은 ViewModel 1편이고 2편을 나중에 써야될꺼 같은 느낌...?

📄 Reference

profile
관우로그

0개의 댓글