IllegalArgumentException on parameter convertView at Adapter.getView Kotlin android - 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.

Related

Getting context in Kotlin Adapter for getItemCount

I'm looking for a way to find the context of a RecyclerView Adapter outside of the viewholder methods. I have two different arrays that I would like to be able to display based on the activity/fragment that is being shown(the final app would have 4 arrays which is why I don't just want to make another adapter for each array). I can get it to work in the onbindviewholder using holder.itemview.context but since the arrays aren't the same size I need a way to use context for an if statement in getitemcount() as well. Passing context in the constructor results in "no value passed for parameter". Was wondering if anyone has found a solution to this problem using either fragments or activities. Worst case scenario I just make a different adapter for every array
class ItemAdapter(var context: Context) : RecyclerView.Adapter<ItemAdapter.ViewHolder>() {
private var weeklyTasks = arrayOf(R.string.app_name, R.string.daily_hint_1, R.string.daily_hint_2, R.string.daily_hint_3)
private var dailyTasks = arrayOf(R.string.daily_hint_1, R.string.daily_hint_2, R.string.daily_hint_3)
private var xp = intArrayOf(R.mipmap.tenxp_round, R.mipmap.fiftyxp_round, R.mipmap.hundredxp_round)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.task_layout, parent, false)
return ViewHolder(v)
}
override fun getItemCount(): Int {
//cant use context here because no value is passed for parameter
return dailyTasks.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
//var context = holder.itemView.context -> this works here but not as a constructor
if(context is ToDoList) {
holder.itemImage.setImageResource(xp[0])
holder.itemDetail.text = dailyTasks[position].toString()
}
else{
holder.itemImage.setImageResource(xp[1])
holder.itemDetail.text = weeklyTasks[position].toString()
}
}
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
var itemImage: ImageView
var itemDetail: TextView
init {
itemImage = itemView.findViewById(R.id.item_image)
itemDetail = itemView.findViewById(R.id.item_detail)
}
}
}

Does the recyclerview/listview adapter have a method called once?

For a certain reason, I have to do one operation inside the adapter. In other words, this process should not be repeated when scrolling. The onCreate method in the activity works once when the activity is opened. Is there a method to do this inside the adapter?
UPDATE
My adapter class:
class EducationAdapter #Inject constructor(
var userManager: UserManager
) : ListAdapter<EducationModel, RecyclerView.ViewHolder>.
(EDUCATION_ITEM_DIFF_CALLBACK) {
companion object {
}
var callBack: EducationAdapterCallBack? = null
interface EducationAdapterCallBack {
fun onClickLayer(educationModel: EducationModel)
}
class EducationViewHolder(var binding: ItemEducationBinding) :
RecyclerView.ViewHolder(binding.root) {
fun setItem(item: EducationModel) {
binding.educationItem = item
}
}
#Nonnull
override fun onCreateViewHolder(
#Nonnull parent: ViewGroup,
viewType: Int
): RecyclerView.ViewHolder {
val binding: ItemEducationBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.item_education, parent, false
)
return EducationAdapter.EducationViewHolder(binding)
}
override fun onBindViewHolder(#Nonnull holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
when (holder) {
is EducationAdapter.EducationViewHolder -> {
holder.setItem(item)
holder.binding.itemEducation.setOnClickListener {
callBack?.onClickLayer(item)
}
}
}
}
}
OncreateViewHolder and onBindViewHolder called when scrolling listview. But I want a single shot process is there any method that runs a single time?
UPDATE 2
I created a dummy operation (My original code was too long, I created summarized dummy operation). OnBind method should be like this:
override fun onBindViewHolder(#Nonnull holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
when (holder) {
is EducationAdapter.EducationViewHolder -> {
holder.setItem(item)
holder.binding.itemEducation.setOnClickListener {
callBack?.onClickLayer(item)
}
var isExpandClicked = false
holder.binding.btnExpandProject.setOnClickListener {
if (isExpandClicked) {
isExpandClicked = false
val context = holder.binding.llTaskFaaliyet.context
holder.binding.btnExpandProject.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.ic_arrow_down
)
)
val headerText = TextView(context)
headerText.apply {
text = subTask.title
textSize = 16f
setTextColor(ContextCompat.getColor(context, R.color.marine))
gravity = Gravity.START
typeface = ResourcesCompat.getFont(context, R.font.ubuntu_bold)
setPadding(48, 16, 4, 0)
setBackgroundColor(ContextCompat.getColor(context, R.color.white))
}
holder.binding.llTask.add(headerText)
}else{
isExpandClicked = true
val context = holder.binding.llTask.context
holder.binding.btnExpandProject.setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.ic_close
)
)
holder.binding.llTask.removeAllViews()
}
}
}
}
}
I am trying to create those views below:
Expanded View:
Shrinked View
Views should be expanded at the first opening. It should be single time when the view is created.
If you put the function on bind method, it should be triggered whenever the item is shown. You can make a simple way, use boolean on adapter scope that will be changed to false after the expand function is triggered. Then use this boolean to check whether value is true, so it should trigger the expand function.

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?

Set onClick event in adapter's getView method

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

(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?) {
...
}

Categories

Resources