Set onClick event in adapter's getView method - android

I have a button on the fist activity(listView) which takes you to the second activity (where an image will be added relating to that clicked item). How do I set the onClick event in the adapter's getView method?
Adapter
class ChallengeListAdapter: BaseAdapter {
private var challengeDatabase: ChallengeDatabase? = null
private var context: Context? = null
constructor(context: Context) {
challengeDatabase = ChallengeDatabase()
this.context = context
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val challenge: Challenge = challengeDatabase?.challengesList?.get(position)
?: Challenge(
"No Name", "No Description")
var challengeView: View
var layoutInflater: LayoutInflater = context?.getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
challengeView = layoutInflater.inflate(R.layout.challenge_row, null)
challengeView.lblChallengeName.setText(challenge.name)
challengeView.lblChallengeDesc.setText(challenge.desc)
return challengeView
}
override fun getItem(position: Int): Any {
return challengeDatabase?.challengesList?.get(position) ?: Challenge(
"No Name", "No Des")
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getCount(): Int {
return challengeDatabase?.challengesList?.size ?: 0
}
}
How do I fix this?
challengeView.setOnClickListener {
val solutionButton: Button = findViewById(R.id.solution_button)
val Intent = Intent(context, SolutionActivity::class.java)
Intent.putExtra()
startActivity(context!!, Intent, null)
return challengeView

As Furqan said, you set the click listener to the item's view.. in this case set it to challengeView.
To open the activity from it you need to put the desired info inside a Bundle and create an Intent to open the activity with it. You can start the activity from the view's context or propagate that event up to the adapter's owner until you reach the activity and start it from there (usually better if you are seeking for division of responsibilities).
Some tips to write it in a more idiomatic way:
the context is not necessary in the constructor. It's a bad habit to pass the context along. You can inflate the view like that:
LayoutInflater.from(parent.context)
.inflate(R.layout.challenge_row, null)
you can make challengeView immutable and even inline it like this:
...
return LayoutInflater.from(parent.context)
.inflate(R.layout.challenge_row, null)
?.apply {
lblChallengeName.text = challenge.name
lblChallengeDesc.text = challenge.desc
setOnClickListener {
//add your code to load activity
}
}
challangeDatabase can be a val and not null
private val challengeDatabase = ChallengeDatabase()

You need to handle the parameters for Adapter setOnClickListener
challengeView.setOnItemClickListener { parent, view, position, id ->
val element = adapter.getItemAtPosition(position) // The item that was clicked
val intent = Intent(this, SolutionActivity::class.java)
intent.putExtra("key", element)
startActivity(intent)
}

Add this to your getView method
challengeView.setOnClickListener{
//add your code to load activity
}
Now add your getView method will look like
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val challenge: Challenge = challengeDatabase?.challengesList?.get(position)
?: Challenge(
"No Name", "No Description")
var challengeView: View
var layoutInflater: LayoutInflater = context?.getSystemService(
Context.LAYOUT_INFLATER_SERVICE
) as LayoutInflater
challengeView = layoutInflater.inflate(R.layout.challenge_row, null)
challengeView.lblChallengeName.setText(challenge.name)
challengeView.lblChallengeDesc.setText(challenge.desc)
challengeView.setOnClickListener{
val element = adapter.getItemAtPosition(position)
val intent = Intent(this, SolutionActivity::class.java)
intent.putExtra("key", element)
parent.context.startActivity(intent)
}
return challengeView
}
Make sure your Challange class implementes Serializable

Related

How to implement implicit intent to RecyclerView on Kotlin

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 to press two times an item for delete it using setOnClickListener when i have only one item in the list

I have this code...the idea is that when the user push imagedeletenumberlistview component the element that the user has pressed is deleted. If I have multiple items it works fine, but when I have one item I have to double press the item for it to delete me.
numberViewModel.numbers().observe(viewLifecycleOwner){ listN->
if (listN.isNotEmpty()){
val adapter=PhonesAdapter(requireContext(),listN)
binding.listPhones.adapter=adapter
binding.listPhones.setOnItemClickListener { _, view, position, _ ->
view.findViewById<ImageView>(R.id.imagedeletenumberlistview).setOnClickListener {
Toast.makeText(requireContext(),"Contact delete ${listN[position].contactName}",Toast.LENGTH_SHORT).show()
numberViewModel.deletenumber(listN[position])
}
}
}else{
val adapter=PhonesAdapter(requireContext(),listN)
binding.listPhones.adapter=adapter
}
}
UPDATE 1
PhonesAdapter class:
class PhonesAdapter (private var contex:Context, private val phones:List<NumberEntity>) : ArrayAdapter<NumberEntity> (contex,0,phones){
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val v= LayoutInflater.from(context).inflate(R.layout.item_phones,parent,false)
val phones=phones[position]
v.findViewById<TextView>(R.id.contactName).text=phones.contactName
v.findViewById<TextView>(R.id.phoneNumber).text=phones.number
return v
}
}emphasized text
Firstly, you have to create an interface listener in your PhoneAdapter and set OnClickListener for imagedeletenumberlistview in getView method like this:
class PhonesAdapter(private var context: Context, private val phones: List<NumberEntity>) :
ArrayAdapter<NumberEntity>(contex, 0, phones) {
private lateinit var mListener: ItemClicker
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val v = LayoutInflater.from(context).inflate(R.layout.item_phones, parent, false)
val phones = phones[position]
v.findViewById<TextView>(R.id.contactName).text = phones.contactName
v.findViewById<TextView>(R.id.phoneNumber).text = phones.number
v.findViewById<ImageView>(R.id.imagedeletenumberlistview).setOnClickListener {
mListener.onDeleteClick(position)
}
return v
}
interface ItemClicker {
fun onDeleteClick(position: Int)
}
fun setItemClickListener(itemClicker: ItemClicker) {
mListener = itemClicker
}
}
and then change your Flow observing method to this:
numberViewModel.numbers().observe(viewLifecycleOwner) { listN ->
if (listN.isNotEmpty()) {
val adapter = PhonesAdapter(requireContext(), listN)
binding.listPhones.adapter = adapter
adapter.setItemClickListener(object : PhonesAdapter.ItemClicker {
override fun onDeleteClick(position: Int) {
Toast.makeText(requireContext(), "Contact delete ${listN[position].contactName}", Toast.LENGTH_SHORT).show()
numberViewModel.deletenumber(listN[position])
}
})
} else {
val adapter = PhonesAdapter(requireContext(), listN)
binding.listPhones.adapter = adapter
}
}
Use adapter.notifyDataSetChanged() if necessary.

