Kotlin Android View Binding: findViewById vs Butterknife vs Kotlin Android Extension
Asked Answered
D

6

32

I'm trying to figure out the best way to do Android View Binding in Kotlin. It seems like there are a few of options out there:

findViewById

val button: Button by lazy { findViewById<Button>(R.id.button) }

Butterknife

https://github.com/JakeWharton/butterknife

@BindView(R.id.button) lateinit var button: Button

Kotlin Android Extensions

https://kotlinlang.org/docs/tutorials/android-plugin.html

import kotlinx.android.synthetic.main.activity_main.*

I'm pretty familiar with findViewById and Butterknife in java land, but what are the pros and cons of each view binding approach in Kotlin?

Does Kotlin Android Extensions play well with the RecyclerView + ViewHolder pattern?

Also how does Kotlin Android Extensions handle view binding for nested views via include?

ex: For an Activity using activity_main.xml, how would View custom1 be accessed?

activity_main.xml

<...>
    <include layout="@layout/custom" android:id="@+id/custom" />
</>

custom.xml

<...>
    <View android:id="@+id/custom1" ... />
    <View android:id="@+id/custom2" ... />
</>
Desiderate answered 29/9, 2017 at 4:52 Comment(0)
S
17

There are a lot of ways to access views in Android. A quick overview:

enter image description here

My advise would be:

  1. findViewById: old school. Avoid.
  2. ButterKnife: old school, but less boilerplate and some added functionality. Still lacks compile time safety. Avoid if possible.
  3. Kotlin Synthetic: really a elegant cached version of findViewbyId. Better performance and way less boilerplate but still no (real) compile time safety. Will be no longer supported from Kotlin 1.8. Avoid if possible.
  4. ViewBinding: Google's recommendation nowadays. It's faster than databinding and prevents logic errors inside XML (hard to debug). I use this option for all new projects.
  5. Data Binding: most versatile option, since it allows code inside XML. Still used on a lot of existing projects. But can slow down build times (uses annotation processor just like ButterKnife) and a lot of logic inside XML has become a bit of anti pattern.

See also: https://www.youtube.com/watch?v=Qxj2eBmXLHg

Funny to note that Jake Wharton (original author of ButterKnife) has now joined Google and works on ViewBinding.

Stiegler answered 29/9, 2019 at 13:31 Comment(0)
A
16

kotlin-android-extensions is better for Kotlin. ButterKnife is also good but kotlin-android-extensions is a better and smart choice here.

Reason : Kotlin uses synthetic properties and those are called on demand using caching function(Hence slight fast Activity/Fragment loading) while ButterKnife binds all view at a time on ButterKnife.bind()(that consumes slight more time). With Kotlin you don't even need to use annotation for binding the views.

Yes it also plays good with RecyclerView + ViewHolder pattern, you just need to import kotlinx.android.synthetic.main.layout_main.view.*(if layout_main.xml is Activity/Fragment layout file name).

You do not need to do any extra effort for layout imported using include. Just use id of imported views.

Have a look at following official documentation notes:

Kotlin Android Extensions is a plugin for the Kotlin compiler, and it does two things:

  1. Adds a hidden caching function and a field inside each Kotlin Activity. The method is pretty small so it doesn't increase the size of APK much.
  2. Replaces each synthetic property call with a function call.

    How this works is that when invoking a synthetic property, where the receiver is a Kotlin Activity/Fragment class that is in module sources, the caching function is invoked. For instance, given

class MyActivity : Activity()
fun MyActivity.a() { 
    this.textView.setText(“”)
}

a hidden caching function is generated inside MyActivity, so we can use the caching mechanism.

However in the following case:

fun Activity.b() { 
    this.textView.setText(“”)
}

We wouldn't know if this function would be invoked on only Activities from our sources or on plain Java Activities also. As such, we don’t use caching there, even if MyActivity instance from the previous example is the receiver.

Link to above documentation page

I hope it helps.

Adriene answered 29/9, 2017 at 6:2 Comment(0)
E
4

I can't flag this question as a duplicate, as you're asking multiple things that have been answered / discussed under different questions.

What are the pros and cons of each view binding approach in Kotlin?

This has been discussed here.

How does Kotlin Android Extensions handle view binding for nested views via include? ex: For an Activity using activity_main.xml, how would View custom1 be accessed?

All Kotlin Android Extensions does is call findViewById for you. See here.

Does Kotlin Android Extensions play well with the RecyclerView + ViewHolder pattern?

Yes, it does. However, you have to use save the Views you get from it into properties, as there is no cache for them like in Activities or Fragments. See here.


If you still have unanswered questions, feel free to ask for clarification.

Explosion answered 29/9, 2017 at 5:38 Comment(1)
How about the Data Binding library?Mellott
S
4

Take care of using

val button: Button by lazy { findViewById<Button>(R.id.button) }

I already confront the problem when the view is destroyed, and as the instance of your fragment survive(I think in the case of acitivities it doesn't apply), they hold the lazy property referencing to the old view.

Example:

You have an static value in the layout, let say android:text="foo"

//calling first time
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
    button.setText("bar")
    // button is called for the first time, 
    // then button is the view created recently and shows "bar"
}

Then the fragment get destroyed because you replace it, but then ou comeback and it regenerated callin onCreateView again.

//calling second after destroyed
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
    button.setText(Date().time.toString())
    //button is already set, then you are setting the value the to old view reference
    // and in your new button the value won't be assigned
    // The text showed in the button will be "foo"
}
Suzysuzzy answered 29/9, 2017 at 13:13 Comment(0)
G
3

Now there is a fourth option which is called View Binding, available with Android Studio 3.6 Carnary 11

Quoting from docs.

View Binding

View binding is a feature that allows you to more easily write code that interacts with views. Once view binding is enabled in a module, it generates a binding class for each XML layout file present in that module. An instance of a binding class contains direct references to all views that have an ID in the corresponding layout.

In most cases, view binding replaces findViewById.


Differences from findViewById

View binding has important advantages over using findViewById:

  • Null safety: Since view binding creates direct references to views, there's no risk of a null pointer exception due to an invalid view ID. Additionally, when a view is only present in some configurations of a layout, the field containing its reference in the binding class is marked with @Nullable.

  • Type safety: The fields in each binding class have types matching the views they reference in the XML file. This means that there's no risk of a class cast exception.


Differences from the data binding library

View binding and the data binding library both generate binding classes that you can use to reference views directly. However, there are notable differences:

  • The data binding library processes only data binding layouts created using the <layout> tag.
  • View binding doesn't support layout variables or layout expressions, so it can't be used to bind layouts with data in XML.

Usage

To take advantage of View binding in a module of your project, add the following line to its build.gradle file:

android {
    viewBinding.enabled = true
}

For example, given a layout file called result_profile.xml:

<LinearLayout ... >
    <TextView android:id="@+id/name" />
    <ImageView android:cropToPadding="true" />
    <Button android:id="@+id/button"
        android:background="@drawable/rounded_button" />
</LinearLayout>

In this example, you can call ResultProfileBinding.inflate() in an activity:

private lateinit var binding: ResultProfileBinding

@Override
fun onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)
    binding = ResultProfileBinding.inflate(layoutInflater)
    setContentView(binding.root)
}

The instance of the binding class can now be used to reference any of the views:

binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }
Gramineous answered 12/9, 2019 at 4:21 Comment(3)
I get "unresolved reference" on the attempt to declare binding: ResultProfileBinding. I've added the lines to enable view-binding in my module's Gradle file, and this is in Android Studio 4. Any ideas?Minimum
@Minimum did you rename the fragment? if so make sure binding name is correct.Gramineous
Thanks. The problem was the name of the generated class. I was doing one of Google's tutorials that starts in the main activity, so of course the name should have been ActivityMainBinding.Minimum
B
0

if you using datainding library. you should databinding view binding.

because it is explict more then kotlin-extensions

p.s findviewbyid is very inconvenience and boilerplate code

Bren answered 13/2, 2019 at 2:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.