When I add an import of the form kotlinx.android.synthetic.main.<layout-name>.view.*
to a Kotlin source it alters the behavior of the Android Studio editor. Specifically, it now considers any property of type View
to have a property corresponding to each view in the layout file that is assigned an id. I am assuming that I am doing this in a class that is not an Activity
or Fragment
.
For example, say I have a ViewHolder
declared in the source file and I add a property to it called x
of type View
. Further, say that I have a layout named item_test
that has three view declarations that have been assigned the ids, a
, b
, and c
. When I add a synthetic import of the form above with a layout-name of item_test
suddenly x
has three new properties, a
, b
, and c
. Or, more correctly, the editor makes it appear as though x
has these properties.
After some research, I have concluded the following:
The addition of layout view ids as properties is done blindly. Any view id implied by a such a synthetic import is added to any property of the class that is of type View
(or a subclass of View
). This includes properties of such type inherited by the class.
Because the properties are added blindly it is incumbent upon the developer to ensure that the runtime view corresponding to the synthetic import is assigned before the synthetic properties are accessed or an exception will be thrown.
If two or more such imports are specified in a file then the union of their view ids will be blindly added to all class properties of type View
.
The purpose of allowing multiple imports is to account for the case where one layout file includes another.
Are these conclusions correct?
Are any other interesting subtleties around the implementation of this feature?
I am using version 1.1.2-5 of the kotlin-gradle-plugin.
If I understand correctly, you are assuming the following:
import kotlinx.android.synthetic.main.activity_main.item_test
import kotlinx.android.synthetic.main.activity_main.item_test_2
class MyClass {
lateinit var x: View
lateinit var y: View
fun foo() {
val foo1 = x.item_test // Compiles
val foo2 = y.item_test // Compiles as well
val bar1 = x.item_test_2 // Compiles too
val bar2 = y.item_test_2 // Compiles yet again
}
}
So it appears to me that any property that is of type View will have the synthetic properties associated with a synthetic import regardless of whether it is valid or not. So the extension implementation appears to be brute force and will not tip-off the developer if anything is wrong.
That is correct.
Now, the item_test
import is essentially an extension property on the View
class (simplified example[1]):
val View.item_test get() = findViewById(R.id.item_test)
For each of the views in your layout, the plugin generates these properties, and puts them in their relative files. These properties also exist on Activity
and Fragment
.
Thus, when you call x.item_test
, you're essentially calling x.findViewById(R.id.item_test)
[1].
Upon compilation, the plugin replaces these calls back to findViewById
. So the simplified decompiled version of the above program[1] (in Java):
final class MyClass {
public View x;
public View y;
public final void foo() {
TextView foo1 = (TextView) x.findViewById(id.item_test);
TextView foo2 = (TextView) y.findViewById(id.item_test);
TextView bar1 = (TextView) x.findViewById(id.item_test_2);
TextView bar2 = (TextView) y.findViewById(id.item_test_2);
}
}
Since this is just an extension function on View
, there are no sanity checks to check whether item_test
exists in the view hierarchy of x
or y
, just like they are not there for findViewById
. If the findViewById
call returns null
, the value is null
or a KotlinNullPointerException
is thrown.
- The addition of layout view ids as properties is done blindly. Any view id implied by a such a synthetic import is added to any property of the class that is of type View (or a subclass of View). This includes properties of such type inherited by the class.
Yes, since the property is an extension property on View
, as explained above.
- Because the properties are added blindly it is incumbent upon the developer to ensure that the runtime view corresponding to the synthetic import is assigned before the synthetic properties are accessed or an exception will be thrown.
I'm not sure what you're asking here. If you mean that you'd have to initialize x
before calling x.item_test
, then that's right. Also, x
should have a view item_test
in its hierarchy.
Yes.
[1]: Actually, behind the scenes these lookups are cached. See Under the hood in the documentation.