I have Spinner with ArrayAdapter. ArrayAdapter code show like:
class HouseholdsArrayAdapter(
context: Context,
resource: Int,
) : ArrayAdapter<Household>(context, resource, arrayListOf()) {
fun submitList(list: List<Household>) {
this.clear()
this.addAll(list)
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return super.getView(position, convertView, parent).also {
(it as TextView).text = getItem(position)!!.fullAddress
}
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
return super.getDropDownView(position, convertView, parent).also {
(it as TextView).text = getItem(position)!!.fullAddress
}
}
Also i have households list. It is LiveData in my ViewModel. I observe it in onViewCreated() method:
viewModel.householdList().observe(viewLifecycleOwner){
householdAdapter.submitList(it)
}
My issue:
Spinner DOES NOT save selected position after rotating screen.
What is more: Spinner save position after rotating if i submit households list immediately. But it does not work with LiveData mechanism.
This is very strange because how i understand Spinner must save state with the help onSaveInstanceState() and onRestoreInstanceState().
Related
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 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)
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.
I'm implementing a SpinnerAdapter in Android project. So I have to override getView(i: Int, convertView: View, parent: ViewGroup) method. So convertView is here in order to reuse existing view and reduce memory usage and GC occurrences. So if it is null I have to create view and use already created otherwise.
So in fact I have to write something like this (officially recomended by google):
if (view == null) {
view = View.inflate(context, R.layout.item_spinner, parent)
view.tag(Holder(view))
} else {
(view.tag as Holder).title.text = getItem(i)
}
But Kotlin does not allow to write to param.
What I found on the internet is an official blog post that says that it is not possible since Feb, 2013.
So I'm wondering if there is any workaround ?
There are two issues here.
First, you are mistakenly assuming that modifying view in Java does anything outside of the current function scope. It does not. You setting that parameter to a new value affects nothing outside of the local function scope.
View getView(int i, View view, ViewGroup parent) {
// modify view here does nothing to the original caller reference to view
// but returning a view does do something
}
Next, in Kotlin all parameters are final (JVM modifier, also same as final modifier in Java). The Kotlin if statement version of this code would be:
fun getView(i: Int, view: View?, parent: ViewGroup): View {
return if (view == null) {
val tempView = View.inflate(context, R.layout.item_spinner, parent)
tempView.tag(Holder(tempView))
tempView
} else {
(view.tag as Holder).title.text = getItem(i)
view
}
}
or avoiding the new local variable:
fun getView(i: Int, view: View?, parent: ViewGroup): View {
return if (view == null) {
View.inflate(context, R.layout.item_spinner, parent).apply {
tag(Holder(this)) // this is now the new view
}
} else {
view.apply { (tag as Holder).title.text = getItem(i) }
}
}
or
fun getView(i: Int, view: View?, parent: ViewGroup): View {
if (view == null) {
val tempView = View.inflate(context, R.layout.item_spinner, parent)
tempView.tag(Holder(tempView))
return tempView
}
(view.tag as Holder).title.text = getItem(i)
return view
}
or using the ?. and ?: null operators combined with apply():
fun getView(i: Int, view: View?, parent: ViewGroup): View {
return view?.apply {
(tag as Holder).title.text = getItem(i)
} ?: View.inflate(context, R.layout.item_spinner, parent).apply {
tag(Holder(this))
}
}
And there are another 10 variations, but you can experiment to see what you like.
It is considered less-than-a-good practice (but allowed) to shadow variables by using the same name, that is why it is a compiler warning. And why you see a change in the variable name above from view to tempView
Mutable parameters are not supported in Kotlin.
I would like to refer you to this discussion in kotlinlang.org
There's a dirty but useful way to achieve that.
fun a(b: Int) {
var b = b
b++ // this compiles
}
Officially speaking, you are not allowed to override a method param. The best you can do is "shadow" the param variable.
So you can do it similar to (not sure why you would want to shadow though but you can)
getView(i: Int, view: View?, parent: ViewGroup) {
val view = view ?: View.inflate(context, R.layout.item_spinner, parent)
.apply { tag(Holder(view)) }
(view.tag as Holder).title.text = getItem(i)
}
I've extended ArrayAdapter for spinner:
class OrderAdapter(context: Context, resource: Int, objects: List<Order>) : ArrayAdapter<Order>(context, resource, objects) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? {
val view = super.getView(position, convertView, parent)
view?.let { view.find<TextView>(android.R.id.text1).text = getItem(position).name }
return view
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View? {
val view = super.getDropDownView(position, convertView, parent)
view?.let {view.find<TextView>(android.R.id.text1).text = getItem(position).name }
return view
}
}
I'm getting exception:
java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter convertView
at com.github.blabla.endlesss.ui.adapter.OrderAdapter.getView(OrderAdapter.kt:0)
Any ideas how to fix it?
This issue was caused by incremental compile. Just needed to rebuild project after converting from Java.
Please check the sample below:
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View? {
return getCustomView(position, convertView, parent)
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View? {
return getCustomView(position, convertView, parent)
}
fun getCustomView(position: Int, convertView: View?, parent: ViewGroup): View? {
val rootView: View? = LayoutInflater.from(context).inflate(R.layout.my_custom_view, parent, false)
return rootView
}
The returned value should be nullable.
In my case, I just had to add the "?" to the convertView parameter
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
override fun getView(i: Int, view: View, parent: ViewGroup): View {
var itemView = LayoutInflater.from(context).inflate(R.layout.item_dashbord_data, parent, false)
return itemView
}
// change name of View object (itemView) differ from getView second object (view). so change view to itemView or other name