When working in the UI layer of an Android app, it won’t be long before we need to perform an operation upon one of our views. In order to do this, we first need to retrieve it via findViewById. Although using the API can seem simple, it does introduce a block of boilerplate to our Activities. On top of this the code to bind all the views usually ends up in onCreate, completely separate from the view properties themselves. 😕

Now that Kotlin has entered the scene with new features we didn’t previously have access to it seems like the perfect time to look for a new way of doing things. We will explore some different approaches, before looking in detail at how lazy initialisation in Kotlin can be used to solve this problem.

The situation in Java

To set the scene and to demonstrate how Kotlin can help we first need to look at how these tasks are achieved in Java. To bind views to a field, we would use findViewById, historically casting the resulting view to the correct subclass. For an activity, the views would commonly be bound within onCreate, however, this may be elsewhere if views are retrieved at another time.

public class PlanningActivity extends AppCompatActivity {
  private TextView planningText;
  private ImageView appIcon;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_planning);
    // Before: Needed to cast
    planningText = (TextView) findViewById(R.id.planning_text);
    // Now: No longer need to
    appIcon = findViewById(R.id.app_icon);

    planningText.setText("Hello!");
  }
}

The example here is very simple, containing only two views, whereas in reality many different views may need to be accessed. To avoid this repetitive boilerplate Butter Knife was created by Jake Wharton, which uses annotation processing to aid the binding of views. 🍴

public class PlanningActivity extends AppCompatActivity {
  @BindView(R.id.planning_text) TextView planningText;
  @BindView(R.id.app_icon) ImageView appIcon;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_planning);
    ButterKnife.bind(this);
    planningText.setText("Hello!");
  }
}

Adoption of Kotlin

The Kotlin programming language has many new features and so it feels natural that it may offer a better solution to what we were using before. The first option we will likely come across are the Kotlin Android Extensions provided as a plugin to the Kotlin language. The plugin automatically generates properties that match the IDs of the views in our layout file. Views no longer need to be stored manually as properties, as they are instead accessed via synthetic properties generated by the plugin.

import kotlinx.android.synthetic.main.activity_planning.*

class PlanningActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_planning)
    planning_text.text = "Hello"
  }
}

The approach is dependent on a Gradle plugin and as with all generated code there will likely be occasional issues that require a Gradle sync to get things working again. Whether Kotlin Android Extensions is the approach to take may come down to personal preference as thanks to the power of Kotlin there is another really nice option that isn’t dependent on generated code. As with any potential solution it is best to try it out, see how it works and whether it fits with all the use cases a particular project has. 👍

Lazy binding

When initialising properties there is a feature of Kotlin that allows the getter and setter to be delegated, enabling capabilities such as lazy initialisation. The idea is that on first access the provided function is used to initialise the property and future accesses will simply return it. This is perfect for binding views, as the first access will call findViewById and then subsequent calls will simply returned the stored view. It is important to note that we are currently applying this technique to an Activity, we will discuss using it in other situations afterwards.

Delegated properties are employed via the by keyword, with lazy being a global function that takes a lambda as an argument, making it very easy to apply.

private val planningText by lazy {
  findViewById<TextView>(R.id.planning_text)
}

By checking its signature we will find that lazy can also be provided with a LazyThreadSafetyMode argument, which by default will synchronise access to the lazy property to keep things thread safe. As our lazily bound views will only be touched from the main thread, we can optimise performance and disable this behaviour.

private val planningText by lazy(LazyThreadSafetyMode.NONE) {
  findViewById<TextView>(R.id.planning_text)
}

Once this solution is applied across our app, the same lazy call will be made many times, opening up the possibility of extracting it out to a global function to keep things tidy. 🧹

// LazyExt.kt
fun <T> lazyUnsychronized(initializer: () -> T): Lazy<T> = 
  lazy(LazyThreadSafetyMode.NONE, initializer)

// PlanningActivity.kt
private val planningText by lazyUnsychronized {
  findViewById<TextView>(R.id.planning_text)
}

Bind view

It turns out the only part of the lazy findViewById block that changes with each use, is the generic type. The application of an Activity extension function can therefore allow the whole block to be reused each time we want to bind a view.

fun <ViewT : View> Activity.bindView(@IdRes idRes: Int): Lazy<ViewT> {
  return lazyUnsychronized {
    findViewById<ViewT>(idRes)
  }
}

This leaves us with our final lazy view binding implementation. There is a small matter of choice when it comes to assigning the view, the view type can either be specified on the property or as a generic constraint to bindView. The best choice will be a matter of personal preference or specific to the project code style.

class PlanningActivity : AppCompatActivity() {
  private val planningText by bindView<TextView>(R.id.planning_text)
  // or
  private val planningText: TextView by bindView(R.id.planning_text)

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_planning)
    planningText.text = "Hello!"
  }
}

The sequence of steps we took have resulted in a succinct and clear call site. We have full control over our view binding code and no generated code is required. As our bindView function is essentially a wrapper around findViewById, the same extension could also be used to bind views elsewhere in the codebase beyond just in Activities. 🚀

Other usages

Our lazy view binding technique can easily be applied to views, such as when creating a ViewHolder to be used within a RecyclerView. We can create a very similar extension on ViewHolder to give us access to the same API we had within Activities.

Where our technique encounters some issues are when used within a Fragment due to differences in their lifecycle when compared to Activities and Views. If applied in the same way, an issue can be encountered where the lazy property refers to the previous view instance after the Fragment view has been recreated. One solution may be to just assign views in onCreateView ourselves and to avoid using lazy properties within Fragments.

We can, however, use Lifecycle from the Architecture Components to work around the issue. By creating our own version of Lazy we can reset the stored value in onDestroyView, causing the next access to call findViewById again. An example implementation demonstrating this idea can be found within the sample code for the article. 👍

Conclusion

By making our view properties lazy delegated properties, we can strike a great balance between brevity, avoidance of boilerplate and reducing reliance on generated code. Maintaining full control over how our views are bound can be really helpful for people trying to understand the code and for debugging it if something goes wrong. The solution does require a little bit of infrastructure, however, pretty much all of the code is shared between each view binding, helping to keep the call site clean.

What is your preferred technique for accessing your views? Do you use lazy delegated properties or do you think it is something you might try out? Feel free to reach out to me on Twitter @lordcodes and let me know what you think or if you have any other feedback or questions.

If you like what you have read, please don’t hesitate to share the article and subscribe to my feed if you are interested.

Thanks for reading and happy coding! 🙏

Check out the sample code for this article on GitHub.