본문 바로가기

개발 코딩 정보 공유/안드로이드 자바 코틀린

안드로이드 앱 아키텍처 가이드 - ViewModel 편

개요

안드로이드 클린아키텍처 가이드에 따라서 이번편은 ViewModel 을 알아보겠습니다.

ViewModel은 MVVM 패턴의 중간다리 역할 입니다. 분류를 하자면 presenter 쪽에 넣을수 있겠네요.

Activity나 Fragment에서 들어온 요청을 받아서 넘기는 역할을 하게 되죠.

기존의 Activity 의 매시브한 클래스가 MVVM 패턴을 통해 한것 다이어트 되는 형태가 되는것이죠.

제가 느낀 핵심은 각각의 경계를 지키고 자기 할일만 하면됩니다.

 


시작

ViewModel은 클린아키텍트 3레이어에서 presentation 영역에 속합니다.

 

 

ViewModel 사용의 가장 중요한 목표는 ViewModel 은 수명 주기를 고려하여 UI 관련 데이터를 저장하고 관리하도록 설계되었다는 것입니다. ViewModel 클래스를 사용하면 화면 회전과 같이 구성을 변경할 때도 데이터를 유지할 수 있습니다. 회전동작등에서 화면이 destroy 되었다가 다시 create 될때 기존의 데이터가 보존이 안되는 문제 혹은 관리의 어려움, 작은 데이터는 onSaveInstanceState 등을 활용해서 번들에서 꺼내쓰면 되지만 비트맵과 같은 큰 데이터는 불가능하다. 그리고 UI 를 다시 그리면서 들어가는 네트워크 리소스 이다. 다시 비동기로 땡겨와야 한다.

 

클린아키텍처 적으로 생각하자면 Activity나 fragment (View)에서는 원래의 본 목적에 맞는 UI표시와 사용자와의 상호작용 등에만 신경써야 합니다. 기존처럼 여기서 데이터를 지지고 볶고 하면 화면단에 과도한 책임이 몰리고 안정성이 크게 떨어진다.

그래서 필요한게 관심사의 분리이다. 각자의 레이어에 맞는 클래스에서 자기 할일만 해야하는 것이죠.

 

ViewModel 객체의 범위는 ViewModel을 가져올 때 ViewModelProvider에 전달되는 Lifecycle로 지정됩니다. ViewModel은 범위가 지정된 Lifecycle이 영구적으로 경과될 때까지, 즉 Activity 에서는 Activity가 끝날 때까지 그리고 프래그먼트에서는 프래그먼트가 분리될 때까지 메모리에 남아 있습니다.

 

 

보다시피 ViewModel의 scope는 Activity 전역 입니다. Acitivty 가 회전하건 어찌되건 해당 데이터는 살아있게 된다는 의미이죠. 또한 알아서 Clear 되주니 그야말로 캡짱. 이는 ViewModel과 LiveData의 조합으로 가능합니다.

 

 


 

간단한 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에서 LiveData를 생성하고 있습니다. 사용은 아래와 같습니다.

 

class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        //...
        val model: MyViewModel by viewModels() //생성은위임한다.
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            // update UI
            //라이브데이터를 관찰하여 변경점이 있을때 알아서 업데이트 되도록 짠다.
			//rx 생각하면 이해가 쉽다.
        }) 

    }
}

 

UI에서는 해당 LiveData를 구독만 하면 상태값이 변경될때마다 알아서 들어오게 됩니다. 이는 RX계열의 라이브러리를 사용했다면 굉장히 익숙하게 느껴지실 겁니다. (그렇다고 Rx를 완벽히 대체한다는건 아닙니다. FLOW 등을 섞어서 쓴다면 모르겠지만요.)

 

주의: ViewModel은 뷰, Lifecycle 또는 Activity 컨텍스트 참조를 포함하는 클래스를 참조해서는 안 됩니다. 메모리 leak 원인이 됩니다.

 

