안드로이드 아키텍처 패턴 MVVM (ViewModel)

홍석규·2022년 3월 20일
0

안드로이드

목록 보기
2/2

MVVM

  • MVVM은 Model - View - ViewModel의 줄임말로 아키택처 패턴의 일부이다.
  • MVVM 패턴은 다음과 같은 장점들을 가지게 된다.
    • 최대한 작은 기능으로 나눈다.

    • 테스트가 쉬워지고 관리하기가 용이하다.

    • SOLID 구조를 지향

    • 약한 결합 컴포넌트를 가지게된다.

      가장 큰 목적은 유지 보수가 쉽고 테스트가 용이한 코드를 작성 하는것이다.

AAC와 View Model

  • 일반적으로 이야기하는 MVVM 아키텍처와 안드로이드에서 얘기하는 MVVM은 조금 다르다. 안드로이드는 AAC(Android Architecture Components) 를 발표하면서 ViewModel이라는것을 발표했다. 안드로이드는 앱의 확장성, 품질, 유지보수, 테스트 등이 용이한 다음과 같은 아키텍처를 권장하고 있다.
  • 화면에 데이터를 표시하고 인터렉션을 진행하는 UI Layer
  • 앱의 비즈니스 로직과 데이터를 제공해주는 Data Layer
  • 선택적으로 사용할 Domain Layer

위와 같은 앱 아키텍처를 권장하면서 앱 개발자가 수월하게 아키텍처를 구현할 수 있게 Android Architecture Components를 통해서 지원을 해주는것이다.

  • UI Layer 또는 Presentation Layer라고 하는 부분을 살펴보면 UI elements 인 View와 State holders 역할을 하는 ViewModel로 이루어져 있다. 그림을 보면 ViewModel에서 직접 View를 참조하고 있는 부분이 없으며 단방향 레이어를 가지는 것을 볼 수 있다. View는 옵저버 패턴을 이용해 ViewModel을 참조하고 ViewModel의 데이터가 갱신될 때 마다 등록된 옵저버를 통해 이를 파악하고 처리할 수 있게 된다.
  • 이런 역할들을 수월하게 제공하기 위해 AAC에서는 ViewModel과 LiveData라는것을 제공 해준다.

ViewModel

  • ViewModel은 클래스의 생명 주기 를 고려해서 UI와 관련된 데이터를 저장하고 관리하도록 설계되었다. MVC 패턴을 떠올려 보면 View와 Controller를 명확하게 나누지 못해 View와 Controller가 상당히 비대해진다는 문제가 있다. 액티비티나 프래그먼트와 같은 UI 컨트롤러는 사용자와 interaction하고, view를 표시하는 역할을 수행해야 하는데 Controller와의 경계가 명확하지 않아 UI Controller 클래스가 가지는 책임이 커져버리면 테스트 코드를 작성하기가 상당히 까다로워진다.
  • ViewModel은 뷰나, 액티비티 컨텍스트를 포함하는 클래스를 참조하면 안된다
    • View는 ViewModel의 reference를 가지지만 ViewModel은 View에 대한 정보가 없어야한다. View에 대한 참조를 가지고 있어버리면 unit test 작성하기가 까다로워 지기 때문이다.
  • ViewModel 클래스를 사용하는 가장 큰 이점중 하나는 화면이 rotated 되어 onDestory가 호출 되더라도 ViewModel 객체의 데이터는 사라지지 않는다는 것이다. ViewModel Scope를 그림을 통해 자세히 살펴보자.
  • 액티비티가 생성 됨을 알리는 onCreate 콜백 메서드가 호출된다.
  • 중간에 화면 회전이 발생하면 onDestroy 메서드가 호출되고 액티비티가 종료되고 다시 생성된다.
  • finish() 메서드가 호출되서 액티비티가 완전히 종료되면 onDestory 메서드가 호출된다.
  • finish() 메서드가 호출되서 완전히 액티비티가 종료되고 난 이후에 onCleared()가 호출되고 viewModel객체가 메모리에서 사라지게 된다.

ViewModel은 액티비티가 Destory되고 다시 Create 되는 동안에도 계속 살아있기 때문에 ui가 가지고 있어야할 데이터를 안전하게 가지고 있을 수 있다. 어떻게 가능한 것일까

ViewModel을 생성하고 관리하는 방법

  • 사용자가 생성하는 ViewModel은 반드시 ViewModel을 상속 해야한다.

사용자가 생성하는 ViewModel은 ViewModelStore 객체를 통해서 관리된다. ViewModelStore 객체는 내부에 HashMap<String,ViewModel>타입의 map 객체를 두어 key,value 형태로 ViewModel을 관리하게 된다. 별도로 ViewModel 객체를 관리하기 때문에 ViewModel은 activity의 수명주기를 파악해서 activity와 맞는 Scope를 가질 수 있게된다.

public class ViewModelStore {

