Data Binding & View Binding with Custom Views in Kotlin Android Development

In modern Android development, Data Binding and View Binding are powerful tools that simplify the process of accessing views and setting data within your layouts. While they work seamlessly with standard Android UI components, making them work with custom views in Kotlin XML can introduce additional complexities. This article will guide you through the process of effectively integrating custom views with data binding and view binding in your Android applications.

Understanding Data Binding and View Binding

Before diving into the specifics of custom views, let’s recap the essentials of Data Binding and View Binding.

Data Binding

Data Binding is a support library that allows you to bind UI components in your layouts directly to data sources. This declarative approach minimizes boilerplate code and enhances readability and maintainability. It’s useful for complex UI-driven logic where dynamic content rendering is prevalent.

Benefits of Data Binding:
  • Reduces boilerplate code for UI updates.
  • Provides compile-time type safety for binding expressions.
  • Supports bidirectional data flow.
  • Enables testability through cleaner code.

View Binding

View Binding is a feature that generates a binding class for each XML layout file present in your module. Instances of these classes contain direct references to all views that have an ID in the corresponding layout. View Binding simplifies view access without the performance overhead of findViewById.

Benefits of View Binding:
  • Provides compile-time safety when referencing views.
  • Simple to set up and use.
  • No risk of ClassCastException.
  • Faster than Data Binding due to less overhead.

Creating a Custom View in Kotlin

Let’s start by creating a basic custom view. This view could be anything—a custom button, a fancy progress bar, or a specialized text view.


import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout
import com.example.databindingcustomview.databinding.CustomViewLayoutBinding

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

    private val binding: CustomViewLayoutBinding

    init {
        binding = CustomViewLayoutBinding.inflate(LayoutInflater.from(context), this, true)
        // Initialize and set up your custom view here
        // Example: Set initial text
        binding.customTextView.text = "Hello from CustomView!"
    }

    fun setText(text: String) {
        binding.customTextView.text = text
    }
}

In this example:

  • We define a CustomView class that extends ConstraintLayout.
  • The constructor includes the necessary parameters for a custom view.
  • Inside the init block, we inflate the layout for this custom view.
  • A method setText() allows us to programmatically update the text in the view.

Layout for Custom View

Create an XML layout for your custom view, ensuring the layout has views with IDs if you’re using View Binding:


<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/customTextView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Custom Text"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Enabling View Binding and Data Binding

First, ensure that View Binding and Data Binding are enabled in your build.gradle file.

Enable View Binding:

android {
    ...
    buildFeatures {
        viewBinding true
    }
}

Enable Data Binding:

android {
    ...
    buildFeatures {
        dataBinding true
    }
}

Now sync your project with Gradle files to generate binding classes.

Using Data Binding with Custom Views

To use Data Binding effectively with custom views, you will need to wrap your layout inside a <layout> tag, declare a <data> section for data binding variables, and utilize these variables to bind data.

Step 1: Update the Custom View’s Layout for Data Binding

Convert the custom view layout to a data binding layout:


<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="customViewModel"
            type="com.example.databindingcustomview.CustomViewModel" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/customTextView"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="@{customViewModel.message}"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Key changes:

  • The entire layout is wrapped within a <layout> tag, which tells the Data Binding library that this layout is designed for data binding.
  • A <data> block is defined, which contains <variable> tags that declare data items that can be used within this layout.
  • We define a customViewModel variable of type CustomViewModel.
  • The android:text attribute in the TextView now binds to the message property of the customViewModel.

Step 2: Create a ViewModel

Define a ViewModel that holds the data to be bound:


import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class CustomViewModel : ViewModel() {
    private val _message = MutableLiveData("Initial Text from ViewModel")
    val message: LiveData = _message

    fun updateMessage(newMessage: String) {
        _message.value = newMessage
    }
}

Step 3: Update Custom View to Accept Binding

Modify your custom view to utilize Data Binding.


