After finally implementing icons for device types in my server, I replaced the temporary drawables I previously used with the icons from the server and load them using coil
. The ImageView
for them is inside an adapter btw. On first draw, everything is "aligned" properly, the labels and images are the right pairing. But if I do some sort of sorting, the images in the ImageView
s doesn't align with the labels anymore or they're completely gone. I have a behavior in my app that first I'll get and display data from the server and if the local data is equal to the server's, then I'd just use the local data. But I don't think that should affect anything as I'm saving every field of the server data on the database. I also added some logging to see what URL should be retrieved by coil
, and they are the correct URL for each icon. I call notifyDatasetChanged()
to redraw the adapters because the sorting might return a different length of items than the default list, which is all the items I have.
Here's my adapter's code:
class ActuatorDeviceInfoAdapter(
val ctx: FragmentActivity,
var itemLst: MutableList<ActuatorDeviceInfo>,
val showToggle: Boolean
) : RecyclerView.Adapter<ActuatorDeviceInfoAdapter.ViewHolder>() {
private lateinit var binding: AdapterActuatorBinding
var onItemClick: ((ActuatorDeviceInfo) -> Unit)? = null
var onToggleClick: ((ActuatorDeviceInfo, Boolean, AdapterActuatorBinding) -> Unit)? = null
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ActuatorDeviceInfoAdapter.ViewHolder {
binding = AdapterActuatorBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ActuatorDeviceInfoAdapter.ViewHolder, position: Int) {
val pic = itemLst[position]
holder.bind(pic)
}
override fun getItemCount(): Int = itemLst.size
inner class ViewHolder(private val binding: AdapterActuatorBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(actuatorDeviceInfo: ActuatorDeviceInfo) {
binding.actuatorDevInfo = actuatorDeviceInfo
if (showToggle) {
binding.tglStatus.visibility = View.VISIBLE
binding.tglStatus.isChecked = actuatorDeviceInfo.status
} else {
binding.tglStatus.visibility = View.GONE
}
setIcon(actuatorDeviceInfo.type!!)
}
init {
itemView.setOnClickListener {
onItemClick?.invoke(itemLst[adapterPosition])
}
binding.tglStatus.setOnClickListener {
onToggleClick?.invoke(itemLst[adapterPosition], binding.tglStatus.isChecked, binding)
}
}
}
private fun setIcon(type: ActuatorType) {
val iconPath = "${Utils.instance.getServerHost(ctx)}${type.iconPath}"
binding.imgIcon.load(iconPath)
}
companion object{
const val TAG = "ActuatorDeviceInfoAdapter"
}
}
The problem is in the private function setIcon
.
You need to update each Item's view with each ActuatorDeviceInfo.
You need to use the Binding of Item's view for setting the data and image.
For statements inside bind() uses the each item's binding. But setIcon uses some other binding that has the last initialized view which it should not as per your requirement.
private lateinit var binding: AdapterActuatorBinding
.val binding =
.Above 1,2 steps are optional.
class ActuatorDeviceInfoAdapter(
val ctx: FragmentActivity,
var itemLst: MutableList<ActuatorDeviceInfo>,
val showToggle: Boolean
) : RecyclerView.Adapter<ActuatorDeviceInfoAdapter.ViewHolder>() {
// 1. Removed
// private lateinit var binding: AdapterActuatorBinding
var onItemClick: ((ActuatorDeviceInfo) -> Unit)? = null
var onToggleClick: ((ActuatorDeviceInfo, Boolean, AdapterActuatorBinding) -> Unit)? = null
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ActuatorDeviceInfoAdapter.ViewHolder {
// 2. Declared
val binding = AdapterActuatorBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ActuatorDeviceInfoAdapter.ViewHolder, position: Int) {
val pic = itemLst[position]
holder.bind(pic)
}
override fun getItemCount(): Int = itemLst.size
inner class ViewHolder(private val binding: AdapterActuatorBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(actuatorDeviceInfo: ActuatorDeviceInfo) {
binding.actuatorDevInfo = actuatorDeviceInfo
if (showToggle) {
binding.tglStatus.visibility = View.VISIBLE
binding.tglStatus.isChecked = actuatorDeviceInfo.status
} else {
binding.tglStatus.visibility = View.GONE
}
setIcon(actuatorDeviceInfo.type!!)
}
// 3. Moved
private fun setIcon(type: ActuatorType) {
val iconPath = "${Utils.instance.getServerHost(ctx)}${type.iconPath}"
binding.imgIcon.load(iconPath)
}
init {
itemView.setOnClickListener {
onItemClick?.invoke(itemLst[adapterPosition])
}
binding.tglStatus.setOnClickListener {
onToggleClick?.invoke(itemLst[adapterPosition], binding.tglStatus.isChecked, binding)
}
}
}
companion object{
const val TAG = "ActuatorDeviceInfoAdapter"
}
}
You can avoid these kinds of issues by following best practices.