I need to catch a click on a spinner element and based on this change the elements of another spinner
clicking on the spinner element does not work.
OnItemSelectedListener is not called.
something is wrong with castomadapter, I can't figure out what the problem is
xml spiner
<Spinner
android:id="#+id/clientSpinner"
android:spinnerMode="dialog"
android:layout_marginTop="16dp"
style="#style/spinner_margins"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
adapter in this fragment, I call the creation of a castomadapter
val clientsAdapter =
CustomArrayAdapter<Client>(requireContext(), android.R.layout.simple_spinner_item, R.layout.spinner_item) {
it.name
}
binding.clientSpinner.adapter = clientsAdapter
castomadapter my class
class CustomArrayAdapter<T>(
context: Context,
#LayoutRes private val layoutResource: Int,
#LayoutRes private val dropdownLayoutResource: Int = layoutResource,
#IdRes private val textViewResourceId: Int = 0,
private val show: (T) -> String
) : ArrayAdapter<T>(context, layoutResource) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = createViewFromResource(convertView, parent, layoutResource)
return bindData(getItem(position)!!, view)
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = createViewFromResource(convertView, parent, dropdownLayoutResource)
return bindData(getItem(position)!!, view)
}
private fun createViewFromResource(
convertView: View?, parent: ViewGroup, layoutResource: Int
): TextView {
val context = parent.context
val view =
convertView ?: LayoutInflater.from(context).inflate(layoutResource, parent, false)
return try {
if (textViewResourceId == 0) view as TextView
else {
view.findViewById(textViewResourceId) ?: throw RuntimeException(
"Failed to find view with ID " + "${
context.resources.getResourceName(
textViewResourceId
)
} in item layout"
)
}
} catch (ex: ClassCastException) {
Timber.e("You must supply a resource ID for a TextView")
throw IllegalStateException(
"ArrayAdapter requires the resource ID to be a TextView", ex
)
}
}
private fun bindData(value: T, view: TextView): TextView {
view.text = show(value)
return view
}
}
fun <T> ArrayAdapter<T>.bindItems(items: List<T>) {
clear()
addAll(items)
notifyDataSetChanged()
}
fun <T> Flow<Int>.mapToItems(adapter: ArrayAdapter<T>): Flow<T> = transform {
adapter.getItem(it)?.let { item -> emit(item) }
}
enter code here
enter code here
onItemSelectedListener pressing method
binding.clientSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?, view: View?, position: Int, id: Long
) {
Log.e("S", "нажатие")
trainingsAdapter =
CustomArrayAdapter<Training>(requireContext(), android.R.layout.simple_spinner_item, R.layout.spinner_item) {
it.name + it.id?.let { it1 -> getStringTreningOnCurrent(it1) }
}.also {
binding.trainingSpinner.adapter = it
}
}
override fun onNothingSelected(parent: AdapterView<*>?) {
Log.e("Sd : ", " ")
}
}
Related
First it said that my listView is null. Fixed this. Then i debuged the Adapter and i found out that the getView and getItem are never called.
Here is my Adapter:
class PropertiesAdapter(private val context: Context, private val properties: ArrayList<Property>) : BaseAdapter() {
override fun getCount(): Int {
Log.d("getCount", "${properties.size}")
return properties.size
}
override fun getItem(position: Int): Any {
Log.d("getItem", "${properties[position]}")
return properties[position]
}
override fun getItemId(position: Int): Long {
Log.d("getItemId", "${position.toLong()}")
return position.toLong()
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
Log.d("getView", "$properties")
val property = properties[position]
val layoutInflater = context.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
val myView = layoutInflater.inflate(R.layout.property, null)
myView.title.text = property.title
myView.descr.text = property.descr
myView.location.text = property.location
myView.price.text = property.price
return myView
}
}
Here is my Log:
D/getCount: 3
D/getCount: 3
D/getItemId: 0
D/getItemId: 0
I have an ExpandableListView where some groups have children and some not, what I need to do is to expand only the groups that have children.
Part of the body array elements is empty and because of that, I'm getting an IndexOutOfBoundsException
class ExpandableInnerCartAdapter(
var context: Context,
var expandableListView: ExpandableListView,
var header: MutableList<Cart>,
val isTerminadoFragment:Boolean
) : BaseExpandableListAdapter() {
val map = SparseBooleanArray()
var body: List<List<String>> = listOf()
override fun getGroup(groupPosition: Int): Cart {
return header[groupPosition]
}
override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean {
return true
}
override fun hasStableIds(): Boolean {
return false
}
fun getCart(): MutableList<Cart> = header
fun getCheckedArray():SparseBooleanArray = map
override fun getGroupView(
groupPosition: Int,
isExpanded: Boolean,
convertView: View?,
parent: ViewGroup?
): View {
var convertView = convertView
if(convertView == null){
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
convertView = inflater.inflate(R.layout.layout_group,null)
}
val item = header[groupPosition]
body = listOf(item.optionList)
expandableListView.expandGroup(groupPosition)
expandableListView.setGroupIndicator(null)
convertView.item_name.text = item.productName
return convertView
}
override fun getChildrenCount(groupPosition: Int): Int {
return body[groupPosition].size
}
override fun getChild(groupPosition: Int, childPosition: Int): Any {
return body[groupPosition][childPosition]
}
override fun getGroupId(groupPosition: Int): Long {
return groupPosition.toLong()
}
override fun getChildView(
groupPosition: Int,
childPosition: Int,
isLastChild: Boolean,
convertView: View?,
parent: ViewGroup?
): View {
var convertView = convertView
if(convertView == null){
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
convertView = inflater.inflate(R.layout.layout_child,null)
}
if(getChildrenCount(groupPosition) > 0){
val title = convertView?.findViewById<TextView>(R.id.tv_title)
title?.text = "Opción ${childPosition+1} -> ${getChild(groupPosition,childPosition)}"
}
return convertView!!
}
override fun getChildId(groupPosition: Int, childPosition: Int): Long {
return childPosition.toLong()
}
override fun getGroupCount(): Int {
return header.size
}
}
The error seems like it's happening when no group has children and try to do
expandableListView.expandGroup(groupPosition)
I have tried to fix the issue with an if statement:
if(body.isNotEmpty()){
expandableListView.expandGroup(groupPosition)
}
but this solution does not work.
How do I avoid groups that do not have children?
Thanks
As you use Kotlin you have a lot of useful extension functions at your disposal. One of them is filter for which you can specify condition, of course.
The solution would be to filter out empty arrays from the list you set as the new body value:
body = listOf(item.optionList).filter { it.isNotEmpty() }
Filter function definition can be seen here.
I'm using databiding into a RecyclerView in my layout like below :
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/topup_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:items="#{viewModel.items}"
tools:listitem="#layout/list_item_content" />
and the list and binding adapters like :
#BindingAdapter("app:items")
fun setItems(listView: RecyclerView, items: List<ListItem>?) {
items?.let {
(listView.adapter as MyListAdapter).submitList(items)
}
}
//------------------
class MyListAdapter() :
ListAdapter<ListItem, ViewHolder>(myListItemDiffCallback()) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
class ViewHolder private constructor(private val binding: ListItemContentBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: ListItem) {
binding.item = item
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = TopUpListItemContentBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
class myListItemDiffCallback : DiffUtil.ItemCallback<ListItem>() {
override fun areItemsTheSame(oldItem: ListItem, newItem: ListItem): Boolean {
return oldItem.label == newItem.label
}
override fun areContentsTheSame(
oldItem: ListItem,
newItem: ListItem
): Boolean {
return oldItem == newItem
}
}
But it's not the same case using a simple ListView. For many reasons, I prefer using a listview and I tried but I don't know how to customize my adapter for databiding like I used in RecycleView.
Below is My adapter for Listview :
class MyListAdapter(context: Context, items: List<ListItem>) :
ArrayAdapter<ListItem>(context, 0, items) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val binding = ListItemContentBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
binding.item = getItem(position)
return binding.root
}
}
My Item class Model :
data class ListItem(
val iconResId: Int,
val label: String,
val action: () -> Unit
)
Any one has a clue ?
Try to update your adapter like below:
class MyListAdapter(context: Context, var items: List<ListItem> = arrayListOf()) :
ArrayAdapter<ListItem>(context, 0, items) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val binding = ListItemContentBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
binding.item = items[position]
return binding.root
}
override fun getCount(): Int {
return items.size
}
fun submitList(items: List<ListItem>) {
this.items = items
notifyDataSetChanged()
}
}
I have a spinner that loads dynamic data from server. Each item in the spinner also has custom layout .
I managed to show the data in the spinner. Can somebody help me in getting the selected value ?
I have tried spinner.getItemIdAtPosition(position) but i'm getting the result as 0 even if i click any item.
This is my adapter code:
class CustomDropDownAdapter(val context: Context, var batchList: Array<BatchList>) : BaseAdapter() {
val mInflater: LayoutInflater = LayoutInflater.from(context)
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val view: View
val vh: ItemRowHolder
if (convertView == null) {
view = mInflater.inflate(R.layout.batch_row_spinner, parent, false)
vh = ItemRowHolder(view)
view?.tag = vh
} else {
view = convertView
vh = view.tag as ItemRowHolder
}
vh.date.text = batchList.get(position).expiry_date
vh.availQty.text = "Available: ${batchList.get(position).available_quantity}"
return view
}
override fun getItem(position: Int): Any? {
return null
}
override fun getItemId(position: Int): Long {
return 0
}
override fun getCount(): Int {
return batchList.size
}
private class ItemRowHolder(row: View?) {
val date: TextView
val availQty: TextView
init {
this.date = row?.findViewById(R.id.date) as TextView
this.availQty = row?.findViewById(R.id.available) as TextView
}
}
}
This is my Function where i load in spinner
/**Method to load all items in spinner */
private fun loadBatch(medicineId:String,pharmaId:String)
{
val call=RetrofitClient.instance.api.displayBatchList("Bearer $token",20.toString(),medicineId,0.toString(),pharmaId)
call.enqueue(object :Callback<Array<BatchList>>{
override fun onResponse(call: Call<Array<BatchList>>, response: Response<Array<BatchList>>) {
if(response.code()==200)
{
var spinnerAdapter: CustomDropDownAdapter = CustomDropDownAdapter(context!!, response.body()!!)
updateMedView.pharmaSpinerbatch.adapter = spinnerAdapter
}
}
override fun onFailure(call: Call<Array<BatchList>>, t: Throwable) {
Log.e("Batch Load error",t.message)
}
})
}
I need to get the selected item
Add listner for your spiner
//item selected listener for spinner
mySpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(p0: AdapterView<*>?) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
Toast.makeText(this#MainActivity, myStrings[p2], LENGTH_LONG).show()
}
}
I'm trying to use a RecyclerView to show an array of values in CardViews in a specific order i.e. "Title A" & "Subtitle A" in 1 CardView; "Title B" & "Subtitle B" in another. Is there a way to obtain the values from the arrays in my fragment without having to create a new verbose class, or do I have to use a data class to achieve this?
Fragment class
class MyFragment : androidx.fragment.app.Fragment() {
private lateinit var mRecyclerView: RecyclerView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_rv, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
val v = view
mRecyclerView = v!!.findViewById<RecyclerView>(R.id.my_recyclerview)
mRecyclerView.layoutManager = LinearLayoutManager(activity)
val myList = mutableListOf(
RVAdapterTConnections.ITEM_A,
RVAdapterTConnections.ITEM_B,
RVAdapterTConnections.ITEM_B
)
val titlesList = mutableListOf(
"Title A",
"Title B"
)
val subtitlesList = mutableListOf(
"Subtitle A",
"Subtitle B"
)
val mAdapter = MyRVAdapter(myList, titlesList)
mRecyclerView.adapter = mAdapter
super.onActivityCreated(savedInstanceState)
}
}
Adapter class
internal class MyRVAdapter(private val listViewType: List<Int>, private val myList: List<CharSequence>) : RecyclerView.Adapter<MyRVAdapter.ViewHolder>() {
companion object {
const val ITEM_A = 1
const val ITEM_B = 2
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
ITEM_A -> ViewHolderItemA(inflater.inflate(R.layout.layout_item_a, parent, false))
else -> ViewHolderItemB(inflater.inflate(R.layout.layout_item_b, parent, false))
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val viewType = listViewType[position]
when (viewType) {
ITEM_A -> {
val viewHolderA = holder as ViewHolderItemA
viewHolderA.textView.text = "Lorem Ipsum"
}
ITEM_B -> {
val viewHolderB = holder as ViewHolderItemB
viewHolderB.ivExpandCollapse.setImageDrawable(ContextCompat.getDrawable(holder.ivExpandCollapse.context, R.drawable.ic_keyboard_arrow_down))
viewHolderB.tvTitle.text = titlesList[position]
viewHolderB.tvSubtitle.text = subtitlesList[position]
}
else -> {
val viewHolderA = holder as ViewHolderItemA
viewHolderA.textView.text = "Lorem Ipsum"
}
}
}
override fun getItemCount(): Int = listViewType.size
override fun getItemViewType(position: Int): Int = listViewType[position]
open inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
inner class ViewHolderItemA(itemView: View) : ViewHolder(itemView) {
val textView: TextView = itemView.findViewById(R.id.my_custom_tv)
}
inner class ViewHolderItemB(itemView: View) : ViewHolder(itemView) {
val tvTitle: TextView = itemView.findViewById(R.id.tv_title)
val tvSubtitle: TextView = itemView.findViewById(R.id.tv_subtitle)
}
}
Data class
data class MyItem (val title: String, val subtitle: String)
the view-type ordinary can be derived from the data model, most commonly from some type of groupId. these titlesList and subtitlesList need to be replaced; use BaseAdapter to populate the RecyclerView.Adapter. for example (it loads string resources, where SpinnerItem is a simple data model):
abstract class BaseArrayAdapter : BaseAdapter {
private var mItems: ArrayList<SpinnerItem>? = null
private var layoutInflater: LayoutInflater? = null
internal constructor(#NonNull context: Context) {
this.layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
}
internal constructor(#NonNull context: Context, #NonNull #ArrayRes arrayKeys: Int, #NonNull #ArrayRes arrayValues: Int) {
this.layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
this.setItems(context, arrayKeys, arrayValues)
}
#NonNull
#Override
override fun getItem(position: Int): SpinnerItem {
return this.mItems!!.get(position)
}
#Override
override fun getItemId(position: Int): Long {
return this.mItems!!.get(position).id!!
}
#NonNull
#Override
override fun getView(position: Int, #Nullable view: View?, #NonNull parent: ViewGroup): View {
var convertView = view
if (convertView == null) {
convertView = this.layoutInflater!!.inflate(R.layout.support_simple_spinner_dropdown_item, parent, false)
}
convertView!!.setTag(this.mItems!!.get(position))
val textView: TextView = convertView.findViewById(android.R.id.text1)
textView.text = this.mItems!![position].name
textView.setAllCaps(true)
return convertView
}
private fun clearItems() {
if (this.mItems != null) {
this.mItems!!.clear()
} else {
this.mItems = ArrayList()
}
}
internal fun setItems(#NonNull context: Context, #NonNull #ArrayRes arrayKeys: Int, #NonNull #ArrayRes arrayValues: Int) {
this.clearItems()
val res = context.getResources()
val keys = res.getStringArray(arrayKeys)
val values = res.getStringArray(arrayValues)
for (i in keys.indices) {
this.mItems!!.add(i, SpinnerItem((i + 1).toLong(), keys[i], values[i]))
}
}
override fun getCount(): Int {
return this.mItems!!.size
}
}
some sub-class of the abstract class:
class SomeAdapter(#NonNull context: Context) : BaseArrayAdapter(context) {
init {
this.setItems(context, R.array.some_keys, R.array.some_values)
}
}
and then in the RecyclerView.Adapter:
init {
this.setItems(new SomeAdapter(context));
}