use executePendingBindings() with gridView

I am trying to use executePendingBinding with gridView adapter. On some data change the binding is not executing at first but other UI operations are being executed before that. How do I prevent that. Here is my ImageAdapter :
class ImageAdapter constructor(
private val mContext: Context,
private val resource_layout: Int,
private val viewModelList: ArrayList<ProfileViewModel>
) :
ArrayAdapter<ImageAdapter.ViewHolder>(mContext, resource_layout) {
private lateinit var imageCardBinding: ImageCardBinding
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var convertView = convertView
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(resource_layout, null)
imageCardBinding = DataBindingUtil.bind(convertView)!!
convertView.tag = position
convertView.tag = imageCardBinding
} else imageCardBinding = convertView.tag as ImageCardBinding
imageCardBinding.profileViewModel = viewModelList[position]
imageCardBinding.executePendingBindings()
return imageCardBinding.root
}
override fun getCount(): Int {
return viewModelList.size
}
inner class ViewHolder
}
And on some value successfully changed I am executing this:
gridView.get(imageGridPosition).grid_item_progress.visibility = View.GONE
profile_root.snackBar("Image is set ! You can tap on the image to change it")
But as the binding is not done immediately the gridView.size is 0 and if I comment out the line the picture is set some times later but snackbar is showing before that.
Can it be fixed with executePendingBinding(), if that then how to do that ? or is there any other way?

(Kotlin) Position of RecyclerView returns -1 when trying to click on an item

I'm new to Android development (and Kotlin).
I'm trying to implement a RecyclerView (which works fine) and when I click on a specific row it opens a new activity (Intent).
However, whenever I've press/click on one of the rows, I'm only able to get the value "-1" returned.
I've tried a number of different approaches (you should see the number of tabs in my browser).
This seems like it should be a fairly straightforward occurrence for something as common as a RecyclerView, but for whatever reason I'm unable to get it working.
Here is my RecyclerView Adapter file:
class PNHLePlayerAdapter (val players : ArrayList<PNHLePlayer>, val context: Context) : RecyclerView.Adapter<ViewHolder>() {
var onItemClick: ((Int)->Unit) = {}
// Gets the number of items in the list
override fun getItemCount(): Int {
return players.size
}
// Inflates the item views
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(context).inflate(
R.layout.pnhle_list_item,
parent,
false
)
val viewHolder = ViewHolder(itemView)
itemView.setOnClickListener {
onItemClick(viewHolder.adapterPosition)
}
return ViewHolder(itemView)
}
// Binds each item in the ArrayList to a view
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.tvPlayerName?.text = players[position].Name
holder.tvPlayerRank?.text = position.toString()
holder.tvPNHLe?.text = players[position].PNHLe.toString()
holder.tvTeam?.text = players[position].Team
holder.ivLeague?.setImageResource(leagueImageID)
}
}
class ViewHolder (view: View) : RecyclerView.ViewHolder(view) {
val linLayout = view.hor1LinearLayout
val ivTeam = view.teamImageView
val tvPlayerName = view.playerNameTextView
val tvPlayerRank = view.rankNumTextView
val tvPNHLe = view.pnhleTextView
val tvTeam = view.teamTextView
val ivLeague = view.leagueImageView
}
As you can see, there is a class property "onItemClick" which uses a lambda as the click callback.
I setOnClickListener in the onCreateViewHolder method after the view is inflated.
Next, in my Activity I add the list to my Adapter and set the call back.
However, every time I 'Toast' the position it is displayed as '-1'.
val adapter = PNHLePlayerAdapter(list, this)
adapter.onItemClick = { position ->
Toast.makeText(this, position.toString(),Toast.LENGTH_SHORT).show()
var intent = Intent(this, PlayerCardActivity::class.java)
//startActivity(intent)
}
rv_player_list.adapter = adapter
Perhaps I'm not thinking about this properly, but shouldn't the position represent the row number of the item out of the RecyclerView???
Ideally, I need to use the position so that I can obtain the correct item from the 'list' (ArrayList) so that I can pass information to my next Activity using the Intent
I found the issue.
Change this line in onCreateViewHolder:
return ViewHolder(itemView)
to this one:
return viewHolder
I would reorganize the adapter like this:
class PNHLePlayerAdapter : androidx.recyclerview.widget.RecyclerView.Adapter<Adapter.ViewHolder>() {
interface AdapterListener {
fun onItemSelected(position: Int?)
}
var players: List<Player> = listOf()
set(value) {
field = value
this.notifyDataSetChanged()
}
var listener: AdapterListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_car_selector, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position)
}
override fun getItemCount(): Int {
return brands.size
}
inner class ViewHolder(view: View): androidx.recyclerview.widget.RecyclerView.ViewHolder(view) {
private var position: Int? = null
private val baseView: LinearLayout? = view.findViewById(R.id.baseView) as LinearLayout?
...
init {
baseView?.setOnClickListener {
listener?.onManufacturerSelected(position)
}
}
fun bind(position: Int) {
this.position = position
...
}
}
}
And from your activity/fragment set the listener as adapter.listener = this, and implement the onItemSelected(position: Int?)
override fun onItemSelected(position: Int?) {
...
}

IllegalArgumentException on parameter convertView at Adapter.getView Kotlin android

java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter convertView
Adapter.getView
at android.widget.AbsListView.obtainView(AbsListView.java:2346)
at android.widget.ListView.makeAndAddView(ListView.java:1876)
at android.widget.ListView.fillDown(ListView.java:702)
at android.widget.ListView.fillFromTop(ListView.java:763)
at android.widget.ListView.layoutChildren(ListView.java:1671)
at android.widget.AbsListView.onLayout(AbsListView.java:2148)
This is logcat of android.
I tried with java it's working fine base adapter something wrong in Adapter or other.
I tried with the public constructor and also array list count 3 found i checked it. Alway it's crash at getView
MyAdapter Code::
inner class MyAppAdapter constructor(private val parkingList: ArrayList<App>, private val mContext: Context) : BaseAdapter() {
override fun getCount(): Int {
return this.parkingList.size
}
override fun getItem(position: Int): Any {
return position
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getView(position: Int, convertView: View, parent: ViewGroup): View? {
val viewHolder: ViewHolder
var rowView: View? = convertView
if (rowView == null) {
rowView = LayoutInflater.from(mContext).inflate(R.layout.item_more_apps, parent, false)
viewHolder = ViewHolder()
viewHolder.appIcon = rowView.findViewById(R.id.appIcon)
viewHolder.appName = rowView.findViewById(R.id.appName)
viewHolder.appDescription = rowView.findViewById(R.id.appDescription)
rowView.tag = viewHolder
} else {
viewHolder = convertView.tag as ViewHolder
}
viewHolder.appName!!.text = String.format("%s", this.parkingList[position].name)
viewHolder.appDescription!!.text = String.format("%s", this.parkingList[position].description)
Glide.with(applicationContext).load(this.parkingList[position].icon).into(viewHolder.appIcon!!)
rowView?.setOnClickListener {
try {
startActivity(Intent("android.intent.action.VIEW", Uri.parse("market://details?id=" + this#MyAppAdapter.parkingList[position].link)))
} catch (e: ActivityNotFoundException) {
startActivity(Intent("android.intent.action.VIEW", Uri.parse("http://play.google.com/store/apps/details?id=" + this#MyAppAdapter.parkingList[position].link)))
}
}
return rowView
}
inner class ViewHolder {
var appDescription: TextView? = null
var appIcon: ImageView? = null
var appName: TextView? = null
}
}
Used at AsyncTask -> onPostExecute
myAppAdapter = MyAppAdapter(appArrayList, applicationContext)
lvPoses!!.adapter = myAppAdapter
Variable Decleared like this
lateinit var myAppAdapter: MyAppAdapter
private val appArrayList = ArrayList<App>()
private var lvPoses: ListView? = null
convertView can be null if no view has been created yet. Fix parametr declaration:
override fun getView(position: Int, convertView: View?, parent:
ViewGroup): View? {
...
}
I had similar issues with Asynctasks, not sure if this will help you, but if you just execute the asynctask (without the .get() ). Your code after calling asynctask will run, but the element is still NULL as the Asynctask didnt execute yet, and when it did it was already too late. Try adding .get() after execute (it will freeze the UI, but the postExecute method will be called in time). Or another approach is to set a boolean variable false and make it true when post executed was executed then check every second or so (Timer, Thread) if the variable is true and then execute the rest of the code. For better UI experience add progressdialog between preExecute and postExecute.
Or you can also set the view to be NULL but I dont think that fixes your problem as you dont want it to be NULL.

Categories

Resources