This is the first blog post exploring some implementation details from Norris, an Android project I’ve shared on Github a couple days ago. Norris not only showcases a powerful unidirectional data flow architecture powered by Kotlin’s Coroutines but also has several hidden gems like the one I chose to start this series. I hope you enjoy it!

Introduction

In my experience, developers moving from Java to Kotlin usually find Companion’s Objects “weird” at the very fist glimpse (to be polite); in fact, I myself questioned the “overhead” to declare simple static constants using “these Companions” at my first days of Kotlin.

I don’t pretend to repeat the oficial documentation on why Kotlin has Companions; instead, I pretend to demonstrate why Kotlin’s Companion Objects are awesome.

The Fake Constructor

At Norris, the unidirectional data flow implementation is driven by the idea of State Machines. We’ll learn more about this implementation in a upcoming blog post, but the naive understanding you should have here is : a (finite) state machine is a computational model that expresse the capability of some system (or subsystem) to move from one state to another, given a well-defined event (or transition, because names are hard) :

You may check the State Machine implementation in Norris if you want to; but for now, I’m going with my StateTransition abstraction as my first example :


sealed class StateTransition<out T> {

    interface Parameters

    class Unparametrized<T> internal constructor(
        val task: suspend () -> T
    ) : StateTransition<T>()

    class Parametrized<T> internal constructor(
        val task: suspend (Parameters) -> T,
        val parameters: Parameters
    ) : StateTransition<T>()

    companion object {

        operator fun <T> invoke(task: suspend () -> T) = Unparametrized(task)

        operator fun <T> invoke(
            task: suspend (Parameters) -> T,
            data: Parameters
        ) = Parametrized(task, data)
    }
}

We’ll learn more about the design of this abstraction as well, but for now one could ask : “why do we have a companion object like this”?

The straightforward answer here is : I’m using the Companion of StateTransition to host the factory methods for the two (and only two) inheritors.

In addition to that, I’m not only hidding the constructor for such derivatives of StateTransition : once the factory method is defined as an overloaded implementation of the invoke function, the Companion Object (which has no name here) can also be used as a function.

This means that one can grab a new instance for the StateTransition.Unparametrized class using this


val transition = StateTransition(::yourSuspendFunction)

instead of this (the regular public constructor usage)


val transition = StateTransition.Unparametrized(::yourSuspendFunction)

I like to call this technique by “the fake constructor”, once the final user of StateTransition believes that he/she is actually using the constructor signature of the abstraction, while he/she is actually using the factory method.

Quite cool, right? 😄

There are usages two more usages such Fake Constructor technique that I use very often - not only at Norris - I would like to enlight as well.

Fake constructors when real ones are not an option

If you are an Android developer probably you know the pain : the classic errored usage


val fragment = MyCustomFragment("The custom parameter I wanted to pass")

that is induced by the not-so-nice design of the Fragment abstraction.

How many newcomers in the Android development world were actually bitten by this “mistake”, specially in the early days of the Fragment’s APIs?

The workaround here is in the documentation as you can check : when you need the pass parameters to your Fragment, you need to use the setArguments() API, once the constructor will be called according to the Fragment’s lifecycle and not only at the Fragment’s transaction execution level :


class DetailsFragment : Fragment() {

    val shownIndex: Int by lazy {
        arguments?.getInt("index", 0) ?: 0
    }

    // Stuff

    companion object {
        fun newInstance(index: Int): DetailsFragment {
            val f = DetailsFragment()
            val args = Bundle()
            args.putInt("index", index)
            f.arguments = args
            return f
        }
    }
}

The example above is borrowed from the official Android docs; using it, you can grab an instance of DetailFragment using the factory method


val fragment = DetailsFragment.newInstance(42)

However, one could ask : how about a Fake Constructor for such Fragment?


class DetailsFragment : Fragment() {

    // Stuff

    companion object {
        operator fun invoke(index: Int): DetailsFragment {
            val f = DetailsFragment()
            val args = Bundle()
            args.putInt("index", index)
            f.arguments = args
            return f
        }
    }
}

By using such technique, you make harder to a newcomer to misuse DetailsFragment while still preserving the natural constructor signature!


val fragment = DetailsFragment(42)

Final remarks

In this post, we learned a bit more about non conventional usages for Kotlin Companion Objects, specially by showing some practical usages of the Fake Constructor technique; more fancy usages are out there!

In the next blog post, I’ll dig a bit more inside the Norris architecture, and we will learn how I managed to build an Android application 100% powered by Coroutines, ie, just suspend functions and Flow API.

Stay tuned!

Thanks to Filip Babić and Ash Davies for proof reading this article