import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.databinding.DataBindingUtil
import com.example.databindingcustomview.databinding.CustomViewLayoutBinding

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

    private val binding: CustomViewLayoutBinding

    init {
        binding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.custom_view_layout, this, true)
    }

    fun setViewModel(viewModel: CustomViewModel) {
        binding.customViewModel = viewModel
        binding.executePendingBindings() // Ensure the UI updates immediately
    }
}

Changes made:

  • Inflate the layout using DataBindingUtil.inflate instead of the regular LayoutInflater.inflate.
  • A setViewModel method is created to bind the CustomViewModel to the binding class.
  • binding.executePendingBindings() is called to immediately update the UI.

Step 4: Use Custom View in Activity/Fragment

Now, you can use this custom view in your activity and bind data to it.


import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import com.example.databindingcustomview.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private lateinit var customViewModel: CustomViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        customViewModel = ViewModelProvider(this)[CustomViewModel::class.java]

        // Set the ViewModel for Data Binding
        binding.myCustomView.setViewModel(customViewModel)

        // Example: Update the message after a delay
        binding.buttonUpdateText.setOnClickListener {
            customViewModel.updateMessage("New Text from Activity!")
        }
    }
}

Make sure your main activity layout includes the custom view:


<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <com.example.databindingcustomview.CustomView
            android:id="@+id/myCustomView"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />

        <Button
            android:id="@+id/buttonUpdateText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Update Text"
            app:layout_constraintTop_toBottomOf="@+id/myCustomView"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Using View Binding with Custom Views

View Binding provides a straightforward way to reference views, even within custom components. The primary benefit of View Binding over manually calling findViewById is type safety. This helps prevent crashes that might arise from incorrect view casting.

Step 1: Enable View Binding

Make sure view binding is enabled in your build.gradle as explained previously.

Step 2: Modify the Custom View for View Binding


import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.constraintlayout.widget.ConstraintLayout
import com.example.databindingcustomview.databinding.CustomViewLayoutBinding

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

    private val binding: CustomViewLayoutBinding

    init {
        binding = CustomViewLayoutBinding.inflate(LayoutInflater.from(context), this, true)
        // Initialize and set up your custom view here
        // Example: Set initial text
        binding.customTextView.text = "Hello from CustomView!"
    }

    fun setText(text: String) {
        binding.customTextView.text = text
    }
}

With View Binding enabled, a binding class CustomViewLayoutBinding is generated automatically from your layout XML (custom_view_layout.xml). This simplifies view referencing.

Step 3: Integrate Custom View in Activities/Fragments

Use the CustomView within an activity or fragment’s layout.


<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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">

    <com.example.databindingcustomview.CustomView
        android:id="@+id/customView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <Button
        android:id="@+id/updateButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Update Text"
        app:layout_constraintTop_toBottomOf="@id/customView"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Now use View Binding in your activity or fragment.


import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.databindingcustomview.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.updateButton.setOnClickListener {
            binding.customView.setText("Text Updated via View Binding!")
        }
    }
}

Key points:

  • Retrieve views safely and efficiently with binding objects.
  • Access custom view functionalities directly.

Data Binding vs. View Binding: Choosing the Right Tool

When integrating custom views, it’s crucial to choose the right approach for your project’s needs.

Data Binding:

  • Complexity: Adds complexity due to the learning curve and setup requirements.
  • Use Case: Ideal for dynamic content that changes frequently based on complex logic.
  • Bidirectional Data Flow: Supports two-way data binding for advanced scenarios.

View Binding:

  • Simplicity: Easier to implement and requires less boilerplate.
  • Use Case: Perfect when you primarily need to access views safely without complex dynamic updates.
  • Performance: Provides faster compilation times and runtime performance due to its lightweight nature.

Conclusion

Effectively using Data Binding and View Binding with custom views in Kotlin XML requires a clear understanding of each technology’s strengths. While Data Binding offers advanced features like bidirectional data flow, View Binding simplifies view access without compromising type safety. Choose the one that best fits the complexity and requirements of your Android project to write cleaner, more maintainable code.