Migrating to Kotlin Android Extensions

Moyinoluwa Adeyemi
Power the People
Published in
3 min readJan 28, 2019

--

At Zola Electric, we recently migrated completely off ButterKnife to Kotlin Android Extensions and this article shows the common use-cases we had.

Yuuuuuuuge PR!

ButterKnife was very useful for us while we had it until it wasn’t. Our gradle build speeds were slowing down and avoiding annotation processors was a small step in the right direction. We also were happening on a lot of errors that were usually resolved with a clean and rebuild, but sometimes required invalidating caches and restarting Android Studio.

There are a lot of articles that already explain what Kotlin Android Extensions are and how to set it up (I recommend this article by Antonio Leiva) so I won’t be covering that here. We used Butterknife in a lot of different ways in our app—to bind values, bind views and set listeners — and I’ll explain how they were replaced in this article.

Binding values (e.g. booleans, dimensions)

Here’s an example of how we used the BindDimen annotation.

@JvmField
@BindDimen(R.dimen.height)
var dialogSizePx: Int = 0
dialog.window?.setLayout(MATCH_PARENT, dialogSizePx)

And here’s how it was replaced:

dialog.window?.setLayout(MATCH_PARENT,
context?.dimen(R.dimen.height))

Binding views and setting listeners

In Activities

We now call views in anActivity with their ids. The major things to watch out for are the listeners which were previously called with an annotation on a method but now have to be set manually.

Here’s how we did that with Butterknife:

class MainActivity {

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

@OnClick(R.id.cancel_button)
fun handleCancelClick() {
// do something
}
}

And how we do that now with the Kotlin Android Extensions:

class MainActivity {

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

cancel_button.setOnClickListener { handleCancelClick() }
}
fun handleCancelClick() {
// do something
}
}

In Fragments

This works the same way as in an Activity but with one major difference. A NullPointerException occurs whenever a view is called from the onCreateView method. All views should be called from the onViewCreated method instead.

In Custom DialogFragments

Even though the DialogFragment extends the Fragment, the onViewCreated method is never called by default. This is because the onViewCreated method should be called immediately after onCreateView but since the onCreateDialog is used instead in aDialogFragment then that never happens.

So, overriding the onViewCreated method here will not result in any action. Instead, we can override the getView method and pass in a containerView. This will allow us call the views normally from the onCreateDialog method.

val containerView by lazy {
inflate(context, R.layout.fragment_dialog, null) as ViewGroup
}

override fun getView() = containerView
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(requireContext())

text_view.setOnFocusChangeListener { v, _ -> closeKeyboard(v) }

builder.setView(containerView)

return builder.create()
}

In Custom Views

We implement a LayoutContainer and override the containerView. The layout is inflated in the initializer block and listeners are set in the onFinishInflate method.

class LoginMethodView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.style.AppTheme
) : ScrollView(context, attrs, defStyleAttr), LayoutContainer {

override val containerView: View?
get() = this

init {
inflate(context, R.layout.view_login_method, this)
}

public override fun onFinishInflate() {
super.onFinishInflate()

hello_button.setOnClickListener { sayHello() }
}
}

In ViewHolders

We implement a LayoutContainer in the ViewHolder class, override the containerView in the constructor and handle any listeners with an initializer block.

With ButterKnife:

inner class ViewHolder(v: View) : RecyclerView.ViewHolder(v),    View.OnClickListener {

@BindView(R.id.appliance_image) lateinit var applianceImage: ImageView

init {
ButterKnife.bind(this, v)
v.setOnClickListener(this)
}

override fun onClick(v: View?) {
// handle view click
}
}

With KAE:

inner class ViewHolder(override val containerView: View)
: RecyclerView.ViewHolder(containerView),
View.OnClickListener,
LayoutContainer {
init {
containerView.setOnClickListener(this)
}

override fun onClick(v: View?) {
// handle view click
}
}

This was a very huge change for us and it took some time to complete, but we believe it’s a step in the right direction. Have you found other ways to use the Kotlin Android Extensions? Please let us know in the comments!

--

--