I'm writing an app to show a tree view of drug groups, drugs, and their information. Essentially, it's an ExpandableListView of drug groups, which shows individual drug names as the children views and opens a new fragment with more information on click.
I'm stuck with populating the child views with correct data. The adapter seems to get the group data correctly and from logging and debugging it seems that the child data is also passed on correctly. However, the text in the childViews in the ExpandableListView is only correct for the first group I open, every next group shows seemingly random contents (order of opening doesn't matter). The number of childViews is correct. The detail views (onClick) show correct info and on pressing the back button, the menu is then being showed with the correct info (however, any newly opened group then still shows wrong contents).
I've done at least 20 rounds checking and clarifying any dubious code but to no avail.
Screenshots for clarification:
list view with two groups expanded
detail view, showing correct info (but not matching that shown in list view)
list view upon returning (notice contents now shown correctly)
Here's the ExpandableListAdapter:
class MedicationsListAdapter(
private val context: Context,
private val groupList: List<String>,
private val itemList: List<List<String>>
) : BaseExpandableListAdapter() {
override fun getGroupCount(): Int {
return groupList.size
}
override fun getChildrenCount(groupPosition: Int): Int {
return itemList[groupPosition].size
}
override fun getGroup(groupPosition: Int): List<String> {
return itemList[groupPosition]
}
override fun getChild(groupPosition: Int, childPosition: Int): String {
return itemList[groupPosition][childPosition]
}
override fun getGroupId(groupPosition: Int): Long {
return groupPosition.toLong()
}
override fun getChildId(groupPosition: Int, childPosition: Int): Long {
return childPosition.toLong()
}
override fun hasStableIds(): Boolean {
return true
}
override fun getGroupView(
groupPosition: Int,
isExpanded: Boolean,
convertView: View?,
parent: ViewGroup?,
): View {
var groupView = convertView
if (groupView == null) {
val layoutInflater =
this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
groupView = layoutInflater.inflate(R.layout.medication_list_group, null)
val groupTextView: TextView = groupView.findViewById(R.id.text_group_name)
groupTextView.text = groupList[groupPosition]
} else return groupView
return groupView
}
override fun getChildView(
groupPosition: Int,
childPosition: Int,
isLastChild: Boolean,
convertView: View?,
parent: ViewGroup?
): View {
var childView = convertView
if (childView == null) {
val layoutInflater =
this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
childView = layoutInflater.inflate(R.layout.medication_list_item, null)
childView.findViewById<TextView>(R.id.text_medication_name).text = getChild(groupPosition, childPosition)
} else return childView
return childView
}
override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean {
return true
}
}
Here's the detail view fragment:
class MedicationItemFragment : Fragment() {
private lateinit var medicationName: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//get medication name from SafeArgs
arguments?.let {
medicationName = it.getString("medicationName").toString()
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_medication_item, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// get the correct medication data
val medication: Medication = MedicationsListData().getMedication(medicationName)
// populate the view with current medication's data
view.findViewById<TextView>(R.id.text_pharmacodynamics_body).text =
medication.pharmacodynamics
view.findViewById<TextView>(R.id.text_contraindications_body).text =
medication.contraindications
}
companion object {
fun newInstance(): ParametersFragment = ParametersFragment()
}
}
Here's the class providing the adapter's data:
class GroupListData {
fun getItemData(): List<List<String>> {
return listOf(
listOf("amoxicillin + clavulanate","penicillin","clindamycin","vancomycin"),
listOf("epinephrine","norepinephrine","dopamine"),
listOf("metoprolol","adenosine","amiodarone"),
listOf("metoclopramide")
)
}
fun getGroupData(): List<String> {
return listOf(
"antibiotics",
"vasopressors",
"antiarrhythmics",
"antiemetics"
)
}
}
I can elaborate or explain anything if neccessary. Any help is very much appreciated!
After reading around I now understand that the problem is the recycling views not populating correctly. A fix to this was to just stop recycling the views and creating a new one for every child instead:
override fun getChildView(
groupPosition: Int,
childPosition: Int,
isLastChild: Boolean,
convertView: View?,
parent: ViewGroup?
): View {
var childView = convertView
val layoutInflater = this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
childView = layoutInflater.inflate(R.layout.medication_list_item, null)
val medicationName = GroupListData().getItemData()[groupPosition][childPosition]
val medication: Medication = MedicationsListData().getMedication(medicationName)
childView.findViewById<TextView>(R.id.text_medication_name).text = medication.medicationName
if(medication.brandName != "null") {
childView.findViewById<TextView>(R.id.text_brand_name).text = medication.brandName
}
return childView
}
I wouldn't exactly consider this a solution as much as a workaround, since the amount of (non-recycled) views will probably cause some performance issues. But if anyone else is struggling with the same issue (and doesn't anticipate performance problems), this works.