Migrating Android App from Synthetic Views to ViewBinding

--

Introduction

In the ever-evolving world of Android development, keeping your apps up-to-date with the latest best practices is crucial for maintaining performance, readability, and ease of maintenance. One such transition involves moving from Synthetic Views to ViewBinding. This guide will walk you through the process of migrating your Android app, ensuring a smoother development experience and a more robust codebase.

What are Synthetic Views and ViewBinding?

Synthetic Views:

Introduced by Kotlin Android Extensions, Synthetic Views allow you to access views directly in your Kotlin code without using findViewById or any external library like Butterknife. While convenient, they come with drawbacks such as an increased risk of memory leaks and the need to handle view nullability carefully.

ViewBinding:

Introduced in Android Studio 3.6, ViewBinding generates a binding class for each XML layout file, offering a type-safe way to interact with views. It eliminates the need for findViewById and ensures compile-time safety.

Why Migrate to ViewBinding?

Reason for lazy programmers 😁: As a lazy person, I never touch any working code until someone forcefully asks to refactor it 🥱. However, after Gradle version 6.2 and Android Studio version 4.0.1, I received a deprecation message for the Kotlin-android-extention 😕.

Reason for good programmers 👨‍💻: In the programming world, we need to adhere to the latest best practices. Keeping your apps up-to-date is crucial for maintaining performance, readability, and ease of maintenance.

What kind of programmer are you 🤨? Please tell the viewers with interesting comments.

Benefits of ViewBinding:

  • Type Safety: ViewBinding ensures type-safe access to views, reducing runtime crashes.
  • Null Safety: It minimizes the risk of null pointer exceptions.
  • Performance: ViewBinding is more efficient at runtime compared to Synthetic Views.
  • Maintenance: It is easier to maintain and read, especially in large projects.

Tip: Most of the solutions on Stackoverflow and ChatGPT will start migration by removing 'kotlin-android-extensions' plugin from Gradle. But I recommenend you to do this step in the last of your migration because as soon as you remove this plugin you will get compile time error Unresolved reference: import kotlinx.android.synthetic and you will be blocked. Migration is long running task and you have to run you app multiple time to check if every thing is working fine.

Let’s dive into the coding! </>

Step 1: Setting Up ViewBinding

To enable ViewBinding in your project, add the following code to your build.gradle(:app)file:

android {
...
viewBinding {
enabled = true
}
}

Sync Nowyour project with Gradle files to apply the changes.

Step 2: Update Your Activity

Replace synthetic imported views with ViewBinding initialization. Here’s how to do it for an Activity:

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.app.databinding.ActivityMainBinding
// remove all synthetic import
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

// Define binding variable of type layout binding
// if your layout name is activity_main then your view binding
// type will be ActivityMainBinding
private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// initialize binding with layout inflater
binding = ActivityMainBinding.inflate(layoutInflater)

// setContentView(R.layout.activity_main) // <-- replace this
setContentView(binding.root) // <-- with this

// replace view variables with
// my_button.setOnClickListener {
// Your code
// }

// with binding variables
binding.myButton.setOnClickListener {
// Your code
}
}
}

In this way, you can replace all the my_view with binding.myView

Step 2: Update Your Fragment

Similarly, update Fragments to use ViewBinding:

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.example.app.databinding.FragmentExampleBinding

class ExampleFragment : Fragment() {

// Define _binding nullable variable of type layout binding
private var _binding: FragmentExampleBinding? = null
private val binding get() = _binding!!

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentExampleBinding.inflate(inflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

// example_text_view.text = "Hello, ViewBinding!" <-- remove this
binding.exampleTextView.text = "Hello, ViewBinding!" // <-- add this
}

// always set binding to null on view destory to avoid memory leaks
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

Step 3: RecyclerView Adapter Migration

class MyAdapter : RecyclerView.Adapter<MyAdapter.ViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
// Remove adapter view initialization
// val view: View =
// LayoutInflater.from(parent.context).inflate(R.layout.item_layout, parent, false)

// add binding initialization
val binding = ItemLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)

// return ViewHolder(view) <-- replace return
return ViewHolder(binding) // <-- with this
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.binding.textView.text = "Item $position"
}

override fun getItemCount() = 20

// update ViewHolder class from
// inner class ViewHolder(view: View) : RecyclerView.ViewHolder(binding.root) {
// your code
// }

// update ViewHolder class to
inner class ViewHolder(val binding: ItemLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
// your code
}
}

All done! Oh, I almost forgot about the large layouts that include other layouts. No problem, let’s write some more code!

Let’s consider your main_activity.xml includes toolbar layout from toolbar_layout.xml

  • No need to change anything in toolbar_layout.xml
  • Your main_activity.xml will looks like:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/home_parent_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">

<include
android:id="@+id/custom_toolbar"
layout="@layout/toolbar_layout" />

<!-- other views -->

</androidx.constraintlayout.widget.ConstraintLayout>
  • With <include/> view your MainActivity.kt looks like:
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.app.databinding.ActivityMainBinding
// remove all synthetic import
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

// Define binding variable of type layout binding
// if your layout name is activity_main then your view binding
// type will be ActivityMainBinding
private lateinit var binding: ActivityMainBinding

// add toolbarBinding to access views if toolbar_layout
private lateinit var toolbarBinding: ToolbarBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// initialize binding with layout inflater
binding = ActivityMainBinding.inflate(layoutInflater)

// initialize toolbarBinding
toolbarBinding = binding.customToolbar

// setContentView(R.layout.activity_main) // <-- replace this
setContentView(binding.root) // <-- with this

// replace view variables with
// my_button.setOnClickListener {
// Your code
// }

// set values in toolbarBinding views
toolbarBinding.myTitle = "Custom Toolbar"

// with binding variables
binding.myButton.setOnClickListener {
// Your code
}
}
}

Now it’s all done with the migration of Synthetic Views to ViewBinding.

What to do next?

synthetic was the feature of 'kotlin-android-extensions' plugin which is added to your build.gradle(:app) file. So now we don’t need it anymore and we can remove it from Gradle?

With synthetic this plugin also provides features of Parcelable. If your app uses Parcelable you can use 'kotlin-parcelize' instead of 'kotlin-android-extensions'.

Now you are free to remove kotlin-android-extensions plugin your project.

To do this remove the following plugin to your build.gradle(:app)file:

plugins {
...
id 'kotlin-android-extensions' // Remove or comment this line
id 'kotlin-android' // Remove or comment this line
id 'kotlin-parcelize' // Add this line if your app uses parcelable
}

Sync Now your project with Gradle files to apply the changes and run the app.

Hope everything working well. Happy Coding!

Comments are welcome if I still forgot something.

--

--

Rahul Rathore (Flutter and Android Developer)
Rahul Rathore (Flutter and Android Developer)

No responses yet