I'm trying to make a spinner with custom objects.
When I do getposition from spinner I get result -1. I do not know the cause
this my code:
Model
class User(var name: String?, var mail: String?) {
override fun toString(): String {
return name.toString()
}
}
Activity
val userList = ArrayList<User>()
val user1 = User("Jim","jim#gmail.com")
userList.add(user1)
val user2 = User("John","john#gmail.com")
userList.add(user2)
val user3 = User("peki", "pek#gmail.com")
userList.add(user3)
val adapter = ArrayAdapter<User>(this,
android.R.layout.simple_spinner_item, userList
)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.setAdapter(adapter)
val ambilPosisi : Int = adapter.getPosition(User("peki","pek#gmail.com"))
Toast.makeText(this, (ambilPosisi).toString(), Toast.LENGTH_LONG).show()
Just declare your User class as data class:
data class User(var name: String?, var mail: String?)
It will generate equals() and toString() methods. So you can use it in object comparison.
You are trying to call
adapter.getPosition()
On an item that is effectively not in the list that was submitted to the adapter. You are creating a new User instance as a parameter to the function call, which, although has the identical fields with user3, does not refer to the same variable.
Calling
adapter.getPosition(user3)
Should return the correct index.
Related
So I am trying to populate two Spinners in the same Fragment, both using the same list, but to display different items.
I have the following data class:
data class ProductTypeObject (
//ProductType fields (2 fields)
var productType: String = "",
var productGroup: String = "",
#ServerTimestamp
var dateEditedTimestamp: Date? = null,
#Exclude #set:Exclude #get:Exclude
var productTypeID: String = ""
) : Serializable {
override fun toString(): String {
return productType
}
}
The Spinner is populated in the Fragment when the list is observed from the ViewModel as below:
// Observe ProductTypes and populate Spinner
businessViewModel.allAppDataProductTypes.observe(viewLifecycleOwner, Observer { productTypeArrayList ->
if (!productTypeArrayList.isNullOrEmpty()){
val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, productTypeArrayList)
binding.inventoryAddEditProductGroupSpinner.adapter = adapter
}
})
This shows a list of product types as I have specified this in the toString()of the object, but is there a way to direct a second Spinner to show a list ofproduct group?
If you don't need to retrieve the values from the spinners, it's easiest to map the values to a new list:
businessViewModel.allAppDataProductTypes.observe(viewLifecycleOwner, Observer { productTypeArrayList ->
if (!productTypeArrayList.isNullOrEmpty()){
//...
val adapter2 = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item,
productTypeArrayList.map(ProductTypeObject::productGroup)
//...
}
})
If you need both Spinners to be able to retrieve the original item type, then you can't use the ArrayAdapter class as is, since it relies purely on the toString() of your class. You can subclass it like this for a more flexible version that lets you pass property or lambda that is used instead of toString(). I didn't test it, but I think it will do what you want. If you use this class, you don't need to override toString() in your original data class.
class CustomArrayAdapter<T : Any>(
context: Context,
items: List<T>,
val itemToCharSequence: T.() -> CharSequence = Any::toString
) : ArrayAdapter<T>(context, 0, items) {
private val inflater = LayoutInflater.from(context)
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
return (convertView ?: inflater.inflate(android.R.layout.simple_spinner_item, parent, false))
.apply {
val item = getItem(position)!! // will never be null inside getView()
(this as TextView).text = itemToCharSequence(item)
}
}
}
Usage:
val typeAdapter = CustomArrayAdapter(requireContext(), productTypeArrayList, ProductTypeObject::productType)
val groupAdapter = CustomArrayAdapter(requireContext(), productTypeArrayList, ProductTypeObject::productGroup)
In my quarkus application I have an endpoint that takes in a DTO, with a field that has a default value. When I don't send that field, I still get the exception
com.fasterxml.jackson.databind.exc.ValueInstantiationException: Cannot construct instance of
`FooDTO`, problem: Parameter specified as non-null is null: method
io.otherstuff.FooDTO.<init>, parameter someListVariable
at [Source: (io.quarkus.vertx.http.runtime.VertxInputStream); line: 4, column: 1]
The class looks like this:
class FooDTO(
override var someStringVar: String,
override var someListVariable: List<Int> = emptyList(),
): BarDTO
---------------------------------------------
interface BarDTO {
var someStringVar: String
var someListVar: List<Int>
}
Now if I send a payload like this
{
"someStringVar": "Hello Stackoverflow",
"someListVar": []
}
it is working perfectly fine, but when I drop "someListVar" I get the exception from above, even though it should just initialize it as an empty list.
Any help is much appreciated!
The problem is, that during desalinization, the library (fasterxml) calls the primary constructor with null: FooDTO("Hello Stackoverflow", null). The call ends up with the exception as the someListVariable parameter is not nullable (default value is used only when the paremeter is not provided at all, not when it's null).
One option of solving the problem would be providing an explicit JsonCreator:
class FooDTO(
override var someStringVar: String,
override var someListVariable: List<Int> = emptyList()) : BarDTO {
companion object {
#JvmStatic
#JsonCreator
fun of(
#JsonProperty("someStringVar") someStringVar: String,
#JsonProperty("someListVariable") someListVariable: List<Int>?) =
FooDTO(someStringVar, someListVariable ?: emptyList())
}
}
Another posibility is using secondary constructor instead of the default value:
class FooDTO : BarDTO {
override var someStringVar: String
override var someListVariable: List<Int>
#JsonCreator
constructor(
#JsonProperty("someStringVar") someStringVar: String,
#JsonProperty("someListVariable") someListVariable: List<Int>?) {
this.someStringVar = someStringVar
this.someListVariable = someListVariable ?: emptyList()
}
}
Both options are unfortunately a bit verbose.
how can I set properties of a dataclass by its name. For example, I have a raw HTTP GET response
propA=valueA
propB=valueB
and a data class in Kotlin
data class Test(var propA: String = "", var propB: String = ""){}
in my code i have an function that splits the response to a key value array
val test: Test = Test()
rawResp?.split('\n')?.forEach { item: String ->
run {
val keyValue = item.split('=')
TODO
}
}
In JavaScript I can do the following
response.split('\n').forEach(item => {
let keyValue = item.split('=');
this.test[keyValue[0]] = keyValue[1];
});
Is there a similar way in Kotlin?
You cannot readily do this in Kotlin the same way you would in JavaScript (unless you are prepared to handle reflection yourself), but there is a possibility of using a Kotlin feature called Delegated Properties (particularly, a use case Storing Properties in a Map of that feature).
Here is an example specific to code in your original question:
class Test(private val map: Map<String, String>) {
val propA: String by map
val propB: String by map
override fun toString() = "${javaClass.simpleName}(propA=$propA,propB=$propB)"
}
fun main() {
val rawResp: String? = """
propA=valueA
propB=valueB
""".trimIndent()
val props = rawResp?.split('\n')?.map { item ->
val (key, value) = item.split('=')
key to value
}?.toMap() ?: emptyMap()
val test = Test(props)
println("Property 'propA' of test is: ${test.propA}")
println("Or using toString: $test")
}
This outputs:
Property 'propA' of test is: valueA
Or using toString: Test(propA=valueA,propB=valueB)
Unfortunately, you cannot use data classes with property delegation the way you would expect, so you have to 'pay the price' and define the overridden methods (toString, equals, hashCode) on your own if you need them.
By the question, it was not clear for me if each line represents a Test instance or not. So
If not.
fun parse(rawResp: String): Test = rawResp.split("\n").flatMap { it.split("=") }.let { Test(it[0], it[1]) }
If yes.
fun parse(rawResp: String): List<Test> = rawResp.split("\n").map { it.split("=") }.map { Test(it[0], it[1]) }
For null safe alternative you can use nullableString.orEmpty()...
I have a data class:
data class Person (
val login: String,
val password: String
)
Sometimes I need to instantiate it with my custom data, but sometimes I need to initialize my user by another class instance:
val authPerson = api.getAuthPerson() // AuthPerson class has the same fields
val user = User(authPerson)
I wrote secondary constructor, but it doesn't work:
data class User (
val login: String,
val password: String
) {
constructor(authPerson: AuthPerson) {
login = authPerson.login;
password = authPerson.password
}
}
Can anybody advise me correct decision please?
Or if you don't want to use a factory, you can do:
data class User (
val login: String,
val password: String
) {
constructor(anotherUser: User): this(anotherUser.login, anotherUser.password)
}
Secondary Constructors must call their primary constructor.
You can create a factory method:
data class User(
val login: String,
val password: String
) {
companion object {
fun fromPerson(person: Person) = User(person.login, person.password)
}
}
...
val user = User.fromPerson(person)
or create an extension function:
fun Person.toUser() = User(login, password)
...
val user = person.toUser()
I have an object User defined as below
class User(){
var id: Int? = null
var name: String? = null}
For certain reasons, I have to create new object User of same parameters and I have to copy data from old to new type.
class UserNew(){
var id: Int? = null
var name: String? = null}
I was looking for easiest way to convert from old type to a new one. I want to do simply
var user = User()
var userNew = user as UserNew
But obviously, I am getting This cast can never succeed. Creating a new UserNew object and set every parameter is not feasible if I have a User object with lots of parameters. Any suggestions?
as is kotlin's cast operator. But User is not a UserNew. Therefore the cast fails.
Use an extension function to convert between the types:
fun User.toUserNew(): UserNew {
val userNew = UserNew()
userNew.id = id
userNew.name = name
return userNew
}
And use it like so
fun usingScenario(user: User) {
val userNew = user.toUserNew()
If you don't want to write a boilerplate code, you can use some libraries that will copy values via reflection (for example http://mapstruct.org/), but it's not the best idea.
To achieve you can Simply use Gson and avoid boilerplate code:
var user = User(....)
val json = Gson().toJson(user)
val userNew:UserNew =Gson().fromJson(json, UserNew::class.java)
you should follow this logic for this case.
note: #Frank Neblung answer i implemented
fun main(args: Array<String>) {
val user = User()
user.id = 10
user.name = "test"
var userNew = user.toUserNew()
println(userNew.id) // output is 10
println(userNew.name)// output is test
}
class User()
{
var id: Int? = null
var name: String? = null
fun toUserNew(): UserNew {
val userNew = UserNew()
userNew.id = id
userNew.name = name
return userNew
}
}
class UserNew() {
var id: Int? = null
var name: String? = null
}
You have two options. Either create interface and implement it in both classes. then you can use this interface in both places (User,UserNew) If this is not what you want, i would use copy constructor in UserNew taking User as parameter, You can create new
NewUser nu = new UserNew(userOld)
if you have lots of properties answer from ppressives is way to go
To achieve that you can use the concept of inheritance:
https://www.programiz.com/kotlin-programming/inheritance
Example:
open class Person(age: Int) {
// code for eating, talking, walking
}
class MathTeacher(age: Int): Person(age) {
// other features of math teacher
}