Kotlin compiler: Data binding error, cannot find method - kotlin

Mirgating from Java to Kotlin I try to use static function with Data Binding:
<data>
<import type="com.package.domain.tools.helper.StringValidator"/>
...
</data>
Then I call function hideNumber:
<com.hastee.pay.ui.view.Text
...
android:text='#{StringValidator.hideNumber(account.number)}'
app:layout_constraintRight_toRightOf="#+id/number"
app:layout_constraintTop_toBottomOf="#+id/number" />
Using databinding here causes error:
[kapt] An exception occurred:
android.databinding.tool.util.LoggedErrorException: Found data binding
errors.
****/ data binding error ****msg:cannot find method
hideNumber(java.lang.String) in class
com.package.domain.tools.helper.StringValidator....
Here's this object:
object StringValidator {
...
fun hideNumber(number: String): String {
return "****" + number.substring(number.length - 4)
}
}
How can I reach this function using Kotlin and Data Binding?

The data binding compiler is looking for a static method.
Since a named object alone is not enough to make all methods inside that object static, you need an additional #JvmStatic annotation on your hideNumber-method:
#JvmStatic
fun hideNumber(number: String): String {
return "****" + number.substring(number.length - 4)
}
see also: https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#static-methods

Related

When I use the generic method in Route then I am getting compiler Backend Internal error: Exception during IR lowering in Ktor

I am trying to create a dynamic service based on the data class model defined by user and they registerDataModels() method appDataModule() it should automatically create all based method in the router service. When I try to achive using generics in those method I am getting a compiler error. Is there any other better way to dynamically create route methods like by defining the datamodel by developer and then service should be automatically created?
org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
File being compiled: */api/AppConfigService.kt
The root cause java.lang.RuntimeException was thrown at: org.jetbrains.kotlin.backend.jvm.codegen.FunctionCodegen.generate(FunctionCodegen.kt:47)
File is unknown
The root cause java.lang.AssertionError was thrown at: org.jetbrains.kotlin.codegen.coroutines.CoroutineTransformerMethodVisitor.spillVariables(CoroutineTransformerMethodVisitor.kt:636)
fun Application.registerDataModels() {
appDataModule<M1>()
appDataModule<M2>()
appDataModule<M3>()
}
inline fun <reified T: DBModel> Application.appDataModule() {
routing {
createAppData<T>()
updateAppData<T>()
deleteAppData<T>()
}
}
inline fun <reified T: DBModel> Route.createAppData() {
put("/api/data/${getName<T>()}/create") {
authenticated {
create<T>{}
}
}
}
inline fun <reified T: DBModel> Route.updateAppData() {
put("/api/data/${getName<T>()}/update") {
authenticated {
update<T>{}
}
}
}
inline fun <reified T: DBModel> Route.deleteAppData() {
put("/api/data/${getName<T>()}/delete") {
authenticated {
delete<T>{}
}
}
}
Note: This answer assumed that code would be loaded at runtime, which seems not to be the case, and is therefore not completely matching OP's question.
You are using inline functions with reified.
To make a long story short, inline functions are compiled and 'copied' to the location where they are being used, already with a fixed (thats what reified does) class. So when you use an inline function
inline fun <reified T> foo(t: T): T { ... }
and you call it like this:
val myVal = foo("test").uppercase()
then at compile time of that calling line of code, the type of T is known to be String and the target line is compiled accordingly, so you know at runtime which type T is within your function.
It is (for this one calling line) as if that function was like this to begin with:
fun foo(t: String): String { ... }
Because you want to compile these classes dynamically, however, this process fails, because the class obviously does not exist yet. This is simply due to the nature of reified. If you can somehow remove it, it might work.
I agree that the error message of the compiler could be more telling here. Maybe you can raise a task on kotlin's issue tracking platform?: https://youtrack.jetbrains.com/issues/kt?_gl=1*5r6x4d*_ga*MTQyMDYxMjc2MS4xNjMzMzQwMzk5*_ga_9J976DJZ68*MTY2OTM1NjM1MS4yMS4xLjE2NjkzNTYzNTcuMC4wLjA.&_ga=2.265829455.1332696793.1669356352-1420612761.1633340399

mockk, how to verify a function is called with Map type and interface type

