I have a problem. I can print a toast message when I click on any item in Recyclerview. But I want to switch to a different activity when clicked. Could you please help me in a very specific way? It is a very important project for me. The code blocks I wrote are as follows. Thanks in advance .
CustomersAdapter
class CustomersAdapter(private val companynameArray:ArrayList,private val companyoffArray:ArrayList, private val companyPhoneArray:ArrayList):
RecyclerView.Adapter<CustomersAdapter.CustomerHolder>() {
class CustomerHolder(view: View) : RecyclerView.ViewHolder(view) {
var recycleCompanyName: TextView?=null
var recycleOffName: TextView?=null
var recycleOffPhone: TextView?=null
init {
recycleCompanyName=view.findViewById(R.id.recycleCompanyName)
recycleOffName=view.findViewById(R.id.recycleOffName)
recycleOffPhone=view.findViewById(R.id.recycleOffPhone)
itemView.setOnClickListener {
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomerHolder {
val inflater= LayoutInflater.from(parent.context)
val view= inflater.inflate(R.layout.recycler_view_customer_row,parent,false)
return CustomersAdapter.CustomerHolder(view)
}
override fun getItemCount(): Int {
return companynameArray.size
}
override fun onBindViewHolder(holder: CustomerHolder, position: Int) {
holder.recycleCompanyName?.text="Şirket Adı:"+companynameArray[position]
holder.recycleOffName?.text="Şirket Yetkilisi :"+companyoffArray[position]
holder.recycleOffPhone?.text="İrtibat No : "+companyPhoneArray[position]
}
}
I did not specify the activity I want to go to. I would appreciate it if you could help as a method.
If it helps, I write the codes of the client class here.
class Customers : AppCompatActivity()
{
private lateinit var auth: FirebaseAuth
private lateinit var db: FirebaseFirestore
var companynameFB:ArrayList<String> = ArrayList()
var companyoffFB:ArrayList<String> = ArrayList()
var companyphoneFB:ArrayList<String> = ArrayList()
var adapter :CustomersAdapter?=null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_customers)
auth= FirebaseAuth.getInstance()
db= FirebaseFirestore.getInstance()
getDataFromFirestore()
var layoutManager= LinearLayoutManager(this)
recyclerView.layoutManager=layoutManager
adapter= CustomersAdapter(companynameFB,companyoffFB,companyphoneFB)
recyclerView.adapter=adapter
}
fun getDataFromFirestore(){
db.collection("Customers")
.addSnapshotListener { snapshot, exception ->
if(exception!=null){
Toast.makeText(applicationContext,exception.localizedMessage.toString(),Toast.LENGTH_LONG).show()
}else{
if(snapshot!=null){
if (!snapshot.isEmpty){
companynameFB.clear()
companyoffFB.clear()
companyphoneFB.clear()
val documents=snapshot.documents
for (document in documents){
val compname=document.get("compname") as String
val compoff=document.get("compoff") as String
val compphone=document.get("compphone") as String
val user=document.get("user") as String
val timeStamp=document.get("date") as Timestamp
val date=timeStamp.toDate()
companynameFB.add(compname)
companyoffFB.add(compoff)
companyphoneFB.add(compphone)
adapter!!.notifyDataSetChanged()
}
}
}
}
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
val menuInflater=getMenuInflater()
menuInflater.inflate(R.menu.add_customer,menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if(item.itemId==R.id.add){
val intent= Intent(this,AddCustomers::class.java)
intent.putExtra("info","new")
startActivity(intent)
}
return super.onOptionsItemSelected(item)
}
For starting a new Activity you need at least a context instance. And as is written in some other answers here, you could start the activity directly inside ViewHolder with View's context. But for better separations of responsibilities, I suggest you move your navigation to your Fragment/Activity, where all other navigations happen. That way you know at the look of Activity/Fragment, which navigations could be done from that screen, and you don't need to search for some hidden navigations that happen inside Adapter or ViewHolder. Also, if you need to perform startActivityForResult, then an instance of Context is not enough to do that. You need an Activity or Fragment instance for that and we have another reason to put all navigations there.
So you can do this like that:
ADD LAMBDA TO ADAPTER CONSTRUCTOR:
class CustomersAdapter(private val companynameArray: ArrayList,
private val companyoffArray: ArrayList,
private val companyPhoneArray: ArrayList,
private val onItemClick: () -> Unit) : RecyclerView.Adapter<CustomersAdapter.CustomerHolder>() {
NOTE: put lambda as the last constructor parameter.
ADD LAMBDA TO VIEWHOLDER CONSTRUCTOR:
class CustomerHolder(view: View,
private val onItemClick: () -> Unit) : RecyclerView.ViewHolder(view) {
MODIFY VIEWHOLDER CONSTRUCTOR CALL:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomerHolder {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.recycler_view_customer_row,parent,false)
return CustomersAdapter.CustomerHolder(view, onItemClick) //Add onItemClick lambda instance from adapter here
}
INVOKE LAMBDA ON CLICK:
itemView.setOnClickListener {
onItemClick()
}
FIX ADAPTER CONSTRUCTOR CALL:
val adapter = CustomersAdapter(companynameArray, companyoffArray, companyPhoneArray) {
startActivity() // do your navigation here
}
Why did we place lambda as the last parameter of a constructor in our adapter? If the last parameter of a function is a function, then a lambda expression passed as the corresponding argument can be placed outside the parentheses(SOURCE). And we did exactly that with the last junk of code above where we handle our navigation.
put your click listener inside onBindViewHolder method
override fun onBindViewHolder(holder: CustomerHolder, position: Int) {
holder.recycleCompanyName?.text="Şirket Adı:"+companynameArray[position]
holder.recycleOffName?.text="Şirket Yetkilisi :"+companyoffArray[position]
holder.recycleOffPhone?.text="İrtibat No : "+companyPhoneArray[position]
holder.itemView.setOnClickListener {
// do what ever you want here
holder.itemView.context.startActivity( /*your intent here*/)
}
}
}
Step 1 : pass context here
class CustomersAdapter(val context:Context,private val
companynameArray:ArrayList,private val
companyoffArray:ArrayList, private val companyPhoneArray:ArrayList):
RecyclerView.Adapter<CustomersAdapter.CustomerHolder>() {
step 2:
itemView.setOnClickListener {
val intent=Intent(context,yourActivity::java.class)
startActivity(intent)
}
you need a context for intent so you can get it form the activity or fragment that are associated with this adapter and pass it to your intent or you can get it from any view in your inflated layout like this
val context=holder.title.context
val intent = Intent( context, EventDetail::class.java)
context.startActivity(intent)
Related
I want to add onClick on my RecyclerView item images for navigating to Browser but it requires context how can I access the context
class MovieAdapter(val movies: List<Data>, val activity: Activity) : RecyclerView.Adapter<MovieAdapter.ViewHolder>(){
class ViewHolder(itemView: View):RecyclerView.ViewHolder(itemView) {
val txtTitle = itemView.findViewById<TextView>(R.id.txtName)
val txtYear = itemView.findViewById<TextView>(R.id.txtPrice)
val image = itemView.findViewById<ImageView>(R.id.image)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_movie,parent,false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return movies.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val movie:Data=movies[position]
holder.txtTitle.setText(movie.animeName)
holder.txtYear.setText(movie.animeİd.toString())
Glide.with(activity).load(movie.animeİmg).into(holder.image)
holder.image.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW,Uri.parse("https://www.google.com/search?q="+holder.txtTitle))
startActivity(intent)
}
}
}
Pass Context instead of Activity.
class MovieAdapter(val movies: List<Data>, val context: Context) RecyclerView.Adapter<MovieAdapter.ViewHolder>() {
{
So in my Fragment I could just call it like
MovieAdapter(requireActivity().baseContext)
I think passing Context is enough for Adapters.
You can add a listener to your adapter. You can listen to this listener from your adapter. I will put a code snippet below to give you an idea.
MovieAdapter(val movies: List<Data>, val fooListener: (Sting) -> Unit)
//When you create the adapter instance(Activity)
MovieAdapter(list){ url ->
val intent = Intent(Intent.ACTION_VIEW,Uri.parse(url))
startActivity(intent)
}
You already have context in form of activity. You can use that.
activity.startActivity(intent)
You can also get a Context from any View using view.context, for example holder.image.context
I have a RecyclerView list, I want the color of the clicked item to be changed. But whenever I am clicking an item the RecyclerView items change randomly.
I have also taken a boolean in my data class to keep track of item selection.
data class
data class OrderAvailableDaysResponseItem(
val actual_date: String,
val day_name: String,
val date_name: String,
val day_num: Int,
// For item selection
var isDateSelected: Boolean)
Inside my fragment, checking if the clicked item matches the list item and updating the isDateSelected to true, then calling notifydatasetchanged.
private var availableDaysList: OrderAvailableDaysResponse = OrderAvailableDaysResponse()
orderAvailableDaysAdapter.setDateClickListener {
for(resItem in availableDaysList){
resItem.isDateSelected = resItem.date_name == it.date_name
}
orderAvailableDaysAdapter.notifyDataSetChanged()
}
Adapter clicklistener
var onDateClickListener: ((OrderAvailableDaysResponseItem) -> Unit)? = null
fun setDateClickListener(listener: (OrderAvailableDaysResponseItem) -> Unit){
onDateClickListener = listener
}
inner class AvailableDaysViewHolder(binding: ItemAvailableDaysBinding) : RecyclerView.ViewHolder(binding.root) {
fun setBindings(itemRes: OrderAvailableDaysResponseItem){
binding.resItem = itemRes
binding.executePendingBindings()
binding.clRoot.setOnClickListener {
onDateClickListener?.let {
it(itemRes)
}
}
}
}
Please refer to the attachment for a better understanding of the situation
As you can see on clicking on an item the items are changing randomly. Please help me. Am I missing something?
Edit 1:
Complete adapter code
class OrderAvailableDaysAdapter(var orderAvailableDaysResponseList: OrderAvailableDaysResponse) : RecyclerView.Adapter<OrderAvailableDaysAdapter.AvailableDaysViewHolder>() {
private lateinit var binding: ItemAvailableDaysBinding
var onDateClickListener: ((OrderAvailableDaysResponseItem) -> Unit)? = null
fun setDateClickListener(listener: (OrderAvailableDaysResponseItem) -> Unit){
onDateClickListener = listener
}
inner class AvailableDaysViewHolder(binding: ItemAvailableDaysBinding) : RecyclerView.ViewHolder(binding.root) {
fun setBindings(itemRes: OrderAvailableDaysResponseItem){
binding.resItem = itemRes
binding.executePendingBindings()
binding.clRoot.setOnClickListener {
onDateClickListener?.let {
it(itemRes)
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AvailableDaysViewHolder {
binding = ItemAvailableDaysBinding.inflate(LayoutInflater.from(parent.context),parent,false)
return AvailableDaysViewHolder(binding)
}
override fun onBindViewHolder(holder: AvailableDaysViewHolder, position: Int) {
holder.setBindings(orderAvailableDaysResponseList[position])
}
override fun getItemCount(): Int {
return orderAvailableDaysResponseList.size
}}
remove private lateinit var binding: ItemAvailableDaysBinding from adapter, don' keep it "globally", initialisation is made only once, in onCreateViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AvailableDaysViewHolder {
ItemAvailableDaysBinding binding = ItemAvailableDaysBinding.inflate(
LayoutInflater.from(parent.context),parent,false)
return AvailableDaysViewHolder(binding)
}
same naming of this "global" object and in AvailableDaysViewHolder inner class may confuse it and setBindings may be called on lastly initialised (global kept) object rather that this passed in constructor
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 have an Android project in MVVM Structure. In that project, consist RecyclerView. This is my code.
1.MyViewModel.kt
class MyViewModel(context: Application, val myRepository: MyRepository) : AndroidViewModel(context), Observable{
... other code ...
val listUri: MutableLiveData<MutableList<Uri>> by lazy {
MutableLiveData<MutableList<Uri>>().apply {
value = mutableListOf()
}
}
... other code ...
}
2.MyAdapter.kt
class MyAdapter(var items: MutableList<Uri>, val context: Context) : RecyclerView.Adapter<ViewHolder>() {
var listItems: ArrayList<Uri> = items as ArrayList<Uri>
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(context).inflate(R.layout.pod_list_item, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.imageIcon.setImageURI(listItems[position])
holder.deleteIcon.setOnClickListener {
val viewModel: MyViewModel? = null
....THIS PART....
viewModel?.listUri?.value.removeAt(position)
....UNTIL THIS PART....
}
}
override fun getItemCount(): Int {
return editedItems.size
}
}
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
var imageIcon = view.icon_image
var deleteIcon = view.icon_delete
}
When user click the delete icon in the recyclerview item, the application suppose to delete the selected value of MutableLiveData<MutableList<Uri>> in MyViewModel which name listUri.
In my marked code above, when i debug the code, it return null value, even though the variable listUri in ViewModel have value.
So i think, my way to access the value of ViewModel from Adapter is wrong.
How to access value of ViewModel from the adapter and then manipulate it?
If can't do that, any suggestion would be nice.
you can access your ViewModel from the context which you are getting into the adapter.
I guess instead of passing the context in the constructor, pass the ViewModel instead:
class MyAdapter(var items: MutableList<Uri>, val viewModel: MyViewModel) : RecyclerView.Adapter<ViewHolder>()
Then instead of:
holder.deleteIcon.setOnClickListener {
val viewModel: MyViewModel? = null
....THIS PART....
viewModel?.listUri?.value.removeAt(position)
....UNTIL THIS PART....
}
You can do:
holder.deleteIcon.setOnClickListener {
viewModel.listUri.value?.removeAt(position)
}
I hope this helps
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.