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()
}
}
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 update the below recycler view to show the new data that input from the form in the second activity. However, its only showing the original list that I had in there. What am I doing wrong here?
Here is the main activity:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
var workoutList = mutableListOf(
Workout("a","d","d","d"),
Workout("a","d","d","d"),
Workout("a","d","d","d")
)
val adaptor = WorkoutAdaptor(workoutList)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnNext.setOnClickListener {
Intent(this, SecondActivity::class.java).also {
startActivity(it)
}
}
binding.recyclerView.adapter = adaptor
binding.recyclerView.layoutManager = LinearLayoutManager(this)
}
override fun onRestart() {
super.onRestart()
var workout = intent.getSerializableExtra("EXTRA_WORKOUT") as Workout
workoutList.add(workout)
adaptor.notifyDataSetChanged()
}
}
Here is the adapter:
class WorkoutAdaptor (
var workouts: List<Workout>
) : RecyclerView.Adapter<WorkoutAdaptor.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val workoutCardBinding = WorkoutCardBinding.inflate(
LayoutInflater.from(parent.context), parent, false)
return ViewHolder(workoutCardBinding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(workouts[position])
}
override fun getItemCount(): Int {
return workouts.size
}
inner class ViewHolder(private val workoutCardBinding: WorkoutCardBinding) :
RecyclerView.ViewHolder(workoutCardBinding.root) {
fun bind(workout: Workout) {
workoutCardBinding.tvWorkoutCard.text = workout.toString()
}
}
}
And here is the activity where I get the workout object from:
class SecondActivity : AppCompatActivity() {
private lateinit var binding: ActivitySecondBinding
lateinit var spWorkoutsPosition: String
lateinit var spIncrementsPosition: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySecondBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.spWorkoutTypes.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
spWorkoutsPosition = p0?.getItemAtPosition(p2) as String
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
binding.spIncrements.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(adapterView: AdapterView<*>?, view: View?, position: Int, id: Long) {
spIncrementsPosition = adapterView?.getItemAtPosition(position) as String
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
binding.btnSave.setOnClickListener {
val workoutName = binding.tvWorkoutName.text.toString()
val workoutType = spWorkoutsPosition.toString()
val workoutIncrement = spIncrementsPosition.toString()
val workoutRestTime = binding.etRestTime.text.toString()
val workout = Workout(workoutName, workoutType, workoutIncrement, workoutRestTime)
Intent(this, MainActivity::class.java).also {
it.putExtra("EXTRA_WORKOUT", workout)
startActivity(it)
}
}
}
}
As #John Doe commented you should probably use ActivityResultLauncher. refer here.
and as for the update issue the recommended way of implementing recyclerView's adapter is androidx.recyclerview.widget.ListAdapter. The ListAdapter handles addition and removal without the need to redraw the entire view, and even animate those changes.
here's the implementation for your adapter:
class WorkoutAdaptor :
ListAdapter<Workout, WorkoutAdaptor.ItemViewHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
return ItemViewHolder(
ItemAutocompleteSearchResultBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
holder.bind(position)
}
inner class ItemViewHolder(val binding: ItemAutocompleteSearchResultBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(position: Int) {
binding.apply {
workoutCardBinding.tvWorkoutCard.text = getItem(position).toString()
}
}
}
private class DiffCallback : DiffUtil.ItemCallback<Workout>() {
override fun areContentsTheSame(oldItem: Workout, newItem: Workout): Boolean {
return oldItem == newItem
}
override fun areItemsTheSame(oldItem: Workout, newItem: Workout): Boolean {
return oldItem.param1 == newItem.param1 &&
oldItem.param2 == newItem.param2 &&
oldItem.param3 == newItem.param3 &&
oldItem.param4 == newItem.param4
}
}
}
then you only have to call,
adapter.submitList(workoutList)
after you update the workoutList and the ListAdapter handles the rest.
and also call the adapter.submitList() in onStart() or onResume() methods.
For a school project, I made a custom arrayadapter with a context, resources and items attribute. Today I received feedback from my teacher and he wants me to find a solution where I don't have a Context attribute because he doesnt like that I always need to specify the context.
This is my code:
class TalentListAdapter(
var mCtx: Context,
var resources: Int,
var items: MutableList<Talent>
) : ArrayAdapter<Talent>(mCtx, resources, items) {
lateinit var mItem: Talent
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val layoutInflater: LayoutInflater = LayoutInflater.from(mCtx)
val view: View = layoutInflater.inflate(resources, null)
mItem = items[position]
//set name of talent
val talentTextView: TextView = view.findViewById(R.id.talentName)
talentTextView.text = mItem.toString()
return view
}
}
He wants to get rid of the mCtx: Context attribute, but I don't find a solution for it. Any suggestions?
The adapter is created like this atm:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val listView: ListView = binding.talentList
// set custom adapter for talent_list
val adapter = TalentListAdapter(view.context, R.layout.talentlayout, binding.talentViewModel?.getTalents()?.value as MutableList<Talent>)
listView.adapter = adapter
}
Can you try this
class YourAdapter() : RecyclerView.Adapter<YourAdapter.ViewHolder>() {
inner class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
var Data: List<YourResponse> = emptyList()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(
R.layout.your_layout, parent, false
)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return Data.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val getYourModel = Data[position]
val Binding = holder.view
Binding.yourTextView.apply{
text = getYourModel.yourField
}
.....
}
}
and pass Data from your Fragment or Activity (my example uses Retrofit2)
private var theList: List<YourResponse> = emptyList()
private val theAdapter =yourAdapter()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
onUpdateData()
}
private fun onUpdateData() {
...
override fun onResponse(call: Call<YourResponse>, response: Response<YourResponse>) {
val body = response.body()?.results
if (response.isSuccessful && body != null) {
onUpdateDataSuccess(body)
} else {
// Something
}
}
...
}
private fun onUpdateDataSuccess(data: List<YourResponse>) {
theList = data
theAdapter.Data = data
}
...
You only need context while inflating so you should use parent.context instead of explicit context.
Parent.Context represent the Activity/Fragment where this recycler view is implemented.
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()
}
}