Skip to content

Instantly share code, notes, and snippets.

@chrisbanes
Last active February 7, 2021 15:25
Show Gist options
  • Star 49 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save chrisbanes/fc4392dcbdc0aa5d99147dc551616676 to your computer and use it in GitHub Desktop.
Save chrisbanes/fc4392dcbdc0aa5d99147dc551616676 to your computer and use it in GitHub Desktop.
LifecycleAware KotterKnife
package kotterknife
import android.app.Activity
import android.app.Dialog
import android.app.DialogFragment
import android.app.Fragment
import android.arch.lifecycle.Lifecycle
import android.arch.lifecycle.LifecycleObserver
import android.arch.lifecycle.LifecycleOwner
import android.arch.lifecycle.OnLifecycleEvent
import android.support.v7.widget.RecyclerView.ViewHolder
import android.view.View
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
import android.support.v4.app.DialogFragment as SupportDialogFragment
import android.support.v4.app.Fragment as SupportFragment
fun <V : View> View.bindView(id: Int)
: ReadOnlyProperty<View, V> = required(id, viewFinder)
fun <V : View> Activity.bindView(id: Int)
: ReadOnlyProperty<Activity, V> = required(id, viewFinder)
fun <V : View> Dialog.bindView(id: Int)
: ReadOnlyProperty<Dialog, V> = required(id, viewFinder)
fun <V : View> DialogFragment.bindView(id: Int)
: ReadOnlyProperty<DialogFragment, V> = required(id, viewFinder)
fun <V : View> SupportDialogFragment.bindView(id: Int)
: ReadOnlyProperty<SupportDialogFragment, V> = required(id, viewFinder)
fun <V : View> Fragment.bindView(id: Int)
: ReadOnlyProperty<Fragment, V> = required(id, viewFinder)
fun <V : View> SupportFragment.bindView(id: Int)
: ReadOnlyProperty<SupportFragment, V> = required(id, viewFinder)
fun <V : View> ViewHolder.bindView(id: Int)
: ReadOnlyProperty<ViewHolder, V> = required(id, viewFinder)
fun <V : View> View.bindOptionalView(id: Int)
: ReadOnlyProperty<View, V?> = optional(id, viewFinder)
fun <V : View> Activity.bindOptionalView(id: Int)
: ReadOnlyProperty<Activity, V?> = optional(id, viewFinder)
fun <V : View> Dialog.bindOptionalView(id: Int)
: ReadOnlyProperty<Dialog, V?> = optional(id, viewFinder)
fun <V : View> DialogFragment.bindOptionalView(id: Int)
: ReadOnlyProperty<DialogFragment, V?> = optional(id, viewFinder)
fun <V : View> SupportDialogFragment.bindOptionalView(id: Int)
: ReadOnlyProperty<SupportDialogFragment, V?> = optional(id, viewFinder)
fun <V : View> Fragment.bindOptionalView(id: Int)
: ReadOnlyProperty<Fragment, V?> = optional(id, viewFinder)
fun <V : View> SupportFragment.bindOptionalView(id: Int)
: ReadOnlyProperty<SupportFragment, V?> = optional(id, viewFinder)
fun <V : View> ViewHolder.bindOptionalView(id: Int)
: ReadOnlyProperty<ViewHolder, V?> = optional(id, viewFinder)
fun <V : View> View.bindViews(vararg ids: Int)
: ReadOnlyProperty<View, List<V>> = required(ids, viewFinder)
fun <V : View> Activity.bindViews(vararg ids: Int)
: ReadOnlyProperty<Activity, List<V>> = required(ids, viewFinder)
fun <V : View> Dialog.bindViews(vararg ids: Int)
: ReadOnlyProperty<Dialog, List<V>> = required(ids, viewFinder)
fun <V : View> DialogFragment.bindViews(vararg ids: Int)
: ReadOnlyProperty<DialogFragment, List<V>> = required(ids, viewFinder)
fun <V : View> SupportDialogFragment.bindViews(vararg ids: Int)
: ReadOnlyProperty<SupportDialogFragment, List<V>> = required(ids, viewFinder)
fun <V : View> Fragment.bindViews(vararg ids: Int)
: ReadOnlyProperty<Fragment, List<V>> = required(ids, viewFinder)
fun <V : View> SupportFragment.bindViews(vararg ids: Int)
: ReadOnlyProperty<SupportFragment, List<V>> = required(ids, viewFinder)
fun <V : View> ViewHolder.bindViews(vararg ids: Int)
: ReadOnlyProperty<ViewHolder, List<V>> = required(ids, viewFinder)
fun <V : View> View.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<View, List<V>> = optional(ids, viewFinder)
fun <V : View> Activity.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<Activity, List<V>> = optional(ids, viewFinder)
fun <V : View> Dialog.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<Dialog, List<V>> = optional(ids, viewFinder)
fun <V : View> DialogFragment.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<DialogFragment, List<V>> = optional(ids, viewFinder)
fun <V : View> SupportDialogFragment.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<SupportDialogFragment, List<V>> = optional(ids, viewFinder)
fun <V : View> Fragment.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<Fragment, List<V>> = optional(ids, viewFinder)
fun <V : View> SupportFragment.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<SupportFragment, List<V>> = optional(ids, viewFinder)
fun <V : View> ViewHolder.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<ViewHolder, List<V>> = optional(ids, viewFinder)
private val View.viewFinder: View.(Int) -> View?
get() = { findViewById(it) }
private val Activity.viewFinder: Activity.(Int) -> View?
get() = { findViewById(it) }
private val Dialog.viewFinder: Dialog.(Int) -> View?
get() = { findViewById(it) }
private val DialogFragment.viewFinder: DialogFragment.(Int) -> View?
get() = { dialog.findViewById(it) }
private val SupportDialogFragment.viewFinder: SupportDialogFragment.(Int) -> View?
get() = { dialog.findViewById(it) }
private val Fragment.viewFinder: Fragment.(Int) -> View?
get() = { view.findViewById(it) }
private val SupportFragment.viewFinder: SupportFragment.(Int) -> View?
get() = { view!!.findViewById(it) }
private val ViewHolder.viewFinder: ViewHolder.(Int) -> View?
get() = { itemView.findViewById(it) }
private fun viewNotFound(id:Int, desc: KProperty<*>): Nothing =
throw IllegalStateException("View ID $id for '${desc.name}' not found.")
@Suppress("UNCHECKED_CAST")
private fun <T, V : View> required(id: Int, finder: T.(Int) -> View?)
= Lazy { t: T, desc -> t.finder(id) as V? ?: viewNotFound(id, desc) }
@Suppress("UNCHECKED_CAST")
private fun <T, V : View> optional(id: Int, finder: T.(Int) -> View?)
= Lazy { t: T, desc -> t.finder(id) as V? }
@Suppress("UNCHECKED_CAST")
private fun <T, V : View> required(ids: IntArray, finder: T.(Int) -> View?)
= Lazy { t: T, desc -> ids.map { t.finder(it) as V? ?: viewNotFound(it, desc) } }
@Suppress("UNCHECKED_CAST")
private fun <T, V : View> optional(ids: IntArray, finder: T.(Int) -> View?)
= Lazy { t: T, desc -> ids.map { t.finder(it) as V? }.filterNotNull() }
// Like Kotlin's lazy delegate but the initializer gets the target and metadata passed to it
private class Lazy<T, V>(private val initializer: (T, KProperty<*>) -> V)
: ReadOnlyProperty<T, V>, LifecycleObserver {
private object EMPTY
private var value: Any? = EMPTY
private var attachedToLifecycleOwner = false
override fun getValue(thisRef: T, property: KProperty<*>): V {
checkAddToLifecycleOwner(thisRef)
if (value == EMPTY) {
value = initializer(thisRef, property)
}
@Suppress("UNCHECKED_CAST")
return value as V
}
private fun checkAddToLifecycleOwner(thisRef: T) {
if (!attachedToLifecycleOwner && thisRef is LifecycleOwner) {
thisRef.lifecycle.addObserver(this)
attachedToLifecycleOwner = true
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun destroy() {
value = EMPTY
}
}
@cs-lucas-conceicao
Copy link

At line 99 why not use ?. instead !! so it will be null-safe?

@JakeWharton
Copy link

That would hide bugs. The absence of a view at a time when you're trying to touch the view is crash-worthy.

@kassim
Copy link

kassim commented Nov 21, 2018

new import statements for androidx :)

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import androidx.fragment.app.DialogFragment as SupportDialogFragment
import androidx.fragment.app.Fragment as SupportFragment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment