Companion objects

You may not want to rely on top-level functions and properties in cases where you would ideally like to scope, or associate, a function or value with a particularl class. This might be the case for things such as factory methods or scoped constants. For these situations, we can rely on companion objects to achieve that type of relationship.

Let's say that we want to provide constants to represent the result state of a page. We could achieve this using companion objects, like so:

class AuthScreen {
companion object {
const val RESULT_AUTHENTICATED = 0
const val RESULT_ERROR = 1
}
}

By defining the constants within the companion object, we avoid polluting the global namespace with top-level properties. We will then be able to access the values, in the same way as we would when working with static fields in Java:

val result = AuthScreen.RESULT_AUTHENTICATED

In this case, working with the properties is very similar in Java as well. In the following snippet, we can see that from Java, we can access and store the property in the same way:

int result = AuthScreen.RESULT_AUTHENTICATED;

In Chapter 8, Controlling the Story, we'll examine some additional edge cases to be aware of when working with companion object properties.

As mentioned earlier, we can also define methods within our companion objects to replicate the functionality of static methods in Java. This may be useful when trying to avoid polluting the global namespace with a method specific to another class.

In this example, we've defined a factory method that allows us to instantiate AuthScreen, even though it has a private constructor:

class AuthScreen private constructor(){
companion object {
const val RESULT_AUTHENTICATED = 0
const val RESULT_ERROR = 1

fun create(): AuthScreen {
return create()
}
}
}

By adding this method to the companion object, we have to reference the enclosing class name directly before invoking the function that allows us to follow the same naming conventions for other classes without conflict:

val authScreen = AuthScreen() // wont compile
val screen = AuthScreen.create()

This syntax doesn't work quite as well from Java, however. The following snippet demonstrates the more verbose syntax required to access the method from Java:

AuthScreen authScreen = AuthScreen.Companion.create();

You'll notice that, in Java, to call the method on the companion object, you must reference the companion object directly. In Chapter 8, Controlling the Story, we'll explore how you can improve this.

In the next section, we will see why two languages exist and how they help us while writing code.