Ternary operator for two way data binding - android-databinding

I am using ternary operator on observable field to set the text to textview in xml.But its gives me following error at compile time.
****/ data binding error ****msg:The expression ((vmEnteredAmountGetJavaLangString0) ? ("") : (vmEnteredAmountGet)) cannot be inverted: The condition of a ternary operator must be constant: android.databinding.tool.writer.KCode#1a6539af
Below is my code:
<EditText
android:id="#+id/txtAmount"
style="#style/AmountText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text='#={vm.enteredAmount.get()=="0"?"":vm.enteredAmount}'
app:decimalLen='#{6}' />
Any help will be appreciated.Thank you.

I have this problem too, I think ternary operation doesn't work well with two-way DataBinding. I have following solutions.
Method 1 | Apply to All EditText:
object DataBindingUtil { //Kotlin singleton class
#BindingAdapter("android:text")
#JvmStatic
fun setText(editText: EditText, text: String?) {
if (text == "0" || text == "0.0") editText.setText("") else editText.setText(text)
}
}
Method 2 | Apply to Selected EditText:
object DataBindingUtil {
#BindingAdapter("emptyIfZeroText") //use this instead "android:text"
#JvmStatic
fun setText(editText: EditText, text: String?) {
if (text == "0" || text == "0.0") editText.setText("") else editText.setText(text)
}
#InverseBindingAdapter(attribute = "emptyIfZeroText", event = "android:textAttrChanged")
#JvmStatic
fun getText(editText: EditText) = editText.text.toString()
}
Apply to your EditText: app:emptyIfZeroText="#={`` + viewModel.currentStudent.gpa}"

Related

Kotlin: Filtering a list return the original list if no match found

I want an inline function that filters through a list and returns the original list if there is no match rather than returning null
I have this feature that filters through the list and returns the filtered list or empty
fun List<Obj>?.filterQueryText(queryText : String?) = this?.filter {
queryText.equals(it.objName)
}.orEmpty()
How do I make it so that it will return to the original list if no match is found in the query?
This should do the trick
fun List<Obj>?.filterQueryText(queryText : String?) = this?.filter {
queryText.equals(it.objName)
}?.run {if (isEmpty()) this#filterQueryText else this}.orEmpty()
see it in action here
Generic filter for List<T>:
fun <T> List<T>?.filterOrOriginal(predicate: (T) -> Boolean) : List<T>? =
this?.filter(predicate).takeUnless { it.isNullOrEmpty() } ?: this
Specific filter for List<Obj>:
fun List<Obj>?.filterQueryTextOrOriginal(queryText : String?) : List<Obj>? =
this?.filterOrOriginal { queryText == it.objName }

How to return a listmap with non null item, from a map?

I have a piece of code as below (simplified to explain the issue). From the rawData, I would like to filter out those that doesn't have a converter type provided in converter, and then for the remaining, convert the data to listitem
data class RawData(val type: String, val data: Data)
interface Converter {
fun convert(data: Data): ListItem
}
fun transform(): List<ListItem> {
val providerTypeMap = modelViewProvider.associateBy({it.type}, {it})
return rawDataList.filter {
converter[it.type] != null
}.map {
converter[it.type]?.create(it.data) ?: object: ListItem {}
}
}
Note: I want the return type as List<ListItem> and not List<ListItem?>. In order to do that, I need to have this line
converter[it.type]?.create(it.data) ?: object: ListItem {}
Which to me the ? and ?: is pretty ugly since we know by then, we already filter and only keep that converter[it.type] != null
Is there a way for me to get rid of the ? and ?: ListItem{} in my code?
There is a solution:
return rawDataList.mapNotNull {
converter[it.type]?.create(it.data)
}
But i don't know in which Kotlin's version mapNotNull() method appeared. If you haven't it you can use construction map {}.filterNotNull() or write your own mapNotNull extension method.

assign variable only if it is null

on Ruby one have something like this:
#var ||= 'value'
basically, it means that #var will be assigned 'value' only if #var is not assigned yet (e.g. if #var is nil)
I'm looking for the same on Kotlin, but so far, the closest thing would be the elvis operator. Is there something like that and I missed the documentation?
The shortest way I can think of is indeed using the elvis operator:
value = value ?: newValue
If you do this often, an alternative is to use a delegated property, which only stores the value if its null:
class Once<T> {
private var value: T? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): T? {
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
this.value = this.value ?: value
}
}
You can now create a property that uses this like so:
var value by Once<String>()
fun main(args: Array<String>) {
println(value) // 'null'
value = "1"
println(value) // '1'
value = "2"
println(value) // '1'
}
Note that this is not thread-safe and does not allow setting back to null. Also, this does evaluate the new expression while the simple elvis operator version might not.
Other way we could do it is by using ifEmpty..
From the docs: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/if-empty.html
Usage:
val empty = ""
val emptyOrNull: String? = empty.ifEmpty { null }
println(emptyOrNull) // null
val emptyOrDefault = empty.ifEmpty { "default" }
println(emptyOrDefault) // default
val nonEmpty = "abc"
val sameString = nonEmpty.ifEmpty { "def" }
println(sameString) // abc
EDIT:
Seems like this does not work if the initial value is NULL and only for strings..

