개발하는 두더지

Android Architecture Components ViewModel이란? 본문

Java,Android

Android Architecture Components ViewModel이란?

덜지 2018. 8. 12. 16:50

Android Architecture Components ViewModel이란?


ViewModel 클래스는 UI 관련 데이터를 저장하고 관리하기 위해 설계되었습니다.

즉,  스크린 회전 같은 상태 변화에도 데이터가 보존될 수 있도록 허용해줍니다.


안드로이드 프레임워크는 UI 컨트롤러인 액티비티와 프래그먼트의 생명주기를 관리합니다.

프레임워크는 특정 사용자 동작 또는 완전히 제어할 수 없는 디바이스 이벤트에 대한 응답으로

UI 컨트롤러를 destroy하거나 다시 re-creates 하도록 결정합니다.


만약 시스템이 UI 컨트롤러를 destory하거나 re-creates한다면 별도로 저장되지 않은 데이터를 잃게 됩니다.

여러분의 앱이 사용자의 리스트를 포함하고 있다고 생각해봅시다. 만약 액티비티가 상태 변경으로 재 생성이 된다면,

새 액티비티는 사용자 리스트를 re-fetch 해야 합니다.


간단한 데이터의 경우 onSaveInstanceState() 메소드를 써서 onCreate()에서 다시 데이터를 받을 수 있지만,

이 방식은 bitmap과 리스트 형식의 많은 양의 데이터가 아닌 serialized, deserialize가 가능한 작은 데이터에 적합합니다.



또 다른 문제는 UI 컨트롤러가 리턴을 받기위해 상당시간 소요되는 비동기방식의 call이 빈번하게 일어날 경우 입니다.

메모리 릭을  피하기 위해 시스템이 call들을 정리해야 합니다. 이런 관리는 많은 유지 관리가 필요하며 

상태 변화로 인해 객체를 재 생성하는 경우, 이미 만들어진 객체를 다시 호출해야 하므로 리소스가 낭비됩니다.


액티비티와 프래그먼트같은 UI 컨트롤러는 주로 UI 데이터를 보여주고, 사용자 액션에 반응하고, 퍼미션 요청을 처리합니다.

그러므로 UI 컨트롤러가 데이터베이스 또는 네트워크에서 데이터를 로드하는 작업까지 요청되면 너무 헤비한 클래스가 되어버립니다.

이런식으로 UI 컨트롤러에 과한 책임을 할당하면 테스트가 훨씬 더 어려워집니다.


그래서 ViewModel을 통해 UI 컨트롤러 로직에서 데이터 소유권을 분리시키는 것이 훨씬 효율적입니다.



안드로이드 아키텍처 컴포넌트에서 ViewModel이라는 UI 컨트롤러를 위한 헬퍼 클래스를 제공합니다. UI를 위한 데이터를 준비하는 역할을 합니다.

ViewModel 객체는 자동으로 화면 회전같은 상태 변화동안 자동으로 유지되고 다음 액티비티 또는 프래그먼트 인스턴스에 즉시 사용 가능합니다.


public class MyViewModel extends ViewModel {
   
private MutableLiveData<List<User>> users;
   
public LiveData<List<User>> getUsers() {
       
if (users == null) {
            users
= new MutableLiveData<List<User>>();
            loadUsers
();
       
}
       
return users;
   
}

   
private void loadUsers() {
       
// Do an asynchronous operation to fetch users.
   
}
}


public class MyViewModel extends ViewModel {
   
private MutableLiveData<List<User>> users;
   
public LiveData<List<User>> getUsers() {
       
if (users == null) {
            users
= new MutableLiveData<List<User>>();
            loadUsers
();
       
}
       
return users;
   
}

   
private void loadUsers() {
       
// Do an asynchronous operation to fetch users.
   
}
}


public class MyActivity extends AppCompatActivity {
   
public void onCreate(Bundle savedInstanceState) {
       
// 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.

       
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
        model
.getUsers().observe(this, users -> {
           
// update UI
       
});
   
}
}

만약 액티비티가 재 생성이 된다면, 처음 만들어진 MyViewModel 인스턴스를 받게됩니다. 이 인스턴스를 호출한 액티비티 또는 프래그먼트가 destory 되어 메모리 해제가 되기전까지 데이터를 유지하고 있어서 데이터를 보관하고 있다가 화면 회전같은 상태 변화가 발생해도 데이터를 유지하게 됩니다.