The class has a function:
fun theFunc(uri: Uri, theMap: Map<String, String>?, callback: ICallback) {
......
}
and would like to verify it is called with proper params type
io.mockk.verify { mock.theFunc(ofType(Uri::class), ofType(Map<String, String>::class), ofType(ICallbak::class)) }
the ofType(Uri::class) is ok,
the ofType(Map<String, String>::class got error:
the ofType(ICallbak::class) got error:
ICallback does not have a companion object, thus must be initialized
here.
How to use the ofType() for Map and interface?
The problem is that generic parameters are lost at runtime due to type erasure, and for this reason the syntax doesn't allow generic parameters to be specified in that context. You can write Map::class but not Map<String, String>::class because a Map<String, String> is just a Map at runtime.
So, you can call it like this:
verify { mock.theFunc(ofType(Uri::class), ofType(Map::class), ofType(ICallback::class)) }
that will work. However, there is also a version of function ofType which takes generic parameters, so you can use this:
verify { mock.theFunc(ofType<Uri>(), ofType<Map<String, String>>(), ofType<ICallback>()) }
You need to use mapOf<String,String>::class
io.mockk.verify { mock.theFunc(ofType(Uri::class), ofType(mapOf<String,String>()::class), ofType(ICallbak)) }
For interface, you can create mocck object. And put it into ofType.
val callbackMock: ICallback = mockk()
io.mockk.verify { mock.theFunc(ofType(Uri::class), ofType(mapOf<String,String>()::class), ofType(callbackMock::class)) }

kotlinFunction returns null if method is defined on an interface with a type parameter

A quick demo of a problem:
import kotlin.reflect.jvm.kotlinFunction
interface A<T> {
fun aaa(t: T): String {
return ""
}
}
class B : A<String>
fun main() {
println(B::class.java.methods[0].kotlinFunction) // returns null
}
Calling kotlinFunction on a method without type parameter returns an instance of KFunction as expected.
The reason is type erasure, that occurs in Java, but not in Kotlin:
Java:
public java.lang.String B.aaa(java.lang.Object)
Kotlin:
public java.lang.String B.aaa(java.lang.String)
https://github.com/JetBrains/kotlin/blob/master/core/reflection.jvm/src/kotlin/reflect/jvm/ReflectJvmMapping.kt#L134
Note that it's just Kotlin compiler preserving some more information for reflection, types will be still erased by JVM at runtime, Kotlin or not.
If you need to access Kotlin method, do it directly:
println(B::class.functions.first())

GSON Deserialization of subtypes in Kotlin

I'm not sure if this is a limitation, a bug or just bad use of GSON. I need to have a hierarchy of Kotlin objects (parent with various subtypes) and I need to deserialize them with GSON. The deserialized object has correct subtype but its field enumField is actually null.
First I thought this is because the field is passed to the "super" constructor but then I found out that "super" works well for string, just enum is broken.
See this example:
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory
open class Parent(val stringField: String,
val enumField: EnumField) {
enum class EnumField {
SUBTYPE1,
SUBTYPE2,
SUBTYPE3
}
}
class Subtype1() : Parent("s1", EnumField.SUBTYPE1)
class Subtype2(stringField: String) : Parent(stringField, EnumField.SUBTYPE2)
class Subtype3(stringField: String, type: EnumField) : Parent(stringField, type)
val subtypeRAF = RuntimeTypeAdapterFactory.of(Parent::class.java, "enumField")
.registerSubtype(Subtype1::class.java, Parent.EnumField.SUBTYPE1.name)
.registerSubtype(Subtype2::class.java, Parent.EnumField.SUBTYPE2.name)
.registerSubtype(Subtype3::class.java, Parent.EnumField.SUBTYPE3.name)
fun main() {
val gson = GsonBuilder()
.registerTypeAdapterFactory(subtypeRAF)
.create()
serializeAndDeserialize(gson, Subtype1()) // this works (but not suitable)
serializeAndDeserialize(gson, Subtype2("s2")) // broken
serializeAndDeserialize(gson, Subtype3("s3", Parent.EnumField.SUBTYPE3)) // broken
}
private fun serializeAndDeserialize(gson: Gson, obj: Parent) {
println("-----------------------------------------")
val json = gson.toJson(obj)
println(json)
val obj = gson.fromJson(json, Parent::class.java)
println("stringField=${obj.stringField}, enumField=${obj.enumField}")
}
Any ideas how to achieve to deserialization of enumField?
(deps: com.google.code.gson:gson:2.8.5, org.danilopianini:gson-extras:0.2.1)
P.S.: Note that I have to use RuntimeAdapterFactory because I have subtypes with different set of fields (I did not do it in the example so it is easier to understand).
Gson requires constructors without arguments to work properly (see deep-dive into Gson code below). Gson constructs raw objects and then use reflection to populate fields with values.
So if you just add some argument-less dummy constructors to your classes that miss them, like this:
class Subtype1() : Parent("s1", EnumField.SUBTYPE1)
class Subtype2(stringField: String) : Parent(stringField, EnumField.SUBTYPE2) {
constructor() : this("")
}
class Subtype3(stringField: String, type: EnumField) : Parent(stringField, type) {
constructor() : this("", EnumField.SUBTYPE3)
}
you will get the expected output:
-----------------------------------------
{"stringField":"s1","enumField":"SUBTYPE1"}
stringField=s1, enumField=SUBTYPE1
-----------------------------------------
{"stringField":"s2","enumField":"SUBTYPE2"}
stringField=s2, enumField=SUBTYPE2
-----------------------------------------
{"stringField":"s3","enumField":"SUBTYPE3"}
stringField=s3, enumField=SUBTYPE3
Gson deep-dive
If you want to investigate the internals of Gson, a tip is to add an init { } block to Subtype1 since it works and then set a breakpoint there. After it is hit you can move up the call stack, step through code, set more breakpoints etc, to reveal the details of how Gson constructs objects.
By using this method, you can find the Gson internal class com.google.gson.internal.ConstructorConstructor and its method newDefaultConstructor(Class<? super T>) that has code like this (I have simplified for brevity):
final Constructor<? super T> constructor = rawType.getDeclaredConstructor(); // rawType is e.g. 'class Subtype3'
Object[] args = null;
return (T) constructor.newInstance(args);
i.e. it tries to construct an object via a constructor without arguments. In your case for Subtype2 and Subtype3, the code will result in a caught exception:
} catch (NoSuchMethodException e) { // java.lang.NoSuchMethodException: Subtype3.<init>()
return null; // set breakpoint here to see
}
i.e. your original code fails since Gson can't find constructors without arguments for Subtype2 and Subtype3.
In simple cases, the problem with missing argument-less constructors is worked around with the newUnsafeAllocator(Type, final Class<? super T>)-method in ConstructorConstructor, but with RuntimeTypeAdapterFactory that does not work correctly.
I may be missing something in what you're trying to achieve, but is it necessary to use the RuntimeTypeAdapterFactory? If we take out the line where we register that in the Gson builder, so that it reads
val gson = GsonBuilder()
.create()
Then the output returns the enum we would expect, which looks to be serialising / deserialising correctly. I.e. the output is:
-----------------------------------------
{"stringField":"s1","enumField":"SUBTYPE1"}
stringField=s1, enumField=SUBTYPE1
-----------------------------------------
{"stringField":"s2","enumField":"SUBTYPE2"}
stringField=s2, enumField=SUBTYPE2
-----------------------------------------
{"stringField":"s3","enumField":"SUBTYPE3"}
stringField=s3, enumField=SUBTYPE3
It also may be an idea to implement Serializable in Parent. i.e.
open class Parent(val stringField: String, val enumField: EnumField) : Serializable {
enum class EnumField {
SUBTYPE1,
SUBTYPE2,
SUBTYPE3
}
}
Try adding #SerializedName annotation to each enum.
enum class EnumField {
#SerializedName("subtype1")
SUBTYPE1,
#SerializedName("subtype2")
SUBTYPE2,
#SerializedName("subtype3")
SUBTYPE3
}

How to handle nullable generics with Java interop

I have a Java class that is out of my control, defined as:
public #interface ValueSource {
String[] strings() default {}
}
I am trying to use this class from a Kotlin file I control, like so:
class Thing {
#ValueSource(string = ["non-null", null])
fun performAction(value: String?) {
// Do stuff
}
}
I get a compiler error
Kotlin: Type inference failed. Expected type mismatch: inferred type is Array<String?> but Array<String> was expected.
I understand why the inferred type is Array<String?>, but why is the expected type not the same? Why is Kotlin interpreting the Java generic as String! rather than String?? And finally, is there a way to suppress the error?
Kotlin 1.2.61
This isn't a Kotlin issue - this code isn't valid either, because Java simply doesn't allow null values in annotation parameters:
public class Thing {
#ValueSource(strings = {"non-null", null}) // Error: Attribute value must be constant
void performAction(String value) {
// Do stuff
}
}
See this article and this question for more discussion on this.