ViewModel 객체는 뷰 또는 LifecycleOwners(예를 들면 Activity)의 특정 인스턴스화보다 오래 지속되도록 설계되었습니다. ViewModel 객체에는 LiveData 객체와 같은 LifecycleObservers가 포함될 수 있습니다. 그러나 ViewModel 객체는 LiveData 객체와 같이 수명 주기를 인식하는 Observable의 변경사항을 관찰해서는 안 됩니다.(아마도 메모리 누수의 문제) 예를 들어 ViewModel은 시스템 서비스를 찾는 데 Application 컨텍스트가 필요하면 AndroidViewModel 클래스를 확장하고 생성자에 Application을 받는 생성자를 포함할 수 있습니다.(Application 클래스가 Context를 확장하므로). 직접적인 참조(호출)은 하면 안된다.

 

 

 

"거참...까다롭죠?"

 

 


 

화면간의 데이터 교환

ViewModel과 LiveData를 좀더 활용하는 방법을 알아보겠습니다. 흔히들 겪는 데이터 공유 문제에 대해서 예를 들어 보겠습니다. Fragment 와 Fragment 의 데이터 공유를 생각해보면 아실겁니다. 굉장히 지저분하게 서로가 서로를 알고 있어야 하고 종속적으로 코딩하게 됩니다. 물론 안그렇게 하시는분도 있겠지만요. 그러한 부분은 ViewModel , LiveData 를 활용해 간단하게 해결할수 있습니다.

 

//데이터 공유를 위한 뷰모델
class SharedViewModel : ViewModel() {
    val selected = MutableLiveData<Item>()

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

//메인 화면 
class MasterFragment : Fragment() {

    private lateinit var itemSelector: Selector

    // Use the 'by activityViewModels()' Kotlin property delegate
    // from the fragment-ktx artifact
    
    //액티비티스코프의 뷰모델 생성을 위임한다. (fragment-ktx의 추가가 필요합니다)
    //알아서 생성해준다.
    private val model: SharedViewModel by activityViewModels() 

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        itemSelector.setOnClickListener { item ->
            model.select(item) //데이터의 전달
        }
    }
}

//서브 화면 
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
        }) //뷰모델의 라이브데이터를 관찰한다 
    }
}

 

어떤가요? 이런식으로 라면. 매우 간단하게 처리가 가능하죠. 데이터만 바라볼뿐 다른것은 하지 않습니다. 화면끼리 서로를 알필요도 없구요. 

각 프래그먼트는 ViewModelProvider를 가져올 때 Activity 로 범위가 지정된 동일한 SharedViewModel 인스턴스를 받습니다.

 

 


 

수명주기인식

뷰모델은 Activity의 수명주기를 인식하여 작업을 처리 할수도 있습니다. 

 

class MyViewModel (/*...*/) : ViewModel (), LifecycleObserver {...}

뷰모델에 LifecycleObserver를 확장하고 

 

@OnLifecycleEvent (Lifecycle.Event.ON_PAUSE) 
fun anyNameFunction () { 
    // onPause에서 무엇을하는지 
}

 

이런식으로 함수에 어노테이션 처리를 해주면 Lifecycle.Event.ON_PAUSE 에 따라서 Activity의 라이프 사이클에 따라 콜백이 들어옵니다. 

 

Activity에서도 해줄 일이 있는데요. 

 

lifecycle.addObserver (viewModel)

이렇게 추가 해주면 됩니다.

해당 Owner(Activity)의 onCreate, onStart, onResume, onPause, onStop, onDestroy 라이프사이클을 ViewModel에서 관찰할 수 있습니다.

 


 

요약

이렇게 ViewModel과 LiveData를 활용하여 즐코딩이 가능합니다.

끝.

 

 

 

 

 

https://developer.android.com/jetpack/guide

😎 해당 글은 구글아키텍처 문서를 참고 하여 작성하였습니다.