Simple one-liner ViewBinding in Fragments and Activities with Kotlin
There’s a new feature in Android Studio 3.6 called “view binding”. It’s just like databinding, except all it does is take your existing layout, and generate a binding for it — no changes necessary, no <layout
tags, and especially no <data
tags. Just pure layouts!
First we enable it in our build.gradle:
android {
viewBinding {
enabled = true
}
Then we take our regular layout
// main_activity.xml<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello ViewBinding!"/>
</FrameLayout>
and use it via the binding
/* Copyright (C) 2020 The Android Open Source Project */
class MainActivity: AppCompatActivity() {
private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.button.onClick {
showToast("hello world!")
}
And in Fragments, we can do something similar.
/* Copyright (C) 2020 The Android Open Source Project */
class FirstFragment : Fragment(R.layout.first_fragment) {
// Scoped to the lifecycle of the fragment's view (between onCreateView and onDestroyView)
private var fragmentFirstBinding: FragmentFirstBinding? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) val binding = FragmentBlankBinding.bind(view)
fragmentFirstBinding = binding binding.button.onClick {
showToast("Hello binding!")
}
}
override fun onDestroyView() {
// Consider not storing the binding instance in a field
// if not needed.
fragmentBlankBinding = null
super.onDestroyView()
}
}
The regular way of doing this is from the ViewBinding sample.
In this article though, I’m going to show you how to make it a one-liner (like in the above examples).
How to actually make it a one-liner
First we add a few Jetpack Lifecycle components to our build.gradle:
implementation "androidx.lifecycle:lifecycle-runtime:2.2.0"
implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
Then we learn about Kotlin read-write properties and read-only properties (and how to create custom delegates in Kotlin).
Once we know that and we’ve read the source code for AutoClearedValue, it’s quite easy to set up in Fragments. All we need to do is pass the MyBinding.bind
function reference to our delegate, and initialize the View’s binding using that function. Then, we clear this binding value when the views are destroyed.
Note: this gist was updated in 2021–01–21 because a bug was found in the original AutoClearedValue.
This fragment delegate is also available as a library from Jitpack.
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}dependencies {
implementation 'com.github.Zhuinden:fragmentviewbindingdelegate-kt:1.0.0'
}
In our Activity, we can instead use a lazy delegate that inflates the binding, as we don’t need to worry about a separate view lifecycle.
inline fun <T : ViewBinding> AppCompatActivity.viewBinding(
crossinline bindingInflater: (LayoutInflater) -> T) =
lazy(LazyThreadSafetyMode.NONE) {
bindingInflater.invoke(layoutInflater)
}
Now we can finish our original examples, and use a single-liner initialization for our view binding.
class MainActivity : AppCompatActivity() {
private val binding by viewBinding(MainActivityBinding::inflate)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
binding.button.onClick {
showToast("hello world!")
}
and
class FirstFragment: Fragment(R.layout.first_fragment) {
private val binding by viewBinding(FirstFragmentBinding::bind)
override fun onViewCreated(view: View, bundle: Bundle?) {
super.onViewCreated(view, bundle)
binding.buttonPressMe.onClick {
showToast("Hello binding!")
}
}
With the power of Kotlin delegates and Jetpack Lifecycle components, we have turned our ViewBinding into a single-liner.
Have fun!