안드로이드 프레임워크는 내부적으로 class, ViewModel의 Map을 관리하므로 이미 존재하는 ViewModel의 레퍼런스를 가져올때마다 그 인스턴스를 반환하게 됩니다. 

오너 액티비티가 끝나면 프레임워크가 자동으로 ViewModel 객체의 onCleared() 메소드를 호출하여 리소스를 해제합니다.



ViewModelProvider에 전달된 Lifecycle에 생명주기 범위가 지정됩니다.

즉, 주어진 액티비가 살아있는 동안 메모리에 계속 남아있습니다.

액티비티의 경우에는 finish될 때, 프래그먼트의 경우에는 detached될 때까지


A 액티비티에서 ViewModel 객체를 생성하면 scope는 A 액티비티의 생명주기에 따릅니다.

만약 B 액티비티에서 ViewModel에 저장된 값을 재사용하고 싶다면?


B 액티비티에서 ViewModelProviders로 객체를 다시 구해오면 A에서 만든 객체가 아닌 새 객체를 만들어냅니다.

how to reuse the same ViewModel on different activites

위에서 설명이 되어있듯이 싱글톤 팩토리로 동작하는 custom ViewModel Factory를 전달하여 다른 액티비티에서도 동일한 ViewModel 인스턴스를 받을 수 있습니다.

하지만 다른 생명주기에서 ViewModel 객체를 유지하는 것은 안티 패턴입니다. 

즉, RxJava와 다르게 생명주기에 따라 데이터를 보관/관리 해주는 LiveData의 장점을 버리는 방식이라 생각합니다.

ViewModel의 인스턴스를 유지시키는 것이 아닌 DataSource나 Repository를 싱글톤으로 유지하는 것이 더 추천되는 방식입니다.

https://stackoverflow.com/a/49365126/6602341


예제를 작성하지 못했으므로 위 링크의 샘플 예시를 보면 이해하기 쉬울 겁니다.

ViewModel 인스턴스를 유지시키는 방식이 아닌 Other data layers(data source, data source를 관리하는 repository) 를 싱글톤으로 만들어 데이터를 유지시켜 다른 Activities에서 새로운 ViewModel 객체를 만들어 repository를 통해 보관중인 데이터를 가져오면 방식입니다.


물론 단일 액티비티안에서 2개 이상의 프레그먼트들 사이에서 데이터를 공유할 때는 프레그먼트의 scope를 사용하는 것이 아닌 프레그먼트들을 감싸고있는 액티비티의 scope를 전달하면 됩니다.

public class SharedViewModel extends ViewModel {
   
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

   
public void select(Item item) {
        selected
.setValue(item);
   
}

   
public LiveData<Item> getSelected() {
       
return selected;
   
}
}


public class MasterFragment extends Fragment {
   
private SharedViewModel model;
   
public void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
        model
= ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector
.setOnClickListener(item -> {
            model
.select(item);
       
});
   
}
}

public class DetailFragment extends Fragment {
   
public void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
       
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model
.getSelected().observe(this, item -> {
           
// Update the UI.
       
});
   
}
}

getActivity() 를 통해 ViewModel 객체를 받아오므로 두 프레그먼트 다 동일한 ViewModel 객체를 받습니다.

동일한 생명주기의 scope를 가지고 있으므로 아무 문제 없이 동작되며 데이터를 계속 유지할 수 있습니다.



출처

Android Architecture Component - ViewModel Overview

kotlin + aac + livedata + databinding sample

livedata 공식번역








public class MyViewModel extends ViewModel {
   
private MutableLiveData<List<User>> users;
   
public LiveData<List<User>> getUsers() {
       
if (users == null) {
            users
= new MutableLiveData<List<User>>();
            loadUsers
();
       
}
       
return users;
   
}

   
private void loadUsers() {
       
// Do an asynchronous operation to fetch users.
   
}
}

public class MyViewModel extends ViewModel {
   
private MutableLiveData<List<User>> users;
   
public LiveData<List<User>> getUsers() {
       
if (users == null) {
            users
= new MutableLiveData<List<User>>();
            loadUsers
();
       
}
       
return users;
   
}

   
private void loadUsers() {
       
// Do an asynchronous operation to fetch users.
   
}
}


Comments