View Binding: farewell to Kotlin Syntetic
Kotlin Synthetic was one of the coolest features when I started playing with Kotlin. In the Android Java world, to get on hold of a view, you needed to use findViewById()
and then cast to the appropriate view type. This was one of the most repetitive and boring tasks when creating a new view/screen.
Then Kotlin and its Synthetics came around. If you had a view with id hello
, you would write hello
and you could access it. No boilerplate, no findViewById
, not a thousand member variables in your class.
But it turns out that the concept was kind of too relaxed. They had a global namespace and there were no nullability checks. This meant that you were able to access hello2
on screen 2, even though you were on screen 1, leading to inevitable crashes.
So a step back is probably the right balance. Kotlin Android extensions (which Kotlin Synthetic is part of) are deprecated, and the recommended way to replace them is the View Bindings. This has a bit more boilerplate but offers type and nullability safety.
Set up
In the Android
section of your build.gradle
add this:
android {
[...]
buildFeatures {
viewBinding true
}
}
Auto-generation
Now when you do Build
-> Make project
, for every XML file you have a corresponding binding class will be generated. For instance, for your fragment_screen_1.xml
, the FragmentScreen1Binding
will be created. These classes contain a member variable for every child view in the XML layout.
Tip: You might need to do File
-> Sync Project with Gradle files
to get Android Studio to recognize those generated classes.
Create the bindings
In Fragments
private var binding: FragmentScreen1Binding? = null // 2.
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentScreen1Binding.inflate(inflater, container, false) // 1.
return binding.root // 3.
}
override fun onDestroyView() {
super.onDestroyView()
binding = null // 4.
}
- Instead of the regular
inflate()
you will use the auto-generated class to inflate your view. - This
binding
is non-null only betweenonCreateView()
andonDestroyView()
. - The root view of your
fragment_screen_1.xml
layout. - Note that Fragments outlive their views. Clean up
binding
after destroying the inflated view for avoiding any memory leaks.
In Activities
private lateinit var binding: FragmentScreen1Binding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = FragmentScreen1Binding.inflate(layoutInflater)
setContentView(binding.root)
}
Similar to the fragment without the need for clean up since the inflated view is tightly associated with the activity lifecycle.
Use the bindings
For every child view in the XML layout, there's a member variable with the same name in camel case in your binding
variable. The views without an ID are not accessible.
In case you were using Kotlin Synthetic, remove the imports for kotlinx.android.synthetic.*
and replace them with the data bindings equivalents.
For further reading, check out the official doc (and Kotlin Synthetic migration guide). If you don't enjoy Android Views, be a little more patient until Jetpack Compose becomes stable :) Until then, happy coding!