Android ViewModel - ViewModel
ViewModel
ViewModel은 UI Data를 저장하고 관리하기 위해 설계되었습니다. 안드로이드 특성상 프레임워크가 UI Controller의 생명주기를 관리하기 때문에 개발자 입장에서 생명주기를 고려하여 작업을 진행해야 하며 이는 추가적인 코드를 요구합니다. 또한 UI Controller의 구조가 커지면서 유지 보수와 테스트의 불편함을 느낄 수 있습니다.
ViewModel은 UI Controller가 데이터 표현, 사용자 작업에 반응, System Call(권한 요청, System Service 등)에 집중할 수 있도록 도와줍니다.
생명주기
안드로이드 프레임워크 특성상 Configure Change(화면 회전, 언어 변경 등)가 일어나면 Activity를 destroy하고 create합니다. 이 과정에서 UI Data를 잃게됩니다.
하지만 ViewModel은 UI Controller의 생명주기를 파악하여 Activity가 완전히 종료되어 destroy되거나, Fragment가 완전히 종료되어 detach될 때까지 메모리에 남아있습니다. 즉 Configure Change로 인한 UI Controller 재생성 과정에서 발생할 수 있는 데이터 손실을 막을 수 있습니다.
※ Activity인 경우 finish() 이후 onDestroy()가 호출되고 ViewModel의 onCleared() 호출.
※ Fragment인 경우 Configure Change가 아닌 onDetach()가 호출되고 ViewModel의 onCleared() 호출.
class MyViewModel : ViewModel() {
...
}
class MyContextViewModel(application: Application) : AndroidViewModel(application) {
...
}
SavedStateHandle
ViewModel이 Configure Change에 자유롭기는 하지만, 안드로이드에서 메모리 부족시 Process Kill을 하면 ViewModel도 데이터를 잃습니다. 일반적으로 onSaveInstanceState()으로 백업을 할 수 있지만 ViewModel에서 SavedStateHandle을 지원함으로 효율적으로 UI Data를 따로 관리할 수 있습니다.
ViewModel | SavedStateHandle | |
저장소 | 메모리 | 디스크 |
Configure Change | YES | YES |
Process Kill | NO | YES |
onDestroy() (Not Configure Change) | NO | NO |
데이터 타입 | 메모리에 저장하기 때문에 상관없음 | 객체를 직렬화하여 저장하기 때문에 Serializable, Parcelable한 객체만 저장가능 |
속도 | 상대적으로 빠름(메모리) | 상대적으로 느림(디스크 직렬화) |
package com.taetae98.viewmodel
import android.app.Application
import androidx.lifecycle.*
import androidx.savedstate.SavedStateRegistryOwner
class MainViewModel(
application: Application,
savedStateHandle: SavedStateHandle // 생성자로 쉽게 받을 수 있습니다.
) : AndroidViewModel(application) {
// val text by lazy { MutableLiveData("") } Normal Variable
val text by lazy { savedStateHandle.getLiveData("TEXT", "") } // SavedStateHandle Variable
fun functionWithContext() {
val application = getApplication<Application>()
// Do Something With Context
}
// // No SavedStateHandle ViewModel Factory
// class Factory(
// // Parameter Something
// ) : ViewModelProvider.Factory {
// override fun <T : ViewModel> create(modelClass: Class<T>): T {
// return MainViewModel()
// }
// }
class Factory(
// Parameter Something
private val application: Application,
owner: SavedStateRegistryOwner
) : AbstractSavedStateViewModelFactory(owner, null) {
override fun <T : ViewModel> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {
return MainViewModel(application, handle) as T
}
}
}
Fragment간 데이터 공유
ViewModel을 사용하면 Fragment간 데이터를 쉽게 공유할 수 있습니다. 같은 Activity를 공유하는 Fragment인 경우, ViewModelStoreOwner를 Activiry로 설정하여 서로 같은 ViewModel을 참조할 수 있기 때문에 데이터를 쉽게 공유할 수 있습니다.
class FirstFragment : Fragment() {
...
private val mainViewModel by lazy { ViewModelProvider(requireActivity()).get(MainViewModel::class.java) } // Activity ViewModel
...
}
class SecondFragment : Fragment() {
...
private val mainViewModel by lazy { ViewModelProvider(requireActivity()).get(MainViewModel::class.java) }
...
}
Code
def lifecycle_version = "2.4.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// Saved state module for ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
MainViewModel
class MainViewModel(
application: Application,
savedStateHandle: SavedStateHandle // 생성자로 쉽게 받을 수 있습니다.
) : AndroidViewModel(application) {
// val text by lazy { MutableLiveData("") } Normal Variable
val text by lazy { savedStateHandle.getLiveData("TEXT", "") } // SavedStateHandle Variable
fun functionWithContext() {
val application = getApplication<Application>()
// Do Something With Context
}
// // No SavedStateHandle ViewModel Factory
// class Factory(
// // Parameter Something
// ) : ViewModelProvider.Factory {
// override fun <T : ViewModel> create(modelClass: Class<T>): T {
// return MainViewModel()
// }
// }
class Factory(
// Parameter Something
private val application: Application,
owner: SavedStateRegistryOwner
) : AbstractSavedStateViewModelFactory(owner, null) {
override fun <T : ViewModel> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {
return MainViewModel(application, handle) as T
}
}
}
FirstFragment
class FirstFragment : Fragment() {
// private val mainViewModel by lazy { ViewModelProvider(this).get(MainViewModel::class.java) } // Fragment ViewModel
private val mainViewModel by lazy { ViewModelProvider(requireActivity()).get(MainViewModel::class.java) } // Activity ViewModel
// private val mainViewModel by viewModels<MainViewModel>() // Navigation Component를 사용하여 ViewModel 생성
// private val mainViewModel by activityViewModels<MainViewModel>() // Navigation Component를 사용하여 ActivityViewModel 생성
// // ViewModel With Parameter
// private val mainViewModel by lazy {
// ViewModelProvider(
// owner = requireActivity(),
// factory = MainViewModel.Factory(
// requireActivity().application, this)
// ).get(MainViewModel::class.java)
// }
// // ViewModel With Parameter Navigation Component
// private val mainViewModel by viewModels<MainViewModel> {
// MainViewModel.Factory(
// application = requireActivity().application,
// owner = requireActivity()
// )
// }
private var _binding: FragmentFirstBinding? = null
private val binding: FragmentFirstBinding
get() {
return _binding!!
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = DataBindingUtil.inflate<FragmentFirstBinding>(inflater, R.layout.fragment_first, null, false).apply {
lifecycleOwner = viewLifecycleOwner
}
return _binding?.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.mainViewModel = mainViewModel
binding.setOnNext {
findNavController().navigate(FirstFragmentDirections.actionFirstFragmentToSecondFragment())
}
}
}
SecondFragment
class SecondFragment : Fragment() {
private val mainViewModel by lazy { ViewModelProvider(requireActivity()).get(MainViewModel::class.java) }
private var _binding: FragmentSecondBinding? = null
private val binding: FragmentSecondBinding
get() {
return _binding!!
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = DataBindingUtil.inflate<FragmentSecondBinding>(inflater, R.layout.fragment_second, null, false).apply {
lifecycleOwner = viewLifecycleOwner
}
return _binding?.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.mainViewModel = mainViewModel
}
}
Git (예제코드)
https://github.com/KangTaeJong98/TaeTae98/tree/main/Android/ViewModel
GitHub - KangTaeJong98/TaeTae98: Study Repository
Study Repository. Contribute to KangTaeJong98/TaeTae98 development by creating an account on GitHub.
github.com