Exploring View Binding on Android

April 16, 2020

az.JPG

View Binding (not to be confused with Data Binding) was recently introduced by Google. Why do we need it when we have other alternatives such as Kotlin synthetics, findViewById, Data Binding, Butterknife.

There are several issues with the status quo.

findViewById is verbose and not type-safe. You can cast a Button into a TextView if. you are not careful and you won’t know that you did it until your app crashes when running.

Kotlin synthetics way comes with its own set of different issues. For one, it’s too easy to mistakenly import a layout that does not belong in a particular Activity, Fragment, etc. Unless you examine the imports, you may not catch it until your app crashes. In addition, Kotlin synthetics references are not lifecycle-aware: you can easily try to update a view after it was already destroyed (e.g. after some animation finishes). You can code around or at the very least use null-safe check myButton?.doSomething() but admit it, it’s too easy to forget to do it.

View Binding to the rescue

This is where View Binding comes in handy. It’s meant to be type-safe, null-safe alternative to the above options.

If you use Android Studio 3.6+, enabling View Binding is easy. Just add to your build.gradle:

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

There is no noticeable performance overhead when using view bindings in Android Studio.

Activity

Using View Binding in an Activity is simple:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: MainActivityBinding

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

Fragments

Using it in Fragments is not as elegant since it has to deal with Fragment lifecycle.

Fragments outlive their views so you need to clean up any references to the binding class instance in the fragment's onDestroyView()

class UserReposFragment : Fragment() {

    private var _binding: UserReposFragmentBinding? = null
    private val binding get() = _binding!!

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.userName.text = "Foo"
   }
    
    override fun onDestroyView() {
        _binding = null
        super.onDestroyView()
    }

If onViewCreated() is the only place where you need to access the view widgets, the code becomes much cleaner as in this Google sample

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()
    }
}

RecycleView Adapter

To my surprise, I have not been able to find examples of how View Binding can be used in a RecyclerView Adapter so I wrote one:

class RepoAdapter :
    ListAdapter<Repo, RepoAdapter.RepoViewHolder>(RepoDiffCallback()) {
    override fun onCreateViewHolder(parent: ViewGroup, position: Int): RepoViewHolder {
        val binding = RepoItemBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return RepoViewHolder(binding)
    }

    override fun onBindViewHolder(holder: RepoViewHolder, position: Int) 
        
    

    inner class RepoViewHolder(
        private val binding: RepoItemBinding
    ) : RecyclerView.ViewHolder(binding.root) {

        private val repoNameView = binding.repoName
        private val repoOwnerView = binding.repoOwner

        fun bind(item: Repo) {
            repoNameView.text = item.name
            repoOwnerView.apply {
                text = item.owner.login
                setOnClickListener 
                    
                
            }
        }
    }
}

Disable View Binding

You can disable View Binding on a layout basis by doing so:

<LinearLayout
  ...
  tools:viewBindingIgnore="true" />

Conclusion

I will probably use View Binding in all my projects going forward. I like that it provides both type-safety and null-safety.

It does come with some verbosity though. Especially when dealing with View Binding in Fragments.

P. S. Gabor Varadi wrote this article to address the verbosity thru the use of Kotlin Delegates. Check it out here.

 
Previous
Previous

Git reset and friends

Next
Next

Deploying to Google Play Store using Github Actions