I would like to make my code more readable by simplifying the observers of the MutableLiveData objects. But I'm not really sure how to do that. I thought about moving the contents of each observe method into a separate class, but that would only improve the readability of the fragment and the new class would be confusing again.
private fun HomeViewModel.setupObservers() {
messages.observe(viewLifecycleOwner) { response ->
when (response) {
is Resource.Success -> {
response.data?.let { messagesResponse ->
getMessagesWithImageUrl(chatID, messagesResponse)
}
}
is Resource.Error -> {
response.message?.let { message ->
Log.e(TAG, "An error occurred: $message")
}
hideProgressBarLoadMessages()
hideProgressBarSendMessage()
}
is Resource.Loading -> {}
}
}
messagesWithImageUrl.observe(viewLifecycleOwner) {response ->
when(response) {
is Resource.Error -> {
response.message?.let { message ->
Log.e(TAG, "An error occurred: $message")
}
hideProgressBarLoadMessages()
hideProgressBarSendMessage()
}
is Resource.Loading -> {}
is Resource.Success -> {
response.data?.let { messagesResponse ->
adapterMessage
.differ
.submitList(messagesResponse.thread.values.toList())
binding.recyclerViewMessages
.scrollToPosition(adapterMessage.itemCount-1)
hideProgressBarLoadMessages()
hideProgressBarSendMessage()
}
}
}
}
messageMediaUpload.observe(viewLifecycleOwner) { response ->
when(response) {
is Resource.Error -> {
response.message?.let { message ->
Log.e(TAG, "An error occurred: $message")
}
}
is Resource.Loading -> {
showProgressBarSendMessage()
}
is Resource.Success -> {
response.data?.let {
it.metadata?.let { metadata ->
metadata.name?.let { imageName ->
val content = imageName.dropLast(4)
viewModel.sendMessage(
POSTMessage(content, media = true),
POSTLastMessage(content, media = true, msgRead = true),
chatID,
receiverID)
}
}
}
}
}
}
username.observe(viewLifecycleOwner) { response ->
when(response) {
is Resource.Error -> {
response.message?.let { message ->
Log.e(TAG, "An error occurred: $message")
}
}
is Resource.Loading -> { /* TODO: some loading animation */ }
is Resource.Success -> {
response.data?.let { username ->
binding.headlineText.text = username
}
}
}
}
sendMessage.observe(viewLifecycleOwner) { response ->
when(response) {
is Resource.Error -> {
response.message?.let { message ->
Log.e(TAG, "An error occurred: $message")
}
hideProgressBarSendMessage()
}
is Resource.Loading -> {
showProgressBarSendMessage()
}
is Resource.Success -> {}
}
}
setMessageRead.observe(viewLifecycleOwner) { response ->
when(response) {
is Resource.Error -> {
response.message?.let { message ->
Log.e(TAG, "An error occurred: $message")
}
}
is Resource.Loading -> {}
is Resource.Success -> {}
}
}
}
My Resource class looks like this:
sealed class Resource<T>(
val data: T? = null,
val message: String? = null) {
class Success<T>(data: T) : Resource<T>(data)
class Error<T>(message: String, data: T? = null) : Resource<T>(data, message)
class Loading<T> : Resource<T>()
}
If I changed it like this:
sealed interface Resource<T> {
class Success<T>(val data: T) : Resource<T>
class Error<T>(val message: String) : Resource<T>
class Loading<T> : Resource<T>
}
Then I have to modify methods in my ViewModel as follows:
//old Resource
fun getChats() = viewModelScope.launch {
chats.postValue(Resource.Loading())
homeRepository.getChats().collect() {
it.data?.let { getChats ->
setChatIDs(getChats)
getUnreadMessages(getChats)
}
chats.postValue(it)
}
}
//new Resource
private fun getChats() = viewModelScope.launch {
chats.postValue(Resource.Loading())
homeRepository.getChats().collect() {
if(it is Resource.Success) {
val data = (it as Resource.Success).data
setChatIDs(data)
getUnreadMessages(data)
}
chats.postValue(it)
}
}
That would make my Oberver methods more readable, but I'm not sure if that would be bad practise in the ViewModel.
You can't really break these out into classes cleanly because it's working with Fragment internals.
But, from looking at your code and seeing all those
?.lets, I think you need a better sealed class/interface implementation.I'm guessing your sealed class looks something like this:
when it should really look like:
This way, you don't have a useless
messageproperty in your Success class or a uselessdataproperty in your Error class. But more importantly,messageanddataare not nullable because they don't need to be--they are only used in the classes where they are relevant.Then you won't need to use all those
?.letcalls, which are making your code really ugly.Other than that, you could define:
and use that to avoid the code duplication.
And in your
whenbranches, when there is only a single function call, you can change it to a one-liner with no brackets{ }surrounding it. For example: