How to initialise spinner using data binding - android

I am using data binding to bind my spinner
Everything works well except, the first time when I load data from server, I initialize the spinner and I set a selected value from my viewModel, but it doesn't work
If I close and reopen the page, I find the value selected in the spinner
Here is my spinner xml:
<Spinner
android:id="#+id/construction_site"
android:layout_width="match_parent"
android:layout_height="40dp"
android:prompt="#string/select_existing_construction_site"
app:entries="#{viewModel.constructionSiteAdapter}"
app:selectedValue="#{viewModel.constructionSite}"
style="#style/Widget.AppCompat.DropDownItem.Spinner">
</Spinner>
In the viewModel, I initialize the spinner in this way :
class RightDrawerViewModel : ViewModel(){
val constructionSiteAdapter: ArrayAdapter<ConstructionSite> by lazy {
ConstructionSiteAdapter(
App.context,
R.layout.cell_construction_site_list_item,
ArrayList()
)
}
fun bind(constructionSite: ConstructionSite) {
constructionSiteAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
constructionSiteAdapter.clear()
this.constructionSite.value = constructionSite // here I set the selected site to the spinner using data binding
setConstructionSites(constructionSite)
loadConstructionSites() // this method, load all sites from server and add them to the spinner
}
}
Here is my binding adapter:
#BindingAdapter(value = ["selectedValue", "selectedValueAttrChanged", "entries"], requireAll = false)
fun setEntries(view: Spinner, newSelectedValue: ConstructionSite?, bindingListener: InverseBindingListener?, adapter: ArrayAdapter<*>) {
view.adapter = adapter
view.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
bindingListener?.onChange()
}
override fun onNothingSelected(parent: AdapterView<*>) {}
}
if (newSelectedValue != null) {
val pos = (view.adapter as? ArrayAdapter<ConstructionSite>)?.getPosition(newSelectedValue)
if (pos != null) view.setSelection(pos, true)
}
}
#InverseBindingAdapter(attribute = "selectedValue", event = "selectedValueAttrChanged")
fun getSelectedValue(view: Spinner): ConstructionSite {
return view.selectedItem as ConstructionSite
}
So the problem is this.constructionSite.value = constructionSite doesn't work and for the first load, the spinner doesn't have a selected value

Related

How can i change the visibility of a textview in every element of the recyclerview?

I have an arraylist (called Itemlist) of all recyclerview elements. In each element there are 2 textviews - a german and english word. only one of them is shown (because they overlap). when i click on the element it shows the other language (for example: the german word is set to gone and the english word is visible now).
Now I want a function which sets all english textviews (in every element) to gone and the german to visible. My problem is - i dont know how to reach all elements in this arraylist and check the visibility of the textviews. in my example it resets only the first word.
For better understanding
Here is the code:
fun reset_to_EN() {
ItemList.forEach { test_if_german() }
}
OR
fun reset_to_EN2() {
for (item in ItemList) {
test_if_german()
}
}
Check visibility
fun test_if_german(){
if (text_view_de.visibility == View.VISIBLE) {
text_view_en.visibility = View.VISIBLE
text_view_de.visibility = View.GONE
}
adapter.notifyDataSetChanged()
}
If you can please show me a code example for better understanding.
Thanks to everyone who tries to help.
Or here is the whole code for the adapter and mainActivity if it's needed:
class Adapter(
val c: Context,
private val ArrList: ArrayList<Item>):
RecyclerView.Adapter<Adapter.ViewHolder>()
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false)
return ViewHolder(inflater)
}
override fun getItemCount() = ArrList.size
inner class ViewHolder(v: View) : RecyclerView.ViewHolder(v), View.OnClickListener {
var textViewDe: TextView = v.text_view_de
var textViewEn: TextView = v.text_view_en
private var menueImage: Button
init {
v.setOnClickListener(this)
textViewDe = v.findViewById(R.id.text_view_de)
textViewEn = v.findViewById(R.id.text_view_en)
menueImage = v.findViewById(R.id.menu_button)
menueImage.setOnClickListener { popupMenu(it) }
}
private fun popupMenu(v:View) {
val drop = PopupMenu(c, v)
val position = ArrList[adapterPosition]
drop.inflate(R.menu.drop_menu)
drop.setOnMenuItemClickListener {
when(it.itemId){
R.id.edit_menu->{
val v2 = LayoutInflater.from(c).inflate(R.layout.add_item_layout,null)
val DE = v2.findViewById<EditText>(R.id.editText)
val EN = v2.findViewById<EditText>(R.id.editText2)
AlertDialog.Builder(c)
.setView(v2)
.setPositiveButton("Ok"){
dialog,_->
position.Englisch = DE.text.toString()
position.Deutsch = EN.text.toString()
notifyDataSetChanged()
//Toast.makeText(c,"User Information is Edited",Toast.LENGTH_SHORT).show()
dialog.dismiss()
}
.setNegativeButton("Cancel"){
dialog,_->
dialog.dismiss()
}
.create()
.show()
true
}
R.id.delete_menu-> {
ArrList.removeAt(adapterPosition)
notifyDataSetChanged()
//Toast.makeText(c,"entfernt",Toast.LENGTH_SHORT).show()
true
}
else -> true
}
}
drop.show()
val popup = PopupMenu::class.java.getDeclaredField("mPopup")
popup.isAccessible = true
val menu = popup.get(drop)
menu.javaClass.getDeclaredMethod("setForceShowIcon",Boolean::class.java)
.invoke(menu,true)
}
override fun onClick(p0: View?) {
if (textViewDe.visibility == View.VISIBLE) {
textViewDe.visibility = View.GONE
textViewEn.visibility = View.VISIBLE
} else {
textViewDe.visibility = View.VISIBLE
textViewEn.visibility = View.GONE
}
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentItem = ArrList[position]
holder.textViewDe.text = currentItem.Deutsch
holder.textViewEn.text = currentItem.Englisch
}
And MainActivity:
class MainActivity : AppCompatActivity() {
//DEFINITION
private lateinit var addButton: FloatingActionButton
private lateinit var ItemList: ArrayList<Item>
private lateinit var recy: RecyclerView
private lateinit var adapter: Adapter
//ONCREATE
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//FINDVIEWBYID
addButton = findViewById(R.id.addingBtn)
ItemList = ArrayList()
recy = findViewById(R.id.recycler_view)
//RECYCLERVIEW
adapter = Adapter(this, ItemList)
recy.layoutManager = LinearLayoutManager(this)
recy.adapter = adapter
//FUNCTION-CALL
addButton.setOnClickListener { addInfo() }
}
//FUNKTIONENS
private fun addInfo() {
val inflter = LayoutInflater.from(this)
val v = inflter.inflate(R.layout.add_item_layout, null) //
val eng = v.findViewById<EditText>(R.id.editText)
val deu = v.findViewById<EditText>(R.id.editText2)
val addDialog = AlertDialog.Builder(this)
addDialog.setView(v)
addDialog.setPositiveButton("OK"){ dialog, _->
val eng2 = eng.text.toString()
val deu2 = deu.text.toString()
val UUID = UUID.randomUUID()
ItemList.add(Item(UUID, eng2, deu2))
adapter.notifyDataSetChanged()
//Toast.makeText(this, "Adding Success", Toast.LENGTH_SHORT).show()
dialog.dismiss()
}
addDialog.setNegativeButton("Cancel"){ dialog, _->
dialog.dismiss()
}
addDialog.create()
addDialog.show()
}
fun clearData() {
ItemList.clear()
adapter.notifyDataSetChanged()
Toast.makeText(this, "Alles gelöscht", Toast.LENGTH_SHORT).show()
}
fun reset_all_EN() {
//ArrayList = ItemList
val size: Int = ItemList.size
for (i in 0 until size) {
if (text_view_de.visibility == View.VISIBLE) {
text_view_en.visibility = View.VISIBLE
text_view_de.visibility = View.GONE
}
adapter.notifyDataSetChanged()
}
}
fun reset_to_EN() {
// using forEach() method
ItemList.forEach { test_if_german() }
}
fun reset_to_EN2() {
for (item in ItemList) {
test_if_german()
}
}
fun test_if_german(){
if (text_view_de.visibility == View.VISIBLE) {
text_view_en.visibility = View.VISIBLE
text_view_de.visibility = View.GONE
}
adapter.notifyDataSetChanged()
}
//MENU CLASSES
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.open_menu -> {
val intent = Intent(this, InfoActivity::class.java)
startActivity(intent)
}
R.id.open_menu2 -> {
val intent = Intent(this, SettingsActivity::class.java)
startActivity(intent)
}
R.id.reset_all -> {
reset_to_EN2()
}
}
return super.onOptionsItemSelected(item)
}
}
Since I don't see any declaration of text_view_de or text_view_en, I'm guessing you're using synthetic view properties from the deprecated Android Kotlin Extensions. Assuming that is the case:
When you use text_view_de, it is performing a search in your view hierarchy for the first view it finds with the matching ID. So even though you are doing it within a for loop that iterates through your list of items, you are only working with the same view, over and over.
Edit:
I realized you want to be able to toggle individual views and you were only asking how to add a button to reset all views back to the same language. If this is the case, it does not make sense to add a property to the adapter that controls the state of all views at once like I had suggested in the previous revision of this answer.
Instead, you need to change your data model to have a Boolean that determines which specific language that specific item should show. The problem with how you're doing it now in your click listener is that it is trying to use the Views themselves to determine what state the item is when you change it, but this will cause weird glitches when items scroll off of the screen and back on because ViewHolders get recycled and assigned to different items when they go off and back on screen.
To get started, add a Boolean for the state of the item to your Item class. I don't know exactly what your class looks like now, so adapt this as needed:
data class Item (
val UUID: Long,
val english: String,
val deutsch: String,
var isShowDeutch: Boolean = true
)
A good practice is to have your Adapter class expose a callback for items being clicked so the outside class (Activity) is responsible for manipulating the data model and the Adapter's responsibility is limited to connecting data to views, not manipulating data. So create a callback that the Activity can implement that toggles a single Item's isShowDeutsch property. And when you bind data to a view, use that item's isShowDeutsch to determine visibility.
In Adapter class:
var onItemClickListener: ((itemPosition: Int)->Unit)? = null
//...
// In ViewHolder:
override fun onClick(view: View) {
itemClickListener?.invoke(adapterPosition)
}
//...
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentItem = ArrList[position]
holder.textViewDe.text = currentItem.Deutsch
holder.textViewEn.text = currentItem.Englisch
holder.textViewDe.isVisible = currentItem.isShowDeutsch
holder.textViewEn.isVibible = !currentItem.isShowDeutsch
}
In your Activity when you set up your adapter, you can define a click listener for it that toggles the state of that single item and notifies the adapter of the change:
//RECYCLERVIEW
adapter = Adapter(this, ItemList)
recy.layoutManager = LinearLayoutManager(this)
recy.adapter = adapter
adapter.onItemClickListener = { position ->
ItemList[position].apply { isShowDeutsch = !isShowDeutsch }
adapter.notifyItemChanged(position)
}
And finally, to reset all items back to their original language, you can iterate the items in your list and then notify the adapter. This is more appropriate to do in your Activity, since the Adapter should not be responsible for manipulating data.
fun resetLanguage() {
for (item in ItemList) {
item.isShowDeutsch = true
}
adapter.notifyDataSetChanged()
}
I also recommend you change lateinit var ItemList: ArrayList<Item> to val ItemList = ArrayList<Item>(). It is error prone to have a mutable list type in a mutable var property because there are two different ways to change it and it creates the possibility of having your adapter looking at a different list than the one your Activity is working with.

Android Kotlin does spinner_listener run through during initialize?

I write a kotlin app and wonder, why the code runs through the listener during initializing it. I try to explain the question with my code:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
findpath()
//check, if Dateien existieren, sonst create in function
checkOrCreateFiles()
binding = ActivityMainBinding.inflate(layoutInflater)
var view = binding.root
setContentView(view) //R.layout.activity_main)
val cadapterk: ArrayAdapter<String> = ArrayAdapter<String>(
this,
android.R.layout.simple_spinner_item, myvokdirs
)
binding.spinnerKasten.adapter = cadapterk
//binding.spinnerKasten.setSelection(0)
binding.spinnerKasten.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>,
view: View,
position: Int,
id: Long
) {
kastenselected = myvokdirs[position].toString()
setnewpath(kastenselected)
}
override fun onNothingSelected(parent: AdapterView<*>) {
kastenselected = myvokdirs[0]
}
}
binding.AuswahlContainer.isEnabled = false
fileAktuell = boxes[0] //dateiAkt
checkAktuell = gut[0] // gutAkt
readDatei(fileAktuell)
binding.spinnerKasten.isEnabled = false
// some addional code
}
The situation / problem
In principle, the code works. Binding is o.k. The spinner "binding.spinnerkasten" is o.k. The associated adapter "cadapterk" is ok and shows data of my list "myvokdirs". BUT:
during initializing the spinner the code runs through "setnewpath". But "setnewpath" should be used only after selecting an item in the spinner.
How can I avoid, that "setnewpath" is fired during init? It seems, that the app runs through the listener during onCreate-function.
What is wrong or what is my misunderstanding, that the code fires "setnewpath" already in init instead of only after selecting an item?
do I habe to combine it with an onCLickListener?
(All other things are correct. The spinner appears on the right place, the spinner shows the correct data
onItemSelected is always called upon loading if you dont want something to run on initial load then surround with a boolean
var firstLoad = true
override fun onItemSelected(
parent: AdapterView<*>,
view: View,
position: Int,
id: Long
) {
if(!firstLoad){
kastenselected = myvokdirs[position].toString()
setnewpath(kastenselected)
}else{
firstLoad = false
}
}

RecyclerView doesn't update its view but List is updated

I am using recyclerView to show list of available apps in device..moreover I am using bottomSheet to show more details about selected app ...in this section, I place uninstall button ...here I use uninstall code and from onActivityResult method in BottomSheetDialog.kt file ... on OK pressed ....I want to delete that app/item from list and update View....here list is correct in coding but recyclerView doesn't update its list
Note: I debug the code and found that list got updated in BottomSheet File...I comment out that ....but
recyclerView doesn't
I searched on internet, but didn't find solution which fits in my case
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView.adapter = Adapter(applicationList) // here I send mutable list of all apps in device to adapter
recyclerView.layoutManager = LinearLayoutManager(this)
private fun getApps(List: MutableList<ResolveInfo>): MutableList<AppData> {
// here I return list to adapter with details of installed apps like icon, name, packageName etc
}
DataClass
data class AppData(
val icon: Drawable,
val name: String,
val packageName: String
.....
.....)
Adapter.kt
class Adapter(private val listOfApps: MutableList<AppData>) :
RecyclerView.Adapter<Adapter.ViewHolder>() {
// here I receive mutableList in constructor of Adapter
class ViewHolder(appView: View) : RecyclerView.ViewHolder(appView), View.OnClickListener,
View.OnLongClickListener {
init { // initiate both click listeners
appView.setOnClickListener(this)
appView.setOnLongClickListener(this)
}
// call elements from activity.xml
val icon: ImageView = appView.App_icon
val name: TextView = appView.App_name
val size: TextView = appView.App_size
override fun onClick(v: View?) {
Toast.makeText(v?.context, "OnClick", Toast.LENGTH_SHORT).show()
}
override fun onLongClick(v: View?): Boolean {
val bottomSheetDialog = BottomSheetDialog(currentItem, appList)
// send currentItem and all List to BottomSheetDialog to show details with the help of function
// Show bottomSheet on LongPress
return true
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder // done
override fun getItemCount() = listOfApps.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentItem = listOfApps[position]
holder.icon.setImageDrawable(currentItem.icon)
holder.name.text = currentItem.name
holder.size.text = currentItem.size
}
BottomSheetDialog.kt ...... here in onActivtyResult I delete item and call notify method ... problem is here
class BottomSheetDialog(private val appData: AppData, private val appList: MutableList<AppData>) :
BottomSheetDialogFragment() {
// here I receive appData and AppList in constructor from Adapter OnLongPress
override fun onCreateView() // done
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// here when bottomSheet popup and on click of uninstall.....I check whether user click on OK or CANCEL in onActivity Method (overidden below)
Uninstall_App.setOnClickListener {
// permission in manifest added
val intent = Intent(Intent.ACTION_DELETE)
intent.data = Uri.parse("package:${appData.packageName}")
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
startActivityForResult(intent, 1)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// get result from uninstall dialog
if (resultCode == -1) { // ok pressed
Toast.makeText(context, "ok clicked", Toast.LENGTH_SHORT).show()
dismiss()
// here when user pressed OK....delete that item from List
val index = appList.indexOf(appData)
appList.removeAt(index)
Adapter(appList).notifyItemRemoved(index)
Adapter(appList).notifyDataSetChanged()
// I check above three line by debugging it
// 1. val index .. return index of current item
// 2. this line remove that item
// 3. Adapter(appList) .... notify Item removed
// 4. here that indexed item is removed but view is not updated
// Note: if it is wrong which is the best method to do this
} else if (resultCode == 0) { // cancel pressed
Toast.makeText(context, "Cancel Click", Toast.LENGTH_SHORT).show()
}
}
these lines
Adapter(appList).notifyItemRemoved(index)
Adapter(appList).notifyDataSetChanged()
are both creating new adapters, notify them and.. thats all. they aren't attached to any RecyclerView (as you do in onCreate), so won't be drawn anywhere
you should notify adapter already set for RecyclerView - keep reference in Activity and refer to it instead of creating new one
What you did here is created two new adapters (that have no relation to the adapter used by recycler view except the type of adapter is the same):
Adapter(appList).notifyItemRemoved(index)
Adapter(appList).notifyDataSetChanged()
You can create an interface to listen for changes from BottomSheetDialog:
interface OnAppDeletedListener {
fun appDeletedAtIndex(index: Int)
}
Update your BottomSheetDialog to accept an additional argument of type OnAppDeletedListener:
class BottomSheetDialog(private val appData: AppData, private val appList: MutableList<AppData>, private val listener: OnAppDeletedListener) :
BottomSheetDialogFragment() {
...
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
val index = appList.indexOf(appData)
listener.appDeletedAtIndex(index)
dismiss()
}
}
}
Update your adapter. It must not be responsible for showing any dialogues. Activity or Fragment is responsible for that.
class Adapter(private val listOfApps: MutableList<AppData>, private val longClickListener: View.OnLongClickListener) :
RecyclerView.Adapter<Adapter.ViewHolder>() {
// here I receive mutableList in constructor of Adapter
class ViewHolder(appView: View) : RecyclerView.ViewHolder(appView), View.OnClickListener {
init { // initiate both click listeners
appView.setOnClickListener(this)
appView.setOnLongClickListener(longClickListener)
}
// call elements from activity.xml
val icon: ImageView = appView.App_icon
val name: TextView = appView.App_name
val size: TextView = appView.App_size
override fun onClick(v: View?) {
Toast.makeText(v?.context, "OnClick", Toast.LENGTH_SHORT).show()
}
}
}
And update your activity code:
class MainActivity : AppCompatActivity() {
private lateinit var adapter: Adapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val longClickListener = object: View.OnLongClickListener {
override fun onLongClick(v: View?): Boolean {
displayAppInfoDialog()
return true
}
}
adapter = Adapter(applicationList)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
}
private fun displayAppInfoDialog() {
val listener = object: OnAppDeletedListener() {
fun appDeletedAtIndex(index: Int) {
adapter.notifyItemRemoved(index)
}
}
val bottomSheetDialog = BottomSheetDialog(currentItem, appList, listener)
bottomSheetDialog.show()
}
...
}
As already stated, you are not updating the existing Adapter, you are instead creating two new instances.
Replace this line:
recyclerView.adapter = Adapter(applicationList) // here I send mutable list of all apps in device to adapter
With
this.adapter = Adapter(applicationList)
recyclerView.adapter = this.adapter
Also add val adapter: Adapter? to your class.
Now you have a reference to the adapter the RecyclerView has.
Finally, when you want to "update" it:
// here when user pressed OK....delete that item from List
val index = appList.indexOf(appData)
appList.removeAt(index)
Adapter(appList).notifyItemRemoved(index)
Adapter(appList).notifyDataSetChanged()
Should become...
// here when user pressed OK....delete that item from List
val index = appList.indexOf(appData)
appList.removeAt(index)
this.adapter.notifyItemRemoved(index)
this.adapter.notifyDataSetChanged()
IMPORTANT CAVEAT: There are other issues here with the separation of concerns in your code, but among them, the fact that the appList you use here, is a local list; does it contain the items from applicationList (the one you used when you created the adapter)? If it doesn't then you need to expose said list so you can either modify it/replace it, etc.
The Adapter does NOT manage the list for you, it merely uses it to adapt each item to a ViewHolder. If you modify the list the adapter has, and you tell it that you inserted an item at certain position, etc. All the adapter does is (a lot behind the scenes) and "re-binds" the view at that position (if it's visible) with the new data.

how to write a binding adapter for spinner which receives LiveData as input?

i have a spinner defined in the xml like this
<Spinner
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="#+id/expense_category"
app:sourceData="#{()->createExpenseViewModel.getAllSourceItems(1)}"
app:layout_constraintStart_toStartOf="#+id/textView"
android:layout_marginTop="20dp"
app:layout_constraintTop_toBottomOf="#+id/textView" app:layout_constraintWidth_percent="0.7"
/>
createExpenseViewModel.getAllSourceItems(1) this method returns LiveData <List<Source>>, so i have written a binding adapter for that case
#BindingAdapter("app:sourceData")
fun setSourceData(spinner: Spinner, sourceList: List<Source>) {
val categoryItems = ArrayList<String>()
categoryItems.addAll(sourceList.map { it.sourceName })
val spinnerAdapter =
ArrayAdapter<String>(spinner.context, R.layout.simple_spinner_dropdown_item, categoryItems)
spinner.adapter = spinnerAdapter
}
when building the app, i am getting the following error,
****/ data binding error ****msg:Cannot find the proper callback class for app:sourceData. Tried java.util.List but it has 25 abstract methods, should have 1 abstract methods. file:/home/naveen/Desktop/project-expense/app/src/main/res/layout/activity_create_expense.xml loc:94:34 - 94:80 ****\ data binding error ****
what does this error actually mean,how to resolve this error?
Edit:
what i intend to do is get the list returned by live data and convert to type ArrayList , i need my binding adapter to be triggered once the livedata returns the list, but if i use this app:sourceData="#{createExpenseViewModel.getAllSourceItems(1)}" and set the binding adapter, the adapter get only null list
You are binding a method to app:sourceData, but you're expecting a variable for it in your binding adapter. That cannot work.
I guess you want to populate the List into the Spinner. For that I would create a property in your viewModel and bind this property in the xml. I did just that in an app where I had a list of projects to display in the Spinner. Here's the code including the InverseBindingAdapter to automatically save the selected Project in another variable of the ViewModel.
ViewModel:
// getProjects() returns the LiveData
val projects = metaDataRepository.getProjects()
// use _selectedProject only within ViewModel. Do not expose MediatorLiveData to UI.
// in UI observe selectedProject
private val _selectedProject = MediatorLiveData<Project>()
val selectedProject: LiveData<Project>
get() = _selectedProject
Layout XML:
<Spinner
android:id="#+id/spProjects"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:projects="#{viewModel.projects}"
app:selectedProject="#={viewModel.selectedProject}" />
BindingAdapter (to populate data from the viewModel into the UI):
/**
* fill the Spinner with all available projects.
* Set the Spinner selection to selectedProject.
* If the selection changes, call the InverseBindingAdapter
*/
#BindingAdapter(value = ["projects", "selectedProject", "selectedProjectAttrChanged"], requireAll = false)
fun setProjects(spinner: Spinner, projects: List<Project>?, selectedProject: Project, listener: InverseBindingListener) {
if (projects == null) return
spinner.adapter = ProjectAdapter(spinner.context, android.R.layout.simple_spinner_dropdown_item, projects)
setCurrentSelection(spinner, selectedProject)
setSpinnerListener(spinner, listener)
}
Helper Methods for BindingAdapter:
private fun setSpinnerListener(spinner: Spinner, listener: InverseBindingListener) {
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) = listener.onChange()
override fun onNothingSelected(adapterView: AdapterView<*>) = listener.onChange()
}
}
private fun setCurrentSelection(spinner: Spinner, selectedItem: Project?): Boolean {
if (selectedItem == null) {
return false
}
for (index in 0 until spinner.adapter.count) {
val currentItem = spinner.getItemAtPosition(index) as Project
if (currentItem.name == selectedItem.name) {
spinner.setSelection(index)
return true
}
}
return false
}
Simple Adapter for your Spinner. Change this to your needs:
/**
* Adapter for displaying the name-field of an Project in a Spinner
*/
class ProjectAdapter(context: Context, textViewResourceId: Int, private val values: List<Project>) : ArrayAdapter<Project>(context, textViewResourceId, values) {
override fun getCount() = values.size
override fun getItem(position: Int) = values[position]
override fun getItemId(position: Int) = position.toLong()
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val label = super.getView(position, convertView, parent) as TextView
label.text = values[position].name
return label
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
val label = super.getDropDownView(position, convertView, parent) as TextView
label.text = values[position].name
return label
}
}
InverseBindingAdapter (to store the selected Spinner item in the viewModel)
/**
* get the selected projectName and use it to return a
* Project which is then used to set appEntry.value.project
*/
#InverseBindingAdapter(attribute = "selectedProject")
fun getSelectedProject(spinner: Spinner): Project {
return spinner.selectedItem as Project
}
I have followed the core idea of what #muetzenflo suggested, i have created a property on view model like this
class MainViewModel #Inject constructor(
val expenseSourceItems:LiveData<List<Source>> = getAllSourceItems(1)
fun getAllSourceItems(sourceType:Int?): LiveData<List<Source>> {
val res = sourceRepository.getAllSourceItems(sourceType)
return res
}
// the methods below are omitted for brevity
}
then i have bound to the spinner using property access syntax
<Spinner
android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="#+id/expense_category"
app:sourceData="#{createExpenseViewModel.expenseSourceItems}"
app:layout_constraintStart_toStartOf="#+id/textView"
android:layout_marginTop="20dp"
app:layout_constraintTop_toBottomOf="#+id/textView" app:layout_constraintWidth_percent="0.7"
/>
and then used the same binding adapter
#BindingAdapter("app:sourceData")
fun setSourceData(spinner: Spinner, sourceList: List<Source>) {
val categoryItems = ArrayList<String>()
categoryItems.addAll(sourceList.map { it.sourceName })
val spinnerAdapter =
ArrayAdapter<String>(spinner.context, R.layout.simple_spinner_dropdown_item, categoryItems)
spinner.adapter = spinnerAdapter
}
for live data calling a method inside data binding only works for callbacks like onclick, and property access is needed to be used for normal data binding like populating a spinner.

Android Kotlin pass spinner values to mutable list

I have an application with a spinner that contains several items.
I created it and all that
Could anyone give me an example of how I can pass these values to a list I have? Using a mutableList?
Cheers
class NewKitListActivity : AppCompatActivity() {
var spinnerArray = arrayOf("Dumbell", "Punching Bag", "Yoga Ball", "Skipping Rope")
val kitMutableList = mutableListOf(spinnerArray)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_new_kit_list)
val spinner = newKitItemSpinner
val spinnerArrayAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, spinnerArray)
//selected item will look like a spinner set from XML
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = spinnerArrayAdapter
spinner.onItemSelectedListener = object : OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
val selectedItem = parent.getItemAtPosition(position).toString()
if (selectedItem == "Dumbell") {
// mutableListAdapter.toMutableList()
//mutableList.adapter = mutableListAdapter
}
} // to close the onItemSelected
override fun onNothingSelected(parent: AdapterView<*>) {
}
}
I believe you can do like this.
1. Make custom adapter or adapters
2. Make first list which hold string values
3. Make mutable list which holds selected values
4. when spinner loaded first time it load values from first adapter and list
5. when user select item it clear first adapter then notify changes then set new adapter load values from mutable list ( I am not sure last will require to notify changes to adapter)
//Mutable List for storing selected items
val selectedItems: MutableList<String>? = null
//Listen On select for spinner
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
//Performing action onItemSelected and onNothing selected
override fun onItemSelected(arg0: AdapterView<*>, arg1: View, position: Int, id: Long) {
// Add selected item in Mutable List
selectedItems.add(spinnerArray[position])
// Clear Adapter
spinner.adapter = null
// Notify data set changed
spinnerArrayAdapter.notifyDataSetChanged()
// Set New Data adapter
spinner.adapter = ArrayAdapter(this, android.R.layout.simple_spinner_dropdown_item, selectedItems)
}
override fun onNothingSelected(arg0: AdapterView<*>) {
// TODO: Auto-generated method stub
}
}
Create a list to store your selectedItems:
val selectedItems = mutableListOf<String>()
then add items when they are selected:
override fun onItemSelected(arg0: AdapterView<*>, arg1: View, position: Int, id: Long) {
selectedItems.add(spinnerArray[position])
}

Categories

Resources