Android ViewModel — Should we delegate out presentation logic from ViewModel?

Jossy Paul
3 min readAug 24, 2021

In most cases(or at least during the initial phase of development), a ViewModel will normally be developed keeping in mind a single view. As application grows larger, there can be instances where the same presentation logic needs to implemented in different views. We would have a tendency to use the same ViewModel in that view as well. If we need to add another functionality to the new view, we would add that to the old ViewModel. And after several months of development you got yourself a giant ViewModel which holds half the application’s presentation logic.

From what I can see, we do have three choices to prevent this from happening. The obvious choice is have separate ViewModels for each view. But what about our code reusability? If there is a need for same presentation logic in different views, the code will be duplicated in several ViewModels.

Other choice is to have really small ViewModels that can be reused in all the views. But then we would need to initialise all these ViewModels in the view making our view bulky. And another issue with this is the case where we are running two api calls in parallel and we want our ProgressBar to be shown as long as either one of them is still loading. These scenarios will be difficult to handle, if we have these two api calls running in separate ViewModels. There is a chance that some of the presentation logic will leak out to the view.

And what about inheritance? Okay, let’s see whether inheritance can help us out here. We are creating a note taking application. In the first view we have only one functionality: Add notes. Simple, we can create our AddNotesViewModel. Now in the second view we can Add and fetch the notes. Thats also simple, we can inherit from AddNotesViewModel and create FetchAndAddViewModel. But the next requirement is to have a page where user can Fetch and Delete notes. We can inherit from FetchAndAddViewModel. But the issue is that we don’t need Add functionality. There is a very famous analogy about this.

You asked for a banana, but you got a gorilla holding the banana.

We can create another ViewModel from scratch. But here we would be duplicating our code. As you can see, inheritance can get more complex with each level.

Solution

I think kotlin delegation is a great option in these types of scenarios. This allows us to use the principle “Composition over Inheritance”. The idea here is to create small units of code that is reusable. Whenever we need that functionality, we will just include it to our viewModel using delegates.

In case what we want in our viewModel is the functionality to add note, we will create a reusable component which has a single responsibility to add notes. Something like this:

interface AddNotesDelegate {
var random: LiveData<String>
fun addNote()
}

class AddNotesDelegateImpl @Inject constructor(private val repository: NotesRepository) :
AddNotesDelegate {
private var _random: MutableLiveData<String> = MutableLiveData("")
override var random: LiveData<String> = Transformations.map(_random) {
"Note added with ID $it"
}

override fun addNote() {
_random.value = repository.addNote().toString()
}

}

Now in our viewmodel we can just use this AddNotesDelegate.

@HiltViewModel
class MainViewModel @Inject constructor(private val addNotesDelegateImpl: AddNotesDelegateImpl) : ViewModel(), AddNotesDelegate by addNotesDelegateImpl

Now the viewmodel becomes a single line code. It has become really concise. All the logic has been delegated out. If we want Edit Notes functionality, we will delegate out that as well. Now our viewmodel will contain only the functionalities that is required by the view.

Conclusion

I am not suggesting to delegate out each functionalities inside the viewmodel. But if there are some presentation logic that can be reused in other views, this is a great solution. An important thing to remember while using kotlin delegates in our viewModel is that if we are working with coroutine inside the delegate, we might have to manually pass the viewModelScope to that delegate. If we are using liveData in view layer, then we can use the liveData builder provided by ktx and from there coroutine can be launched.

--

--