I've extended ArrayAdapter for spinner:
class OrderAdapter(context: Context, resource: Int, objects: List<Order>) : ArrayAdapter<Order>(context, resource, objects) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? {
val view = super.getView(position, convertView, parent)
view?.let { view.find<TextView>(android.R.id.text1).text = getItem(position).name }
return view
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View? {
val view = super.getDropDownView(position, convertView, parent)
view?.let {view.find<TextView>(android.R.id.text1).text = getItem(position).name }
return view
}
}
I'm getting exception:
java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter convertView
at com.github.blabla.endlesss.ui.adapter.OrderAdapter.getView(OrderAdapter.kt:0)
Any ideas how to fix it?
This issue was caused by incremental compile. Just needed to rebuild project after converting from Java.
Please check the sample below:
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View? {
return getCustomView(position, convertView, parent)
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? {
return getCustomView(position, convertView, parent)
}
fun getCustomView(position: Int, convertView: View?, parent: ViewGroup): View? {
val rootView: View? = LayoutInflater.from(context).inflate(R.layout.my_custom_view, parent, false)
return rootView
}
The returned value should be nullable.
In my case, I just had to add the "?" to the convertView parameter
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
override fun getView(i: Int, view: View, parent: ViewGroup): View {
var itemView = LayoutInflater.from(context).inflate(R.layout.item_dashbord_data, parent, false)
return itemView
}
// change name of View object (itemView) differ from getView second object (view). so change view to itemView or other name
Related
I have Spinner with ArrayAdapter. ArrayAdapter code show like:
class HouseholdsArrayAdapter(
context: Context,
resource: Int,
) : ArrayAdapter<Household>(context, resource, arrayListOf()) {
fun submitList(list: List<Household>) {
this.clear()
this.addAll(list)
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return super.getView(position, convertView, parent).also {
(it as TextView).text = getItem(position)!!.fullAddress
}
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
return super.getDropDownView(position, convertView, parent).also {
(it as TextView).text = getItem(position)!!.fullAddress
}
}
Also i have households list. It is LiveData in my ViewModel. I observe it in onViewCreated() method:
viewModel.householdList().observe(viewLifecycleOwner){
householdAdapter.submitList(it)
}
My issue:
Spinner DOES NOT save selected position after rotating screen.
What is more: Spinner save position after rotating if i submit households list immediately. But it does not work with LiveData mechanism.
This is very strange because how i understand Spinner must save state with the help onSaveInstanceState() and onRestoreInstanceState().
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.
I created a custom spinner adapter, populated with data, on runtime it drops the list, but nothing can be selected. The selection works with the stock ArrayAdapter, but the custom. Please help, what am I missig...
DetailsActivity.kt:
'''class DetailsActivity : AppCompatActivity() {
.
.
.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.word_details)
val spinner: Spinner = findViewById(R.id.topic_spinner)
spinner.adapter = cAdapter(applicationContext, fillSpinner())
val listener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(parent: AdapterView<*>?, view: View, pos: Int, id: Long) {
Toast.makeText(applicationContext, "Selected: $pos", Toast.LENGTH_SHORT).show()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
spinner.onItemSelectedListener = listener
.
.
.
'''
fillSpinner function:
'''
fun fillSpinner(): ArrayList{
val topics = ArrayList<String>()
topics.clear()
val h_cursor = dbHandler.get_h_AllRow()
h_cursor.moveToFirst()
while (!h_cursor.isAfterLast){
topics.add(h_cursor.getString(h_cursor.getColumnIndex(DBHelper.H_COLUMN_TOPIC)))
h_cursor.moveToNext()
}
return topics
}
'''
cAdapter class:
'''
class cAdapter(val ctx: Context, val items: ArrayList) :
ArrayAdapter(ctx, 0, items) {
override fun getView(position: Int, recycledView: View?, parent: ViewGroup): View {
return this.createView(position, recycledView, parent)
}
override fun getDropDownView(position: Int, recycledView: View?, parent: ViewGroup): View {
return this.createView(position, recycledView, parent)
}
private fun createView(position: Int, recycledView: View?, parent: ViewGroup): View {
val item = getItem(position)
val view = recycledView ?: LayoutInflater.from(context).inflate(
R.layout.spinner_layout,
parent,
false)
view.IB_cancel.setImageResource(android.R.drawable.ic_delete)
view.IB_cancel.setOnClickListener {
Toast.makeText(context, "DELETE SOMETHING!", Toast.LENGTH_SHORT).show()
}
view.t_sp_holder.text = item
return view
}
}
'''
As I see, the problem should lie somewhere here in theese lines. Please give some advice, I am new at Kotlin.
I can't find a solution to my problem : I want a hint text displayed on my Spinner but the adapter I set only accepts enum type (IdentityType enum)so I cannot add a String to it (for the hint)
Do you have any solution still using the enum in the adapter?
private fun initDriverIdentityTypeSpinner() {
driverIdentityTypeSpinner.adapter = object : ArrayAdapter<IdentityType>(context!!, android.R.layout.simple_spinner_item,IdentityType.values()) {
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View =
(super.getDropDownView(position, convertView, parent) as CheckedTextView).also{
it.setText(getItem(position)!!.stringRes())
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup) =
(super.getView(position, convertView, parent) as TextView).also {
it.setText(getItem(position)!!.stringRes())
}
override fun isEnabled(position: Int): Boolean = position != 0
}.also {
it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
}
//IdentityType Extension
#StringRes
fun IdentityType.stringRes(): Int {
return when(this) {
IdentityType.DRIVING_LICENSE -> R.string.driving_license
IdentityType.ID_CARD -> R.string.id_card
IdentityType.PASSPORT -> R.string.passport
}
}
In Kotlin there is possibility to put properties inside enum (here it calls enum class). You can define it in constructor like following:
enum class IdentityType(val stringResId: Int) {
DRIVING_LICENSE(R.string.driving_license),
ID_CARD(R.string.id_card),
PASSPORT(R.string.passport)
}
Then you can use it like it is a common property of a class.
val type: IdentityType = ...
val string = getString(type.stringResId)
I want to use a Spinner inside my Dialog. The Spinner appears and contains the data i want to show. But as soon as i select one of the items, memory usage shoots up and i end up getting an out of memory Exception. In logcat the first entry after selectin the item is D/AndroidRuntime: Shutting down VM. I have no stacktraces whatsoever. Only some info output from the gc and two warnings about throwing an OOM.
class SearchTypeSpinner(context: Context, attributeSet: AttributeSet? = null): LinearLayout(context, attributeSet), AdapterView.OnItemSelectedListener {
private val types: List<String>
private val mapping: MutableMap<String, Int>
private val spinner: Spinner
init {
types = context.resources.getStringArray(R.array.filter_types).asList()
spinner = Spinner(context)
spinner.adapter = Adapter()
spinner.onItemSelectedListener = this
spinner.layoutParams = ViewGroup.LayoutParams(context, attributeSet)
addView(spinner)
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {}
override fun onNothingSelected(parent: AdapterView<*>?) {}
private inner class Adapter: ArrayAdapter<String>(context, android.R.layout.simple_spinner_item) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val view = TextView(context)
view.layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)
view.text = getTypeNameAtPosition(position)
return view
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup?): View {
return getView(position, convertView, parent)
}
override fun getCount(): Int {
return types.size
}
}
}
Please let me know if i can provide additional information and thanks in advance.