I don't know how to add onclicklistener to here,
I've tried many ways but it doesn't work,
i made this with recyclerview,
I've tried to add a new variable in the adapter section but error,
I've also made activity details so I just need to hit recyclerview and go to details page, I gave the code that has not been added onclicklistener
this MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var adapter: MakeUpAdapter
#SuppressLint("NotifyDataSetChanged")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
rv_kamu.setHasFixedSize(true)
rv_kamu.layoutManager = LinearLayoutManager(this)
adapter = MakeUpAdapter()
adapter.notifyDataSetChanged()
rv_kamu.adapter = adapter
getDataFromApi()
}
private fun getDataFromApi() {
Retrofit.instance.getMakeUpList()
.enqueue(object : Callback<ArrayList<MakeUpModel>>{
override fun onResponse(
call: Call<ArrayList<MakeUpModel>>,
response: Response<ArrayList<MakeUpModel>>
) {
if(response.isSuccessful) {
showData(response.body()!!)
}
}
override fun onFailure(call: Call<ArrayList<MakeUpModel>>, t: Throwable) {
Log.e("Fail", "Be Fail")
}
})
}
private fun showData(data: ArrayList<MakeUpModel>) {
val result = data
adapter.setData(result)
}
}
and this adapter
class MakeUpAdapter : RecyclerView.Adapter<MakeUpAdapter.MakeUpViewHolder>(){
private val data = ArrayList<MakeUpModel>()
inner class MakeUpViewHolder (itemView: View) : RecyclerView.ViewHolder(itemView){
fun bind(data: MakeUpModel) {
//lib3
Glide.with(itemView.context)
.load(data.Picture)
.apply(
RequestOptions()
.override(100, 100)
)
.into(itemView.iv_item)
itemView.tv_Name.text = data.Name
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MakeUpViewHolder {
val view: View =
LayoutInflater.from(parent.context).inflate(R.layout.item_makeup, parent, false)
return MakeUpViewHolder(view)
}
override fun onBindViewHolder(holder: MakeUpViewHolder, position: Int) {
holder.bind(data[position])
}
override fun getItemCount(): Int = data.size
#SuppressLint("NotifyDataSetChanged")
//buat mainac
fun setData(list: ArrayList<MakeUpModel>) {
data.clear()
data.addAll(list)
notifyDataSetChanged()
}
}
This is my way.
first, create a interface to listen click event:
interface RecycleViewOnClickListener {
fun onItemClick(pos :Int)
}
and in your adapter class, put interface in param, then call listener in onBindViewHolder:
class MakeUpAdapter(private val clickListener: RecycleViewOnClickListener) : RecyclerView.Adapter<MakeUpAdapter.MakeUpViewHolder>(){
override fun onBindViewHolder(holder: MakeUpViewHolder, position: Int) {
holder.bind(data[position])
holder.itemView.setOnClickListener {
clickListener.onItemClick(position)
}
}
}
}
In your activity, implement listener like this:
class MainActivity : AppCompatActivity(), RecycleViewOnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
adapter = MakeUpAdapter(this)
}
override fun onItemClick(pos: Int) {
//do anything with your position here, such as get item from result
}
}
Hope it's helpful for you
on onBindViewHolder() you pass the position when calling the callback method:
override fun onBindViewHolder(holder: Holder?, position: Int) {
var item : MyObject = objects[position]
// Calling the clickListener sent by the constructor
holder.vieww.setOnClickListener {
// click event
}
}
There are a few ways one could add a click listener, one way writing this more idiomatically might be:
class MainActivity : AppCompatActivity() {
private val adapter by lazy { MakeUpAdapter() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
rv_kamu.layoutManager = LinearLayoutManager(this)
adapter.notifyDataSetChanged()
adapter.listener = { /* TODO handle click event */ }
rv_kamu.adapter = adapter
getDataFromApi()
}
}
class MakeUpAdapter : RecyclerView.Adapter<MakeUpAdapter.MakeUpViewHolder>(){
var listener: (MakeUpModel) -> Unit = {}
private val data = ArrayList<MakeUpModel>()
inner class MakeUpViewHolder (itemView: View) : RecyclerView.ViewHolder(itemView){
fun bind(data: MakeUpModel) {
itemView.setOnClickListener { listener(data) }
//bind data
}
}
// plug in rest of your code
}
You might also want to consider moving the api call with retrofit into an Android ViewModel so that the network call will survive configuration changes (device rotation). This is a very simple example, but gets the point across and encourage you to check out the official android documentation on the subject.
class MakeUpViewModel( : ViewModel() {
init {
viewModelScope.launch {
val response = Retrofit.instance.getMakeUpList()
// update state with response
}
}
}
Related
I'm using ListAdapter with a DiffUtil.ItemCallback and I have made the DiffUtil callback methods return true for all items to inspect its behavior. And it seems that the RecyclerView views are still getting updated and showing the new result ('D', 'E', 'F'). I did not expect this, Shouldn't the RecyclerView only update views/items that have changed?
Adapter:
class ItemsAdapter : ListAdapter<Item, ItemViewHolder>(ItemDiff) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
return ItemViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_item, parent, false))
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(getItem(position))
}
}
object ItemDiff : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return true
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return true
}
}
class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val tvName = itemView.findViewById<TextView>(R.id.tvName)
fun bind(item: Item) {
tvName.text = item.name
}
}
data class Item(val name: String)
MainActivity:
class MainActivity : AppCompatActivity() {
private val adapter = ItemsAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
rvItems.layoutManager = LinearLayoutManager(this)
rvItems.adapter = adapter
adapter.submitList(listOf(Item("A"), Item("B"), Item("C")))
adapter.submitList(listOf(Item("D"), Item("E"), Item("F")))
}
}
I figured out the problem and it seems like it was a concurrency issue, Since I was calling the submitList method twice and ListAdapter calculates the diff on the background thread.
To test this I changed my activity code to the following and the list was not updated.
class MainActivity : AppCompatActivity() {
private val adapter = ItemsAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
rvItems.layoutManager = LinearLayoutManager(this)
rvItems.adapter = adapter
adapter.submitList(listOf(Item("A"), Item("B"), Item("C")))
thread {
Thread.sleep(1000)
runOnUiThread {
adapter.submitList(listOf(Item("D"), Item("E"), Item("F")))
Toast.makeText(this, "second submitList", Toast.LENGTH_LONG).show()
}
}
}
}
What's the objective
Im currently working on an app which has a RecyclerView for the Settings menu. This menu serves to load other fragments. So i needed to implement an OnItemClick function: for this, i followed this video.
What's the probelm
Following the given tutorial, Android Studio flags val adapter = adapterSettings(settingsList), saying No value passed for parameter 'listener'. I suppose that im missing something, since without the code written in the tutorial, the RecyclerView works.
So, am i missing something? Are there any ways to fix this in an easy and clean way?
Code:
activitySettings.kt
class ndActSettings : AppCompatActivity(), adapterSettings.OnItemClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.ndactivity_settings)
topToolbarBack.setNavigationOnClickListener {
finish()
}
var settingsList = listOf(
dataItemsSettings(getString(R.string.look), getString(R.string.lookdescription), R.drawable.ic_colored_color_lens),
dataItemsSettings(getString(R.string.reproduction), getString(R.string.reproductiondescription), R.drawable.ic_colored_view_carousel),
dataItemsSettings(getString(R.string.images), getString(R.string.imagesdscription), R.drawable.ic_colored_image),
dataItemsSettings(getString(R.string.audio), getString(R.string.audiodescription), R.drawable.ic_colored_volume_up),
dataItemsSettings(getString(R.string.about), getString(R.string.aboutdescription), R.drawable.ic_colored_info)
)
val adapter = adapterSettings(settingsList) //ERROR HERE!
rvSettings.adapter = adapter
rvSettings.layoutManager = LinearLayoutManager(this)
}
override fun OnItemClick(position: Int) {
//TODO
}
}
adapterSettings.kt
class adapterSettings(
var settingsList: List<dataItemsSettings>,
var listener: OnItemClickListener
) : RecyclerView.Adapter<adapterSettings.SettingsViewHolder>() {
inner class SettingsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
init {
itemView.setOnClickListener(this)
}
override fun onClick(p0: View?) {
val position : Int = adapterPosition
if (position != RecyclerView.NO_POSITION) {
listener.OnItemClick(position)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_settings, parent, false)
return SettingsViewHolder(view)
}
override fun getItemCount(): Int {
return settingsList.size
}
override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) {
holder.itemView.apply {
rvTitle.text = settingsList[position].stringTitle
rvDescription.text = settingsList[position].stringDescription
rvIcon.setImageResource(settingsList[position].itemIcon)
}
}
interface OnItemClickListener {
fun OnItemClick(position: Int)
}
}
The constructor of class adapterSettings is expecting two parameters
class adapterSettings(
var settingsList: List<dataItemsSettings>,
var listener: OnItemClickListener
)
However, you are instantiating the object with one parameter only:
val adapter = adapterSettings(settingsList)
So, you have to add a second parameter.. An object that implements OnItemClickListener. Since you activity already implements that interface, you can send the activity as second parameter:
val adapter = adapterSettings(settingsList, this)
I am working with MVP for the first time and I believe I get the idea of it but I am not sure about the RecyclerView. As far as I can say, MVP is about making views as passive as possible so all business logic goes to the Presenter but how can this be achieved for the Recycler View?
Here is my code so far:
Contract
public interface PhotosContract {
// View
interface View {//: IBaseActivity {
fun showPhotos(photos: ArrayList<Photo>)
fun showText(message: String)
}
// Presenter
interface Presenter {//: IBasePresenter<View> {
fun getPhotos()
}
}
Presenter
public class PhotosPresenter(var view: PhotosContract.View) :PhotosContract.Presenter {
var dataList = ArrayList<Photo>()
override fun getPhotos() {
//call for endpoint
val call : Call<ArrayList<Photo>> = ApiClient.getClient.getPhotos()
call.enqueue(object: Callback<ArrayList<Photo>> {
override fun onFailure(call: Call<ArrayList<Photo>>, t: Throwable) {
Log.d("FAIL","FAILED")
}
override fun onResponse(
call: Call<ArrayList<Photo>>,
response: Response<ArrayList<Photo>>
)
{
Log.d("SUCCESS","SUCCESSED")
dataList.addAll(response!!.body()!!)
Log.d("SIZELIST",dataList.size.toString())
view.showPhotos(dataList)
view.showText("SUCCESS")
}
})
}
}
RecyclerViewAdapter
class PhotosAdapter(private var dataList: List<Photo>, private val context: Context) : RecyclerView.Adapter<PhotosAdapter.PhotosViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotosAdapter.PhotosViewHolder {
return PhotosViewHolder(LayoutInflater.from(this.context).inflate(R.layout.list_item_home, parent, false))
}
override fun getItemCount(): Int {
return dataList.size
}
override fun onBindViewHolder(holder: PhotosAdapter.PhotosViewHolder, position: Int) {
val dataModel = dataList[position]
holder.titleTextView.text = dataModel.title
}
class PhotosViewHolder(itemLayoutView: View) : RecyclerView.ViewHolder(itemLayoutView){
var titleTextView: TextView = itemLayoutView.tv_title
}
}
Activity
class PhotosActivity : AppCompatActivity(),PhotosContract.View {
private lateinit var presenter: PhotosPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_photos)
presenter = PhotosPresenter(this)
presenter.getPhotos()
}
override fun showPhotos(photos: ArrayList<Photo>) {
photosRecyclerView.layoutManager = LinearLayoutManager(this)
photosRecyclerView.adapter = PhotosAdapter(photos,this)
photosRecyclerView.adapter?.notifyDataSetChanged()
}
override fun showText(message: String) {
Toast.makeText(this,message,Toast.LENGTH_LONG).show()
}
}
Here's how I did it when I used MVP. In your contract, define an additional view named ItemView. The way I do it, each item view holder is a MVP view. The view is dumb, so it just calls the presenter whenever something happens, and the presenter calls it back.
interface MyContract {
interface View {
fun setTitle(title: String)
}
// Add this interface here
interface ItemView {
fun bindItem(item: Item)
}
interface Presenter {
fun attach(view: View)
fun detach()
val itemCount: Int
fun onItemClicked(pos: Int)
fun onBindItemView(itemView: ItemView, pos: Int)
}
}
The adapter is also dumb. When it needs to bind an item view holder, it calls the presenter to do it.
class MyAdapter : RecyclerView.Adapter<ViewHolder>() {
// How many items do we have? We don't know, ask the presenter.
override fun getItemCount() = presenter?.itemCount ?: 0
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// How to bind the item if we only have position? We don't know, ask the presenter.
presenter?.onBindItemView(holder, position)
}
// ...
}
The ViewHolder implements the MyContract.ItemView interface. Again, it's just a view so it has no responsibility by itself. It just delegates to the presenter.
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), MyContract.ItemView {
private val txv: TextView = view.findViewById(R.id.text_view)
init {
view.setOnClickListener {
// What to do here, we only have the item's position? Call the presenter.
presenter?.onItemClicked(adapterPosition)
}
}
override fun bindItem(item: Item) {
txv.text = item.text
}
}
And finally the presenter:
class MyPresenter : MyContract.Presenter {
private var view: View? = null
private val items = mutableListOf<Item>()
override fun attach(view: View) {
this.view = view
// ...
}
override fun detach() {
view = null
}
override val itemCount: Int
get() = items.size
override fun onItemClicked(pos: Int) {
val item = items[pos]
// ...
}
override fun onBindItemView(itemView: ItemView, pos: Int) {
itemView.bindItem(items[pos])
}
// ...
}
The view for completeness, but nothing new here:
class MyView : Fragment(), MyContract.View {
private var presenter: Presenter? = null
override fun onViewCreated(view: View) {
// Attach presenter
presenter = MyPresenter()
presenter?.attach(this)
}
override fun onDestroyView() {
super.onDestroyView()
// Detach the presenter
presenter?.detach()
presenter = null
}
// ...
}
That's just one way to do it, I'm sure there are a lot of others. I just like this one because all the responsibility belongs to the presenter, there's no business logic anywhere else.
Eventually, you'll want to make changes to your list and notify the adapter. For this, add a couple methods in your View contract like notifyItemInserted(pos: Int) and call them when needed from the presenter. Or, better yet, use DiffUtil so you don't have to manage it yourself!
Once you have a good understanding of MVP though, I strongly suggest you move to MVVM as it is the official architecture promoted by Google. Most people also find it a lot more convenient than MVP.
If you have any questions don't hesitate.
I have simple adapter with inner holder class.
I try to implement on item click listener this way, and it's not working:
class ThemeAdapter(private val callback: (position: Int) -> Unit) : RecyclerView.Adapter<ThemeAdapter.ThemeHolder>() {
private lateinit var themeList: Array<String>
override fun getItemCount(): Int {
return themeList.size
}
fun setThemes(themes: Array<String>) {
themeList = themes
notifyDataSetChanged()
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ThemeHolder {
val inflater = LayoutInflater.from(viewGroup.context)
return ThemeHolder(inflater.inflate(R.layout.cell_library_themes, viewGroup, false))
}
override fun onBindViewHolder(holder: ThemeHolder, position: Int) {
holder.bind(themeList[position])
}
inner class ThemeHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
private var theme: TextView = itemView.spinner_library_dropdown
fun bind(themeName: String) {
theme.text = themeName
}
override fun onClick(v: View?) {
Timber.e("OnClick!")
callback.invoke(adapterPosition)
}
}
}
Timber log nothing.
It's simple to fix:
fun bind(themeName: String) {
theme.text = themeName
itemView.setOnClickListener {
Timber.e("OnClick!")
callback.invoke(adapterPosition)
}
}
And i understand why second code working, but can someone explain me, why my first code is't working? What the difference?
try this:in your adapter class
lateinit var mClickHandler: MyAdapterOnClickHandler
interface MyAdapterOnClickHandler {
fun onClick(view: View, position: Int)
}
fun setOnClickHandler(myAdapterOnClickHandler: MyAdapterOnCLickHandler){
mClickHandler = myAdapterOnClickHandler
}
in your Viewholder:
init {
itemView.setOnClickListener(this)
}
override fun onClick(v: View?) {
mClickHandler.onClick(itemView, adapterPosition)
}
your activity or fragment need to implements MyAdapter.MyAdapterOnClickHandler
in your activity:
//in onCreate
adapter = MyAdapter(this)
adapter.setOnClickListener(this)
// then override the onclick
override fun onCLick(view: View, position: Int) {
Log.d(LOGTAG, "clicked! $position")
}
Just bind your ViewHolder that implements View.OnClickListener to your ViewHolder.itemView
fun bind(themeName: String) {
itemView.setOnClickListener(this)
theme.text = themeName
}
You've just implemented needed method from interface View.OnClickListener, but you don't use it anywhere (it is just unused method in your code snippet). Android doesn't know that it is View, cause it implements its interface.
Code that worked for you in Java will look like:
itemView.setOnClickListener(new View.OnClickListener() {
Timber.e("OnClick!");
callback.invoke(adapterPosition);
});
And other that doesn't work
itemView.setOnClickListener(null); // therefore it doesn't work for you
// cause your itemView doesn't have any listener
I am new on kotlin android. I have created the adapter for recyclerview. But I am not able to perform a click event for each recyclerview item. I need the explanation with the reference code.
Kindly help me to do this.
Thanks in advance.
Here is my code for your reference.
class CustomAdapter(val readerList: ReaderResponse, mainActivity:
MainActivity,val btnlistener: BtnClickListener) :
RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
companion object {
var mClickListener: BtnClickListener? = null
}
override fun onCreateViewHolder(viewgroup: ViewGroup, index: Int): ViewHolder
{
val view=LayoutInflater.from(viewgroup?.context).inflate(R.layout.reader_list,viewgroup,false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return readerList.results.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
mClickListener = btnlistener
val item = readerList
val reader:ReaderData = readerList.results[position]
/*p0?.imageview?.text=reader.readerIcon*/
holder?.reader_status?.text=reader.readerStatus
holder?.ward_name?.text=reader.wardName
holder?.reader_id?.text=reader.readerID
holder?.reader_name?.text=reader.readerName
holder?.reader_location?.text=reader.readerLocation
if (reader.readerStatus.toLowerCase().equals("yes")){
holder.reader_name.setTextColor(Color.parseColor("#24a314"))
}else if (reader.readerStatus.toLowerCase().equals("no")){
holder.reader_name.setTextColor(Color.parseColor("#f4312d"))
holder.warning.setVisibility(View.VISIBLE)
}
}
class ViewHolder(itemView: View) :RecyclerView.ViewHolder(itemView) {
val imageview = itemView.findViewById(R.id.imageview) as Button
val reader_name = itemView.findViewById(R.id.reader_name) as TextView
val reader_location = itemView.findViewById(R.id.floor_no) as TextView
val ward_name = itemView.findViewById(R.id.ward_name) as TextView
val reader_id = itemView.findViewById(R.id.reader_id) as TextView
val reader_status = itemView.findViewById(R.id.reader_status) as TextView
val warning=itemView.findViewById(R.id.warning) as Button
}
open interface BtnClickListener {
fun onBtnClick(position: Int)
}
}
You could use the following approach. This is taken from this blog by Antonio Leiva
Assuming your data class is ReaderData
class CustomAdapter(val readers: List, val listener: (ReaderData) -> Unit) {
/* Other methods */
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
/*...*/
holder.imageview.setOnClickListener {
listener(readers[position])
}
}
}
Now in your Activity or Fragment
recyclerview.adapter = CustomAdapter(readersList) { readerData ->
Log.i(TAG, "${readerData.readerID} clicked")
}
The idea is you pass a lambda which will be executed when your desired item is clicked.
You just need to implement BtnClickListener in the corresponding Activity in which this adapter is initialized. Once you have implemented the BtnClickListener it would override the function onBtnClick in the activity.
The only thing you need to do in the adapter is to initialize the onClickListener on the element you need and in that method just call imageview.setOnClickListener { mClickListener?.onBtnClick(position) }. It would send the position back in activity and you can perform your specific task there. For example I have implemented the ClickListener in one Activity and printed the log there it works fine. Below is the demo code for it.
class Main2Activity : AppCompatActivity(), CustomAdapter.BtnClickListener {
override fun onBtnClick(position: Int) {
Log.d("Position", position.toString())
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayout.VERTICAL, false)
val readerResponseList = ArrayList<YourModelClassName>()
val adapter = CustomAdapter(readerResponseList,this,this)
recyclerView.adapter = adapter
}
Hope it Helps.