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
Related
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 : ", " ")
}
}
I'm trying to refactor my app to use ViewBinding. I've gone through all the fragments and activities; however, I have an ArrayAdapter that I'm unsure of the proper convention to use view binding to prevent memory leaks.
What is the proper way to use a viewbinding in an ArrayAdapter?
I have been using this method for fragments:
private var _binding: BINDING_FILE_NAME? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = BINDING_FILE_NAME.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
I call my adapter like so:
var myadapter : MyCustomAdapter = MyCustomAdapter(requireContext(), R.layout.row_autocomplete_item, myListOfStrings())
MyCustomAdapter class
class MyCustomAdapter(ctx: Context, private val layout: Int, private val allItems: List<String>) : ArrayAdapter<String>(ctx, layout, allItems) {
var filteredItems: List<String> = listOf()
override fun getCount(): Int = filteredItems.size
override fun getItem(position: Int): String = filteredItems[position]
#SuppressLint("SetTextI18n")
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = convertView ?: LayoutInflater.from(parent.context).inflate(layout, parent, false)
val item = filteredItems[position]
view.apply {
// HERE IS WHERE I AM NEEDING TO BIND THE VIEW
tvName?.text = item
}
return view
}
override fun getFilter(): Filter {
return object : Filter() {
override fun publishResults(charSequence: CharSequence?, filterResults: FilterResults) {
#Suppress("UNCHECKED_CAST")
filteredItems = filterResults.values as List<String>
notifyDataSetChanged()
}
override fun performFiltering(charSequence: CharSequence?): FilterResults {
val queryString = charSequence?.toString()?.lowercase(Locale.ROOT)
val results = FilterResults()
results.values = if (queryString == null || queryString.isEmpty())
allItems
else
allItems.filter {
it.lowercase(Locale.ROOT).contains(queryString)
}
return results
}
}
}
}
I did like this, its working. But Im not sure, whether it is correct way or not
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val binding: LayoutCustomSpinnerBinding
var row = convertView
if (row == null) {
val inflater =
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
binding = LayoutCustomSpinnerBinding.inflate(inflater, parent, false)
row = binding.root
} else {
binding = LayoutCustomSpinnerBinding.bind(row)
}
binding.txtContent.text = spinnerList[position].ValueData
return row
}
Based on this answer, got this:
If convertView is not null, then bind to it. Inflate otherwise.
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val binding: MyLayoutBinding =
if (convertView != null) MyLayoutBinding.bind(convertView)
else MyLayoutBinding.inflate(LayoutInflater.from(context), parent, false)
// use binding
val item = getItem(position)
binding.text = item.name
return binding.root
}
class HoursAdapter(private val hoursList: List<HoursItem>)
:RecyclerView.Adapter<HoursAdapter.HoursViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
HoursViewHolder {
val binding = HoursListItemsBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return HoursViewHolder(binding)
}
override fun getItemCount() = hoursList.size
override fun onBindViewHolder(holder: HoursViewHolder, position: Int) {
with(holder){
with(hoursList[position]) {
binding.topLearnerName.text = name
val hours = "$hours learning hours, $country"
binding.topLearnerTime.text = hours
GlideApp.with(holder.itemView.context)
.load(badgeUrl)
.into(binding.topLearnerImage)
holder.itemView.setOnClickListener {
Toast.makeText(holder.itemView.context, hours,
Toast.LENGTH_SHORT).show()
}
}
}
}
inner class HoursViewHolder(val binding: HoursListItemsBinding)
:RecyclerView.ViewHolder(binding.root)
}
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()
}
}