본문 바로가기
안드로이드/코틀린

(android-jetpack)AAC에서의 뷰모델

by 나이아카 2021. 3. 30.

 안드로이드에서 제공하는 안드로이드 아키텍쳐 컴포넌트(Android Architecture Component)의 ViewModel에 대해서 이야기하려고 합니다. 기존 MVVM 모델의 뷰 모델과 동일한 기능을 가진 존재였다면, 그저 디자인 패턴 설명 시에 했던 모델로서 설명이 끝이겠지만, MVVM 모델을 설명하면서 했던 ViewModel과는 분명 다른 존재라고 안드로이드 공식 문서에서도 분명하게 설명하고 있습니다.

 

 위와 같은 말이 안드로이드의 MVVM 디자인 패턴을 공부하는 과정에서 제일 많이 들을 수 있고, AAC나 jetpack에 대해 공부하는 과정에서 제일 알기 쉬운 주제입니다. 그러나 글을 쓰는 이유는 안드로이드 프로젝트에 MVVM 모델을 채용하면서 동시에 버전 업을 위해 AAC를 채용하게 되었더니 정작 이 차이를 가끔씩 무시하곤 합니다. 그로 인해 정리를 한 번 해야 할 필요가 생겨서 이번에 정리를 해 봅니다.

 

developer.android.com/topic/libraries/architecture/viewmodel?hl=ko

 

ViewModel 개요  |  Android 개발자  |  Android Developers

ViewModel을 사용하면 수명 주기를 인식하는 방식으로 UI 데이터를 관리할 수 있습니다.

developer.android.com

 안드로이드 공식 문서에서의 viewModel의 설명입니다. 공식 문서에서의 설명을 보면 viewModel은 안드로이드의 수명주기를 고려해 UI 관련 데이터를 저장하고 관리하는 기능이라고 설명되어 있습니다. 실제로 이 뷰모델이 내장하고 있는 기능은 viewModel이 속해 있는 activity 및 fragment의 생명주기와 같이 하는 것입니다. 이것은 의외로 굉장히 중요한 부분일 수 있습니다. view의 데이터를 view가 살아있는 동안 자연스럽게 유지되고, view가 죽을 때 같이 죽는다는 것을 의미하니까요.

 

 또한 생명주기가 죽는 특정 상황(회전과 같은 상황)에서도 기존 UI와는 다르게 데이터를 보존하고 있습니다. 이는 기존의 방식이 onCreate -> onDestroy로 이루어지는 일련의 생명주기에서 UI 클래스가 동일 선상에 있었다면, viewModel은 이와 별개의 새로운 객체에서 따로 관리되기 때문에 죽지 않습니다.

 

 AAC viewModel은 생명주기를 기준으로 싱글톤 형태로 구성되어 있습니다. 하나의 액티비티 내에는 동일한 뷰 모델이 존재한다는 것을 의미합니다. 그러나 이 말은 같은 생명주기를 공유하지 않으면 같은 액티비티에서 작성한 같은 클래스의 뷰 모델이 항상 동일하다는 것을 의미하지는 않습니다. 아래의 viewModel 생성 코드를 보면서 설명하겠습니다.

 

private val model: SharedViewModel by activityViewModels()

