Default implementations for interfaces

Maria Neumayer
A problem like Maria
3 min readSep 25, 2017

It all started as a crazy idea… during our Kotlin hour at Deliveroo we were talking about use cases for default implementations for interfaces. I threw in (jokingly): we could have a default implementation for all our methods in our base View interface. I quickly tried it out and… it worked.

At first we thought: that’s crazy, right? We can’t actually do that, right? But still… there was something in there. The View interface needs to be implement in various classes — Activity, Fragment, DialogFragment etc. This gets tedious. Adding a new method means duplicating the code in all those classes. Even if it’s just calling another class which handles the actual implementation to avoid even more duplication.

But let’s backtrack a bit…

What’s a default implementation?

Kotlin interfaces are very similar to interfaces in Java 8. Sadly as an Android developer Java 8 features are only (partially) supported since the latest Android Studio 3.0 Preview.

Before Java 8 interfaces could only contain abstract methods. In Java 8 (and in Kotlin) they can also contain method implementations and — in Kotlin — properties. Interfaces cannot keep any state and properties need to either be abstract or provide accessor implementations, so they can be overridden.

Let’s see some code

So let’s take this simple interface:

interface View {
fun showToast(text: String)
}

Now for this interface we can create a default implementation:

interface DefaultView : View {
val context: Context

override fun showToast(text: String) {
Toast.makeText(context, text, Toast.LENGTH_SHORT).show()
}
}

That’s all! You might’ve noticed that I have created a separate interface DefaultView. The View could also contain the implementation, but it makes it harder to test. All other View interfaces can extend View, but any class implementing it can implement DefaultView directly, like this:

abstract class BaseActivity : Activity(), DefaultView {
override val context: Context
get() = this
}

The actual implementation is hidden in the interface — all we need to implement here is the getter for the Context. Now this can also be an advantage — it’s not obvious where the implementation for View is. In Android Studio that’s not a big problem — clicking the arrow next to the abstract method will show you all the implementations — which should now only be DefaultView.

How does this work?

Let’s have a look at the decompiled bytecode for this.

public interface DefaultView extends View {
@NotNull
Context getContext();

void showToast(@NotNull String var1);

public static final class DefaultImpls {
public static void showToast(@NotNull DefaultView $this, String text) {
Intrinsics.checkParameterIsNotNull(text, "text");
Toast.makeText($this.getContext(), (CharSequence)text, 0).show();
}
}
}

This is what happens for the DefaultView. There’s a new static class DefaultImpls which contains a static showToast method. The DefaultView instance gets passed through. This way we can access other functions, or properties from the interface.

What about the Activity?

public abstract class BaseActivity extends Activity implements DefaultView {

@NotNull
public Context getContext() {
return (Context)this;
}

public void showToast(@NotNull String text) {
Intrinsics.checkParameterIsNotNull(text, "text");
DefaultView.DefaultImpls.showToast(this, text);
}
}

The Activity implements all methods from the interface and calls the static method on DefaultImpls seen above. So that’s it! This basically is what I’d implement in the past — have a helper class that handles the actual logic, implement all methods of the interface and call the helper class. But now we don’t have to write all this code anymore.

There’s a couple of gotchas:

  • Your BaseActivity needs to be written in Kotlin, otherwise any Java class extending it will need the DefaultView methods implemented manually.
  • You need to be careful with the naming of the properties. In the example above the context property will clash with the getContext() method in the Fragment.

A default implementation probably shouldn’t be the default option. But if an interface needs to be implemented multiple times with exactly the same code it can be a good option to avoid code complication — and mistakes.

--

--

Maria Neumayer
A problem like Maria

European living in London. Principal Engineer at @SkyscannerEng. Tweeting at @marianeum.