Hey I am working in android kotlin. I am getting double value in the form of string from server. I am converting that value in Int and checking that value is not null using toIntOrNull(). But I am getting 0 all the time.
Value coming from server
"value": "79.00"
My Data class
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
#Parcelize
data class Detail(
val value: String? = null,
) : Parcelable {
val valueInDouble by lazy {
value?.toDoubleOrNull() ?: Double.NaN
}
val valueInInt by lazy {
value?.toIntOrNull() ?: 0
}
}
whenever I print the value of valueInInt in console it returns 0. I know that 0 is coming because of toIntOrNull() but my value is coming from server which is not null. But I don't understand why this is happening. Can someone guide me please?
Expected Output
79
Actual Output
0
If the String value has a decimal in it, it cannot be parsed as an integer, so toIntOrNull() will return null.
You should parse as a Double and then convert the Double to an Int based on whatever type of rounding is appropriate for your use case.
val valueInInt by lazy {
value?.toDoubleOrNull()?.roundToInt() ?: 0
}
Related
I'm working in Kotlin Android.
The string is coming from the server, and it might contain digit, character, empty string or null value. I want to convert that value into double because I want to compare values.
So is there any better way to check that string contains only digit value and not empty or null in a Kotlin way. The list is huge, so I want an efficient way.
Price.kt
data class Price(
var value: String? = null
)
main.kt
val defaultValue : Double = 4.23
val list = listOf(Price("2.33"), Price("fr23"), Price(""), Price(null), Price("4.23"), Price("12.23"))
list.forEach{
if(it == defaultValue){
println("Found It")
}
}
Kotlin already has an efficient solution: toDoubleOrNull
Parses the string as a Double number and returns the result or null if
the string is not a valid representation of a number.
if (list.any { it.value.toDobleOrNull() == defaultValue }) {
println("Found It")
}
I have a firebase realtime database
with this simple scheme:
admin
price1: 5
if i get database in kotlin:
val result = it.value as MutableMap<String, Any>
When i try to get price1
var price1 = result["price1"] as Long
price1 = price1 + 1
(PRICE1 can be Double or Int)
the problem is that if price 1 is 5.5 obviously app killed, but if price 1 is 5, works perfectly.
In swift, i put Double every time and it never gives problems
I find it a bit silly to have to check if it is a double or an int without a comma to be able to do the sum
// im doing this at the moment
var price1 = result["price1"].toString()
if (price1.contains(".")){
println(price1.toDouble() + 1)
}else{
println(price1.toInt() + 1)
}
Exist other simple way?
Thanks everyone
Kotlin is very strict about types, which is important for type safety.
In your case, you get a value of type Any out of result. It could be anything, not only an Int or a Double. You know that it can only be an Int or a Double, but the compiler doesn't. Many languages allow implicit stuff like type conversion (int to double), type widening (int to long), etc. But these are often sources of nasty errors. See also this discussion Does anybody find Kotlin’s Type Casting disgusting?
Regarding your code: To test a value for its type you use is.
Here is an example of how you could increment by one:
fun increment(value: Any): Any {
return when (value) {
is Double -> value + 1.0
is Int -> value + 1
else -> throw Exception("Value is neither a Double nor an Int")
}
}
And you would use it like this:
val result: MutableMap<String, Any> = mutableMapOf(
"price1" to 3,
"price2" to 3.45
)
var price1: Any = result["price1"]!! // 3
price1 = increment(price1)
println(price1) // 4
price1 = increment(price1)
println(price1) // 5
var price2: Any = result["price2"]!! // 3.45
price2 = increment(price2)
println(price2) // 4.45
price2 = increment(price2)
println(price2) // 5.45
I don't know if Kotlin will ever have union types. Then a declaration like this would be possible:
val result: MutableMap<String, [Int|Double]> // invalid code
In kotlin all numerable types like Long, Int, Double etc inherit abstract class Number
So your map declaration could be Map<String, Number>.
The Number may be easily converted to Double or any other numerable type and then you can work with it as you do in swift:
val map = hashMapOf<String, Number>(
"1" to 5.5,
"2" to 5
)
var value1 = requireNotNull(map["1"]).toDouble()
val value2 = requireNotNull(map["2"]).toDouble()
value1++
PS: never use serialization to string as a way to check a type, you can use is operator as #lukas.j suggested
from an API call i get as response a body with this structure
open class BaseResponseEntity {
#SerializedName("result")
val result: ResultEnum = ResultEnum.NONE
#SerializedName("errorCode")
val errorCode: String = ""
#SerializedName("errorMessage")
val errorMessage: String = ""
#SerializedName("successMessage")
val successMessage: String = ""
#SerializedName("value")
val code: LastPaymentCodeModel?
}
where the field "value" can be three types: null, String or LastPaymentCodeModel. How can i get this?
I managed to put a ? so that both null and LastPaymentCodeModel are handled, but i don't know how to handle the String type too.
I think the best approach would probably be to use type Any? for code.
Then you should write a custom GSon serializer/deserilizer (JsonDeserializer<BaseResponseEntity>) for the BaseResponseEntity object.
In this Json deserializer, you would need to check the type of value (e.g is it a string or a data structure) and decode it to the correct object type.
Alternative, to avoid the use of Any?, you could leave the model exactly as you have it. You will still need to write a custom JsonDeserializer, however if value is a string then it would still create a LastPaymentCodeModel, using the string value as one of it's properties.
Why the following code failed to compile with the error:
"Type mismatch: inferred type is String? but Comparable<String?>! was expected
What does it mean the '!' after the brackets? And why it says that infered type is String
if it defiently MutableList<String?>? ?
The code:
private fun createAllGamesList(gamesPerCountriesMap: MutableMap<String?, MutableMap<Long, MutableList<AllScoresGameObj>>>?)
:List<BaseDataItem>
{
var countriesNameSet = gamesPerCountriesMap?.keys
var countriesNameList = countriesNameSet?.toMutableList()
Collections.sort(countriesNameList)
countriesNameList?.let{
}
}
Just want to point out that using the Java class Collections in Kotlin is kind of discouraged since the Kotlin standard library has built-in functions that can do the same tasks in ways that fit Kotlin-style syntax better, has better nullability handling, and can be more performant when they use inline lambdas.
For instance, you wouldn't be running into the weird type Comparable<String?>! if using Kotlin's sort() or sortedBy(). The ! symbol after a type means it's maybe nullable and maybe not because the Java code doesn't specify either way.
In Kotlin, you can call sort() directly on your MutableList (it's an extension function) if it has a Comparable type. You'll still run into the problem that String? is not a Comparable<String?>, but you can do custom sorting using compareBy and thenBy to create a Comparator:
countriesNameList.sortedWith(
compareBy<String?> { it ?: "" } // lambda returns String, which is Comparable
.thenBy { it == null } // Sort between empty Strings and null values by returning a Boolean (another Comparable
)
The problem is that countriesNameList is a list of nullable Strings. That is, its type is MutableList<String?>, and not MutableList<String>.
Collections.sort() requires that the collection's type implement Comparable<T>, but String? does not implement Comparable<String?>. I imagine this is because it is not obvious where null values would fall in the natural ordering of String? elements.
This is very easy to observe with the following code:
val list: MutableList<String?> = mutableListOf("hello", "world")
Collections.sort(list)
If I change the type specification to not include the ?, the problem goes away:
val list: MutableList<String> = mutableListOf("hello", "world")
Collections.sort(list)
You can work around this by providing your own comparator:
Collections.sort(countriesNameList) { lhs, rhs -> customCompare(lhs, rhs) }
private fun customCompare(lhs: String?, rhs: String?): Int = when {
lhs == null && rhs == null -> 0
lhs == null -> 1
rhs == null -> -1
else -> lhs.compareTo(rhs)
}
I have text values I retrieve from text inputs. I want to allow the user to not fill in these inputs. But if the user has not filled one or more values I want to display default values for these inputs.
I have a data class that looks something like this:
#Parcelize
data class Profile(
val firstName: String = "",
val lastName: String = "",
val description: String = "",
val imageUri: String = ""
) : Parcelable
On click I call a method from my ViewModel class and pass it the current input values which is then persisted using a Repository class:
viewModel.createProfile(
etFirstName.text.toString(),
etLastName.text.toString(),
etProfileDescription.text.toString(),
profileImageUri.toString()
)
// The createProfile function itself
fun createProfile(
firstName: String = "John",
lastName: String = "Doe",
description: String = "Default Description",
imageUri: String = ""
) {
val profile = Profile(firstName, lastName, description, imageUri)
// Persist data
}
In a another fragment I set some UI data using this persisted data like this:
private fun observeProfile() {
viewModel.getProfile()
viewModel.profile.observe(viewLifecycleOwner, Observer {
val profile = it
tvName.text = getString(R.string.profile_name, profile.firstName, profile.lastName)
tvDescription.text = profile.description
ivProfileImage.setImageURI(Uri.parse(profile.imageUri))
})
}
So currently createProfile expects 4 arguments. I'm able to pass less because I have optional parameters, but how can I conditionally pass arguments to createProfile based on if the value is non null or empty. I can of course create checks for each value, but what is the best way to approach this?
Update
I don't think I was clear enough in my original question, because I don't only pass values from text inputs to createProfile. profileImageUri is a class variable of type Uri? and is initially set to null. The user can select an image and this variable is updated with the image data. The reason I'm passing and storing the image data as a String is because all the profile data also gets persisted to Firestore so Strings are easier to work with.
Compared to your own answer, I'd create a helper function
fun CharSequence?.ifNullOrEmpty(default: String) = if (this.isNullOrEmpty()) default else this.toString()
And use it as
viewModel.createProfile(
etFirstName.text.ifNullOrEmpty("John"),
etLastName.text.ifNullOrEmpty("Doe"),
etProfileDescription.text.ifNullOrEmpty("Default Description"),
profileImageUri.ifNullOrEmpty("Default Uri")
)
EDIT: given the update, I'd consider
fun Any?.ifNullOrEmpty(default: String) =
if (this == null || (this is CharSequence && this.isEmpty()))
default
else
this.toString()
I have found a workaround.
Turns out it's possible to pass if-else statements as parameters, because if statements are expressions in Kotlin:
viewModel.createProfile(
if (!etFirstName.text.isNullOrEmpty()) etFirstName.text.toString() else "John",
if (!etLastName.text.isNullOrEmpty()) etLastName.text.toString() else "Doe",
if (!etProfileDescription.text.isNullOrEmpty()) etProfileDescription.text.toString() else "Default Description",
if (profileImageUri != null) profileImageUri.toString() else ""
)
Using this approach I also don't have to set default values for my data class variables and for my createProfile function parameters.
I additionally added a check in my observeProfile function so if profileImageUri is null it won't try to set the image:
// ...
if (profile.imageUri.isNotEmpty()) {
ivProfileImage.setImageURI(Uri.parse(profile.imageUri))
}
// ...
My initial idea doesn't seem to be possible using a data class. It does seem to be possible using a regular class and varargsbut it has problems:
#Parcelize
class Profile(
vararg val params: String
) : Parcelable
...
val params = arrayOfValues.filter{ !it.isNullOrBlank() } // filter out all unwanted data
val profile = Profile(*params) // pass every param separately using spread operator
Main problem here is that the parameters themselves are obfuscated. I can still get the reference to individual parameters using an index and do stuff with them, but it doesn't work as nicely.
I think what you want to use is the Elvis Operator in Kotlin: ?:.
val test = exampleExpression ?: "alternative value"
If the expression to the left of ?: is not null, the elvis operator returns it, otherwise it returns the expression to the right. Note that the right-hand side expression is evaluated only if the left-hand side is null.
viewModel.createProfile(
etFirstName.text.toString() ?: "John",
etLastName.text.toString() ?: "Doe",
etProfileDescription.text.toString() ?: "Default Description",
profileImageUri.toString() ?: "Default Uri"
)