I want to create a custom onClickListener to use for databinding. This custom click listener prevents the user from spamming the button and triggering the event twice(like showing two dialogues at the same time). I made a custom listener below that works in normal code, but I don't know how to implement it for databinding like the android:onClick="" in xml.
abstract class OneClickListener(var delay: Long) : View.OnClickListener {
private var hasClicked: Boolean = true
constructor() : this(1000)
override fun onClick(it: View) {
if (!hasClicked) {
return
} else {
hasClicked = false
onClicked(it)
GlobalScope.launch {
delay(delay)
hasClicked = true
}
}
}
abstract fun onClicked(it: View)
}
Is it possible to use this listner in databinding like for example
app:OneClickListener="#{viewModel::MyMethod}" in XML? and if yes, could you please tell me how?
Using data binding you can specify which listener to call when an event is fired just by calling it in a lambda. For example, let's say you have a method in your viewmodel, called myOnClick(). You can use it with data binding this way:
android:onClick="#{() -> viewModel.myOnClick()}"
Defining a custom binding adapter called OneClickListener is something different and it would not be called when the click event is fired, unless you use a trick: registering a click listener inside the custom binding adapter. This means you would have to call a method that register an other method: not really the cleanest way to add a listener.
Related
Im trying to create an app with a recyclerview, and I am trying to figure out the following code from an android example. Like what is the onClick value they are putting in the first class, and what is the lambda expression for and what does it do? I notice a similar lambda is in the class below it as well. If anyone can please explain the code. Thank you.
class FlowersAdapter(private val onClick: (Flower) -> Unit) :
ListAdapter<Flower, FlowersAdapter.FlowerViewHolder>(FlowerDiffCallback) {
/* ViewHolder for Flower, takes in the inflated view and the onClick behavior. */
class FlowerViewHolder(itemView: View, val onClick: (Flower) -> Unit) :
RecyclerView.ViewHolder(itemView) {
private val flowerTextView: TextView = itemView.findViewById(R.id.flower_text)
private val flowerImageView: ImageView = itemView.findViewById(R.id.flower_image)
private var currentFlower: Flower? = null
init {
itemView.setOnClickListener {
currentFlower?.let {
onClick(it)
}
}
}
/* Bind flower name and image. */
fun bind(flower: Flower) {
currentFlower = flower
flowerTextView.text = flower.name
if (flower.image != null) {
flowerImageView.setImageResource(flower.image)
} else {
flowerImageView.setImageResource(R.drawable.rose)
}
}
}
The onClick parameter has a type of (Flower) -> Unit. That represents a function, which takes a single Flower parameter (in the parentheses) and returns Unit (i.e. "doesn't return anything").
That means that onClick is a function, and you can call it like onClick(someFlower), which is what's happening in that click listener set up in the init block. The naming might make it a little confusing, but it's basically this:
pass in some handler function
set up a click listener on itemView
when itemView is clicked, call the handler function, passing currentFlower
so it's just a way for you to provide some behaviour to handle a flower being clicked. You still need the click listener - that's a thing that operates on a View and handles click interactions. But inside that listener, you can do what you like when the click is detected, and in this case it's running some externally provided function
I'm trying to implement an item click event of a recyclerview.
In Java, the typical method of creating an interface in an adapter for clicking an item, implementing it in an activity, and passing an anonymous object to the adapter was used.
However, Kotlin accepts lambda expressions and can pass them as arguments.
So I don't necessarily need to use an interface, am I?
Or is there some good reason to use interfaces as much as possible?
You can use high-order functions to accomplish that without using interfaces.
Here is an example:
Fragment/Activity
recycler_view_photos.adapter = PhotosAdapter {
actionAfterClickOnItem()
}
private fun actionAfterClickOnItem() {
//stuff
}
Adapter
class PhotosAdapter(val onItemClicked: () -> Unit) {
//stuff
inner class PhotoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(photo: Photo) {
//stuff
itemView.setOnClickListener {
onItemClicked.invoke()
}
}
}
}
Interfaces are good to create contracts between components, give a type to a class, and create events, but with Kotlin as you mentioned, you can use lambdas and do the same thing with fewer lines of code.
Interfaces are best for click listeners in recyclerView like you have views in each view are three buttons like delete, remove and add button so just make three methods in interface
fun onDeleteClick(position,Item) //Here item of that model which is passed in adapter
fun onAddClick(position,Item)
fun onRemoveClick(position,Item)
implement this interface with your activity and pass it in your adapter and then in your adapter create click listener like this
Holder.itemBinding.delete.setOnClickListener{ clickListener.OnDeleteClick(position, Item) }
So instead of passing lambda function for each button just use interface for all clicks and override in your activity
from reading many website im aware that the optimal way handling onclick on recycerview was to set clicklistener not in OnBindViewHolder, its either in onCreateViewHolder(explained in here) or pass the clickListener in "bind" method(explained in here).
but one thing that really bother me, was how clickListener handled in activity / fragment
val adapter : MyAdapter(){
//click goes here
}
it may be not much but, honestly its not so so readable
i prefer to handle click listener not in constuctor, but with on separate method, meyabe like this
adapter.onItemClickListner = {
val adapter = MyAdapter()
//click more readable, yay!
}
but im not sure, if its will affect performance or not, or do i really need to stick with click on constructor?
I myself have used this method before.. Instead of passing the listener in the constructor, I have set it explicitly from the method call as you described. When I used that approach I had to make sure in the adapter that i checked for initialization of the listener and its null safety. So, you'll have to make sure before calling that it's absolutely present and initialized before calling the listener methods in the adapter.
I gradually moved to constructor as I didn't want to have such conscious checks all over my adapter before any click. (When there were like multiple clickable views.)
create a interface which has some function to handle your onClick.
Now you need to implement this interface in your activity class like this :-
class MainActivity : InterfaceAdapterOnClick (Here InterfaceAdapterOnClick is an interface which will have some functions to handle your onClick)
Let's suppose we have one function named onClickItem in our interface.
So now when you create your adapter like this adapter = MyAdapter() you can pass your interface with that. So it should look like :-
var adapter = MyAdapter(this) (this because you have already implemented interface in your mainActivity class) and your adapter should look like :-
class MyAdapter(onClickHandler: InterfaceAdapterOnClick) {}
So with this at the end you will have a seperate interface method in your mainActivity and your mainAcitvity should look like :-
class MainActivity : InterfaceAdapterOnClick {
// This will be your seperate onClick handler for your recyclerView item
override func onClickItem(// You can take some parameter as input here like `posistion` or `adapterItem` here ) {
}
}
IMHO, If your adapter needs that itemClickListener to function properly then you should pass it through its constructor and passing it using a setter function is not good since you might forgot to pass it at some point you forget to pass the listener and waste your time to check why clicking on items does not work! (It happened to me :D). If you are concerned about readability then you can use an interface and pass the implementation to adapter or use some features of Kotlin like named parameter or pass the callable reference of a function to make it more readable. For example something like this
class MyAdapter(onItemClick: (MyData) -> Unit) {
// ...
}
And initialize it like this
val adapter = MyAdapter(
onItemClick = { data ->
// ...
}
)
or pass the function like this
class MyActivity: AppCompatActivity() {
override fun onCreateView(/**/) {
// ...
val adapter = MyAdapter(
onItemClick = ::onItemClick
)
// or
// val adapter = MyAdapter(::onItemClick)
}
private fun onItemClick(data: MyData) {
}
}
you can simply pass an interface between the activity and the adapter class but the best method is by passing a function to your adapter class, like this
adapter = BasketRVA { basketEntity: BasketEntity, minus: Boolean -> changeQuantity(basketEntity, minus) }
private fun changeQuantity(basketEntity: BasketEntity, minus: Boolean) {
///your code
}
and in the adapter type this
class BasketRVA(private val clickListener: (BasketEntity, Boolean)->Unit): RecyclerView.Adapter<BasketRVA.BasketViewHolder>()
and in your viewHolder class and bind function
inner class BasketViewHolder(binding: BasketItemBinding): RecyclerView.ViewHolder(binding.root) {
#SuppressLint("SetTextI18n")
fun bind(basketEntity: BasketEntity?, clickListener: (BasketEntity, Boolean) -> Unit){
binding.tvProductName.text = basketEntity?.productName
in your binding function create a click listener for your view and pass the function you want to proceed
binding.imMinus.setOnClickListener {
val minus = true
clickListener(basketEntity!!, minus)
}
in your bind viewHolder
override fun onBindViewHolder(holder: BasketViewHolder, position: Int) {
BasketViewHolder(binding).bind(basketList[position], clickListener)
Log.d(TAG, "onBindViewHolder: basket list position is ${basketList[position]}")
}
Ask me if you still have any question
How can an activity access the instance of the view, after it's been set using SetContentView? I need this because the activity uses a custom view which includes logic and I need this view to sent events to the activity, through a custom event listener that the activity needs to set in the view.
I'm programming with android studio in kotlin.
I previously had all the UI control logic in the activity so I was fine, but I am factoring some UI code in a Custom View to re-use it in several activities.
Here is the initialization of the activity
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.custom_view)
// Here need to access the view instance
*xxxxxxx*.setCustomViewListener(new CustomView.MyCustomViewListener() {
#Override
public void onCancelled() {
// Code to handle cancellation from the view controls
}
});)
}
}
Here is the view layout
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:id="#+id/button_do"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="Do" />
<com.kotlin.app.views.CustomView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/view_custom" />
</FrameLayout>
Here is the custom view class CustomView.kt
class CustomView : FrameLayout, View.OnClickListener {
constructor(context: Context) : super(context)
init {
}
interface CustomViewListener {
fun onCancelled()
}
private var listener: CustomViewListener? = null
fun setCustomViewListener(listener: CustomerViewListener) {
this.listener = listener
}
Any idea please?
In Kotlin, it's more common to use Kotlin synthetic:
view_custom.setCustomViewListener(...)
Note: you seem to have written your listener implementation in Java, not Kotlin. Since your interface is defined in Kotlin you need something like this:
view_custom.setCustomViewListener(object : CustomView.MyCustomViewListener {
override fun onCancelled() {
...
}
})
SAM interfaces in Kotlin
Personally I like to use lambdas. Unfortunately you cannot use a lambda as a Kotlin SAM interface. You could however use a typealias instead of an interface:
typealias MyCustomerViewListener = () -> Void
Then you could use this instead:
view_custom.setCustomViewListener {
// listener code
}
How can an activity access the instance of the view, after it's been set using SetContentView?
Step #1: Add an android:id attribute to your root <FrameLayout> element.
Step #2: In onCreate() of your activity, after the setContentView() call, call findViewById() to retrieve the FrameLayout based on the ID that you assigned it in Step #1.
the activity uses a custom view which includes logic and I need this view to sent events to the activity, through a custom event listener that the activity needs to set in the view
You could also just call findViewById() and provide the ID of the custom view (findViewById(R.id.custom_view)).
Note that this covered by pretty much any book on Android app development.
I'm trying to adopt PagedList to my app. One of the functionality I want is the ability to handle button click event within list item.
I was thinking to utilize ViewModel to listen for the click event and taken from the example https://medium.com/#star_zero/singleliveevent-livedata-with-multi-observers-384e17c60a16, I was successfully getting the click event.
so I have,
LivePagedListBuilder(DeviceDataSourceFactory(), defaultConfig)
.build()
.observe(this, Observer { list ->
// here is where I have Observer for the click event
// for example, list?.forEach { it.event.observe(...) }
// but this block isn't called everytime
adapter.submitList(list)
})
As the comment above, I don't always get notified when new item is added to the list. I guess I only got it once from loadInitial and once from loadAfter. After ref is linked, PagedList handles updating the list itself without notifying the Observer. Therefor, I can't correctly setup click event Observer. Any help would be appreciated. been blocked for more than a week. Thanks!
You can always pass the reference to your OnClickListener to your Adapter class.
For example:
//Reference of click listener in your Adapter class
//Initialise clickListener in adapter's init method, You can pass ViewModel's reference to the adapter as the reference of YOUR_LISTENER_IMPL
val clickListener : YOUR_LISTENER_IMPL
//ViewHolder class
class YOUR_VIEW_HOLDER(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(data: YOUR_DATA_TYPE, clickListener: (YOUR_DATA_TYPE) -> Unit) {
itemView.setOnClickListener { clickListener(data)}
}
}
//onBindViewHolder method inside Adapter
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
// Populate ViewHolder with data that corresponds to the position in the list
// which we are told to load
(holder as YOUR_VIEW_HOLDER).bind(DATA_SOURCE_LIST[position], clickListener)
}
Then while initialising your adapter, you can send the clickListener object via constructor. This way all your ViewHolder instances will always have a clickListener and you won't be missing any click events, when the items gets added into the list.
I hope this helps.