I am using Retrofit with simpleXMLConverter to get data out of an xml and use it in my android application. However, I am unable to bind the value of a certain tag on my value in my class. In my situation, I'm trying to bind the mtc:gas_type to my price variable in the Gasoline Class. I did manage to bind a value from a simple XML I found online with just 2 tags so it means I'm doing something wrong with this particular XML...
Error(Which means it can't find the tag):
java.lang.RuntimeException: org.simpleframework.xml.core.ValueRequiredException: Unable to satisfy #org.simpleframework.xml.Element(data=false, name=mtc:gas_type, required=true, type=void) on field 'price' private double com.example.alexander.ridy.Model.domain.Gasoline.price for class com.example.alexander.ridy.Model.domain.Gasoline at line -1
at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:44)
at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:23)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:223)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:186)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall.execute(ExecutorCallAdapterFactory.java:92)
at com.example.alexander.ridy.View.fragments.Ride.VehicleDetails$determineGasolineType$1.run(VehicleDetails.kt:101)
at java.lang.Thread.run(Thread.java:764)
Caused by: org.simpleframework.xml.core.ValueRequiredException: Unable to satisfy #org.simpleframework.xml.Element(data=false, name=mtc:gas_type, required=true, type=void) on field 'price' private double com.example.alexander.ridy.Model.domain.Gasoline.price for class com.example.alexander.ridy.Model.domain.Gasoline at line -1
at org.simpleframework.xml.core.Composite.validate(Composite.java:644)
at org.simpleframework.xml.core.Composite.readElements(Composite.java:449)
at org.simpleframework.xml.core.Composite.access$400(Composite.java:59)
at org.simpleframework.xml.core.Composite$Builder.read(Composite.java:1383)
at org.simpleframework.xml.core.Composite.read(Composite.java:201)
at org.simpleframework.xml.core.Composite.read(Composite.java:148)
at org.simpleframework.xml.core.Traverser.read(Traverser.java:92)
at org.simpleframework.xml.core.Persister.read(Persister.java:625)
at org.simpleframework.xml.core.Persister.read(Persister.java:606)
at org.simpleframework.xml.core.Persister.read(Persister.java:584)
at org.simpleframework.xml.core.Persister.read(Persister.java:562)
at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:36)
at retrofit2.converter.simplexml.SimpleXmlResponseBodyConverter.convert(SimpleXmlResponseBodyConverter.java:23)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:223)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:186)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall.execute(ExecutorCallAdapterFactory.java:92)
at com.example.alexander.ridy.View.fragments.Ride.VehicleDetails$determineGasolineType$1.run(VehicleDetails.kt:101)
at java.lang.Thread.run(Thread.java:764)
XML:
https://www.globalpetrolprices.com/api/getGasXML_weekly.php?gasoline_diesel=2&rate=EUR&countries=183&p=c5ab37e86d514beadb60d3d3d682f363
Endpoint interface:
interface Endpoint {
#GET("/api/getGasXML_weekly.php?gasoline_diesel=2&rate=LC&countries=183&p=c5ab37e86d514beadb60d3d3d682f363")
fun getDiesel(): Call<Gasoline>
#GET("/api/getGasXML_weekly.php?gasoline_diesel=1&rate=LC&countries=183&p=c5ab37e86d514beadb60d3d3d682f363")
fun getSuper95(): Call<Gasoline>
#GET("/api/getGasXML_weekly.php?gasoline_diesel=3&rate=LC&countries=183&p=c5ab37e86d514beadb60d3d3d682f363")
fun getLPG(): Call<Gasoline>
RetrofitClientInstance:
class RetrofitClientInstance {
private var retrofit: Retrofit? = null
private val BASE_URL = "https://www.globalpetrolprices.com"
fun getRetrofitInstance(): Retrofit? {
retrofit = retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(create())
.build()
return retrofit
}
Gasoline Class:
#Root(strict = false) class Gasoline {
#field:Element(name="mtc:gas_type")
var price : Double = 0.0
lateinit var gasolineType : GasolineType
constructor()
The call:
fun determineGasolineType() {
val mySpinner = view?.findViewById(R.id.gasolineSpinner) as Spinner
val text = mySpinner.selectedItem.toString()
val service = RetrofitClientInstance().getRetrofitInstance()!!.create(Endpoint::class.java!!)
lateinit var call : Call<Gasoline>
lateinit var resp : Response<Gasoline>
Thread({
resp = service.getDiesel().execute()
Log.d("",resp.body().toString())
}).start()
Related
I am trying to return List with Generic type from handle(context: ExecutionContext?) method of MicroProfile FallbackHandler using Kotlin. But it's throwing exception like " org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException: Invalid #Fallback on getDistanceData(java.lang.String): fallback handler's type java.util.List<? extends java.lang.String> is not the same as method's return type
"
RestClient :
#GET
#Path("/testFallbackUrl")
#Fallback(DistanceServiceFallback::class)
#CircuitBreaker(
requestVolumeThreshold = 4, failureRatio = 0.75, delay = 5000, successThreshold = 3
)
fun getDistanceData(#QueryParam("date") date: String) : List<String>
Handler:
#RegisterForReflection
class DistanceServiceFallback : FallbackHandler<List<String>> {
#field:Default
#field:Inject
lateinit var logger: Logger
override fun handle(context: ExecutionContext?): List<String> {
logger.error("Inside DistanceServiceFallback handler. ")
return listOf("Hello")
}
}
This is because of a difference in type inference when your kotlin code is processed in Java. The return type of getDistanceData is java.util.List<String> and the handler's return type is as mentioned in the exception java.util.List<? extends java.lang.String>.
The return type java.util.List<String> is observed from the CDI interceptor so I am not sure how exactly it is extracted but obviously it is not carrying <? extends E> information.
The handler's type java.util.List<? extends java.lang.String> is on the other hand extracted from kotlin.collections.List which is defined as public interface List<out E> : Collection<E> which I think is correct as <out E> should translate to <? extends E>.
However, there is an easy workaround:
#GET
#Path("/testFallbackUrl")
#Fallback(DistanceServiceFallback::class)
#CircuitBreaker(
requestVolumeThreshold = 4, failureRatio = 0.75, delay = 5000, successThreshold = 3
)
fun getDistanceData(#QueryParam("date") date: String) : MutableList<String> {
return mutableListOf("Default")
}
#RegisterForReflection
class DistanceServiceFallback : FallbackHandler<MutableList<String>> {
#field:Default
#field:Inject
lateinit var logger: Logger
override fun handle(context: ExecutionContext?): MutableList<String> {
logger.error("Inside DistanceServiceFallback handler. ")
return mutableListOf("Hello")
}
}
which works because MutableList is defined as public interface MutableList<E> : List<E>, MutableCollection<E> and thus it has now an exact generic type.
There's a request that i'm using and it's parameter name is __parameter10/02/2020, the date is sent into request.
Is there a way to deserialize this using the Property annotation? Also how can i do this with the custom serializer?
Json Sample:
{"yearly_return10/02/2020" : "2.87", "__monthlyreturn_current10/02/2020": "-0.0853703899653",}
You may try #JsonAnySetter
class Response {
lateinit var yearlyReturn: BigDecimal
lateinit var monthlyReturnCurrent: BigDecimal
#JsonAnySetter
fun set(key: String, value: String) {
when {
key.contains("yearly_return") -> yearlyReturn = BigDecimal(value)
key.contains("monthlyreturn_current") -> monthlyReturnCurrent = BigDecimal(value)
}
}
}
val objectMapper: ObjectMapper = ObjectMapper().registerModules(
ParameterNamesModule(),
Jdk8Module(),
JavaTimeModule(),
KotlinModule()
)
val response = objectMapper.readValue<Response>("""{"yearly_return10/02/2020" : "2.87", "__monthlyreturn_current10/02/2020": "-0.0853703899653"}""")
I'm trying to pass data class to the service-proxy of Vert.x like this:
data class Entity(val field: String)
#ProxyGen
#VertxGen
public interface DatabaseService {
DatabaseService createEntity(Entity entity, Handler<AsyncResult<Void>> resultHandler);
}
However, the service-proxy requires a DataObject as the parameter type.
Below are what I've tried so far.
First, I rewrite the data class as:
#DataObject
data class Entity(val field: String) {
constructor(json: JsonObject) : this(
json.getString("field")
)
fun toJson(): JsonObject = JsonObject.mapFrom(this)
}
Although this works, the code is redundant, so I tried the kapt with the following generator:
override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
roundEnv.getElementsAnnotatedWith(ProxyDataObject::class.java).forEach { el ->
val className = el.simpleName.toString()
val pack = processingEnv.elementUtils.getPackageOf(el).toString()
val filename = "Proxy$className"
val classBuilder = TypeSpec.classBuilder(filename)
val primaryConstructorBuilder = FunSpec.constructorBuilder()
val secondaryConstructorBuilder = FunSpec.constructorBuilder().addParameter("json", JsonObject::class)
val secondaryConstructorCodeBlocks = mutableListOf<CodeBlock>()
el.enclosedElements.forEach {
if (it.kind == ElementKind.FIELD) {
val name = it.simpleName.toString()
val kClass = getClass(it) // get the corresponding Kotlin class
val jsonTypeName = getJsonTypeName(it) // get the corresponding type name in methods of JsonObject
classBuilder.addProperty(PropertySpec.builder(name, kClass).initializer(name).build())
primaryConstructorBuilder.addParameter(name, kClass)
secondaryConstructorCodeBlocks.add(CodeBlock.of("json.get$jsonTypeName(\"$name\")"))
}
}
secondaryConstructorBuilder.callThisConstructor(secondaryConstructorCodeBlocks)
classBuilder
.addAnnotation(DataObject::class)
.addModifiers(KModifier.DATA)
.primaryConstructor(primaryConstructorBuilder.build())
.addFunction(secondaryConstructorBuilder.build())
.addFunction(
FunSpec.builder("toJson").returns(JsonObject::class).addStatement("return JsonObject.mapFrom(this)").build()
)
val generatedFile = FileSpec.builder(pack, filename).addType(classBuilder.build()).build()
generatedFile.writeTo(processingEnv.filer)
}
return true
}
Then I can get the correct generated file by simply writing the original data class, but when I execute the building after cleaning, I still get the following error:
Could not generate model for DatabaseService#createEntity(ProxyEntity,io.vertx.core.Handler<io.vertx.core.AsyncResult<java.lang.Void>>): type ProxyEntity is not legal for use for a parameter in proxy
It seems that the generated annotation #DataObject is not processed.
So what should I do? Is there a better solution?
I'm a newbie about Kotlin. My first project is to consume a rest api. I already made that using retrofit. But I have a problem when I'm logging the response, my data class is null. I don't know where is the error.
My Rerofit Client
object RetrofitClient {
var retrofit: Retrofit? = null
fun getClient(baseUrl: String): Retrofit? {
if (retrofit == null) {
//TODO While release in Google Play Change the Level to NONE
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder()
.addInterceptor(interceptor)
.connectTimeout(100, TimeUnit.SECONDS)
.readTimeout(100, TimeUnit.SECONDS)
.build()
retrofit = Retrofit.Builder()
.client(client)
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit
}
}
My Interface
public interface ApiLoginService {
#POST("UserManagementCoreAPI/api/v1/users")
fun loginService(#Body request: RequestBody): Call<DataLogin>
}
object ApiUtils {
val BASE_URL = "------"
val apiLoginService: ApiLoginService
get() = RetrofitClient.getClient(BASE_URL)!!.create(ApiLoginService::class.java)
}
My class data
data class DataLogin (
#SerializedName("employeeId") val employeeId : String,
#SerializedName("fullName") val fullName : String,
#SerializedName("loginConfins") val loginConfins : String,
#SerializedName("branchId") val branchId : String,
#SerializedName("isActive") val isActive : String
)
Main Activity
mApiLoginService!!.loginService(requestBody).enqueue(object : Callback<DataLogin>{
override fun onResponse(call: Call<DataLogin>, response: Response<DataLogin>) {
if(response.isSuccessful()){
if(response.body().toString() == null){
Log.d(tag,"Null")
}else{
Log.d(tag,"Logging In " + response.body()!!)
progressBar.visibility = View.GONE
btn_submit.visibility = View.VISIBLE
startActivity(Intent(this#MainActivity, HomeActivity::class.java))
}
}else{
Toast.makeText(applicationContext, "Invalid username or password", Toast.LENGTH_LONG).show()
Log.d(tag,"Error " + response.errorBody().toString())
progressBar.visibility = View.GONE
btn_submit.visibility = View.VISIBLE
}
}
override fun onFailure(call: Call<DataLogin>, t: Throwable) {
progressBar.visibility = View.GONE
btn_submit.visibility = View.VISIBLE
}
})
My Respone Log
message: Logging In DataLogin(employeeId=null, fullName=null, loginConfins=null, branchId=null, isActive=null)
I don't know where is the error and why my data is null. If the response succeeds is still gives me null.
This is a postman example
You have an issue with your schema , Your DataLogin class is different of your postman schema , Retrofit is waiting for : fullName, isActive ...., and the response is : header , data .. , you have to create class that contains header as variable of type Header(errors:List<AnotherClass>), data as variable of type Data(data(List<DataLogin>),totalRecord:Int), i would suggest if you use helper website like JSON to Class , parse your postman response there , and he will give you your correct response class but it's will be in java , you have to rewrite the code yourself of just copy paste in android studio and he will convert the code to Kotlin for you. (in the web site , check Source type: JSON)
You have to match the json structure with your data classes if you do not provide a custom adapter to Gson. So if you want to have a result, maybe something like this will work:
data class Response(
#SerializedName("headers") val headers: List<Any>,
#SerializedName("data") val data: Data
)
data class Data(
#SerializedName("data") val data: List<DataLogin>,
#SerializedName("totalRecord") val totalRecord: Int
)
data class DataLogin (
#SerializedName("employeeId") val employeeId : String,
#SerializedName("fullName") val fullName : String,
#SerializedName("loginConfins") val loginConfins : String,
#SerializedName("branchId") val branchId : String,
#SerializedName("isActive") val isActive : String
)
You need to return a Response object from your retrofit call.
Also a few tips about kotlin, Gson works well for Java, but it has some issues with kotlin (about null safety). I use Moshi when the project language is kotlin and it works well with it.
Try to avoid using !! in kotlin because it will cause RuntimeException. There are other ways of checking null and to protect your code from RuntimeExceptions.
networkCall = NetworkCall(context)
val responceCall = networkCall!!.getRetrofit(true).callReadMeService()
responceCall.clone().enqueue(object : Callback<BaseResponse<*>?> {
override fun onResponse(call: Call<BaseResponse<*>?>, response: Response<BaseResponse<*>?>) {
networkCall!!.dismissLoading()
web_view!!.loadData((response.body()?.data as LinkedTreeMap<*, *>)["description"] as String, "text/html", "UTF-8")
}
override fun onFailure(call: Call<BaseResponse<*>?>, t: Throwable) {
networkCall!!.dismissLoading()
}
})
here is api method
#POST("stories/readme")
fun callReadMeService(): Call<BaseResponse<*>?>
now i am getting this exception
here is my BaseResponse class
class BaseResponse<T> {
var message: String? = null
var status: Boolean = false
var errors: Array<String>? = null
var code: String? = null
var data: T? = null
}
Method return type must not include a type variable or wildcard: retrofit2.Call>
for method IApi.callReadMeService
now i can't remove generic data variable from BaseResponse class, cause i am using this class as a common Api parser
any solution for this
You cannot do this, because the type info needs to be fully specified, otherwise retrofit cannot correctly generate the Service. See this discussion.
You need to create a different API method for each type.