private val model: SharedViewModel by viewModels()

 

 위와 같은 activityViewModels는 현재 이 클래스가 속해있는 액티비티의 생명주기에 존속되는 viewModel을 생성하겠다는 것을 의미합니다. 이는 하나의 액티비티에 하나의 화면으로 구성되어 있다면 이 클래스에서 사용하는 개별의 viewModel이 될 수 있습니다. (번외적인 이야기이지만, 다른 액티비티에서 동일한 클래스를 이용해 viewModel을 생성한다고 하더라도 다른 데이터를 들고 있을 수 있습니다.)

 또한 프래그먼트에서 activityViewModels를 이용한다면, 동일한 액티비티 내부에서 생성되는 각기 다른 프래그먼트에서도 동일한 viewModel을 이용할 수 있습니다. 이는 기존 프래그먼트 간의 통신을 더욱 쉽게 만들어줄 수도 있는 기능이기도 합니다.(통신이랄 것도 없이 하나의 viewModel이 두 프래그먼트를 오갈 수 있기 때문에 고민할 필요도 없이 다른 프래그먼트에서 초기화했을 때 그 데이터를 다른 프래그먼트도 들고 올 수 있을 뿐 아니라, 변경점을 다른 프래그먼트에서도 알 수 있게 되어 UI의 수정이 더욱 간편해집니다.)

 

 그러나 viewModels를 프래그먼트에서 사용하면 조금 다른 결과가 도출됩니다. 액티비티에서는 activityViewModels와 viewModels의 차이점을 확인하기 어려우나 프래그먼트에서 activityViewModels가 아닌 viewModels 사용시, 각기 다른 프래그먼트에서 동일한 뷰 모델을 호출하더라도 각기 다른 새로운 뷰 모델이 생성됩니다.(정확히 각 프래그먼트의 생명주기에 존속되어 메모리 주소 값까지 다른 아예 다른 객체인지, 아니면 onCleared가 호출된 데이터인지는 모르겠습니다. 확실한 건 동시에 2개의 프래그먼트가 살아있는 viewPager에서도 데이터를 동일한 뷰 모델에서 다르게 불러오는 것을 보면 아마 각기 다른 객체일 가능성이 매우 매우 높습니다. 이는 시간이 된다면 직접 코드를 통해 확인해보고 싶긴 합니다.)

 그렇기 때문에 하나의 프래그먼트(하나의 화면을 이루고 있는 UI)에서만 사용하는 viewModel이라면 그 생명주기를 프래그먼트의 생명주기에 맞춰주는 것도 좋습니다. 이를 신경써 준다면 초기화되어야 할 데이터가 초기화되지 않는 불상사를 조금이나마 방지해줄 수 있습니다.

 

 AAC ViewModel에는 조금 특이한 클래스가 하나 있습니다.

 

class SharedViewModel(application: Application): AndroidViewModel(application)

 

 AndroidViewModel은 말 그대로 안드로이드의 환경에 맞춘 뷰 모델이라고 볼 수 있습니다. 코드를 자세히 봐도 크게 ViewModel과 코드의 차이를 보이지 않습니다. 오직 context를 가지기 위해 추가된 클래스입니다.


/**
 * Application context aware {@link ViewModel}.
 * <p>
 * Subclasses must have a constructor which accepts {@link Application} as the only parameter.
 * <p>
 */
public class AndroidViewModel extends ViewModel {
    @SuppressLint("StaticFieldLeak")
    private Application mApplication;

    public AndroidViewModel(@NonNull Application application) {
        mApplication = application;
    }

    /**
     * Return the application.
     */
    @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
    @NonNull
    public <T extends Application> T getApplication() {
        return (T) mApplication;
    }
}

 viewModel에서 어떠한 데이터를 불러올 일이 있을 수 있습니다. 대부분의 UI 관련 데이터를 처리하는 경우 외부 서버에서 데이터를 불러오는 것은 물론 sharedPreference 및 내부 sqlite, 폴더와 같은 곳에서 데이터를 불러오는 경우가 존재합니다. 이때 제일 많이 필요한 것은 context인데요. ViewModel 클래스의 설명에 보면 lifecycle과 같은 activity context의 참조를 지양하고 있다는 것을 알 수 있습니다.(실제로 context를 파라미터로 이용하는 viewModel을 생성해서 사용해도 되지만, androidViewModel을 extends 하는 것과 차이는 느껴지지 않지만 코딩 관련 부분에 있어 더 간단합니다.)

 

 이렇게 만들어진 viewModel은 항시 viewModel만 독립적으로 사용되지는 않습니다. 같은 jetpack 내부에 있는 LiveData 및 Room과 같은 클래스와 같이 사용되게 될 때 더 큰 효율을 발휘할 수 있습니다. jetpack에서 만들어진 대부분의 코드는 같이 사용할 때 시너지를 이끌어내도록 작성되어 있기 때문에 어느정도 익혀둔 다음 같이 사용하는 것이 좋습니다.

댓글