    private final HashMap<String, ViewModel> mMap = new HashMap<>();

ViewModelStore 객체를 관리하는 것은 ViewModelStoreOwner를 통해서 하게 되는데 ViewModelStoreOwner는 다음과 같다.

public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}
  • ViewModelStoreOwner 인터페이스에는 ViewModelStore 객체를 반환하는 getViewModelStore() 추상 메서드가 정의되어 있는것을 볼 수 있다.

그렇다면 ViewModelStoreOwner는 어디서 구현하고 있을까 ?

  • 프래그먼트와 액티비티 즉 ui controller에서 ViewModelStoreOwner를 구현하고 getViewModelStore() 메서드를 재정의 하고 있다.
  • 액티비티와 프래그먼트에서 ViewModelStoreOwner를 구현하고 있다는 것을 잘 알아두자.

지금까지 내용을 살펴보면 ViewModel이 어디선가 관리 된다는 것을 알 수 있는데 단순히 MyViewModel() 처럼 객체를 생성한다고 자동으로 관리되는것 같지는 않다. 그럼 어떻게 해야 ViewModel을 제대로 생성할 수 있을까 ?

ViewModel을 생성하는 방법

  • 뷰모델은 적절한 Scope의 ViewModel을 생성해주고 제공해주는 유틸리티 클래스인 ViewModelProvider를 통해서 생성 해야한다. ViewModelProvider는 총 6가지의 ViewModel을 생성하는 방법을 제공하고 있으며 팩토리 패턴을 이용해 ViewModel을 제공해준다.
  • 뷰모델을 얻기 위해서는 ViewModelProvider 객체를 생성하고 get메서드를 호출해야한다.

가장 간단한 생성자를 살펴보자.

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
                ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
                : NewInstanceFactory.getInstance());
    }
  • ViewModelStoreOwner 타입의 파라미터 1개만을 전달받는 생성자다. ViewModelStoreOwner는 아까 위에서 봤듯이 액티비티와 프래그먼트에서 구현하고 있는 것을 알 수 있는데 즉 ViewModelProvider의 생성자로 액티비티 또는 프래그먼트를 전달 해줘야 하는것이다.

생성자로 액티비티 또는 프래그먼트를 전달해 ViewModelProvider 객체를 생성하고 아래 get 메서드를 호출해 어떤 viewModel 클래스를 얻고싶은지 알려줘야한다.

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }

ViewModel을 생성하기 위해 ViewModelProvider를 이용해야하는것을 알게 되었으니 ViewModelProvider().get을 호출하면 어떻게 ViewModel이 생성되고 관리되는지 프로세스를 하나씩 살펴보자.

ViewModel 요청 프로세스

다음과 같이 Provider객체를 생성하고 get메서드를 호출할 경우 내부 동작을 살펴보겠다.

viewModel = ViewModelProvider(requireActivity()).get(AddTaskViewModel::class.java)
  1. ViewModelProvider 객체 생성

    1. ViewModelProvider는 팩토리 패턴을 사용한다고 언급했다. 파라미터로 팩토리 클래스를 넘겨주지 않으면 ViewModelProvider 내부에 자체적으로 구현해 둔 팩토리 클래스를 사용할 수 있게 저장해둔다.

    2. 위의 생성자가 호출되어 넘겨준 activity가 mViewModelStore에 저장되고 자동으로 생성된 (혹은 파라미터로 넘겨주는) 팩토리 클래스가 mFactory에 저장된다.

  2. ViewModelProvider객체의 get메서드를 호출한다.

    1. get(AddTaskViewModel::class.java) 와 같이 메서드를 호출하게 될 경우 내부에서` AddTaskViewModel 클래스 이름을 이용해 unique한 key를 생성하고 또다른 get 메서드를 호출한다.

    2. 밑줄 친 부분을 보면 넘겨받은 key를 이용해 mViewModelStore에서 ViewModel 객체를 꺼내오는 과정을 수행하는것을 볼 수 있는데 mViewModelStore 는 아까 위에서 보았던 ViewModelStoreOwner의 getViewModelStore()를 통해 가져올 수 있으며 이 모든 구현들이 Activity와 Fragment 단에 추가 되어 있는것이다.

      public interface ViewModelStoreOwner {
          /**
           * Returns owned {@link ViewModelStore}
           *
           * @return a {@code ViewModelStore}
           */
          @NonNull
          ViewModelStore getViewModelStore();
      }
    3. mViewModelStore에서 가져온 ViewModel객체가 파라미터로 넘겨받은 Class와 동일하면 해당 객체를 반환하고 그렇지 않다면 (기존에 생성된 ViewModel이 없다면 생성하고 mViewModelStore에 저장 후 반환한다.

      1. mViewModelStore는 내부적으로 HashMap을 이용해 ViewModel을 관리하고 있다고 했는데 해당 Map에 ViewModel이 있으면 가져오고 없으면 만들어서 넣어두는 과정인 것이다.

이어서 작성 예정 ..

profile
학습한 내용을 공유하고 기록합니다.

0개의 댓글