There are many view binding libraries in Android, especially when you use kotlin. This article will compare view binding libraries, problems of each library has, and how ButterKt solves those problems.

Link to ButterKt Github : https://github.com/Rajin9601/ButterKt

View binding methods in android with kotlin

No library

Without library, you can call findViewById and assigns the view into variable.

class MainActivity: Activity {
  lateinit var idEditText: EditText
  override onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)
    idEditText = findViewById(R.id.id_input)
  }
}

We don’t want this kind of code because declaration and assignment are seperated. We can forget to assign value with findViewById.

ButterKnife

ButterKnife solves this problem with apt and code generation.

class MainActivity: Activity {
  @BindView(R.id.id_input)
  lateinit var idEditText: EditText

  override onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)
    ButterKnife.bind(this)
  }
}

Using apt, code is generated for view binding. When ButterKnife#bind is called, all views in the class is bound. Even though ButterKnife#bind needs to be explicitly called, this is good enough.

KotterKnife

KotterKnife uses kotlin’s property delegation to make declaration more simpler than ButterKnife.

class MainActivity: Activity {
  val idEditText: EditText by bindView(R.id.id_input)

  override onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)
  }
}

There is no need to call ButterKnife#bind. Only declaration is enough. This is possible because view binding happens lazy, which means view is bound at the first access to idEditText.

Kotlin Android Extension

Using Kotlin Android Extension, additional code for binding is not needed. View is accessble as if it already exists in the class.

class MainActivity: Activity {
  override onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)
    id_input.setText("test")
  }
}

Kotlin Android Extension is really convienient because you don’t need to do anything for the binding to work. All you need to do is import specific package for layout file and views are accessible with the name same as id in xml file.

import kotlinx.android.synthetic.main.<layout>.*

Features wanted from view binding libraries

In this article, 4 methods for view binding are mentioned. View#findViewById, ButterKnife, KotterKnife and Kotlin Android Extension. While considering which method to use, there are 3 features I wanted from View Binding Libraries.

Declaration and assignment should be together

If you do not use any library, you should declare variable and assign the view by findViewById in onCreate method. Not only this is cumbersome, but also it reduces code’s readability. To find out which view is bound to idEditText, programmer needs to go to the actual assignment, which is in onCreate method. Also by putting declaration and assignment together, programmer is not likely to forget assignment.

Binding timing should be earlier than dynamic creation of other view

In KotterKnife and Kotlin Android Extension, programmer don’t need to write any code in onCreate method. This is because these two library binds view lazily. When code access the view for the first time, it binds the view to a variable. After first access, it uses the binded view. This could cause problem when layout id overlaps.

This code example explains problem of lazy binding. Note that unnecessary codes are deleted to shorten the code.

class MainActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    setContentView(R.layout.activity_main)
    // activity_main has recycler_view and text_view

    recycler_view.adpater = MyAdapter()

    recyclerView.postDelayed({
      textView.setText(R.string.testing_str)
      // BUG HERE : this text_view accesses to item view in recycler view.
    }, 1000)
  }
}

class MyAdapter: RecyclerView.Adapter<MyAdapter.ViewHolder>() {
  override fun onCreateViewHolder(parent: ViewGroup,
                                  viewType: Int): MyAdapter.ViewHolder {
    val textView = LayoutInflater.from(parent.context)
        .inflate(R.layout.my_text_view, parent, false)
    // my_text_view has TextView with id of text_view
    return ViewHolder(textView)
  }
}

To prevent this, you can a) use distinct layout id or b) binds view before dynamic view creation. Using distinct layout id can be one solution, but layout id can be unnecessarily long. To me, binded view’s name should not have context of which layout it exists because class name already have that context.

To see the actual behavior of this code, visit https://github.com/Rajin9601/ButterKt/tree/master/lazy-binding-sample-app

If possible, no apt and code generation

By using apt for code generation, programmer can reduce repeated code. This is good, but if same feature can be implemented without code generation, I prefer not to use apt and code generation. Annotation processing have some overload of going through the source. (which is really small but still).

Overall view for binding library.

Library Declaration and assignment Binding Timing
(Not lazy)
No APT
View#findViewById x o o
ButterKnife o o x
KotterKnife o x o
Kotlin Android Extension o x o

Not a single library don’t fill my needs for view binding. ButterKnife would be my choice, since no apt is the least important. However with kotlin, ButterKnife could be implemented without code generation, which means view binding library with all these 3 features can be implemented in kotlin.

Introducing ButterKt

Code itself is similar to KotterKnife, but usage syntax is more like ButterKnife.

class MainActivity : AppCompatActivity() {
  private val title by bindView(R.id.item_title)

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    ButterKt.bind(this)
  }
}

This has additional benefits other than mentioned features above.

  • can use view as private field
  • no need for lateinit var
  • can use different naming from layout id

How does it work?

ButterKt keeps track of ViewHolder - ReadOnlyProperty pair with singleton multimap, view holder as key. When ButterKt#bind is called, ButterKt gets all ReadOnlyProperty related to the view holder and binds it to view.

In case programmer forget to add ButterKt#bind or bind call wasn’t excuted because of some reason, ButterKt uses WeakHashmap and WeakReference for GC to work properly and supports lazy loading if it can.

For more information, visit https://github.com/Rajin9601/ButterKt and see actual code.


Sidenote about Android Kotlin Extension (AKE)

Many people prefer Android Kotlin Extension over other view binding library. This is understandable because it doesn’t need any code for view binding to work. For someone who is interested in AKE, this section provides comparison between ButterKt and AKE.

Points where AKE is better than ButterKt

ButterKt AKE
Creates one object per binding No extra instance
Need explicit binding (and unbind in Fragment#onDestroyView) No code needed for binding and unbinding

ButterKt has some drawbacks. It creates one object and indirection per binding because of how kotlin’s property delegation works. Also, you may forget to unbind view in Fragment#onDestoryView. These drawbacks don’t exist in AKE.

Points where ButterKt is better than AKE

ButterKt AKE
Free from lazy binding problem Need to use distinct layout id accross all xmls
Can rely on IDE auto-complete Need to check which package is imported
Can use short naming (e.g. title) Use distinct naming (e.g. todoTaskItemTitle) or becareful of lazy binding problem

What I most disliked about AKE is that it is implemented as extension of Activity(View, Fragment, etc). Any activity have access to an xml’s view if xml’s package is imported. We generally don’t rely on which import is used in kotlin file which makes AKE dangerous of importing wrong package. By making it as extension of Activity, AKE doesn’t use the benefit of type system, which is sad.

Another point I’m against AKE is that I personally think that view’s naming should not contain additional info about which xml it is in, which means I prefer naming such as title over todoTaskItemTitle. Because Code scope already defines which item the class represents, view naming does not need to contain information about it.