Kotlin: How to check if enum contains a given String without messing with Exceptions?

Why there is no contains method in enum classes (as well as in Java)? And how to implement it elegantly? Now I'm using this ugly approach:
val contains: Boolean =
try {
MyEnum.valueOf("some string")
true
} catch (e: IllegalArgumentException) {
false
}
enumContains
You can create an enumContains function similar to Hound Dog's answer but using reified type parameters instead.
You cannot create a JVM independent solution in Kotlin 1.0 but you can in Kotlin 1.1 using enumValues.
Kotlin 1.1
/**
* Returns `true` if enum T contains an entry with the specified name.
*/
inline fun <reified T : Enum<T>> enumContains(name: String): Boolean {
return enumValues<T>().any { it.name == name}
}
Kotlin 1.0
/**
* Returns `true` if enum T contains an entry with the specified name.
*/
inline fun <reified T : Enum<T>> enumContains(name: String): Boolean {
return T::class.java.enumConstants.any { it.name == name}
}
Usage
enumContains<MyEnum>("some string") // returns true or false
enumValueOfOrNull
If you also need the actual enum entry then you might consider creating an enumValueOfOrNull function instead.
Kotlin 1.1
/**
* Returns an enum entry with the specified name or `null` if no such entry was found.
*/
inline fun <reified T : Enum<T>> enumValueOfOrNull(name: String): T? {
return enumValues<T>().find { it.name == name }
}
Kotlin 1.0
/**
* Returns an enum entry with the specified name or `null` if no such entry was found.
*/
inline fun <reified T : Enum<T>> enumValueOfOrNull(name: String): T? {
return T::class.java.enumConstants.find { it.name == name }
}
Usage
enumValueOfOrNull<MyEnum>("some string")
You can just take values Array of your enum and use contains over it. For example:
Planets.values().map { it.name }.contains("EARTH")
But for this you need to have correct string value so you might uppercase it before search.
If you want to find enum by its value take a look at the Reverse Lookup for enums.
Edit:
As #JamesBassett suggested you can optimize it to stop looking once it finds a match.
Planets.values().any { it.name == "EARTH" }
You could do something like this:
fun <T : Enum<*>> KClass<T>.contains(value: String): Boolean {
return this.java.enumConstants.any { it.name == value }
}
MyEnum::class.contains("some string")
or if you are really adventurous
enum MyEnum {
MY_1, MY_2;
companion object {
fun isInEnum(someString: String) {
return try {
valueOf(someString)
true
} catch (e: Exception) {
false
}
}
}
}

List containing nullable values to a nullable List in Kotlin

I have a List<T?> containing null values (which, I suppose, is not forbidden). If one element of this list is null, I want the entire list to be null (what Haskell people call sequence). The following pseudocode demonstrates what I want to do:
fun <T> sequence(a : List<T?>) : List<T>? {
return
a.fold(
listOf(),
{
prevArray, element ->
if(element == null) null else prevArray + element
})
}
This is pseudocode because the compiler complains that Null can not be a value of a non-null type kotlin.collections.List<T>.
What's the idiomatic way to express what I want to in Kotlin? Using Java's Optional type, this is at least compilable:
fun <T> sequence(a : List<T?>) : Optional<List<T>> {
return
a.fold(
Optional.of(listOf()),
{
prevArray, element ->
if(element == null) Optional.empty<List<T>>() else Optional.of(prevArray + element)
})
}
But Kotlin has many operators and functionalities regarding null handling, so I thought using null directly would be more idiomatic.
You can use a non-local return to return from the sequence function:
fun <T> sequence(a: List<T?>): List<T>? {
return a.fold(listOf()) {
prevArray, element ->
if (element == null) return null else prevArray + element
}
}
However, I would solve the problem you described with a simple if-expression, to prevent lots of list allocations which happen because the list addition creates a new list backed by array for each element. The unchecked cast warning is suppressed below because the compiler cannot figure out that the list does not contain nulls at that point, although we can clearly see that is the case:
fun <T> sequence(a: List<T?>): List<T>? {
#Suppress("UNCHECKED_CAST")
return if (a.any { it == null }) null else a as List<T>
}
Using Konad library you can now do the following:
fun <T> sequence(a : List<T?>) : List<T>? = a.flatten()?.toList()