I want to use a Spinner inside my Dialog. The Spinner appears and contains the data i want to show. But as soon as i select one of the items, memory usage shoots up and i end up getting an out of memory Exception. In logcat the first entry after selectin the item is D/AndroidRuntime: Shutting down VM. I have no stacktraces whatsoever. Only some info output from the gc and two warnings about throwing an OOM.
class SearchTypeSpinner(context: Context, attributeSet: AttributeSet? = null): LinearLayout(context, attributeSet), AdapterView.OnItemSelectedListener {
private val types: List<String>
private val mapping: MutableMap<String, Int>
private val spinner: Spinner
init {
types = context.resources.getStringArray(R.array.filter_types).asList()
spinner = Spinner(context)
spinner.adapter = Adapter()
spinner.onItemSelectedListener = this
spinner.layoutParams = ViewGroup.LayoutParams(context, attributeSet)
addView(spinner)
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {}
override fun onNothingSelected(parent: AdapterView<*>?) {}
private inner class Adapter: ArrayAdapter<String>(context, android.R.layout.simple_spinner_item) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val view = TextView(context)
view.layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)
view.text = getTypeNameAtPosition(position)
return view
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup?): View {
return getView(position, convertView, parent)
}
override fun getCount(): Int {
return types.size
}
}
}
Please let me know if i can provide additional information and thanks in advance.
Related
I have a problem with the spinner inside a fragment. The spinner is filled with data, but when I select an item I don't see logs, and in the spinner, it does not select elements. When I used nearly the same code as an activity it worked (Just changed the context to this in the adapter)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_edit_stoper, container, false)
val tagSpinner = view.findViewById<Spinner>(R.id.editSpinner)
val items: MutableList<String> = ArrayList("a","b","c")
tagSpinner.adapter = ArrayAdapter(this.requireActivity(), android.R.layout.simple_spinner_item, items) as SpinnerAdapter
tagSpinner.onItemSelectedListener = this
return view
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
Log.d(TAG,"OnItemSelected: $type")
}
override fun onNothingSelected(parent: AdapterView<*>?) {
Log.d(TAG,"error")
}
}
Is your fragment really a listener for spinner?
Write a custom listener and use that instead of your fragment.
inner class SpinnerStateChangeListener : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
// do sth here
}
override fun onNothingSelected(parent: AdapterView<*>?) {
// do sth here
}
}
Now you can set your listener like this:
tagSpinner.onItemSelectedListener = SpinnerStateChangeListener()
Try moving your implementation from Fragment directly to spinner. remove override from class and instead of
tagSpinner.onItemSelectedListener = this
do
tagSpinner.onItemSelectedListener = object :AdapterView.OnItemSelectedListener{
override fun onNothingSelected(parent: AdapterView<*>?) {
Log.d(TAG,"error")
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
Log.d(TAG,"OnItemSelected: $type")
}
}
The solution is to add requireActivity().applicationContext in the adapter to change the activityFragment to a context:
...
val items: MutableList<String> = ArrayList("a","b","c")
tagSpinner.adapter = ArrayAdapter(requireActivity().applicationContext, android.R.layout.simple_spinner_item, items) as SpinnerAdapter
tagSpinner.onItemSelectedListener = this
...
I created a custom spinner adapter, populated with data, on runtime it drops the list, but nothing can be selected. The selection works with the stock ArrayAdapter, but the custom. Please help, what am I missig...
DetailsActivity.kt:
'''class DetailsActivity : AppCompatActivity() {
.
.
.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.word_details)
val spinner: Spinner = findViewById(R.id.topic_spinner)
spinner.adapter = cAdapter(applicationContext, fillSpinner())
val listener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(parent: AdapterView<*>?, view: View, pos: Int, id: Long) {
Toast.makeText(applicationContext, "Selected: $pos", Toast.LENGTH_SHORT).show()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
spinner.onItemSelectedListener = listener
.
.
.
'''
fillSpinner function:
'''
fun fillSpinner(): ArrayList{
val topics = ArrayList<String>()
topics.clear()
val h_cursor = dbHandler.get_h_AllRow()
h_cursor.moveToFirst()
while (!h_cursor.isAfterLast){
topics.add(h_cursor.getString(h_cursor.getColumnIndex(DBHelper.H_COLUMN_TOPIC)))
h_cursor.moveToNext()
}
return topics
}
'''
cAdapter class:
'''
class cAdapter(val ctx: Context, val items: ArrayList) :
ArrayAdapter(ctx, 0, items) {
override fun getView(position: Int, recycledView: View?, parent: ViewGroup): View {
return this.createView(position, recycledView, parent)
}
override fun getDropDownView(position: Int, recycledView: View?, parent: ViewGroup): View {
return this.createView(position, recycledView, parent)
}
private fun createView(position: Int, recycledView: View?, parent: ViewGroup): View {
val item = getItem(position)
val view = recycledView ?: LayoutInflater.from(context).inflate(
R.layout.spinner_layout,
parent,
false)
view.IB_cancel.setImageResource(android.R.drawable.ic_delete)
view.IB_cancel.setOnClickListener {
Toast.makeText(context, "DELETE SOMETHING!", Toast.LENGTH_SHORT).show()
}
view.t_sp_holder.text = item
return view
}
}
'''
As I see, the problem should lie somewhere here in theese lines. Please give some advice, I am new at Kotlin.
I want to set different values to a spinner when an item is selected, I set a list of country namse concatenate by country codes, on item selected event.
I want to set only the country code on view, but there is no method to set that value.
countryCodeSpinner.onItemSelectedListener = object : AdapterView.OnItemClickListener,
AdapterView.OnItemSelectedListener {
override fun onItemClick(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
countryCode = codesList[position]
}
}
You can do as following:
spinnerRangeForMap.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
}
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
if (position != 0) {
//Your code
}
}
}
and for item selected in spinner you can get as below:
spinnerRangeForMap.selectedItem.toString()
I added this code to get the view of the spinner and edit it after selecting :
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
if (position > 0) {
val code = codesList[position]
view!!.findViewById<TextView>(android.R.id.text1).text =
"+" + code.split("+")[1]
countryCode = code
}}
I want to use a centered spinner where the width of the spinner is only as wide as the selected item text. From my research it seems that this is not natively supported out of the box with an attribute so I found another StackOverflow question/answer and tried implementing that but ran into some issues with it.
So I took option 1 from this SO response and implemented it in Kotlin and It's not working for me
class DynamicWidthSpinner #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatSpinner(context, attrs, defStyleAttr) {
override fun setAdapter(adapter: SpinnerAdapter?) {
super.setAdapter(if (adapter != null) WrapperSpinnerAdapter(adapter) else null)
}
inner class WrapperSpinnerAdapter(val baseAdapter: SpinnerAdapter) : SpinnerAdapter {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return baseAdapter.getView(selectedItemPosition, convertView, parent)
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
return baseAdapter.getDropDownView(position, convertView, parent)
}
override fun getCount(): Int = baseAdapter.count
override fun getItem(position: Int): Any = baseAdapter.getItem(position)
override fun getItemId(position: Int): Long = baseAdapter.getItemId(position)
override fun getItemViewType(position: Int): Int = baseAdapter.getItemViewType(position)
override fun getViewTypeCount(): Int = baseAdapter.viewTypeCount
override fun hasStableIds(): Boolean = baseAdapter.hasStableIds()
override fun isEmpty(): Boolean = baseAdapter.isEmpty
override fun registerDataSetObserver(observer: DataSetObserver) {
baseAdapter.registerDataSetObserver(observer)
}
override fun unregisterDataSetObserver(observer: DataSetObserver) {
baseAdapter.unregisterDataSetObserver(observer)
}
}
}
and in my MainActivity I'm doing this from onCreate
val spinner: DynamicWidthSpinner = findViewById(R.id.global_toolbar_location_spinner)
val tempLocationList = ArrayList<String>()
tempLocationList.add("Test1")
tempLocationList.add("Much longer test string 2")
spinner.adapter = ArrayAdapter(
this,
R.layout.global_toolbar_spinner_item,
tempLocationList
)
spinner.onItemSelectedListener = object : OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, arg3: Long) {
// TODO: do stuff on selection here
}
override fun onNothingSelected(arg0: AdapterView<*>) {
// TODO: do nothing... yet
}
}
spinner.setSelection(0)
and I am using my custom Spinner in the layout xml (ommitting everything else that is not necessary because I am able to get it work just fine using the native <Spinner> or androidx compat Spinner
<com.blablabla.app.ui.DynamicWidthSpinner
android:id="#+id/global_toolbar_location_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:spinnerMode="dialog"
/>
What I see is just the first item "Test1" and nothing happens when I tap on it and arrow seems to have disappeared now as well
I figured out the issue. Turns out that this is one of those cases where the #JvmOverloads doesn't work. Once I converted it to the multiple constructor kotlin syntax it worked without a problem
class DynamicWidthSpinner : AppCompatSpinner {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
...
}
I can't find a solution to my problem : I want a hint text displayed on my Spinner but the adapter I set only accepts enum type (IdentityType enum)so I cannot add a String to it (for the hint)
Do you have any solution still using the enum in the adapter?
private fun initDriverIdentityTypeSpinner() {
driverIdentityTypeSpinner.adapter = object : ArrayAdapter<IdentityType>(context!!, android.R.layout.simple_spinner_item,IdentityType.values()) {
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View =
(super.getDropDownView(position, convertView, parent) as CheckedTextView).also{
it.setText(getItem(position)!!.stringRes())
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup) =
(super.getView(position, convertView, parent) as TextView).also {
it.setText(getItem(position)!!.stringRes())
}
override fun isEnabled(position: Int): Boolean = position != 0
}.also {
it.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
}
//IdentityType Extension
#StringRes
fun IdentityType.stringRes(): Int {
return when(this) {
IdentityType.DRIVING_LICENSE -> R.string.driving_license
IdentityType.ID_CARD -> R.string.id_card
IdentityType.PASSPORT -> R.string.passport
}
}
In Kotlin there is possibility to put properties inside enum (here it calls enum class). You can define it in constructor like following:
enum class IdentityType(val stringResId: Int) {
DRIVING_LICENSE(R.string.driving_license),
ID_CARD(R.string.id_card),
PASSPORT(R.string.passport)
}
Then you can use it like it is a common property of a class.
val type: IdentityType = ...
val string = getString(type.stringResId)