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 Activities, Fragments, 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.
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?
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.
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?
You just inflate the layout as usual, and can access the view as in the activity, in onViewCreated for instance.
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
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?
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.
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!
Yeah! It’s pretty impressive.
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?
I guest I should point out that this has been fixed since AS3 Beta 4. Making custom view with Kotlin is such a pleasant now π
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?
}
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.
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.
You can see the complete repository here: https://github.com/googlecodelabs/mdc-android-kotlin. In MainActivity.ProductViewHolder class, you will see that you must use `findViewById`, or it won’t compile.
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
Any specific part?
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.
Where are you accessing from? Maybe that’s easier if you extend `NavigationView` and use Kotlin Android Extensions for the header there.
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!
I see you got an answer there, awesome! Sorry for the delay answering here.
Very nice! I do like it!
Thanks! Glad you like it π
hi
how to declare textview without using findviewbyid having textview declared more than one in kotlin
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.
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.
is it safe to use _clearFindViewByIdCache before I process anything for my UI
Yeah, it should be safe.
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?
is LayoutContainer production ready?
it is still in experimental but has been there for 1.5 years…
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
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!
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.
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?
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?
How do I get an id resource without inflating the layout in Android
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?
> 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
Honestly, there’s not much to do. If you have the Kotlin Android Extensions plugin in the build.gradle it should just work.
https://proandroiddev.com/the-argument-over-kotlin-synthetics-735305dd4ed0
“kotlinx.android.synthetic is no longer a recommended practice. Removing in favour of explicit findViewById.”
π
Well, it’s in fact being removed in favour of View Binding https://antonioleiva.com/view-binding-android/
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.
“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.
Done