Improving findViewById with Kotlin

If you’re an Android developer chances are that you’re quite familiar with this code:

public class MyActivity extends AppCompatActivity {
    private Button button;
    @Override
protected void onCreate(Bundle savedInstanceState) {
...
button = (Button) findViewById(R.id.button);
}
}

Using findViewById is one of the most common operation in an Android developer everyday life and let’s be honest about it: it’s quite boring and repetitive to do it over and over again.

Although there are several alternatives like ButterKnife to improve this experience, here’s a small trick to do it manually using Kotlin (besides the Kotlin Android Extensions plugin, which is pretty great too).

So how does findViewById look like in Kotlin? You might find something like this if you try to merely convert from Java to Kotlin in Android Studio:

class MyActivity : AppCompatActivity() {
    private var button: Button? = null
    override fun onCreate(savedInstanceState: Bundle?) {
...
button = findViewById(R.id.button) as Button
}
}

You might think that this is not any better than what we had in Java and I’d agree with you. But we’re just getting started: we defined our Button as nullable, but what we really want is lazy initialisation, isn’t it?

class MyActivity : AppCompatActivity() {
    private lateinit var button: Button
    override fun onCreate(savedInstanceState: Bundle?) {
...
button = findViewById(R.id.button) as Button
}
}

We can achieve this result using the keyword lateinit, which allows us to define a not null var that is going to be initialised lazily.

Even if this is slightly better, it still doesn’t fix all the annoying part of findViewById: our button is defined as a var but it’s not really a mutable property. And findViewById returns a View we need to explicitly cast to the correct type.

This is where Kotlin really starts to shine: we can use extension functions to add a behaviour to our Activity and make our life easier:

fun <T : View> Activity.bind(@IdRes res : Int) : T {
@Suppress("UNCHECKED_CAST")
return findViewById(res) as T
}

The bind function takes an Int id as a parameter, calls findViewById for you and finally perform the cast for you.

We are also suppressing the warning since casting a view is not a safe operation and if it crashes it’s probably because we are casting it to the wrong type.

Now we can improve our initial code further:

class MyActivity : AppCompatActivity() {
    private lateinit var button: Button
    override fun onCreate(savedInstanceState: Bundle?) {
...
button = bind(R.id.button)
}
}

We are getting ready for the best: as we said before, our Button shouldn’t be mutable, because once its value is assigned it won’t change. We can’t use lateinit with a val property, but there’s a delegate called lazy which allows us to provide a lambda for lazily initialise a property. So what we are going to do now is combining lazy with our bind function.

fun <T : View> Activity.bind(@IdRes res : Int) : Lazy<T> {
@Suppress("UNCHECKED_CAST")
return lazy { findViewById(res) as T }
}

Pretty simple right? The bind return type is now Lazy<T>, which means it won’t be initialised right away but the first time the value is actually needed, and our findViewById is now included in a lazy function.

The consequence of doing this is that we don’t need to call the function from the onCreate callback now:

class MyActivity : AppCompatActivity() {
    private val button: Button by bind(R.id.button)
}

Or:

class MyActivity : AppCompatActivity() {
    private val button by bind<Button>(R.id.button)
}

Both of those compile and work fine: we are using the by keyword since we are using a delegate, which adds a special behaviour to a property. We could create our custom delegate from scratch, but in this case all we need is lazy initialisation and Kotlin already has a delegate for that.

Do you think this is good enough? Well not yet: there’s a final trick to make the performance of our bind function better: lazy is actually thread safe by default to avoid that the lambda gets computed more than once. We don’t need that in our scenario, since the bind function will be called only by the main thread. So, we can modify it:

fun <T : View> Activity.bind(@IdRes res : Int) : Lazy<T> {
@Suppress("UNCHECKED_CAST")
return lazy(LazyThreadSafetyMode.NONE){ findViewById(res) as T }
}

Of course, you can easily modify the bind function to work with custom views too:

fun <T : View> View.bind(@IdRes res : Int) : Lazy<T> {
@Suppress("UNCHECKED_CAST")
return lazy(LazyThreadSafetyMode.NONE){ findViewById(res) as T }
}

And that’s it! Of course this is just a simple solution created for fun to showcase the incredible flexibility offered by Kotlin. If you want to try it out in your project just create a Kotlin file and paste the following snippet in it: