Kotlin Android Extensions

Kotlin Android Extensions: Say goodbye to findViewById (KAD 04)

Warning! Kotlin Android Extensions are deprecated, you should start using View Binding.

If you’ve been developing Android Apps for some time, you’re probably already tired of working withΒ findViewByIdΒ in your day-to-day life to recover views. Or maybe you gave up and started using the famous Butterknife library. If that’s your case, then you’ll love Kotlin Android Extensions.

Kotlin Android Extensions: What’s this?

Kotlin Android Extensions are another Kotlin plugin that is included in the regular one, and that will allow recovering views from ActivitiesFragments, and Views in an amazing seamless way.

The plugin will generate some extra code that will allow you to access views in the layout XML, just as if they were properties with the name of the id you used in the layout definition.

It also builds a local view cache. So the first time a property is used, it will do a regular findViewById. But next times, the view will be recovered from the cache, so the access will be faster.

How to use them

Let’s see how easy it is. I’ll do this first example with an activity:

Integrating Kotlin Android Extensions in our code

Though the plugin comes integrated into the regular one (you don’t need to install a new one), if you want to use it you have to add an extra apply in the Android module:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

And that’s all you need. You’re now ready to start working with it.

Recovering views from the XML

From this moment, recovering a view is as easy as using the view id you defined in the XML directly into your activity.

Imagine you have an XML like this one:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/welcomeMessage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Hello World!"/>

</FrameLayout>

As you can see, the TextView has welcomeMessage id.

Just go to your MainActivity and write it:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    welcomeMessage.text = "Hello Kotlin!"
}

To be able to use it, you need a special import (the one I write below), but the IDE is able to auto-import it. Couldn’t be easier!

import kotlinx.android.synthetic.main.activity_main.*

As I mentioned above, the generated code will include a view cache, so if you ask the view again this won’t require another findViewById

Let’s see what’s behind the scenes.

The magic behind Kotlin Android Extensions

When you start working with Kotlin, it’s really interesting to understand the bytecode that is being generated when you use one feature or another. This will help you understand the hidden costs behind your decisions.

There’s a powerful action below Tools –> Kotlin, called Show Kotlin Bytecode. If you click here, you’ll see the bytecode that will be generated when the class file you have opened is compiled.

The bytecode is not really helpful for most humans, but there’s another option here: Decompile.

This will show a Java representation of the bytecode that is generated by Kotlin. So you can understand more or less the Java equivalent code to the Kotlin code you wrote.

I’m going to use this on my activity, and see the code generated by the Kotlin Android Extensions.

The interesting part is this one:

private HashMap _$_findViewCache;
...
public View _$_findCachedViewById(int var1) {
   if(this._$_findViewCache == null) {
      this._$_findViewCache = new HashMap();
   }

   View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
   if(var2 == null) {
      var2 = this.findViewById(var1);
      this._$_findViewCache.put(Integer.valueOf(var1), var2);
   }

   return var2;
}

public void _$_clearFindViewByIdCache() {
   if(this._$_findViewCache != null) {
      this._$_findViewCache.clear();
   }

}

Here it is the view cache we were talking about.

When asked for a view, it will try to find it in the cache. If it’s not there, it will find it and add it to the cache. Pretty simple indeed.

Besides, it adds a function to clear the cache: clearFindViewByIdCache. You can use it for instance if you have to rebuild the view, as the old views won’t be valid anymore.

Then this line:

welcomeMessage.text = "Hello Kotlin!"

is converted into this:

((TextView)this._$_findCachedViewById(id.welcomeMessage)).setText((CharSequence)"Hello Kotlin!");

So the properties are not real, the plugin is not generating a property per view. It will just replace the code during compilation to access the view cache, cast it to the proper type and call the method.

Kotlin Android Extensions on fragments

This plugin can also be used on fragments.

The problem with fragments is that the view can be recreated but the fragment instance will be kept alive. What happens then? This means that the views inside the cache would be no longer valid.

Let’s see the code it generates if we move it to a fragment. I’m creating this simple fragment, that uses the same XML I wrote above:

class Fragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment, container, false)
    }

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        welcomeMessage.text = "Hello Kotlin!"
    }
}

In onViewCreated, I again change the text of the TextView. What about the generated bytecode?

Everything is the same as in the activity, with this slight difference:

// $FF: synthetic method
public void onDestroyView() {
   super.onDestroyView();
   this._$_clearFindViewByIdCache();
}

When the view is destroyed, this method will call clearFindViewByIdCache, so we are safe!

Kotlin Android extensions on a Custom View

It will work very similarly on a custom view. Let’s say we have a view like this:

<merge xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">
    
    <ImageView
        android:id="@+id/itemImage"
        android:layout_width="match_parent"
        android:layout_height="200dp"/>
    
    <TextView
        android:id="@+id/itemTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</merge>

I’m creating a very simple custom view and generate the constructors with the new intent that uses @JvmOverloads annotation:

class CustomView @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {

    init {
        LayoutInflater.from(context).inflate(R.layout.view_custom, this, true)
        itemTitle.text = "Hello Kotlin!"
    }
}

In the example above, I’m changing the text to itemTitle. The generated code should be trying to find the view from the cache.  It doesn’t make sense to copy all the same code again, but you can see this in the line that changes the text:

((TextView)this._$_findCachedViewById(id.itemTitle)).setText((CharSequence)"Hello Kotlin!");

Great! We are only calling findViewById the first time in custom views too.

Recovering views from another view

The last alternative Kotlin Android Extensions provide is to use the properties directly from another view.

I’m using a layout very similar to the one in the previous section. Imagine that this is being inflated in an adapter for instance.

You can also access the subviews directly, just using this plugin:

val itemView = ...
itemView.itemImage.setImageResource(R.mipmap.ic_launcher)
itemView.itemTitle.text = "My Text"

Though the plugin will also help you fill the import, this one is a little different:

import kotlinx.android.synthetic.main.view_item.view.*

There are a couple of things you need to know about this:

  • In compilation time, you’ll be able to reference any view from any other view. This means you could be referencing to a view that is not a direct child of that one. But this will fail in execution time when it tries to recover a view that doesn’t exist.
  • In this case, the views are not cached as it did for Activities and Fragments.

Why is this? As opposed to the previous cases, here the plugin doesn’t have a place to generate the required code for the cache.

If you again review the code that is generated by the plugin when calling a property from a view, you’ll see this:

((TextView)itemView.findViewById(id.itemTitle)).setText((CharSequence)"My Text");

As you can see, there’s no call to a cache. Be careful if your view is complex and you are using this in an Adapter. It might impact the performance.

Or you have another alternative: Kotlin 1.1.4

Kotlin Android Extensions in 1.1.4

Since this new version of Kotlin, the Android Extensions have incorporated some new interesting features: caches in any class (which interestingly includes ViewHolder), and a new annotation called @Parcelize. There’s also a way to customize the generated cache.

We’ll see them in a minute, but you need to know that these features are not final, so you need to enable them adding this to you build.gradle:

androidExtensions {
    experimental = true
}

Using it on a ViewHolder (or any custom class)

You can now build a cache on any class in a simple way. The only required thing is that your class implements the interface LayoutContainer. This interface will provide the view that the plugin will use to find the subviews. Imagine we have a ViewHolder that is holding a view with the layout described in the previous examples. You just need to do:

class ViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), 
        LayoutContainer {

    fun bind(title: String) {
        itemTitle.text = "Hello Kotlin!"
    }
}

The containerView is the one that we are overriding from the LayoutContainer interface. But that’s all you need.

From that moment, you can access the views directly, no need of prepending itemView to get access to the subviews.

Again, if you check the code generation, you’ll see  that it’s taking the view from the cache:

((TextView)this._$_findCachedViewById(id.itemTitle)).setText((CharSequence)"Hello Kotlin!");

I’ve used it here on a ViewHolder, but you can see this is generic enough to be used in any class.

Kotlin Android Extension to implement Parcelable

With the new @Parcelize annotation, you can make any class implement Parcelable in a very simple way.

You just need to add the annotation, and the plugin will do all the hard work:

@Parcelize
class Model(val title: String, val amount: Int) : Parcelable

Then, as you may know, you can add the object to any intent:

val intent = Intent(this, DetailActivity::class.java)
intent.putExtra(DetailActivity.EXTRA, model)
startActivity(intent)

And recover the object from the intent at any point (in this case in the target activity):

val model: Model = intent.getParcelableExtra(EXTRA)

Customize the cache build

A new feature included in this experimental set is a new annotation called @ContainerOptions. This one will allow you to customize the way the cache is built, or even prevent a class from creating it.

By default, it will use a Hashmap, as we saw before. But this can be changed to use a SparseArray from the Android framework, which may be more efficient in certain situations. Or, if for some reason, you don’t want a cache for a class, you also have that option.

This is how it’s used:

@ContainerOptions(CacheImplementation.SPARSE_ARRAY)
class MainActivity : AppCompatActivity() {
...
}

Currently, the existing options are these:

public enum class CacheImplementation {
    SPARSE_ARRAY,
    HASH_MAP,
    NO_CACHE;

    ...
}

Conclusion

You’ve seen how easy is to deal with Android views in Kotlin. With a simple plugin, we can forget about all that awful code related to view recovery after inflation. This plugin will create the required properties for us casted to the right type without any issues.

Besides, Kotlin 1.1.4 has added some interesting features that will be really helpful in some cases that were not previously covered by the plugin.

If you like what you’ve seen, I encourage you to sign up for my free training, where I’ll tell you everything you need to learn about how to create your own Android Apps in Kotlin from scratch.

43 thoughts on “Kotlin Android Extensions: Say goodbye to findViewById (KAD 04)”

  1. Hi,
    Nice explanation. You said “The code that the plugin generates is able to store a view cache, so if you ask the view again, this won’t require another findViewById.” Does that mean that we don’t need dependency injection. Once view is cache we can access it any where within the application?

    1. No, you can access to it just from the activity, and the cache will die when the activity dies. Nothing to do with dependency injection.

  2. How does this work within a fragment? What do we need to do inside onCreateView(…) to inflate the layout and access the views like this?

      1. You’re absolutely correct, what I was doing wrong was trying to access the views inside onCreateView, but you can only inflate the view there, the views themselves are not available until onViewCreated

  3. In a RecycleView.ViewHolder class, I did the following to access the subview

    class testViewHolder(v:view): RecycleView.ViewHolder(v) {

    val item1 = v.item1
    val item2 = v.item2

    fun methodOne() {
    item1.text = “Text Changed”
    }

    fun methodTwo(){
    item2.text = “Text Changed”
    }

    }

    Is this correct?

    1. Yeah, totally correct, and a good idea in cases where the view is complex. You’ll avoid finding the view each time you bind new content. All this ViewHolder thing is a matter of taste in many cases. I prefer a more complex bind and leave the adapter as simple as possible, but the opposite also works.

  4. Jovche Mitrejchevski

    What I found also interesting and important for the fragment example is that even if you have the `onDestroyView()` method override, this line `this._$_clearFindViewByIdCache();` is still added which is awesome!

  5. Thank you! Before reading this I had no idea that the views are not cached in ViewHolder.

    About custom view, the only thing that holds me from creating them with Kotlin is because Android Studio can’t show them in XML preview due to “missing custom view class” error. Do you have any idea?

  6. Is it safe to use view cache nullability state to assume that fragment already destroyed? To avoid java.lang.NullPointerException because the view already null.

    For example i’m fetching data from server on different thread(let’s assume the task must finish). i have loadingBar to display infinite progress bar.
    Once the thread done, is it safe to use something like loadingBar?.visibility = View.GONE (as in hideLoadingBar2 function),
    or in your example use welcomeMessage?.text = “Data fetched” ?

    Or i still need to do something like below:
    fun hideLoadingBar1() {
    if (isVisible) { //isVisible is from support library Fragment.java code
    loadingBar.visibility = View.GONE
    }
    }

    fun hideLoadingBar2() {
    loadingBar?.visibility = View.GONE //Is this safe too?
    }

  7. For some reason, when I implement RecyclerView.ViewHolder in Kotlin, I must call myView.findViewById(R.id.price). I cannot simply call myView.price. Is this correct? Thank you.

    1. I would need to see the code, but you shouldn’t need to call `findViewById` when using Kotlin Android Extensions. That’s exactly what it tries to avoid.

  8. Since the Release Candidate of Android Studio 3 was released this seems to be giving me errors.

    Although it seems to compile and work as expected for the most part

  9. Tinashe Mzondiwa

    Thanks for the article.

    So I have a NavigationView with a headerLayout.
    I can directly access a view inside the headerLayout fine, but when orientation changes that view becomes null.
    Would you have an idea why this is so?
    I’ve had to resort to doing null checks first then findViewByid for now.

  10. Thanks for your post! I have some problem with the “GenericAdapter” but remain the cache of view.
    I want to leave the responsibility of the bind the view to the user of adapter it self. But once I’m outside of the ViewHolder(LayoutContainer) scope. I can’t use the cache.
    You can check this SO question for more :https://stackoverflow.com/questions/47977194/can-i-defer-the-view-binding-in-an-adapter-using-kotlin-android-extension/47980306#47980306

    Any idea of this? Thanks!

  11. hi

    how to declare textview without using findviewbyid having textview declared more than one in kotlin

    1. Sorry, not sure if I follow the question. With Kotlin Android Extensions you can recover the TextView without using findViewById. You just use the id, and the cache will help not recovering it more than once.

  12. If I detach and recreate a ui inside onConfigurationChanged I get NPE on the UI calls done through synthetic import. Do you have any recommendations for this.

    1. Not sure if it’s related to the other comment. But if the UI is recreated, you need to call ` _clearFindViewByIdCache`. Was that enough to make it work?

  13. is LayoutContainer production ready?
    it is still in experimental but has been there for 1.5 years…

    1. While it’s experimental, the API can change. My personal opinion: I would use it for production for my own projects. But don’t blame me if it changes in the future

  14. Hi,
    First I want to say that you create great content and you give great explanations!
    I have a question about memory used in two cases
    ->Android Data Binding vs Android Kotlin Extensions.
    In which case will be less memory used on the device?

    –Kotlin Android-extension is calling first findViewById and after that, the result will be stored locally in a cache, this means memory used on the device.

    –DataBinding creates a path between layouts and activities/fragments through the binding class that was created.

    My concern is to use the one that is more efficient when we think of memory usage on the user’s device.

    Could you please help me to figure out the answer?

    Thank you very much for all your work!

    1. So not knowing how databinding works under the hood, my gut feeling is that Kotlin Android Extensions are far more efficient. The only thing that it adds to your code different from just calling findViewById is a cache, which is simply a Map. I can imagine that to make all databinding magic work, it will need much more code, which means more classes, and more memory usage.

  15. Nice article, but I have one question:
    If I have a generic custom view like

    class MyGenericCustomView(context: Context, attrs: AttributeSet) : AnotherView(context, attrs) {

    }

    In the XML I use:

    If I use the old way, I can findViewById<MyGenericCustomView>.
    But if I use the synthetic, I dont have a way to pass the Generic type.
    One solution is to create a specific class like

    class MySpecificCustomView(context: Context, attrs: AttributeSet) : MyGenericCustomView(context, attrs) {
    ….
    }

    But I dont want to create this boilerplate class.
    Is there any solution for this?

    1. I’ve been taking a look, but not sure what you’re trying to do. If you have MyGenericCustomView, and are using it as a view in the XML, why can’t you recover it with KAE?

    1. Not sure what you mean. The id of a view is always got from “R.id.name_of_your_view”. But that doesn’t inflate anything. Was that your question?

  16. > To be able to use it, you need a special import (the one I write below), but the IDE is able to auto-import it.

    How do you get the auto import working? I haven’t been able to figure it out

      1. “At the moment there are no official voices (AFAIK) saying that it’s not recommended, but if you ask me, I recommend you to switch to View Binding.”

        Gradle-Build says it since a few days. That’s official enough for me. πŸ™‚

        You should mark this article here as deprecated.

Comments are closed.