Skip to content

Instantly share code, notes, and snippets.

@strooooke
Created September 10, 2020 13:39
Show Gist options
  • Save strooooke/0d8b0f43a15bf767f3178964e7afe984 to your computer and use it in GitHub Desktop.
Save strooooke/0d8b0f43a15bf767f3178964e7afe984 to your computer and use it in GitHub Desktop.
BoundFunction - directly binding callbacks for RecyclerView items
// Using ListAdapter for the shorter example; the same holds for any use of (Async)ListDiffer - the point here
// is about easy implementation of [areContentsTheSame] by just using equality on data class items.
// The expectation here is that updates to the item list result in the correct adapter notifications (and resulting
// animations).
class ExampleAdapter : ListAdapter<ExampleViewHolder, ExampleItem>(ExampleDiffCallback) {
object ExampleDiffCallback : DiffUtil.ItemCallback<ExampleItem> {
override fun areItemsTheSame(oldItem: ExampleItem, newItem: ExampleItem) = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: ExampleItem, newItem: ExampleItem) = oldItem == newItem
}
// skipped: onCreateViewHolder, onBindViewHolder etc.
class ExampleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(item: ExampleItem) {
with(itemView) {
setText(item.name)
setOnClickListener { item.onClick() }
}
}
}
}
data class ExampleItem(val id: String, val name: String, val onClick: () -> Unit)
// First try: instantiated in presenter like
ExampleItem(thingy.id, thingy.name, { onItemClicked(thingy.id) })
// And that, because of inequality of each separately instantiated lambda, does not result in the desired behaviour.
// Success: instantiate like
ExampleItem(thingy.id, thingy.name, BoundFunction1(this::onItemClicked, thingy.id))
// and all is well. Note that this still relies on the method reference here - substituing `this::onItemClicked` by
// `{ onItemClicked(it }` would fail again for the same reason.
// And now, to build a pit of success here, let's change the signature of ExampleItem to
data class ExampleItem(val id: String, val name: String, val onClick: BoundFunction1<String, Unit>)
// Not arguing here that this is a good (or even the best) architecture for this case - but it does have it's advantages,
// especially when many distinct item types in distinct states with several distinct callbacks are in play. So at least
// for this specific drawback, here's a fix.
/**
* A 1-arg function, with the argument already bound - supporting equality in a way that the otherwise-equivalent lambda expression
* does not.
* Example:
*
* ```
* val aLambda = { this.someFun(1) }
* val anotherLambda = { this.someFun(1) }
*
* assertEquals(aLambda == anotherLambda, false)
*
* val aBoundFun = BoundFunction1(this::someFun, 1)
* val anotherBoundFun = BoundFunction1(this::someFun, 1)
*
* assertEquals(aBoundFun == anotherBoundFun, true)
* ```
*/
data class BoundFunction1<T, R>(val function: (T) -> R, val boundArg: T) : Function0<R> {
override fun invoke(): R{
return function.invoke(boundArg)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment