Hello i am having big problems here this is my first time using retrofit and i am new to kotlin, i don't know why this piece of code is not working.
This is my retrofit client
private const val BASE_URL = "https://89a6t4gtke.execute-api.eu-west-3.amazonaws.com/Prod/"
val instance : IApi by lazy{
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
retrofit.create(IApi::class.java)
}
This is my response class
data class DefaultResponse(val message: String) {}
This is my response:
{
"message": "GET"
}
Interface
interface IApi {
#GET("hello")
fun returnHello():Call<DefaultResponse>
}
The call
toast_button.setOnClickListener{
RetrofitClient.instance
.returnHello()
.enqueue(object: Callback<DefaultResponse>{
override fun onFailure(call: retrofit2.Call<DefaultResponse>, t: Throwable) {
Toast.makeText(context,t.message + "bla",Toast.LENGTH_SHORT).show()
}
override fun onResponse(call: retrofit2.Call<DefaultResponse>,response: Response<DefaultResponse>) {
Toast.makeText(context, "empty?",Toast.LENGTH_SHORT)
}
})
}
No toast messages show, i had an error show once when i made my api just return a string and not a json string but now there is no error as i fixed it.
You should change to your Retrofit Client as following code;
class RetrofitClient {
companion object {
fun getClient(): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://89a6t4gtke.execute-api.eu-west-3.amazonaws.com/Prod/")
.build()
}
}
}
Then add this in your activity ;
val service = RetrofitClient.getClient().create(IApi::class.java)
val call = service.returnHello()
val resp: DefaultResponse? = call.clone().execute().body()
if (resp != null) {
println("your response is -> $resp")
}
Related
I don't know how to get data from nested Json
{
"results":[
{
"id":1,
"name":"Rick Sanchez",
"status":"Alive",
"species":"Human",
"type":"",
"gender":"Male",
Json looks like above, i want to get access to name variable.
My code:
Data class:
data class Movie(
#Json(name = "results") val results: List<MovieDetail>
)
data class MovieDetail(
#Json(name = "name") val name: String
)
ApiService:
private const val BASE_URL = "https://rickandmortyapi.com/api/"
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
interface MovieApiService {
#GET("character")
suspend fun getMovies(): List<Movie>
}
object MovieApi {
val retrofitService : MovieApiService by lazy {
retrofit.create(MovieApiService::class.java)
}
}
And ViewModel:
private val _status = MutableLiveData<String>()
val status: LiveData<String> = _status
init {
getMovies()
}
private fun getMovies() {
viewModelScope.launch {
val listResult = MovieApi.retrofitService.getMovies()
_status.value = "Success: ${listResult.size} names retrieved"
}
}
For plain Json there is no problem but i don't know how to get access to this nested variables, i think that i have to use "results" variable from data class but i don't know where and how.
During running app i've got error: Expected BEGIN_ARRAY but was BEGIN_OBJECT at path $
You should change
#GET("character")
suspend fun getMovies(): List<Movie>
To:
#GET("character")
suspend fun getMovies(): Movie
You are receiving object and not list of objects
By okhttp log I know I get response from remote server. But when I try to parse response I receive null! How to correct parse response from the server to get data?
My Data object:
#Parcelize
data class DataItem(
#field:SerializedName("name")
val name: String? = null,
) : Parcelable
MY Api:
interface Api {
#GET("v1/media/list/image")
suspend fun getImage(): DataItem
}
My Retrofit object:
private val httpClient = OkHttpClient
.Builder()
.protocols(listOf(Protocol.HTTP_1_1))
.addInterceptor(BearerTokenInterceptor(TOKEN))
.addInterceptor(logInterceptor())
//get api by retrofit
private val api: TVApi by lazy {
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build()
retrofit.create(TVApi::class.java)
}
private fun logInterceptor() : HttpLoggingInterceptor {
val interceptor = HttpLoggingInterceptor()
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
return interceptor
}
My try to parse the response:
private val scope = CoroutineScope(Dispatchers.IO)
private fun getNetworkResponse() {
scope.launch {
try {
Log.d(MY_TAG, "api: ${api.getVideo()}")
} catch (e: IOException) {
Log.e(MY_TAG, "IOException: $e")
} catch (e: HttpException) {
Log.e(MY_TAG, "HttpException: $e")
}
}
}
OKhttp log:
{"code":200,"message":"Success","data":[{"name"....}
My Log:
api: DataItem(extension=null, size=null, name=null, url=null)
How to decode json data with Reified, Generics, Interface and Kotlin?
I created a project where i put the code and the instructions to run:
https://github.com/paulocoutinhox/kotlin-gson-sample
But basically the code is:
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
interface Serializer {
fun <T> decodeValue(data: String): T?
}
class JsonSerializer : Serializer {
override fun <T> decodeValue(data: String): T? {
try {
val type = object : TypeToken<T>() {}.type
val gson = Gson()
return gson.fromJson<T>(data, type)
} catch (e: Exception) {
println("Error when parse: ${e.message}")
}
return null
}
}
class Request<T>(val r: T)
inline fun <reified T> callSerializer(json: String): T? {
val serializer = JsonSerializer()
val decoded = serializer.decodeValue<Request<T>>(json)
return decoded?.r
}
fun main() {
val finalValue = callSerializer<Request<String>>("{\"r\": \"test\"}")
println("Decoded data is: $finalValue")
}
The Request class has an inner value called r with generic type.
Im trying convert the json data above to the Request class and bind r from json to string type.
But im getting the error:
> Task :run FAILED
Exception in thread "main" java.lang.ClassCastException: class com.google.gson.internal.LinkedTreeMap cannot be cast to class Request (com.google.gson.internal.LinkedTreeMap and Request are in unnamed module of loader 'app')
at MainKt.main(Main.kt:36)
at MainKt.main(Main.kt)
The gson library think that it is a LinkedTreeMap instead of the Request class.
How to solve this?
Thanks.
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
interface Serializer {
fun <T> decodeValue(data: String): Request<T>?
}
class JsonSerializer : Serializer {
override fun <T> decodeValue(data: String): Request<T>? {
try {
val type = object : TypeToken<Request<T>>() {}.type
val gson = Gson()
return gson.fromJson<Request<T>>(data, type)
} catch (e: Exception) {
println("Error when parse: ${e.message}")
}
return null
}
}
class Request<T>(val r: T)
inline fun <reified T> callSerializer(json: String): T? {
val serializer = JsonSerializer()
val decoded = serializer.decodeValue<T>(json)
return decoded?.r
}
fun main() {
println("Decoded data is: ${callSerializer<String>("{\"r\": \"test\"}")}")
}
So, I have a challenge to make a sign in and sign up features in Android App but I still confuse about how to implement a kotlin retrofit function that need a parameter body raw json. Here is the API looks like in postman
I am using MVVM design pattern and Hilt for Dependency Injection,
these are some of my code related to this case
#Provides
#Singleton
fun provideRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
#Provides
#Singleton
fun provideApi(retrofit: Retrofit): Api = retrofit.create(Api::class.java)
this is my function
#POST("signin")
suspend fun signIn(
#Body raw: JSONObject
): SignInResponse
this is how I call the function in view model
fun signIn(email: String, password: String) {
val param = JSONObject().apply {
add("email", email)
add("password", password)
}
viewModelScope.launch {
try {
signInResponse.value = api.signIn(param)
} catch (e: Exception) {
Log.d(TAG, "signInError: $e")
}
}
}
but it didn't work. I also have tried to change the parameter type from JSONObject to string but it still didn't work
I have just found a solution.
The solution is very simple, I just need to change the parameter type from JSONObject to JsonObject. Here is my final code
#POST("signin")
suspend fun signIn(
#Body raw: JsonObject
): SignInResponse
fun signIn(email: String, password: String) {
val param = JsonObject().apply {
addProperty("email", email)
addProperty("password", password)
}
viewModelScope.launch {
try {
signInResponse.value = api.signIn(param)
} catch (e: Exception) {
Log.d(TAG, "signInError: $e")
}
}
}
I have tried it and it works fine. I hope it can help you if you guys are facing the same case as me.
I have api that return json:
{"countries":[{"id":1,"name":"Australia"},{"id":2,"name":"Austria"}, ... ]}
I write model class (Kotlin lang)
data class Country(val id: Int, val name: String)
And I want do request using retorift that returning List < Models.Country >, from "countries" field in json
I write next:
interface DictService {
#GET("/json/countries")
public fun countries(): Observable<List<Models.Country>>
companion object {
fun create() : DictService {
val gsonBuilder = GsonBuilder()
val listType = object : TypeToken<List<Models.Country>>(){}.type
gsonBuilder.registerTypeAdapter(listType, CountriesDeserializer)
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
val service = Retrofit.Builder()
.baseUrl("...")
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gsonBuilder.create()))
.build()
return service.create(DictService::class.java)
}
}
object CountriesDeserializer : JsonDeserializer<List<Models.Country>> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): List<Models.Country>? {
val res = ArrayList<Models.Country>()
if(json!=null) {
val countries = json.asJsonObject.get("countries")
if (countries.isJsonArray()) {
for (elem: JsonElement in countries.asJsonArray) {
res.add(Gson().fromJson(elem, Models.Country::class.java))
}
}
}
return null;
}
}
}
But I get error:
java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
CountriesDeserializer code dont execute even!
What they want from me?
Maybe I need write my own TypeAdapterFactory?
I dont want use model class like
class Countries {
public List<Country> countries;
}
If your intention is to simplify the interface and hide the intermediate wrapper object I guess the simplest thing to do is to add an extension method to the DictService like so:
interface DictService {
#GET("/json/countries")
fun _countries(): Observable<Countries>
}
fun DictService.countries() = _countries().map { it.countries }
data class Countries(val countries: List<Country> = listOf())
Which can then be used as follows:
val countries:Observable<List<Country>> = dictService.countries()
I found the way:
object CountriesTypeFactory : TypeAdapterFactory {
override fun <T : Any?> create(gson: Gson?, type: TypeToken<T>?): TypeAdapter<T>? {
val delegate = gson?.getDelegateAdapter(this, type)
val elementAdapter = gson?.getAdapter(JsonElement::class.java)
return object : TypeAdapter<T>() {
#Throws(IOException::class)
override fun write(outjs: JsonWriter, value: T) {
delegate?.write(outjs, value)
}
#Throws(IOException::class)
override fun read(injs: JsonReader): T {
var jsonElement = elementAdapter!!.read(injs)
if (jsonElement.isJsonObject) {
val jsonObject = jsonElement.asJsonObject
if (jsonObject.has("countries") && jsonObject.get("countries").isJsonArray) {
jsonElement = jsonObject.get("countries")
}
}
return delegate!!.fromJsonTree(jsonElement)
}
}.nullSafe()
}
}
But it is very complex decision, I think, for such problem.
Are there another one simpler way?
Another one:
I found bug in my initial code from start meassage!!!
It works fine if replace List by ArrayList!
I would use Jackson for this task.
Try this https://github.com/FasterXML/jackson-module-kotlin
val mapper = jacksonObjectMapper()
data class Country(val id: Int, val name: String)
// USAGE:
val country = mapper.readValue<Country>(jsonString)