Kotlin: function setSelection() doesn't work outside onItemSelected() function - android

I have a spinner and when I select something from the list some action happens:
val arrayAdapter =
ArrayAdapter(requireContext(), R.layout.dropdown_item, carBrandList)
binding.spinner.adapter = arrayAdapter
binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
adapterView: AdapterView<*>?,
view: View?,
position: Int,
id: Long,
) {
refuelList.clear()
realTimeUpdates(carIdList[position])
saveData(requireContext(), carIdList[position])
saveSpinnerPosition(requireContext(), position)
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
I want to store spinner position in Shared Preferences and it works.
I want to retrieve spinner position if an user opens particular fragment.
How can I use function setSelection() outside this code? setSelection() only works for me if it is in onItemSelected() function

It should work outside the onItemSelected(), just be sure that you saved the correct spinner position in Shared Preferences.

Related

How to save spinner state on device rotation using a ViewModel?

How do you restore the Spinners selected item index a view model?
Right now I have a setup where you set a selectedIndex property on the ViewModel within the spinners OnItemSelectedListener.
The problem I'm running into is that every time the Fragment recreates, the selected listener gets re-assigned to the spinner, and the OnItemSelectedListener get fired for index 0, overriding whatever is stored in the ViewModel.
I feel like I'm missing something super simple here.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Set the spinners onItemSelectedListener
binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
viewModel.setSelectedItem(p2)
}
override fun onNothingSelected(p0: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
// Restore spinner to previously selected state
viewModel.getSelectedCompanyIndex().let {
binding.spinner.setSelection(it)
}
// Observe the spinner data.
viewModel.spinnerData.observe(this) { spinnerArray ->
ArrayAdapter(
requireContext(),
R.layout.simple_spinner_dropdown_item,
spinnerArray
).also {
binding.spinner.adapter = it
}
}

How to return value from function in Kotlin Android

In my application I want used extension function and I want return String!
I set return value from function and I write below codes, but show me empty value!
My Extension function code :
fun Spinner.setupListWithAdapter(list: MutableList<String>): String {
var itemSelected = ""
val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, list)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
this.adapter = adapter
this.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
itemSelected = list[p2]
Log.e("categoryLog","1 : " + itemSelected)
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
Log.e("categoryLog","2 : " +itemSelected)
return itemSelected
}
When show logs in logcat, first show 2 then show 1 !
Logs :
2022-08-12 14:49:59.261 12074-12074/com.my.app E/categoryLog: 2 :
2022-08-12 14:49:59.310 12074-12074/com.my.app E/categoryLog: 1 : Movies
Why first call log 2 then call log 1 ?!
I used this func in fragment and for this when used this in fragment show me empty value!
How can I fix it?
Change your extension function like below
fun Spinner.setupListWithAdapter(list: MutableList<String>,callback: String -> Unit){
var itemSelected = ""
val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, list)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
this.adapter = adapter
this.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
itemSelected = list[p2]
callback(itemSelected)
Log.e("categoryLog","1 : " + itemSelected)
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
Log.e("categoryLog","2 : " +itemSelected)
}
Then in Activity or fragment
spinner.setupListWithAdapter(list){
// It will invoked when something is selected from spinner
}
Here's the direct answer to your question about why the logs are not in the order you expected:
Your listener is called repeatedly in the future each time items are selected. The current function that creates this listener doesn't wait for any of that to happen. It sets up the listener and returns right away before any items have been selected. It wouldn't make sense for it to wait, because then your app would be frozen, waiting for this function to return because the user has to select an item before the listener is called. But if your app was frozen, then the user could never make a selection, so it would be frozen forever.
The other answers show how you might fix it, but it depends what you're trying to do. It doesn't logically make sense for this function to return a String, because you don't know how many times in the future the user is going to select an item. It could be zero times, one time, or many times.
Use typealias for callback
typealias OnSelectedCategory = (value: String) -> Unit
Update your method with callback
fun Spinner.setupListWithAdapter(list: MutableList<String>,listener : OnSelectedCategory) {
var itemSelected = ""
val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, list)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
this.adapter = adapter
this.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
itemSelected = list[p2]
Log.e("categoryLog","1 : " + itemSelected)
listener.invoke(itemSelected)
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
Log.e("categoryLog","2 : " +itemSelected)
}
call with spinner
spinner.setupListWithAdapter(list){ value ->
Log.e("categoryLog","2 : " +value)
}

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
}
}

How to Set Multiple Click Listener to AutoCompleteTextView in Android?

I have an AutoCompleteTextView that will show a list of Hints when type in few keyword that match with its ArrayAdapter Data.
Currently, I have set an AdapterView.OnItemClickListener so when user click on the Hint, the soft keyboard on screen will be close.
What I trying to achieve :
I would like to add a functionality that allow user to Delete the Data of the Hint from the Database, so I was thinking to add another ClickListener for the AdapterView such as LongClickListener, so when user LongClick on the Hint, it will trigger a Dialog Pop Out and prompt the Delete Confirmation from the User.
I been search for how to set LongClickListener on Autocomplete, however I could not find any solution on the net.
I believe it is important to ensure the Click Listen able to obtain the position(in Int) or text data(in String) of the Hint as I would need to determine what are the Data that being selected and I can tell the Database to delete it.
My Questions:
How can I set a Long Click Listener for the AutoCompleteTextView that will also obtain the selected Hint position?
Any other solution that could help to resolve what I trying to achieve is welcome, thank you.
Note: I would like to keep the functionality of the hide soft keyboard when Hint is selected. I accept Answer In Java Language as well.
What I have done so far :
// Get an ArrayList<String> from database and declare to remarkList
val remarkList: ArrayList<String> = getDataFromDatabase()
// Set remarkList Data into ArrayAdapter
val adapter = ArrayAdapter(context!!, android.R.layout.simple_list_item_1, remarkList)
// Set ArrayAdapter to AutoCompleteTextView
autoComplete_remarks.setAdapter<ArrayAdapter<String>>(adapter)
// When click the hint selection, will trigger close keyboard function
autoComplete_remarks.onItemClickListener =
AdapterView.OnItemClickListener { _: AdapterView<*>, _: View, position: Int, _: Long ->
hideKeyboard(activity!!)
}
In order to have multiple click listeners, you should build a custom Adapter with custom item layout.
class CustomAdapter(context: Context?, resource: Int) : ArrayAdapter<Data>(context, resource) {
private var mListener : IOnItemListener? = null
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? {
var convertView = convertView
// Inflate layout
// Listeners
val textViewItem = convertView!!.findViewById(R.id.textViewItem) as TextView
textViewItem.setOnClickListener(View.OnClickListener {
mListener?.onClick(...)
})
textViewItem.setOnLongClickListener(View.OnLongClickListener {
mListener?.onLongClick(...)
})
return convertView;
}
fun setListener(listener: IOnItemListener) {
mListener = listener
}
interface IOnItemListener {
fun onClick(...)
fun onLongClick(...)
}
}
Then somewhere else, call
val adapter : CustomAdapter = CustomAdapter(context, R.layout...)
adapter.setListener(object: IOnItemListener())
Have created the Custom Adapter Class for the AutoCompleteTextView, Basically This Custom Adapter, in the getView function will create an Unique Individual Listener for each TextView that will be pop out by the AutoCompleteTextView.
Note: The TextView results that pop out by the AutoCompleteTextView is Customizable through inner class ListFilter: Filter() and override fun publishResults(constraint: CharSequence?, results: FilterResults) function
Custom Adapter Class
class AutoCompleteTextViewCustomAdapter(context: Context, resource: Int, data: ArrayList<String>): ArrayAdapter<String>(context, resource) {
private var mListener: IOnItemListener? = null
private var dataList: List <String>? = data
private val listFilter = ListFilter()
private var dataListAllItems: List<String>? = null
override fun getView(position: Int, view: View? , parent: ViewGroup): View {
var adapterView = view
// using Custom XML View
if (adapterView == null) {
adapterView = LayoutInflater.from(parent.context)
.inflate(R.layout.list_row_text, parent, false)
}
val textView = adapterView!!.findViewById(R.id.textView) as TextView
textView.text = getItem(position)
// Custom OnClickListener Setup
textView.setOnLongClickListener {
mListener?.onLongClick("Pass Data")
true
}
// Custom OnClickListener Setup
textView.setOnClickListener {
mListener?.onSingleClick("Pass Data")
}
return adapterView
}
// Custom OnClickListener Setup
fun setListener(listener : IOnItemListener) {
mListener = listener
}
// Custom OnClickListener Setup that will be Called from the Activity/Fragment
interface IOnItemListener {
fun onLongClick(dataToBePass : String)
fun onSingleClick(dataToBePass : String)
}
// Custom Adapter Setup for AutoCompleteTextView
override fun getCount(): Int {
return dataList!!.size
}
override fun getItem(position: Int): String ? {
return dataList!![position]
}
override fun getFilter() : Filter {
return listFilter
}
inner class ListFilter: Filter() {
..// Filtering Logic
return results
}
override fun publishResults(constraint: CharSequence?, results: FilterResults) {
..// Filtering Logic
}
}
So, when each TextView that pop out by the AutoCompleteTextView being onClick or onLongClick, the override method in AutoCompleteTextViewCustomAdapter.IOnItemListener will be called to perform logic function that you desire.
Activity/Fragment
val adapter = AutoCompleteTextViewCustomAdapter(context!!, R.layout.list_row_text, dataList).also {
//Setup the OnClickListener what to perform when the TextView of the Adapter if being onClick
it.setListener(object : AutoCompleteTextViewCustomAdapter.IOnItemListener {
override fun onLongClick(dataReceived: String) {
// DO SOMETHING
}
override fun onSingleClick(dataReceived: String) {
// DO SOMETHING
}
})
}
// Set the adapter to the AutoCompleteTextView View that define in your XML File
autoCompleteTextView.setAdapter(adapter)
So far this solution work for my Scenario, in short, to having a Multiple Click Listener to AutoCompleteTextView, we cannot use the Default Adapter as they only support Single onClickListener, so we have to Create Custom Adapter to customize and setup ALL the ClickListener to support multiple onClick Listener.

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