안드로이드 앱 개발에서 여러 Fragment 간에 ViewModel을 공유하는 것은 일관된 상태 관리와 데이터 흐름을 유지하는데 매우 중요하다.
이 포스트에서는 ViewModel의 인스턴스를 관리하고 데이터들을 공유하는 방법에 대해 작성 하였습니다.
ViewModel
- ViewModel을 사용하여 데이터를 관리할 때, Fragment들이 같은 Activity에 속해 있으면 ViewModel 인스턴스를 공유 할 수 있다.
이를 통해 Fragment 간에 데이터 동기화 및 상태 공유가 가능
class InViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
private val _data2 = MutableLiveData<String>()
val data2: LiveData<String> = _data2
fun updateData(newData: String) {
_data.value = newData
}
fun function1(text: String) {
_data2.value = text
Log.d("sdsd", "function1: ${_data2.value}")
}
fun function2(text: String) {
_data2.value = text
Log.d("sdsd", "function2: ${_data2.value}")
}
}
class HomeFragment : BaseFragment<FragmentHomeBinding>(R.layout.fragment_home) {
private lateinit var viewModel: InViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(InViewModel::class.java)
binding.button.setOnClickListener {
val data = binding.editText.text.toString()
viewModel.updateData(data)
}
binding.button2.setOnClickListener {
findNavController().navigate(R.id.action_nav_home_to_nav_sam)
}
}
}
HomeFragment는 사용자로부터 입력을 받아 ViewModel의 데이터를 업데이트하는 역할을 합니다.
사용자는 EditText에 데이터를 입력하고 버튼을 클릭하여 이 데이터를 ViewModel에 전달할 수 있습니다.
ViewModelProvider
를 통해 Activity 범위의 ViewModel 인스턴스
를 가져옵니다.class SamFragment : BaseFragment<FragmentSamBinding>(R.layout.fragment_sam) {
private lateinit var viewModel: InViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(InViewModel::class.java)
viewModel.data.observe(viewLifecycleOwner) { data ->
binding.textView.text = data
}
}
}
SamFragment는 HomeFragment 에서 업데이트된 데이터를 관찰하고 이를 화면에 표시
LiveData를 사용하여 데이터 변경을 관찰하고 데이터가 변경되면 자동으로 UI를 업데이트
뷰모델을 통해 다른 프래그먼트에 데이터를 전달 하기 위해서 ViewModelProvider
에 requireActivity
를 사용하여
동일한 뷰모델 인스턴스를 공유하고 HomeFragment에서 설정한 데이터를 SamFragment에서도 관찰 할 수 있게 된다.
이 예제를 통해 LiveData를 사용하여 프래그먼트 간에 데이터를 공유하고 변경 사항을 실시간으로 관찰할 수 있음을 확인할 수 있습니다.
중요한 것은 동일한
뷰모델 인스턴스
를 공유해야 한다는 점입니다.
class HomeFragment2 : BaseFragment<FragmentHome2Binding>(R.layout.fragment_home2) {
private lateinit var viewModel: InViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(InViewModel::class.java)
binding.button.setOnClickListener {
val data = binding.editText.text.toString()
viewModel.updateData(data)
}
binding.button2.setOnClickListener {
findNavController().navigate(R.id.action_nav_home2_to_nav_another)
}
}
}
HomeFragment는 사용자로부터 입력을 받아 ViewModel의 데이터를 업데이트하는 역할을 합니다.
ViewModelProvider(this)를 통해 자신만의 ViewModel 인스턴스를 생성하고 관리하고 있다.
class AnotherFragment : BaseFragment<FragmentAnotherBinding>(R.layout.fragment_another) {
private lateinit var viewModel: InViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(InViewModel::class.java)
viewModel.data.observe(viewLifecycleOwner) { data ->
binding.textView.text = data
}
}
}
AnotherFragment는 ViewModel의 data LiveData를 관찰하고 이 데이터가 업데이트될 때마다 textView의 텍스트를 업데이트합니다.
이 프래그먼트는 ViewModelProvider(this)를 사용하여 자신만의 ViewModel 인스턴스를 생성합니다.
ViewModelProvider(this)를 사용하면 해당 프래그먼트에 대한 뷰모델 인스턴스가 생성이 되므로 데이터를 전달 할때는
액티비티 내의 모든 프래그먼트에서는 공유가 불가능하므로 프래그먼트 간에 데이터를 공유하거나 통신하기 위해서는 별도의 메커니즘이 필요하게 된다.
이렇게 생성된 뷰모델 인스턴스는 해당 프래그먼트의 생명 주기에 따라 관리됩니다. 즉, 프래그먼트가 파괴될 때 뷰모델도 함께 파괴된다.
class HomeFragment3 : BaseFragment<FragmentHome3Binding>(R.layout.fragment_home3) {
private lateinit var viewModel: InViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(InViewModel::class.java)
binding.viewModel = viewModel
binding.lifecycleOwner = this
binding.button.setOnClickListener {
viewModel.function1(binding.editText.text.toString())
}
binding.button2.setOnClickListener {
findNavController().navigate(R.id.action_nav_home3_to_nav_other)
}
}
}
HomeFragment3는 데이터 바인딩 객체룰 뷰모델에 연결하여 뷰모델의 데이터를 UI와 직접 바인딩하고, 이벤트 기반으로 뷰모델의 함수를 호출하는 방식
class OtherFragment : BaseFragment<FragmentOtherBinding>(R.layout.fragment_other) {
private lateinit var viewModel: InViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(InViewModel::class.java)
viewModel.data2.observe(viewLifecycleOwner) { text ->
binding.textView.text = text
}
binding.button.setOnClickListener {
val text = binding.textView.text.toString()
viewModel.function2(text)
}
}
}
ViewModelProvider(this)를 사용하여 프래그먼트 자체에 뷰모델 인스턴스를 생성하고,
사용자가 버튼을 클릭하면 현재 textView에 표시된 텍스트를 읽어 ViewModel의 function2 메소드를 호출하여 data2를 업데이트
class InViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
private val _data2 = MutableLiveData<String>()
val data2: LiveData<String> = _data2
fun updateData(newData: String) {
_data.value = newData
}
fun function1(text: String) {
_data2.value = text
Log.d("sdsd", "function1: ${_data2.value}")
}
fun function2(text: String) {
_data2.value = text
Log.d("sdsd", "function2: ${_data2.value}")
}
}
현재 코드에서는
HomeFragment3
와OtherFragment
가 각각 독립적인 뷰모델 인스턴스를 사용하고 있습니다.
즉, HomeFragment3에서 설정한 data2의 값은 OtherFragment에서 접근할 수 없으며 뷰모델의 함수의 로그를 통해 직관적으로 확인 할 수가 있었고 이는 ViewModelProvider(this)를 사용하여 프래그먼트마다 별도의 뷰모델 인스턴스를 생성하기 때문입니다.
아니 그러면 처음부터 편하게 액티비티 범위의 뷰모델 인스턴스로 전부 선언한게 편리하지 않나? 라고 생각 할 수도 있지만 선택은 상황에 따라 다르며,
프래그먼트 간에 데이터를 공유하거나 통신할 필요가 없고 각 프래그먼트가 독립적인 데이터를 다루는 경우에는 ViewModelProvider(this)
를 사용하는 것이 적절하고 코드의 일관성과 가독성을 위해 모든 곳에서 requireActivity()
를 사용하는 것보다는 실제로 필요한 경우에만 사용하여 코드의 의도가 명확해지고, 불필요한 액티비티 범위의 뷰모델 생성을 피할 수 있다.
그리고 같은 인스턴스를 사용함으로써 안드로이드 앱에서 데이터의 일관성과 반응성을 보장하고 여러 Fragment에서 동일한 ViewModel 인스턴스를 공유함으로써 복잡한 데이터 흐름과 상태 관리를 간단하게 처리할 수 있고 이러한 접근 방식은 앱의 유지보수성을 높이고 개발 과정을 효율적으로 만들어 준다는 걸 다시 한번 알게 되었습니다.
ViewModelProvider를 사용하여 선언하지 않고 by viewModels()를 사용하면 ViewModelProvider를 직접 사용하지 않아도 되며 ViewModel 인스턴스를 자동으로 생성하고 관리할 수 있다.
만약 특정 범위에서 ViewModel을 공유하려면 by activityViewModels()를 사용을 하여 선언하는 것도 방법일 것 같다.
깃허브 : https://github.com/GEUN-TAE-KIM/ViewModelInstanceStudy_Sample.git