Retrofit2 + SimpleXML in Kotlin: MethodException: Annotation must mark a set or get method

c8112002 picture c8112002 · Mar 10, 2016 · Viewed 7.2k times · Source

I want to fetch XML data from API and map it to Kotlin model object by using Retrofit2 + SimpleXML in Kotlin.

However, I got such as the following error message from SimpleXML.

org.simpleframework.xml.core.MethodException: Annotation @org.simpleframework.xml.Element(data=false, name=, required=true, type=void) must mark a set or get method

This is fetched XML data

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<response>
    <result code="0">Success</result>
    <token>XXXXXXXXXXXXXXXXXXXX</token>
    <uid>4294967295</uid>
</response>

Kotlin model object is below

@Root(name = "response")
public class User() {
    @Element public var result: String? = null
    @Element public var token: String? = null
    @Element public var uid: String? = null
}

and APIClient is as follows.

interface  MyService {
    @GET("/testLogin.xml")
    fun getUser(): Call<User>
}

val retrofit = Retrofit.Builder()
        .baseUrl(baseURL)
        .addConverterFactory(SimpleXmlConverterFactory.create())
        .build()
val call = retrofit.create(MyService::class.java).getUser()
call.enqueue(object: Callback<User> {
        override fun onResponse(p0: Call<User>?, response: Response<User>?) {
            val response = response?.body()
        }
        override fun onFailure(p0: Call<User>?, t: Throwable?) {
            Log.e("APIClient", t?.message)
        }

I got HTTP status code 200 and correct XML data. So I think my declaration of model object is problem.

Answer

Jayson Minard picture Jayson Minard · Mar 11, 2016

This is the same problem as: kotlin data class + bean validation jsr 303

You need to use Annotation use-site targets since the default for an annotation on a property is prioritized as:

  • parameter (if declared in constructor)
  • property (if the target site allows, but only Kotlin created annotations can do this)
  • field (likely what happened here, which isn't what you wanted).

Use get or set target to place the annotation on the getter or setter. Here it is for the getter:

@Root(name = "response")
public class User() {
    @get:Element public var result: String? = null
    @get:Element public var token: String? = null
    @get:Element public var uid: String? = null
}

See the linked answer for the details.