ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Interface Singleton in Kotlin

Martin Devillers
ProAndroidDev
Published in
3 min readAug 28, 2019

--

Photo by Vorsen Furniture on Unsplash

The Kotlin object declaration is a convenient feature, used to create a singleton instance while avoiding boilerplate. However, it’s also a very strict feature, in the sense that an object is necessarily final. This means that it’s impossible to extend from it.

This can become a problem when refactoring your code. As a general rule, accessing a singleton instance directly goes against the principle of dependency injection, therefore it’s likely to become a problem. But an object presents an even bigger constraint, because it must be accessed as a singleton.

Say, for example, that you wrote the following Server object, because you wanted to have a singleton holder for your HTTP client.

object Server {    private val client = OkHttpClient()    fun execute(request: Request): Response =
client.newCall(request).execute()
}

This way, you could simply refer to the unique Server instance when executing HTTP requests.

private val url = 
"https://en.wikipedia.org/api/rest_v1/page/summary/"
fun getWikipediaArticle(page: String): Response {
val request = Request.Builder()
.url("$url$page")
.header("Accept", "application/json")
.build()
return Server.execute(request)
}

This is convenient. But at some point, your might want to add tests for this code. You then need to rewrite it, with an injected implementation of the Server interface.

interface Server {
fun execute(request: Request): Response
}
class WikipediaArticleRepository(val server: Server) {

private val url =
"https://en.wikipedia.org/api/rest_v1/page/summary/"

fun getWikipediaArticle(page: String): Response {
val request = Request.Builder()
.url("$url$page")
.header("Accept", "application/json")
.build()
return server.execute(request)
}
}

This is better, but now what do you do with all the rest of the code which was calling the singleton Server directly? The task of refactoring everything to use dependency injection at once can be daunting.

Using a trick with the companion object of the Server interface, it’s possible to make this change in a way that’s source-compatible.

interface Server {

fun execute(request: Request): Response

companion object : Server {
private val client = OkHttpClient()

override fun execute(request: Request): Response =
client.newCall(request).execute()
}
}

This way, the code in the first example continues to compile and run, without making any changes!

By creating a companion object for an interface, which implements that very interface, it’s possible to refer to both the interface and its singleton implementation using the same name.

All the code that was previously using the singleton remains compatible. It’s possible to call the methods on the interface, or on the object.

Using this type of trick is very useful during refactoring. It helps migrate progressively to better patterns, without being blocked by the existing anti-patterns.

The question that remains is whether using this interface-object hybrid would be acceptable as an initial design decision. The benefit would be to keep the possibility of using the singleton instance directly, in cases where mocking is irrelevant or when the code is used for quick prototyping. The downside is that it leaves the door open to a poor usage of this singleton, leading to legacy code. This takes us back to the age-old question of whether developers should be forced into standard patterns or left with the responsibility of making their own decisions.

--

--

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Written by Martin Devillers

Fullstack developer at Kiro, formerly Android

Responses (2)

Write a response