How do I get the value from an ArrayList by using an "external" variable for comparing in Kotlin? - kotlin

I am trying to get one value from an Array with a data class in an elegant way depending on a Boolean type value. Here is an example:
...
private val isDone = true/false
...
private fun color(key: String): Int {
val colors = arrayListOf(
Color(TEXT, R.color.text_light, R.color.text_night),
Color(BACK, R.color.background_light, R.color.background_night),
...
)
//Wanna achieve something like this
val color = colors.find { it.key == key }.if { isDone } // if Done is true, the colorA should be retured, if not then the colorB.
return color
}
This is the date class I use:
data class Color(val key: String, #ColorRes val colorA: Int, #ColorRes val colorB: Int)
Is there a simple way by using some extensions of ArrayList to achieve that? I do not want to use .forEach loop or similar.

That "some extensions of ArrayList" is find, which you are already using.
After we find it, we can use let to bind the found color to a name, and then use an if expression to check isDone.
val color = colors.find { it.key == key }?.let { found ->
if (isDone) { found.colorA } else { found.colorB }
}
Note that color is of type Int?. It is nullable because it will be null when key is not found. You should think about what to do when this happens.
Also note that isDone is a val, which is likely to be unintentional (?)

Related

Kotlin sort one List with key and Enum with key and order

I receive data from Request information as list data (List) below code. That data has a "key" parameter by which I want to sort it.
data class ApplianceSetting(
#SerializedName("key") val key: String,
#SerializedName("value") var value: Any,
(...)
I have the required order in the SettingsUtilEnum and want to sort items by that.
After that, I can convert the list using map{} the data and use the function of Enum getSettingByMode() and get the list of Enum values. Then I will sort them and convert them again to List.
But that sounds too inefficient. Is there a better way.
enum class SettingsUtilEnum(
var settingKey: String,
override val order: Int = 99,
var settingName: String = "",
) : AbstractOrderEnum {
FIRST_MODE("first.mode", 0),
SECOND_MODE("second.mode", 1),
(...)
UNKNOWN_MODE("", 99);
companion object {
#JvmStatic
fun getSettingByMode(settingKey: String): SettingsUtilEnum? {
return values().find { it.settingKey == settingKey }
}
k
private fun initDataObserver() {
(activity as FavouriteActivity).viewModel.applianceSettings.observe(activity as FavouriteActivity
) { data ->
(controlRecyclerView.adapter as FavouriteAdditionalControlsAdapter)
val adapter = (controlRecyclerView.adapter as FavouriteAdditionalControlsAdapter)
// public final var data: List<ApplianceSetting>
// old code:
// data.settings
adapter.data = sortAndGetControlModes(data)
adapter.notifyDataSetChanged()
}
}
// TODO: sortAndGetControlModes
private fun sortAndGetControlModes(data: ApplianceSettingsList) =
data.settings.map {
getSettingByMode(it.key)
?: UNKNOWN_MODE.apply {
// If in future new modes are added -> put them as tail
settingKey = it.key
}
}.sortedBy { it.order }
// error i need to return again List<ApplianceSetting>
If you want to compare keys with theirs ASCII values you can just use sortBy { it.key }
If you want to expand possibilities of comparison you can use function sortedWith with passing custom comparator as argument.
Comparator used to compare its two arguments for order. Returns zero if the arguments are equal, a negative number if the first argument is less than the second, or a positive number if the first argument is greater than the second.
Example:
You can use it like that if you want to sort by integer value of key parameter:
data.settings.sortedWith { a, b ->
when {
a.key.toInt() < b.key.toInt() -> -1
a.key.toInt() > b.key.toInt() -> 1
else -> 0
}
}
I fixed it using sortedBy and as comparator I am using received value (order) from getSettingByMode(), if item is not found (null) I give him order value of 99 and put it on tail position:
private fun sortAndGetControlModes(data: ApplianceSettingsList) =
data.settings.sortedBy {
getSettingByMode(it.key)?.order ?:99
}

Kotlin multiple variables Null checker method implementation

I have multiple variables that can be nullable and i need to check them ( Strings and Dates ) .
I need a method where i pass it X number of variables and it returns me a list of the variables that are null.
I was thinking something that i can call like this :
internal fun checkNullVariables ( var x, var y , ..... ) : MutableList<String>{
// yada yada
return listOfNamesOfNullVariables
}
This definitely requires reflection, since you want parameter names. You need to add reflection as a dependency as explained in the documentation to use the below code.
private fun listNullProperties (vararg props: KProperty0<Any?>) : List<String> {
val list = mutableListOf<String>()
for (prop in props)
if (param.get() == null)
list.add(param.name)
return list
}
Usage:
val nullPropertiesByName = listNullParameters(
::myProperty,
::myOtherProperty,
::myDateProperty
)
println(nullPropertiesByName.joinToString())
If it is just about logging, you could:
fun <T> T?.logNull(name: String) {
when(this) {
null -> //log '$name' was null
else -> //do nothing
}
}
and call it like
var a: String? = null
a.logNull("my a variable") // "'my a variable' was null"
I still recommend the Map-approach. You may want to use properties stored in a map to overcome the use of reflection.
Here is an example using a type with 2 dates and 2 strings, both having a nullable and a non-null variant:
class YourData(internal val backedMap : Map<String, Any?>) {
val beginDate : Date by backedMap
val endDate : Date? by backedMap
val maybeString : String? by backedMap
val string : String by backedMap
constructor(beginDate : Date, string : String, endDate: Date? = null, maybeString : String? = null) : this(mapOf(
"beginDate" to beginDate,
"endDate" to endDate,
"maybeString" to maybeString,
"string" to string
))
}
While it may seem more complicated having that additional constructor in place, it just helps to easily create new objects the way you are most comfortable with.
Now you can either supply that function I placed in the comment or any variant of it. I now use an extension function for YourData instead:
fun YourData.getKeysWithNullValues() = backedMap.filterValues { it == null }.keys
Usage then may look as follows:
YourData(Date(), "test string")
.getKeysWithNullValues()
.forEach(::println)
which for this example would print:
endDate
maybeString

Kotlin: How to specify a named arguent with a variable?

Suppose I have two methods:
private fun method1(a: A): A {
return a.copy(v1 = null)
}
private fun method2(a: A): A {
return a.copy(v2 = null)
}
Can I write something like:
private fun commonMethod(a: A, variableToChange: String): A {
return a.copy($variableToChange = null)
}
Another words, can I use a variable to refer to a named argument?
If I understand correctly what you are trying to archive I would recommend to pass a setter to the method e.g.
fun <A> changer (a: A, setter: (a: A) -> Unit ) {
// do stuff
setter(a)
}
Is this what you are looking for?
A possible solution for this problem (with usage of reflection) is:
inline fun <reified T : Any> copyValues(a: T, values: Map<String, Any?>): T {
val function = a::class.functions.first { it.name == "copy" }
val parameters = function.parameters
return function.callBy(
values.map { (parameterName, value) ->
parameters.first { it.name == parameterName } to value
}.toMap() + (parameters.first() to a)
) as T
}
This works with all data classes and all classes that have a custom copy function with the same semantics (as long as the parameter names are not erased while compiling). In the first step the function reference of the copy method is searched (KFunction<*>). This object has two importent properties. The parameters property and the callBy function.
With the callBy function you can execute all function references with a map for the parameters. This map must contain a reference to the receiver object.
The parameters propery contains a collection of KProperty. They are needed as keys for the callBy map. The name can be used to find the right KProperty. If a function as a parameter that is not given in the map it uses the default value if available or throws an exception.
Be aware that this solution requires the full reflection library and therefore only works with Kotlin-JVM. It also ignores typechecking for the parameters and can easily lead to runtime exceptions.
You can use it like:
data class Person (
val name: String,
val age: Int,
val foo: Boolean
)
fun main() {
var p = Person("Bob", 18, false)
println(p)
p = copyValues(p, mapOf(
"name" to "Max",
"age" to 35,
"foo" to true
))
println(p)
}
// Person(name=Name, age=15, foo=false)
// Person(name=Max, age=35, foo=true)

How do I create an enum from an Int in Kotlin?

I have this enum:
enum class Types(val value: Int) {
FOO(1)
BAR(2)
FOO_BAR(3)
}
How do I create an instance of that enum using an Int?
I tried doing something like this:
val type = Types.valueOf(1)
And I get the error:
Integer literal does not conform to the expected type String
enum class Types(val value: Int) {
FOO(1),
BAR(2),
FOO_BAR(3);
companion object {
fun fromInt(value: Int) = Types.values().first { it.value == value }
}
}
You may want to add a safety check for the range and return null.
Enum#valueOf is based on name. Which means in order to use that, you'd need to use valueof("FOO"). The valueof method consequently takes a String, which explains the error. A String isn't an Int, and types matter. The reason I mentioned what it does too, is so you know this isn't the method you're looking for.
If you want to grab one based on an int value, you need to define your own function to do so. You can get the values in an enum using values(), which returns an Array<Types> in this case. You can use firstOrNull as a safe approach, or first if you prefer an exception over null.
So add a companion object (which are static relative to the enum, so you can call Types.getByValue(1234) (Types.COMPANION.getByValue(1234) from Java) over Types.FOO.getByValue(1234).
companion object {
private val VALUES = values()
fun getByValue(value: Int) = VALUES.firstOrNull { it.value == value }
}
values() returns a new Array every time it's called, which means you should cache it locally to avoid re-creating one every single time you call getByValue. If you call values() when the method is called, you risk re-creating it repeatedly (depending on how many times you actually call it though), which is a waste of memory.
Admittedly, and as discussed in the comments, this may be an insignificant optimization, depending on your use. This means you can also do:
companion object {
fun getByValue(value: Int) = values().firstOrNull { it.value == value }
}
if that's something you'd prefer for readability or some other reason.
The function could also be expanded and check based on multiple parameters, if that's something you want to do. These types of functions aren't limited to one argument.
If you are using integer value only to maintain order, which you need to access correct value, then you don't need any extra code. You can use build in value ordinal. Ordinal represents position of value in enum declaration.
Here is an example:
enum class Types {
FOO, //Types.FOO.ordinal == 0 also position == 0
BAR, //Types.BAR.ordinal == 1 also position == 1
FOO_BAR //Types.FOO_BAR.ordinal == 2 also position == 2
}
You can access ordinal value simply calling:
Types.FOO.ordinal
To get correct value of enum you can simply call:
Types.values()[0] //Returns FOO
Types.values()[1] //Returns BAR
Types.values()[2] //Returns FOO_BAR
Types.values() returns enum values in order accordingly to declaration.
Summary:
Types.values(Types.FOO.ordinal) == Types.FOO //This is true
If integer values don't match order (int_value != enum.ordinal) or you are using different type (string, float...), than you need to iterate and compare your custom values as it was already mentioned in this thread.
It really depends on what you actually want to do.
If you need a specific hardcoded enum value, then you can directly use Types.FOO
If you are receiving the value dynamically from somewhere else in your code, you should try to use the enum type directly in order not to have to perform this kind of conversions
If you are receiving the value from a webservice, there should be something in your deserialization tool to allow this kind of conversion (like Jackson's #JsonValue)
If you want to get the enum value based on one of its properties (like the value property here), then I'm afraid you'll have to implement your own conversion method, as #Zoe pointed out.
One way to implement this custom conversion is by adding a companion object with the conversion method:
enum class Types(val value: Int) {
FOO(1),
BAR(2),
FOO_BAR(3);
companion object {
private val types = values().associate { it.value to it }
fun findByValue(value: Int): Types? = types[value]
}
}
Companion objects in Kotlin are meant to contain members that belong to the class but that are not tied to any instance (like Java's static members).
Implementing the method there allows you to access your value by calling:
var bar = Types.findByValue(2) ?: error("No Types enum value found for 2")
Note that the returned value is nullable, to account for the possibility that no enum value corresponds to the parameter that was passed in. You can use the elvis operator ?: to handle that case with an error or a default value.
If you hate declaring for each enum type a companion object{ ... } to achieve EMotorcycleType.fromInt(...). Here's a solution for you.
EnumCaster object:
object EnumCaster {
inline fun <reified E : Enum<E>> fromInt(value: Int): E {
return enumValues<E>().first { it.toString().toInt() == value }
}
}
Enum example:
enum class EMotorcycleType(val value: Int){
Unknown(0),
Sport(1),
SportTouring(2),
Touring(3),
Naked(4),
Enduro(5),
SuperMoto(6),
Chopper(7),
CafeRacer(8),
.....
Count(9999);
override fun toString(): String = value.toString()
}
Usage example 1: Kotlin enum to jni and back
fun getType(): EMotorcycleType = EnumCaster.fromInt(nGetType())
private external fun nGetType(): Int
fun setType(type: EMotorcycleType) = nSetType(type.value)
private external fun nSetType(value: Int)
---- or ----
var type : EMotorcycleType
get() = EnumCaster.fromInt(nGetType())
set(value) = nSetType(value.value)
private external fun nGetType(): Int
private external fun nSetType(value: Int)
Usage example 2: Assign to val
val type = EnumCaster.fromInt<EMotorcycleType>(aValidTypeIntValue)
val typeTwo : EMotorcycleType = EnumCaster.fromInt(anotherValidTypeIntValue)
A naive way can be:
enum class Types(val value: Int) {
FOO(1),
BAR(2),
FOO_BAR(3);
companion object {
fun valueOf(value: Int) = Types.values().find { it.value == value }
}
}
Then you can use
var bar = Types.valueOf(2)
Protocol orientated way with type-safety
interface RawRepresentable<T> {
val rawValue: T
}
inline fun <reified E, T> valueOf(value: T): E? where E : Enum<E>, E: RawRepresentable<T> {
return enumValues<E>().firstOrNull { it.rawValue == value }
}
enum class Types(override val rawValue: Int): RawRepresentable<Int> {
FOO(1),
BAR(2),
FOO_BAR(3);
}
Usage
val type = valueOf<Type>(2) // BAR(2)
You can use it on non-integer type, too.
I would build the 'reverse' map ahead of time. Probably not a big improvement, but also not much code.
enum class Test(val value: Int) {
A(1),
B(2);
companion object {
val reverseValues: Map<Int, Test> = values().associate { it.value to it }
fun valueFrom(i: Int): Test = reverseValues[i]!!
}
}
Edit: map...toMap() changed to associate per #hotkey's suggestion.
try this...
companion object{
fun FromInt(v:Int):Type{
return Type::class.java.constructors[0].newInstance(v) as Type
}
}
This is for anyone looking for getting the enum from its ordinal or index integer.
enum class MyEnum { RED, GREEN, BLUE }
MyEnum.values()[1] // GREEN
Another solution and its variations:
inline fun <reified T : Enum<T>> enumFromIndex(i: Int) = enumValues<T>()[i]
enumFromIndex<MyEnum>(1) // GREEN
inline fun <reified T : Enum<T>> enumFromIndex(i: Int) = enumValues<T>().getOrNull(i)
enumFromIndex<MyEnum>(3) ?: MyEnum.RED // RED
inline fun <reified T : Enum<T>> enumFromIndex(i: Int, default: T) =
enumValues<T>().getOrElse(i) { default }
enumFromIndex(2, MyEnum.RED) // BLUE
It is an adapted version of another answer. Also, thanks to Miha_x64 for this answer.
Another option...
enum class Types(val code: Int) {
FOO(1),
BAR(2),
FOO_BAR(3);
companion object {
val map = values().associate { it.code to it }
// Get Type by code with check existing codes and default
fun getByCode(code: Int, typeDefault_param: Types = FOO): Types {
return map[code] ?: typeDefault_param
}
}
}
fun main() {
println("get 3: ${Types.getByCode(3)}")
println("get 10: ${Types.getByCode(10)}")
}
get 3: FOO_BAR
get 10: FOO

Override getter for Kotlin data class

Given the following Kotlin class:
data class Test(val value: Int)
How would I override the Int getter so that it returns 0 if the value negative?
If this isn't possible, what are some techniques to achieve a suitable result?
After spending almost a full year of writing Kotlin daily I've found that attempting to override data classes like this is a bad practice. There are 3 valid approaches to this, and after I present them, I'll explain why the approach other answers have suggested is bad.
Have your business logic that creates the data class alter the value to be 0 or greater before calling the constructor with the bad value. This is probably the best approach for most cases.
Don't use a data class. Use a regular class and have your IDE generate the equals and hashCode methods for you (or don't, if you don't need them). Yes, you'll have to re-generate it if any of the properties are changed on the object, but you are left with total control of the object.
class Test(value: Int) {
val value: Int = value
get() = if (field < 0) 0 else field
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Test) return false
return true
}
override fun hashCode(): Int {
return javaClass.hashCode()
}
}
Create an additional safe property on the object that does what you want instead of having a private value that's effectively overriden.
data class Test(val value: Int) {
val safeValue: Int
get() = if (value < 0) 0 else value
}
A bad approach that other answers are suggesting:
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
The problem with this approach is that data classes aren't really meant for altering data like this. They are really just for holding data. Overriding the getter for a data class like this would mean that Test(0) and Test(-1) wouldn't equal one another and would have different hashCodes, but when you called .value, they would have the same result. This is inconsistent, and while it may work for you, other people on your team who see this is a data class, may accidentally misuse it without realizing how you've altered it / made it not work as expected (i.e. this approach wouldn't work correctly in a Map or a Set).
You could try something like this:
data class Test(private val _value: Int) {
val value = _value
get(): Int {
return if (field < 0) 0 else field
}
}
assert(1 == Test(1).value)
assert(0 == Test(0).value)
assert(0 == Test(-1).value)
assert(1 == Test(1)._value) // Fail because _value is private
assert(0 == Test(0)._value) // Fail because _value is private
assert(0 == Test(-1)._value) // Fail because _value is private
In a data class you must to mark the primary constructor's parameters with either val or var.
I'm assigning the value of _value to value in order to use the desired name for the property.
I defined a custom accessor for the property with the logic you described.
The answer depends on what capabilities you actually use that data provides. #EPadron mentioned a nifty trick (improved version):
data class Test(private val _value: Int) {
val value: Int
get() = if (_value < 0) 0 else _value
}
That will works as expected, e.i it has one field, one getter, right equals, hashcode and component1. The catch is that toString and copy are weird:
println(Test(1)) // prints: Test(_value=1)
Test(1).copy(_value = 5) // <- weird naming
To fix the problem with toString you may redefine it by hands. I know of no way to fix the parameter naming but not to use data at all.
I have seen your answer, I agree that data classes are meant for holding data only, but sometimes we need to make somethings out of them.
Here is what i'm doing with my data class, I changed some properties from val to var, and overid them in the constructor.
like so:
data class Recording(
val id: Int = 0,
val createdAt: Date = Date(),
val path: String,
val deleted: Boolean = false,
var fileName: String = "",
val duration: Int = 0,
var format: String = " "
) {
init {
if (fileName.isEmpty())
fileName = path.substring(path.lastIndexOf('\\'))
if (format.isEmpty())
format = path.substring(path.lastIndexOf('.'))
}
fun asEntity(): rc {
return rc(id, createdAt, path, deleted, fileName, duration, format)
}
}
I know this is an old question but it seems nobody mentioned the possibility to make value private and writing custom getter like this:
data class Test(private val value: Int) {
fun getValue(): Int = if (value < 0) 0 else value
}
This should be perfectly valid as Kotlin will not generate default getter for private field.
But otherwise I definitely agree with spierce7 that data classes are for holding data and you should avoid hardcoding "business" logic there.
I found the following to be the best approach to achieve what you need without breaking equals and hashCode:
data class TestData(private var _value: Int) {
init {
_value = if (_value < 0) 0 else _value
}
val value: Int
get() = _value
}
// Test value
assert(1 == TestData(1).value)
assert(0 == TestData(-1).value)
assert(0 == TestData(0).value)
// Test copy()
assert(0 == TestData(-1).copy().value)
assert(0 == TestData(1).copy(-1).value)
assert(1 == TestData(-1).copy(1).value)
// Test toString()
assert("TestData(_value=1)" == TestData(1).toString())
assert("TestData(_value=0)" == TestData(-1).toString())
assert("TestData(_value=0)" == TestData(0).toString())
assert(TestData(0).toString() == TestData(-1).toString())
// Test equals
assert(TestData(0) == TestData(-1))
assert(TestData(0) == TestData(-1).copy())
assert(TestData(0) == TestData(1).copy(-1))
assert(TestData(1) == TestData(-1).copy(1))
// Test hashCode()
assert(TestData(0).hashCode() == TestData(-1).hashCode())
assert(TestData(1).hashCode() != TestData(-1).hashCode())
However,
First, note that _value is var, not val, but on the other hand, since it's private and data classes cannot be inherited from, it's fairly easy to make sure that it is not modified within the class.
Second, toString() produces a slightly different result than it would if _value was named value, but it's consistent and TestData(0).toString() == TestData(-1).toString().
Seems to be an old but interesting question.
Just want to contribute an option:
data class Test(#JvmField val value: Int){
fun getValue() = if(value<0) 0 else value
}
Now you can override getValue, and still have component1() working.
This seems to be one (among other) annoying drawbacks of Kotlin.
It seems that the only reasonable solution, which completely keeps backward compatibility of the class is to convert it into a regular class (not a "data" class), and implement by hand (with the aid of the IDE) the methods: hashCode(), equals(), toString(), copy() and componentN()
class Data3(i: Int)
{
var i: Int = i
override fun equals(other: Any?): Boolean
{
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as Data3
if (i != other.i) return false
return true
}
override fun hashCode(): Int
{
return i
}
override fun toString(): String
{
return "Data3(i=$i)"
}
fun component1():Int = i
fun copy(i: Int = this.i): Data3
{
return Data3(i)
}
}
You can follow the Builder Pattern for this I think it'd be much better.
Here is an example:
data class Test(
// Fields:
val email: String,
val password: String
) {
// Builder(User):
class Builder(private val email: String) {
// Fields:
private lateinit var password: String
// Methods:
fun setPassword(password: String): Builder {
// Some operation like encrypting
this.password = password
// Returning:
return this
}
fun build(): Test = Test(email, password)
}
}