본문 바로가기
개발/안드로이드

Android Architecture Components - ViewModel

by darksilber 2019. 5. 29.
반응형

출처 - https://tourspace.tistory.com/27?category=788397

 

 

ViewModel class는 UI와 연관된 데이터를 스크린 가로세로 전환같은 변경 발생시에도 유지하도록 도와주는 class입니다.

예를들어 사용자 리스트를 보여주는 Activity나 fragment가 있다면, 이 데이터는 화면이 re-created 될때(사용자의 의도나, system에 의해서) 다시 re-fetch되야 하죠.

이전 UI의 상태를 저장하기 위해 onSaveInstanceState()을 이용하여 onCreate()에서 bundle로 받을수 있지만, 적은 데이타나 UI 상태를 저장하는 용도이며, 사용자 리스트 같은 큰 데이터를 저장하기엔 적합하지 않습니다.

다른 문제로, UI controller(Activity나 Fragment에서) return값을 받는데 시간이 걸리는 비동기 작업을 빈번하게 수행하면, UI controller는 이를 관리하기 위한 공수가 많이 들어갑니다. (memory leak 관리나 clean up 등등)

또한 configuration change같은 변경이 생겨 object를 re-create하는 경우에 같은 call을 또 해야 한다는 낭비가 발생합니다.

 

data ownership을 UI controller에서 분리하는것이 훨씬 효율적입니다. UI를 위해 data를 준비하는 helper class를 Lifecycles에서 제공하며 이를 ViewModel이라 합니다.

ViewModel은 configuration change시에 data를 자동으로 유지하여 다음 activity나 fragment에서 바로 사용할 수 있도록 해 줍니다.

아래 예제는 Activity나 fragment가 user list 데이터를 얻고 유지하는 책임을 갖도록 하는게 아니라 ViewModel 처리 하도록 위임합니다.

public class MyViewModel extends ViewModel { private MutableLiveData> users; public LiveData> getUsers() { if (users == null) { users = new MutableLiveData>(); loadUsers(); } return users; } private void loadUsers() { // do async operation to fetch users } } // activie에서 access 할때 public class MyActivity extends AppCompatActivity { public void onCreate(Bundle savedInstanceState) { MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class); model.getUsers().observe(this, users -> { // update UI }); } }

activity가 재생성되는 경우 이전에 생성해 놓은 MyViewModle 객체를 다시 전달 받습니다.

만약 Owner activity가 finish되면 frameworkd에서 ViewModel의 onCleared()를 호출하여 resource를 clean up 해야 합니다.

 

ViewModel은 특정 activity나 fragment에 따라서 생명주기가 결정되므로 절대 View나 activity context에 연관된 reference를 가져서는 안됩니다.

만약 ViewModel 내부에서 system service에 접근하거나 다른 이유로 context가 필요한 경우 AndroidViewModel을 상속받아 사용해야 합니다.

AndroidViewModel은 생성자로 application을 받으므로 context를 사용할 수 있습니다.

 

Sharing Data between Fragments

한 activity 안에서 fragment간 데이터를 주고받는건 늘상 있는 일이죠.

하지만 두개의 fragment가 서로 communication 하기 위해서는 interface를 정의하고, 이를 activity에서 각각의 fragment에 연결시켜줘야 하며, fragment 역시 상대 fragment의 상태에 따라 동작하도록 해야하므로 쉬운일이 아닙니다.

 

이런 어려움들은 ViewModel을 사용함으로써 해결할 수 있습니다.(오잉??)

예를 들어 첫번째 fragment에서 user를 선택하면, 다른 fragment에서 선택된 user의 상세 정보를 보여준다고 하면 ViewModel을 이용하여 하나의 activity 범위 안에서 아래와 같이 서로 communication 할 수 있습니다.

public class SharedViewModel extends ViewModel { private final MutableLiveData selected = new MutableLiveData(); public void select(Item item) { selected.setValue(item); } public LiveData 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 LifecycleFragment { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class); model.getSelected().observe(this, { item -> // update UI }); } }

위 두개의 fragment는 ViewModelProviders.of(activity) 를 이용하여 생성된 ViewModelProvider를 얻습니다.

이 ViewModelProvider는 주어진 activity가 살아있는 동안 ViewModel을 유지하고 있습니다.

또한 같은 activity를 인자로 넣었으므로 동일한 SharedViewModel instance를 얻을 수 있습니다.

이때 장점은 아래와 같습니다.

  • acittvity는 fragment간 communication에 전혀 관여하지 않는다.
  • 각 fragmentd의 lifecycle이 다른 fragment의 lifecycle에 영향을 주지 않는다.

 

The Lifecycle of a ViewModel

ViewModel의 생명주기는 ViewModelProvider를 통해서 get()할때 넘겨줬던 Lifecycle owner에 의존됩니다.

따라서 owner가 영원히 메모리에서 날아가기전 까지 ViewModel이 유지됩니다.

영원히 메모리에서 날아가는 때는 Acitivity의 경우 finish 되었을때나 Fragment의 경우 detached 되었을 때입니다.

 

(내부적으로 setRetainInstance(true)를 사용합니다.)

 

전체구조

 

참고자료: https://medium.com/google-developers/viewmodels-persistence-onsaveinstancestate-restoring-ui-state-and-loaders-fc7cc4a6c090

 

ViewModel vs SavedInstanceState

ViewModel은 configuration chagne상태에서는 데이터를 보관할 수 있으나, 앱이 죽거나 사라지는 경우에는 보관하지 않습니다.

다만 savedInstanceState에 bundle로 넣어놓은 데이터나 views,activity,fragment같은 기본 framework component는 OS에서 저장된 상태로 되돌려 줍니다.

 

이런 정보들은 system process memory에 저장되면, 적은 데이터만 저장 가능하며, 실제 app에서 사용하는 데이터는 적당하지 않습니다.

 

예를들어 나라정보를 보여주는 화면이 있을경우 절대 country object를 저장하면 안됩니다.

 

단 View 또는 fragment argument에 저장되지 않은경우 countryId를 save instance를 이용하여 저장하고, 실제 정보는 database에 저장하여 ViewModel을 이용하여 반환할 수 있도록 해야 합니다.

 

반응형

댓글