Do interfaces belong in Android projects?

Recently there’s been a bit of buzz around over-engineering in Android projects. Unfortunately, one person’s mere engineering is another’s over engineering. And vice versa. It’s like we’re trying to strike a median between Fizz Buzz Enterprise Edition and Big Ball of Mud.

For my own side, I want to present an argument for one particular language feature in Kotlin that some consider over-engineering, but I love to see used correctly in Android projects. Think of it as a BuzzFeed article for the enterprise-scale Android engineer: Seven Reasons To Love Interfaces.

1. You’re already using them implicitly

In our small tests (unit tests), we examine the dependencies for a system under test like a ViewModel . Even though these class dependencies may not be open , we mock them and stub a behavior for replay in the test. For example, we cause the mock to always return a certain value:

A final class that we mock and provide alternative behavior for in a test

Whereto this mock we use in our tests. It’s essentially a hollowed-out shell of our class. We end up with just a bare contract to which we specify a behavior. Sounds a bit like an interface, right?

2. Even if you aren’t using them explicitly, you should be

Overruse of mocks can lead to bad tests. But mocks are only one of many test doubles. Fakes (lightweight implementations) are a better choice in many cases. Below is a fake from the Android Architecture Blueprints. Note the canned data served in the getTasks method and a settable flag to cause the repository to serve errors:

The FakeRepository used in the tests for TasksViewModel

Of course, fakes are best done with interfaces.

Your ViewModel or Presenter depends on a repository behind an interface:

Signature for TasksViewModel showing the dependency on the interface

Your DI module will bind the repository interface to the real implementation class:

Binding the interface to the default implementation using Dagger 2

In a test, when you manually instantiate the object graph, you make your fake implementation a dependency for the system under test:

Manually instantiating the object graph in a test

3. Delegation works on interfaces, not classes

As good practitioners of object-oriented programming, “composition over inheritance” should be our mantra. Even more so in Kotlin where we get composition-friendly delegation out-of-the-box with the by keyword. By putting dependencies for a ViewModel behind an interface, we open them up to use of the decorator pattern using delegation.

Let’s say we want to add some more functionality to our repository. Of course, we could just ram the new features inside our closed repository class and add more constructor parameters. Even worse, we could make the class open and subclass needlessly, leading to a fragile base class.

Alternatively, we could use the decorator pattern to add functionality like analytics or broadcasting of events:

The by keyword makes it easy since we only have to worry about the functions that need explicit overrides. Everything else will be automatically forwarded to the delegate. Of course, delegating only makes sense with interfaces:

A playground showing delegation using decorators. Click to expand the whole code fragment.

4. Extract interface is harder later

You could argue that we can always use the extract interface refactor at a later stage when we are confident the interface is needed. But doesn’t this violate the open/closed principle? Software components should be open for extension without having to modify them, and extract interface is a large refactor that can make for a noisy diff.

If we leave extracting the interface until later, haven’t we put a large burden on the maintenance developer? And, unless they have “composition over inheritance” tattooed on their arm, won’t they be more likely to just add open to the class declaration and use sub-classing to provide an alternate implementation?

5. Interfaces work with visibility modifiers

Remember that in Kotlin we don’t have a package-private modifier. The internal modifier restricts access to within the same module. A public interface with internal concrete classes is a way to share an abstraction between modules without the implementation details of one leaking into another.

Module 1’s Worker knows about public interface Helper but can’t see the internal DefaultHelper

6. Names can be idiomatic

Part of the hatred of interfaces seems to stem from bad names like IRepository that remind us of spaghetti monoliths in .NET. Or from enterprise Java development-style names like RepositoryImpl .

Yoda contemplating a 2000-line Fragment in an Android app. Picture by Daniel Huntley. CC BY-SA

As Yoda said, bad names lead to fear of interfaces. Fear of interfaces leads to under-engineering. Under-engineering leads to suffering.

But it ain’t necessarily so, DefaultRepository or RealRepository are perfectly idiomatic names for the concretions, leaving Repository as the name of the abstraction.

7. Shortcut keys rock!

Interfaces can make the code harder to navigate. Lucky we have awesome shortcut keys to help out:

Go to implementation is your friend here:

For Mac, ⌥⌘B