Jordan
Jordan

Reputation: 417

How to use a lambda instead of a kotlin interface

I have a recycler view adapter in android. Part of my adapter class looks like this:

private lateinit var itemLongClick: ItemLongClick

override fun onCreateViewHolder(parent: ViewGroup, a: Int): RecyclerAdapter.ViewHolder {

      // Define And Initialize The Custom View And Its Holder//
      val myView = LayoutInflater.from(parent.context).inflate(customLayout, parent, false)
      val viewHolder = ViewHolder(myView)

      // What Happens When A List Item Is Long Clicked//
      myView.setOnLongClickListener { view ->

          // Differ Action To Class Instance//
          itemLongClick.longClicked(context, viewHolder.layoutPosition, view)

          // End Function//
          true
      }

      // Returns The Custom View//
      return viewHolder
}

fun setItemLongClick(itemLongClick: ItemLongClick) {

    // Sets The Value For this.itemLongClick//
    this.itemLongClick = itemLongClick
}    

I created an interface tat looks like this:

interface ItemLongClick {

    // Function Declaration For When An Item Is Long Clicked//
    fun longClicked(context: Context, position: Int, view: View)
}

Instead of writing my on long click code in the adapter class I want to differ it to the activity that is calling the adapter class. I know one way of doing this is to make a kotlin interface then call it in the other class like this

  userAdapter.setItemLongClick(object: ItemLongClick {
        override fun longClicked(context: Context, position: Int, view: View) {

        }
    })

But this looks messy. I know java interfaces work with SAM but I don't want to do that either. What I want is for the onLongClick to be a Lambda but I'm not sure how to set up a Kotlin lambda expression to make this work and I can't find a good example anywhere.

Thanks in advance

Upvotes: 5

Views: 2980

Answers (4)

cesards
cesards

Reputation: 16359

As the Kotlin documentation for the Kotlin 1.4 release points out:

Before Kotlin 1.4.0, you could apply SAM (Single Abstract Method) conversions only when working with Java methods and Java interfaces from Kotlin. From now on, you can use SAM conversions for Kotlin interfaces as well. To do so, mark a Kotlin interface explicitly as functional with the fun modifier.

fun interface Operation1 {
    operator fun invoke(x: String): String
}

fun interface Operation2 {
    fun doSomething(x: Int): String
}

val operation1 = Operation1 { "$it world!" }
val operation2 = Operation2 { "$it world!" }

fun main() {
    // Usage: First sample.
    println(operation1("Hello"))
    println(operation2.doSomething(0))
    // Usage: Second sample.
    println(Operation1 { "$it world!" }("Hello"))
    println(Operation2 { "$it!" }.doSomething(0))
}

You can read more about functional interfaces here.

Upvotes: 2

Shahbaz Hashmi
Shahbaz Hashmi

Reputation: 2813

In below code I using filterable adapter to do search on list. Here I am using lambda as callback to notify to view model when no data is found for the search.

Instantiating Adapter in ViewModel. And passing lambda

var matterAdapter = MatterAdapter(matterList) {
    //todo - got callback
}

Adapter

class MatterAdapter (var filteredList : MutableList<AndroidViewModel>, val funcNoSearchData : () -> Unit) : DataBindingRecyclerViewAdapter(filteredList), Filterable {

private var mViewModelMap: MutableMap<Class<*>, Int> = mutableMapOf()

private var originalList : MutableList<AndroidViewModel> = mutableListOf()

private val mFilter = ItemFilter()

init {
    mViewModelMap.put(MatterRowViewModel::class.java, R.layout.row_matter)
}

override fun getViewModelLayoutMap(): MutableMap<Class<*>, Int> {
    return mViewModelMap
}

override fun getFilter(): Filter {
    return mFilter
}

private inner class ItemFilter : Filter() {
    override fun performFiltering(constraint: CharSequence): FilterResults {

        val filterString = constraint.toString().toLowerCase()

        val results = FilterResults()

        val list = originalList

        val count = list.size
        val nlist = ArrayList<AndroidViewModel>(count)

        var filterableString: String

        for (i in 0 until count) {
            filterableString = (list.get(i) as MatterRowViewModel).matter.casestitle!!
            if (filterableString.toLowerCase().contains(filterString)) {
                nlist.add(list.get(i))
            }
        }

        results.values = nlist
        results.count = nlist.size

        return results
    }

    override fun publishResults(constraint: CharSequence, results: Filter.FilterResults) {
        filteredList.clear()
        filteredList.addAll(results.values as ArrayList<AndroidViewModel>)
        // sends empty search callback to viewmodel
        if(filteredList.size == 0) {
            funcNoSearchData()
        }
        notifyDataSetChanged()
    }
  }

  fun resetSearch() {
      filteredList.clear()
      filteredList.addAll(originalList)
      notifyDataSetChanged()
  }

  fun refreshData() {
      originalList = ArrayList(filteredList)
      notifyDataSetChanged()
  }
}

Upvotes: 0

I had an adapter that i need to change the data based on a switch and i did something like this:

ListAdapter(private val context: Context, private val switchListener: (Boolean) -> Unit)

Then where i binded the header of my sectioned list i had:

private fun bindHeader(holder: HeaderViewHolder) {
        holder.switch.setOnCheckedChangeListener { _, isChecked ->
            callbackSwitchListener(isChecked)
        }
    }

And in my fragment:

private fun setupRecyclerView() {
        fabricationDataListAdapter =
                FabricationDataListAdapter(context!!) { isChecked: Boolean -> switchControl(isChecked) }
        val layoutManager = ListLayoutManager(context!!)
        this.recycler_view_all.layoutManager = layoutManager
        this.recycler_view_all.adapter = fabricationDataListAdapter
    }

Where the fun switchControl changed the data based on the boolean.

I'm not sure if this is what you need, i'm in a bit of a hurry, but this is called high order functions in kotlin, if i'm not mistaken.

Upvotes: 2

EpicPandaForce
EpicPandaForce

Reputation: 81588

You have two options:

1.) replace interface with typealias

typealias ItemLongClick = (Context, Int, View) -> Unit

2.) add an extension function for setting the interface as a lambda instead of with anonymous object

inline fun UserAdapter.setItemLongClick(crossinline longClick: (Context, Int, View) -> Unit) {
    setItemLongClick(object: ItemLongClick {
        override fun longClicked(context: Context, position: Int, view: View) {
            longClick(context, position, view)
        }
    })
}

Now you can call

userAdapter.setItemLongClick { context, position, view -> 
    ...
}

Upvotes: 8

Related Questions