Reputation: 478
Suppose there's a LiveData object in a ViewModel whose content is of interest of multiple views. Each view is handled differently and may or may not be displayed at any given time, independent of the other views.
Which of these approaches is better/correct?
To have 1 observer to such LiveData object, and have the observer to implement the update logic for all views, making the necessary checks, etc.
To have multiple observers to the LiveData object (or per interested view) and each observer only deals with the logic of that particular view.
What I have noticed so far is that following option 1 can lead to code that is difficult to manage and understand. The same view may be updated in different parts of the code, which reduces readability. Option 2 solves that problem by allowing all code related to a view to be colocated. However, I'm concerned about the performance penalty of having multiple observers on the same LiveData in the same Fragment.
Which one is the most appropriate approach for complex views?
Example:
class NewViewModel() : ViewModel() {
private val _lv1 = MutableLiveData<Boolean>(false)
val lv1: LiveData<Boolean> = _lv1
private val _lv2 = MutableLiveData<Boolean>(false)
val lv2: LiveData<Boolean> = _lv2
}
class NewFragment : Fragment() {
private var _viewBinding: FragmentNewBinding? = null
private val viewBinding
get() = _viewBinding!!
private val viewModel by viewModels<NewViewModel> { getViewModelFactory() }
override fun onCreateView(...): View? {
_viewBinding = FragmentNewBinding.inflate(inflater)
return viewBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
example1()
example2_1()
example2_2()
}
override fun onDestroyView() {
_viewBinding = null
super.onDestroyView()
}
private fun example1() {
val view1 = viewBinding.view1
viewModel.lv1.observe(viewLifecycleOwner, Observer {
// Some complex-ish UI logic that might need
// to retrieve supplement data from somewhere (e.g Glide)
view1.refreshWith(it)
})
val view2 = viewBinding.view2
viewModel.lv1.observe(viewLifecycleOwner, Observer {
// Some complex-ish UI logic that might need
// to retrieve supplement data from somewhere (e.g Glide)
view2.refreshWith(it)
})
}
private fun example2_1() {
val view1 = viewBinding.view1
viewModel.lv2.observe(viewLifecycleOwner, Observer {
// Some complex-ish UI logic that might need
// to retrieve supplement data from somewhere (e.g Glide)
view1.refreshWith(it)
})
}
private fun example2_2() {
val view2 = viewBinding.view2
viewModel.lv2.observe(viewLifecycleOwner, Observer {
// Some complex-ish UI logic that might need
// to retrieve supplement data from somewhere (e.g Glide)
view2.refreshWith(it)
})
}
}
Upvotes: 1
Views: 7777
Reputation: 1128
In my views first approach is far better. because
1) We have single origin of data, single observer, all the data is channelising through one, so there won't be any posdibility of any human error, like in case of multiple observer, people can write similar logics in more than one observer.
2) As you mentioned yourself, overhead of extra oberservers will not be there.
3) The benefit of multiple observer you told, Suppose You are observing data of an API response, you hit API again, you will get all the data, you will have to pass the data back and will habe to repopulate the data to all the views, so even of data for some of the views have not changed, still they will be populated, So before populating, either you should compare newly coming data with the previously loaded data, so you can do it in case of single observer also.
I may be wrong in my observation, correct me if i am wrong.
Upvotes: 0
Reputation: 2859
1) If your multiple views need different parts of LiveData data to use Transformations to map one LiveData to multiple Sub LiveDatas. e.g. I need username from User in one View and I need age from User in another View.
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
user.name + " " + user.lastName
});
2) But if your Views act based on different status, i.e. SUCCESS - FAILED - LOADING Use a Single LiveData with a MyResponse data class.
data class MyResponse<T>(
var status: Status,
var data: T? = null,
var throwable: Throwable? = null
) {
companion object {
fun <T> loading(data: T?=null): MyResponse<T> {
return MyResponse(
status = Status.LOADING,
data = data
)
}
fun <T> success(data: T): MyResponse<T>? {
return MyResponse(
status = Status.SUCCESS,
data = data
)
}
fun <T> failed(throwable: Throwable): MyResponse<T> {
return MyResponse(
status = Status.FAILED,
throwable = throwable
)
}
}
}
enum class Status {
LOADING,
SUCCESS,
FAILED,
}
And In your ViewModel
val mSettingHeaderLiveData = MutableLiveData<MyResponse<UserInfo>>()
Sample Usage
mSettingMainViewModel.mSettingHeaderLiveData.observe(this, Observer { userInfoResponse ->
if (userInfoResponse.status == Status.SUCCESS) {
// Do something on View_1
} else if (userInfoResponse.status == Status.Failed) {
// Do something on View_2
} else if (userInfoResponse.status == Status.Loading) {
// Do something on View_3
}
})
Upvotes: 4