Composition Over Inheritance in Android ViewModels
Introduction
In almost every codebase, there’s a point where two components need to share some functionality or behavior, and someone has to decide how. In object-oriented systems, there are two main techniques for code reuse: class inheritance and object composition.
Inheritance is the one most people reach for first. Maybe because it’s the shortest path (in Kotlin, that’s a colon and one base class and you’re done), or because it’s the concept most of us learn first, or maybe for other reasons.
In 1994, in their book Design Patterns: Elements of Reusable Object-Oriented Software, the Gang of Four stated the second principle¹ of object-oriented design:
“Favor object composition over class inheritance.”
Now, I want to say up front that this article won’t argue that inheritance is bad or evil. That case has already been made many times.
I take a more moderate stance. As the Gang of Four put it, “inheritance and composition each have their advantages and disadvantages”. Evaluating those trade-offs and choosing what fits the problem at hand is precisely our job as software engineers.
So the question is: what if we could use composition as if we were inheriting?
And the answer to that is composition with delegation.
Quoting the Gang of Four again:
“Delegation is a way of making composition as powerful for reuse as inheritance.”
In the example that follows, we’ll look at how composition and delegation make Android ViewModels more flexible and simpler, and how much Kotlin helps us here.
The Example
A ViewModel in Android is, quoting the official docs:
The
ViewModelclass is a business logic or screen level state holder. It exposes state to the UI and encapsulates related business logic.
Sometimes the same piece of state or logic is needed on more than one screen. When that happens, you have to decide where it lives.
Take the following three screens:
This example, like most examples, is too small to be realistic, but hopefully enough to make the point.
Each screen is built from sections, and sections repeat across screens. Hexagon appears on all three. Heart appears on screens 1 and 2. Star appears on screens 2 and 3. Circle appears only on screen 1.
On top of that, screens 2 and 3 send analytics events, and screen 1 doesn’t.
Each screen gets one ViewModel, and each one needs a different combination of the same handful of responsibilities/sections.
Segregating Responsibilities Into Interfaces
We start by giving each responsibility its own interface:
interface AnalyticsSender {
fun sendAnalyticsEvent(event: AnalyticsEvent)
}
interface CircleSectionStateHolder {
val circleSectionState: StateFlow<CircleSectionState>
fun circleAction()
}
interface HeartSectionStateHolder : AutoCloseable {
val heartSectionState: StateFlow<HeartSectionState>
fun heartAction()
}
// Hexagon and Star follow the same shapeSome holders own things that need releasing, like a coroutine scope, a subscription, or a listener. Those extend AutoCloseable, like HeartSectionStateHolder above. ViewModel accepts closeables in its constructor (Closeable from lifecycle v2.5, AutoCloseable from v2.8) and closes them when it’s cleared, so the ViewModel only has to pass them up.
Composition and Delegation
Each screen’s ViewModel implements only the interfaces that screen actually needs. Screen 1 shows Heart, Hexagon and Circle, so it implements those three.
Get Omar Sahl’s stories in your inbox
Join Medium for free to get updates from this writer.
At the same time, it is composed of them: it receives an instance of each implementation through its constructor. Those instances are its delegates.
So the ViewModel is a CircleSectionStateHolder and has a CircleSectionStateHolder. Implementing the interface is what lets the UI treat the ViewModel as the holder, and holding the object is what gives it something to forward to.
class Screen1ViewModel(
heartSectionStateHolder: HeartSectionStateHolder,
hexagonSectionStateHolder: HexagonSectionStateHolder,
circleSectionStateHolder: CircleSectionStateHolder,
) : ViewModel(heartSectionStateHolder),
HeartSectionStateHolder,
HexagonSectionStateHolder,
CircleSectionStateHolder {
override val heartSectionState: StateFlow<HeartSectionState>
get() = TODO("Not yet implemented")
override val hexagonSectionState: StateFlow<HexagonSectionState>
get() = TODO("Not yet implemented")
override val circleSectionState: StateFlow<CircleSectionState>
get() = TODO("Not yet implemented")
override fun heartAction() {
TODO("Not yet implemented")
}
override fun close() {
TODO("Not yet implemented")
}
// The remaining actions...
}To conform to the contracts, the ViewModel forwards every call to the matching delegate:
class Screen1ViewModel(
private val heartSectionStateHolder: HeartSectionStateHolder,
private val hexagonSectionStateHolder: HexagonSectionStateHolder,
private val circleSectionStateHolder: CircleSectionStateHolder,
) : ViewModel(heartSectionStateHolder),
HeartSectionStateHolder,
HexagonSectionStateHolder,
CircleSectionStateHolder {
override val heartSectionState: StateFlow<HeartSectionState>
get() = heartSectionStateHolder.heartSectionState
override val hexagonSectionState: StateFlow<HexagonSectionState>
get() = hexagonSectionStateHolder.hexagonSectionState
override val circleSectionState: StateFlow<CircleSectionState>
get() = circleSectionStateHolder.circleSectionState
override fun heartAction() = heartSectionStateHolder.heartAction()
// The remaining actions...
override fun close() = heartSectionStateHolder.close()
}That works, but it’s a lot of boilerplate for forwarding those calls to the delegates.
That’s where Kotlin comes to the rescue. Instead of manually forwarding each call, we can use Kotlin’s by keyword and let the compiler do it for us:
class Screen1ViewModel(
heartSectionStateHolder: HeartSectionStateHolder,
hexagonSectionStateHolder: HexagonSectionStateHolder,
circleSectionStateHolder: CircleSectionStateHolder,
) : ViewModel(heartSectionStateHolder),
HeartSectionStateHolder by heartSectionStateHolder,
HexagonSectionStateHolder by hexagonSectionStateHolder,
CircleSectionStateHolder by circleSectionStateHolderAll of the boilerplate is gone.
And nothing was given up to get rid of it: Screen1ViewModel is still a real CircleSectionStateHolder. The UI collects circleSectionState and calls circleAction() on the ViewModel exactly as it would have if that code had been inherited.
Screens 2 and 3 are the same idea with a different list:
class Screen2ViewModel(
heartSectionStateHolder: HeartSectionStateHolder,
hexagonSectionStateHolder: HexagonSectionStateHolder,
starSectionStateHolder: StarSectionStateHolder,
analyticsSender: AnalyticsSender,
) : ViewModel(heartSectionStateHolder),
HeartSectionStateHolder by heartSectionStateHolder,
HexagonSectionStateHolder by hexagonSectionStateHolder,
StarSectionStateHolder by starSectionStateHolder,
AnalyticsSender by analyticsSenderAnd Screen 3:
class Screen3ViewModel(
hexagonSectionStateHolder: HexagonSectionStateHolder,
starSectionStateHolder: StarSectionStateHolder,
analyticsSender: AnalyticsSender,
) : ViewModel(),
HexagonSectionStateHolder by hexagonSectionStateHolder,
StarSectionStateHolder by starSectionStateHolder,
AnalyticsSender by analyticsSenderOne thing to watch out for: AutoCloseable’s only member is close(), and delegation hands every member of an interface to the ViewModel. So if two holders on the same screen extend AutoCloseable, the ViewModel inherits two close() implementations and the compiler will make you override it explicitly.
In that case, you can override close() and leave it empty. As long as you pass the AutoCloseables to the ViewModel constructor, the ViewModel will close each of them automatically when it is cleared.
And that’s it. Reading a ViewModel’s declaration now tells you exactly what its screen does, and nothing is shared that wasn’t asked for.
Inheritance
Trying the same thing with class inheritance ends in one of a few scenarios, all of which either don’t compile at all or have serious design problems.
The first is a dead end, because a class can’t extend more than one class:
abstract class HeartViewModel : ViewModel() { /* heart state + action */ }
abstract class StarViewModel : ViewModel() { /* star state + action */ }
// We can't do that in Kotlin
class Screen2ViewModel : HeartViewModel(), StarViewModel()The second is a huge base class with all of the functionality crammed into it, where every subclass inherits everything regardless of whether it needs it or not. A ViewModel carrying the logic for a Heart section even though it doesn’t show one.
The third is a deep class hierarchy, where each level adds a little more and changing a base class can break every subclass under it.
The fourth is a single base class filled with conditionals like open val analyticsEnabled: Boolean = false, where subclasses override them to switch off the functionality they don’t need.
Wrapping Up
Finally, none of this deletes inheritance. Every one of these ViewModels still extends the ViewModel class, and each state holder is free to have a base class of its own, which resonates with what the Gang of Four wrote:
“Inheritance and object composition thus work together.”
Thanks for reading! I hope you found this helpful. If you have any questions or suggestions, let me know in the comments below.
¹ The first being: “Program to an interface, not an implementation.”











