티스토리 뷰
Android
[Android & kotlin] AAC(Android Architecture Components) - ViewModel
YEJINEE 2020. 6. 4. 04:49AAC (Android Architectire Components)
- 안드로이드 아키텍처 구성요소는 테스트와 유지관리가 쉬운 앱을 디자인하도록 돕는 라이브러리의 모음이다
- 앱의 수명 주기를 관리하여 구성 변경을 유지하고 메모리 누수를 방지하며 UI에 쉽게 데이터를 로드할 수 있게 해준다
- 현재 AAC는 아래 5개 라이브러리를 제공한다
- LifeCycle
- LiveData
- ViewModel
- Room
- Paging
- 아래의 그림은 아키텍처 컴포넌트들이 상호작용하는 순서를 나타낸 그림이다
ViewModel
- MVVM 패턴에서 ViewModel은 Repository와 View를 연결해주는 다리 역할을 한다
- ViewModel에서는 UI를 위한 데이터를 가지고 있으며, 화면 회전과 같이 구성을 변경할 때도 데이터를 유지할 수 있다
- AAC의 ViewModel은 데이터를 생명주기와 분리하여 관리할 수 있도록 해준다
- AndroidViewModel을 상속받은 ViewModel의 객체를 ViewModelProvider로 만들어 사용하는데, 이 ViewModel의 범위는 ViewModelProvider의 파라미터로 전달되는 LifeCycleOwner에 의해 지정된다
- 위의 사진을 보면 Activity가 rotation되어도 ViewModel은 사라지지않고 하나로 유지된다
- 이렇게, ViewModel을 사용하면 Activity와 Fragment간의 커뮤니케이션이 필요할 때 하나의 ViewModel을 바라보고 같은 LiveData에 접근하여 데이터를 사용할 수 있다
✔️ AndroidViewModel
- application을 파라미터로 사용하는 ViewModel의 서브클래스이다.
- AndroidViewModel은 activity나 fragment의 생명주기에 의존적인 ViewModel 클래스와 달리 어플리케이션의 Scope와 함께하는 ViewModel 객체를 생성한다
- ViewModel 내에서 context가 필요하다면 AVM을 사용해야한다 (RoomDB의 객체를 생성할 때는 application이 필요한데, 이 때 activity의 context를 사용하면 activity가 destroy된 경우 메모리 누수가 발생할 수 있으므로 싱글톤 application을 사용한다)
✔️ ViewModelProvider
/**
* Creates {@code ViewModelProvider}. This will create {@code ViewModels}
* and retain them in a store of the given {@code ViewModelStoreOwner}.
* <p>
* This method will use the
* {@link HasDefaultViewModelProviderFactory#getDefaultViewModelProviderFactory() default factory}
* if the owner implements {@link HasDefaultViewModelProviderFactory}. Otherwise, a
* {@link NewInstanceFactory} will be used.
*/
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
: NewInstanceFactory.getInstance());
}
- ViewModel 객체를 만들어주는 ViewModelProvider의 코드를 살펴보겠다!
- ViewModelStoreOwner는 ViewModel을 관리하는 ViewModelStore 객체를 만들고 관리하는 인터페이스이다
- (ViewModelStoreOwner 인터페이스 --관리/생성--> ViewModelStore 객체 --관리/생성--> ViewModel 객체)
- activity와 fragment에서 ViewModelStoreOwner를 구현하고 있기 때문에ViewModel 객체를 만들 때는 activity와 fragment가 필요하다 (우리가 파라미터의 어떤 owner를 전달하냐에 따라서 ViewModelScope가 정해지는 것이다)
- 그렇다면 저기있는 Factory는 뭐지!??!
/**
* Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
*/
public interface Factory {
/**
* Creates a new instance of the given {@code Class}.
* <p>
*
* @param modelClass a {@code Class} whose instance is requested
* @param <T> The type parameter for the ViewModel.
* @return a newly created ViewModel
*/
@NonNull
<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
- Factory는 ViewModelProvider 안에 있는 인터페이스로 실질적인 ViewModel 인스턴스를 생성해준다
public static class NewInstanceFactory implements Factory {
private static NewInstanceFactory sInstance;
/**
* Retrieve a singleton instance of NewInstanceFactory.
*
* @return A valid {@link NewInstanceFactory}
*/
@NonNull
static NewInstanceFactory getInstance() {
if (sInstance == null) {
sInstance = new NewInstanceFactory();
}
return sInstance;
}
@SuppressWarnings("ClassNewInstance")
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//noinspection TryWithIdenticalCatches
try {
return modelClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot create an instance of " + modelClass, e);
}
}
}
- 바로 이렇게 Factory 인터페이스를 상속받는 NewInstanceFactory 클래스 안에 있는 create 메소드로 인스턴스를 만들어 반환해준다
✔️ ViewModel 객체 생성하기
- ViewModel 클래스 타입 객체 생성
val viewModel = ViewModelProvider(this).get(PlaceSearchViewModel::class.java)
- ViewModel 클래스를 상속받고 파라미터가 없는 ViewModel 객체는 위와같이 ViewModelProvider를 사용해 생성하여 사용한다
- 여기서 해당 객체가 생성되고 있는 activity를 owner로 보내준 것이다
viewModel = ViewModelProvider(requireActivity()).get(PlaceSearchViewModel::class.java)
- fragment에서 viewModel 생성 시 fragment가 호출되는 activity를 owner로 넘기면 된다
- AndroidViewModel 클래스 타입 객체 생성
- AVM 타입 객체를 생성을 위해서는 owner와 함께 factory 객체를 파라미터로 함께 넘겨줘야한다🌟
viewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(PlaceSearchViewModel::class.java)
- 특별히 Factory 인터페이스를 커스텀하지 않아도 되는 경우 위에서 살펴본 NewInstanceFactory 클래스로 인스턴스를 생성하여 넘겨줄 수 있다
- Factory를 커스텀하여 ViewModel 객체 생성
class placeSearchViewModel(val idx: Int) : ViewModel() {
...
}
- 만약 위와 같이 viewModel에 파라미터로 값이 전달되는 경우에는 Factory를 커스텀해서 사용해야한다
class ViewModelFactory(val idx: Int) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return modelClass.getConstructor(Int::class.java).newInstance(idx)
}
}
- Factory 클래스를 상속받는 커스텀 클래스를 작성하여 파라미터를 받아 그 값을 이용해 ViewModel 인스턴스를 생성해 반환해주게 된다
val viewModel = ViewModelProvider(this, ViewModelFactory(1)).get(PlaceSearchViewModel::class.java)
- ViweModel 객체 생성 시에는 커스텀한 Factory 클래스로 인스턴스를 만들어 파라미터로 넘겨주면 된다
✔️ ViewModel 을 직접 사용하기
StartPlaceSearchActivity와 PlaceListFragment에서 하나의 PlaceSearchViewModel 객체를 사용해 같은 LiveData를 사용하는 코드를 짜는 것이 나의 임무이다
open class BaseViewModel(application: EarlyBuddyApplication) : AndroidViewModel(application){
private val compositeDisposable = CompositeDisposable()
fun addDisposable(disposable: Disposable) {
compositeDisposable.add(disposable)
}
override fun onCleared() {
compositeDisposable.clear()
super.onCleared()
}
}
- AndroidViewModel을 상속받는 BaseViewModel을 다음과 같이 만들어놓았다
- parameter로 application을 전달해야하는 것이 ViewModel을 상속받을 때와는 다른 차이점이다
class PlaceSearchViewModel : BaseViewModel(EarlyBuddyApplication.getGlobalApplicationContext()) {
private var _placeList = MutableLiveData<List<PlaceSearch>>()
val placeList : LiveData<List<PlaceSearch>> get() = _placeList
fun getPlaceSearchData(query: String){
...
}
}
- BaseViewModel을 상속받는 PlaceSearchViewModel 클래스를 만들어주었다
- 상속받는 클래스를 정의할 때 BaseViewModel의 파라미터로 application을 생성해 넘겨줘야한다
- StartPlaceSearchActivity와 PlaceListFragment 둘 다 바로 여기있는 placeList라는 LiveData에 접근을 해야한다
class StartPlaceSearchActivity : BaseActivity<ActivityStartPlaceSearchBinding, PlaceSearchViewModel>() {
override val layoutResID: Int
get() = R.layout.activity_start_place_search
override lateinit var viewModel: PlaceSearchViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(PlaceSearchViewModel::class.java)
...
}
fun getPlaceData(){
val query = act_start_place_search_et_search.text.toString()
viewModel.getPlaceSearchData(query)
}
}
- activity에서 ViewModelProvider를 이용해 ViewModel 객체를 만들어줬다
- owner로는 this로 해당 activity를 보내고, NewInstanceFactory클래스로 인스턴스를 생성해 파라미터로 함께 보낸다
- getPlaceData에서는 viewModel 객체로 ViewModel 클래스 내부 통신 함수에 접근하여 LiveData를 업데이트 해준다
class PlaceListFragment : BaseFragment<FragmentPlaceListBinding, PlaceSearchViewModel>() {
override val layoutResID: Int
get() = R.layout.fragment_place_list
override lateinit var viewModel: PlaceSearchViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity(), ViewModelProvider.NewInstanceFactory()).get(PlaceSearchViewModel::class.java)
viewModel.placeList.observe(viewLifecycleOwner, Observer {
(viewDataBinding.fragPlaceListRv.adapter as BaseRecyclerViewAdapter<PlaceSearch, ItemPlaceListBinding>)
.replaceAll(it)
(viewDataBinding.fragPlaceListRv.adapter as BaseRecyclerViewAdapter<PlaceSearch, ItemPlaceListBinding>)
.notifyDataSetChanged()
})
}
}
- fragment에서 역시 activity에서와 같은 방식으로 ViewModel 객체를 생성해준다
- owner로는 activity를 넘긴다
- 생성한 ViewModel 객체를 이용해 ViewModel 클래스 내부의 LiveData를 observe하고 있다가 activity에서 이루어지는 통신을 통해 LiveData에 업데이트가 일어나면 RecyclerView의 내용을 바꿔준다!
포스팅을 끝내니까 4시간이나 지났다
그래도 이렇게 하나하나 찾아보며 공부를 하니 내 실수때매 발생한 에러에 화를 내던 내 자신을 반성하게 되었다 😥
또 정말 똑똑하고 멋지신 분들이 블로그에 이미 너무 잘 정리해놓으신 덕에 도움이 많이 되었다❗️
나도 언젠가 그런 사람이 되고 싶다는 생각을 하며 얼른 프로젝트에 Room을 적용해 다음주에는 Room에 대한 글을 포스팅해야겠다 🥊
참고자료 🕊
'Android' 카테고리의 다른 글
[Android & kotlin] AAC(Android Architecture Components) - Room (0) | 2020.06.22 |
---|---|
[kotlin] DI 의존성 주입 - koin 사용하기 (0) | 2020.06.14 |
[디자인패턴] MVVM 패턴 (BaseActivity, BaseViewModel 사용하기) (0) | 2020.05.03 |
[디자인패턴] MVVM패턴 (DataBinding) (0) | 2020.03.30 |
[kotlin] 안드버디 스터디 정리 -2 (0) | 2020.03.22 |