Api call failed Unable to create converter for class Retrofit/Moshi - android

I am implementing retrofit and moshi to make requests to a server (I am newbie using retrofit). I follow some guides that I found on the internet on how to implement it but when launching the app I receive the following error:
Error Api call failed Unable to create converter for class com.example.kvn.data.model.JConfig
for method ApiClient.getApiConfig
This is the code i use :
AppModule.kt
#Module
#InstallIn(ApplicationComponent::class)
object AppModule {
#Singleton
#Provides
fun providerDB(#ApplicationContext ctx: Context) =
Room.databaseBuilder(ctx, AppDB::class.java, DB_NAME).build()
#Singleton
#Provides
fun providerDao(db: AppDB) = db.getDao()
#Singleton
#Provides
fun provideHttpClient(): OkHttpClient {
return OkHttpClient
.Builder()
.readTimeout(15, TimeUnit.SECONDS)
.connectTimeout(15, TimeUnit.SECONDS)
.build()
}
#Singleton
#Provides
fun provideConverterFactory() = MoshiConverterFactory.create()
#Singleton
#Provides
fun provideRetrofit(
okHttpClient: OkHttpClient,
moshiConverterFactory: MoshiConverterFactory
): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(moshiConverterFactory)
.build()
}
#Singleton
#Provides
fun providerApi(retrofit: Retrofit) = retrofit.create(ApiClient::class.java)
}
Remote.kt
/*#JsonClass(generateAdapter = true)
data class JConfig(
#Json(name = "data") val data:List<Config>
)
data class Config(
#Json(name = "codigo") var codigo: Int,
#Json(name = "tipo") var tipo: String,
#Json(name = "empresa") var empresa: Int,
#Json(name = "sucursal") var sucursal: Int,
#Json(name = "esquema") var esquema: Int,
#Json(name = "horainicio") var hini: String,
#Json(name = "horafinal") var hfin: String,
#Json(name = "fecha") var fecha: String,
#Json(name = "seguimiento") var seguimiento: Int
)*/
#JsonClass(generateAdapter = true)
data class JConfig(
#Json(name = "data") val data:List<Config>
)
data class Config(
var codigo: Int,
var tipo: String,
var empresa: Int,
var sucursal: Int,
var esquema: Int,
var horainicio: String,
var horafinal: String,
var fecha: String,
var seguimiento: Int
)
ApiClient.kt
interface ApiClient {
#POST(API_CONFIGURACION)
suspend fun getApiConfig(#Body imei: String): Response<JConfig>
}
WebDataSource.kt
class WebDataSource #Inject constructor(private val web:ApiClient) {
suspend fun getWebConfiguracion(imei:String): Response<JConfig> {
return web.getApiConfig(imei)
}
}
AppViewModel.kt
class AppViewModel #ViewModelInject constructor(private val repository: Repository) : ViewModel() {
private val _response: MutableLiveData<NetworkResult<JConfig>> = MutableLiveData()
val response: LiveData<NetworkResult<JConfig>> = _response
fun fetchConfigResponse(imei:String) = viewModelScope.launch {
repository.getWebConfiguracion(imei).collect { values ->
_response.value = values
}
}
fun saveOrUpdateConf(conf:Config) {
viewModelScope.launch {
if (isConfigEmpty()) {
repository.saveConfiguracion(conf)
}else {
repository.updateConfiguracion(conf)
}
}
}
suspend fun setupConfig(T:()->Unit) {
if (isConfigEmpty()) {
T()
}else {
val cf = repository.getConfiguracion().value!!
if (yesterday(cf[0].fecha)) {
T()
}
}
}
}
FBase.kt
#AndroidEntryPoint
class FBase : Fragment() {
private val viewmodel by activityViewModels<AppViewModel>()
private var _bind:FragmentFBaseBinding? = null
private val bind get() = _bind!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_bind = FragmentFBaseBinding.inflate(inflater, container, false)
return bind.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupObserver("888888882222331")
}
private fun setupObserver(imei:String) {
fetchResponse(imei)
viewmodel.response.observe(viewLifecycleOwner) { response ->
when(response) {
is NetworkResult.Loading -> { /*Notification*/ }
is NetworkResult.Success -> {
val rs = response.data?.data?.get(0)!!
viewmodel.saveOrUpdateConf(rs)
}
is NetworkResult.Error -> { Log.d("TAG","Error ${response.message}") }
}
}
}
private fun fetchResponse(imei: String) {
viewmodel.fetchConfigResponse(imei)
//add notification
}
}
Dependencies and plugins
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
// HILT
implementation 'com.google.dagger:hilt-android:2.28.1-alpha'
kapt 'com.google.dagger:hilt-android-compiler:2.28.1-alpha'
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
// TOOLS
implementation 'com.squareup.moshi:moshi-kotlin:1.13.0'
implementation 'com.squareup.moshi:moshi:1.13.0'
kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.13.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
Before retrofit, I used volley for requests together with moshi without any problem, but with retrofit I don't know how to solve it.
Sorry for my English.
Thanks

I found the source of the error, when using moshi with retrofit, all data classes must have the annotation of #JsonClass(generateAdapter = true)
We must change the code:
#JsonClass(generateAdapter = true)
data class JConfig(
#Json(name = "data") val data:List<Config>
)
data class Config(
var codigo: Int,
var tipo: String,
var empresa: Int,
var sucursal: Int,
var esquema: Int,
var horainicio: String,
var horafinal: String,
var fecha: String,
var seguimiento: Int
)
For this:
#JsonClass(generateAdapter = true)
data class JConfig(
#Json(name = "data") val data:List<Config>
)
#JsonClass(generateAdapter = true)
data class Config(
var codigo: Int,
var tipo: String,
var empresa: Int,
var sucursal: Int,
var esquema: Int,
var horainicio: String,
var horafinal: String,
var fecha: String,
var seguimiento: Int
)

In my case, it was because I was not passing the moshi object into MoshiConverterFactory.create() e.g.
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create())
.baseUrl(BASE_URL)
.build()
Compiles fine but throws same error OP is seeing at runtime.
Fixed by passing moshi into MoshiConverterFactory
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
moshi object is defined as
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()

Also, ensure that you have enabled KotlinJsonAdapterFactory and have added it to your Moshi.Builder:
//First Build Moshi Object
private val moshi =
Moshi.Builder()
.addLast(KotlinJsonAdapterFactory())
.build()
//Then Build Retrofit Object and passing in moshi object created above
val retrofit =
Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi)) //for parsing KotlinObjects i.e.
// picture of the day
.baseUrl(BASE_URL)
.build()
There is also a similar question here

In my case I forgot to add these dependencies:
implementation "com.squareup.moshi:moshi:$moshi_version"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
You can try that if no one from above suggestions works for you.

Related

com.squareup.moshi.JsonDataException: Required value 'access_token' missing at $

I try to do post request using retrofit and I use moshi to serialize but I'm not very well versed in these matters. And I am facing such an error, what is the reason for this?
error
E/AndroidRuntime: FATAL EXCEPTION: main
com.squareup.moshi.JsonDataException: Required value 'access_token' missing at $
at com.squareup.moshi.internal.Util.missingProperty(Util.java:649)
at com.squareup.moshi.kotlin.reflect.KotlinJsonAdapter.fromJson(KotlinJsonAdapter.kt:103)
at com.squareup.moshi.internal.NullSafeJsonAdapter.fromJson(NullSafeJsonAdapter.java:41)
at retrofit2.converter.moshi.MoshiResponseBodyConverter.convert(MoshiResponseBodyConverter.java:46)
at retrofit2.converter.moshi.MoshiResponseBodyConverter.convert(MoshiResponseBodyConverter.java:27)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:243)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:153)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:920)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException:
[StandaloneCoroutine{Cancelling}#4eb43d6, Dispatchers.Main.immediate]
my example response data that I try in postman is like this
{
"token_type": "Bearer",
"scope": "read",
"access_token": "WupDsILidLiLşöSAMKvLyI0asdasec",
"expires_in": 5000,
"refresh_token": "R9XJBe9FYpgajhqaRO1nIyXscssaSd"
}
my post request response data
#JsonClass(generateAdapter = true)
#Parcelize
data class RegisterDto(
#Json(name = "access_token")
val access_token: String,
#Json(name = "expires_in")
val expires_in: Int,
#Json(name = "refresh_token")
val refresh_token: String,
#Json(name = "scope")
val scope: String,
#Json(name = "token_type")
val token_type: String
) : Parcelable
my data that I send to post request
#JsonClass(generateAdapter = true)
#Parcelize
data class RegisterDataModel(
#Json(name = "name")
val name: String,
#Json(name = "email")
val email: String,
#Json(name = "password")
val password: String,
#Json(name = "token")
val token: String
) : Parcelable
my register api
interface RegisterService {
#POST(API)
suspend fun postUserData(#Body requestBody:RegisterUiModel ) : Response<RegisterDto>
}
my repositoryImpl in Data layer
class RegisterRepositoryImpl #Inject constructor(
private val registerService: RegisterService
) : RegisterRepository {
override suspend fun postRegisterData(dataModel: RegisterUiModel): Response<RegisterDto> {
return registerService.postUserData(dataModel)
}
}
Moshi Module in di package
#Module
#InstallIn(SingletonComponent::class)
internal class MoshiModule {
#Provides
#Singleton
fun provideMoshi(): Moshi {
return Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
}
}
my App module in di package
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Provides
#Singleton
fun provideRetrofit(
moshi: Moshi
): Retrofit {
return Retrofit.Builder()
.baseUrl(API)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
}
#Provides
#Singleton
fun provideRegisterService(retrofit: Retrofit): RegisterService {
return retrofit.create(RegisterService::class.java)
}
#Provides
#Singleton
fun provideRegisterRepository(service: RegisterService): RegisterRepository {
return RegisterRepositoryImpl(service)
}
}
my post function in viewmodel
fun postService(
registerUiModel: RegisterUiModel
) {
viewModelScope.launch {
val response1 = postRegisterUseCase.postRegisterData(registerUiModel)
withContext(Dispatchers.Main) {
if ( response1.code() == 200) {
val result1 = response1.toString()
// Convert raw JSON to pretty JSON using GSON library
// val adapter = Moshi.Builder().build().adapter(Any::class.java).indent(" ")
// val result1 = adapter.toJson(response1.body())
Log.d("Pretty Printed JSON : ", result1)
} else {
Log.e("RETROFIT_ERROR", response1.toString())
}
}
}
}
In logcat, the following does not appear when it should, most likely the problem is here
Log.d("Pretty Printed JSON : ", result1)
and i feel like i am not using moshi properly. I didn't do anything related to Moshi, as far as I know, retrofit already does it for us. While adding the retrofit in AppModule, I did it like this .addConverterFactory(MoshiConverterFactory.create(moshi)). Doesn't that make it do it automatically anyway?

What is the right way to post with retrofit 2 and moshi

I've been trying to make a POST with Retrofit 2 and Moshi but I've been unable to get it to work.
My data classes look like this:
data class Order(
val items: List<Item>?,
val customerName: String,
val customerPhoneNo: String,
val customerAddress: String,
val note: String
)
data class Item(
val productUid: String,
var quantity: Int
)
The interface looks like this
interface ProductService {
#POST("/api/order/saveorder")
suspend fun postProds(
#Field("customerName") customerName: String,
#Field("customerPhoneNo") customerPhone: String,
#Field("customerAddress") address: String,
#Field("note") customerNote:String,
#Field("items") orderItems: List<Item>
): Response<Order>
#GET("/api/product/allproducts")
suspend fun getProds(): Response<List<ProdsItem>>
}
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
object Network {
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi)
.asLenient()
)
.build()
object ProdsApi {
val retrofitService: ProductService by lazy {
retrofit.create(ProductService::class.java)
}
}
}
The postProds fun is called like this:
suspend fun sendOrder(order: Order) {
withContext(Dispatchers.Main){
try {
val orderResponse = Network.ProdsApi.retrofitService.postProds(
order.customerName,
order.customerPhoneNo,
order.customerAddress,
order.note,
order.items )
}
catch (e: Exception) {
Timber.e(e)
}
}
}
Trying to POST this way keeps yielding this response:
Response{protocol=h2, code=400, message=, url=
However, I tried converting the Order object to json directly in my viewModel as follows:
val moshi: Moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val jsonAdapter: JsonAdapter<Order> = moshi.adapter(Order::class.java)
val jsonString = jsonAdapter.toJson(customerOrder)
Timber.d(jsonString)
I then tested the generated jsonString on Postman and got a 200 response.
I need some help figuring out what I'm doing wrong in my code, please.
In postman, you are sending data in the body of the request. But in your code, it is going as key-value params. Try to send it in the body from your code. Try below snippet.
Update your Order Data class:
#JsonClass(generateAdapter = true)
data class Order(
#Json(name = "items")
val items: List<Item>?,
#Json(name = "customerName")
val customerName: String,
#Json(name = "customerPhoneNo")
val customerPhoneNo: String,
#Json(name = "customerAddress")
val customerAddress: String,
#Json(name = "note")
val note: String
)
#JsonClass(generateAdapter = true)
data class Item(
#Json(name = "productUid")
val productUid: String,
#Json(name = "quantity")
var quantity: Int
)
Now the ProductService Interface:
interface ProductService {
#POST("/api/order/saveorder")
suspend fun postProds(
#Body request: Order
): Response<Order>
#GET("/api/product/allproducts")
suspend fun getProds(): Response<List<ProdsItem>>
}
Now Pass the request object in your function call:
suspend fun sendOrder(order: Order) {
withContext(Dispatchers.Main){
try {
val orderResponse = Network.ProdsApi.retrofitService.postProds(order)
}
catch (e: Exception) {
Timber.e(e)
}
}
}

Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $ when using Retrofit2 which returns onFailure

I'm getting this error ** Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $**
gradles below which I used for retrofit
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.0'
Retrofit class
class RetrofitClient private constructor() {
val myApi: Api
companion object {
#get:Synchronized
var instance: RetrofitClient? = null
get() {
if (field == null) {
field = RetrofitClient()
}
return field
}
private set
}
init {
val gson = GsonBuilder()
.setLenient()
.create()
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val client : OkHttpClient = OkHttpClient.Builder().addInterceptor(interceptor).build()
val retrofit: Retrofit = Retrofit.Builder().baseUrl("http://ctyf.co.in/api/")
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
myApi = retrofit.create(Api::class.java)
}
}
Api interface
public interface Api {
#Headers("Content-Type: application/json")
#GET("companyvehiclelatestinfo?token=F934A0C439/")
fun getLatLngs(): Call<ResponseLoc?>?
}
data class
data class ResponseLoc(
val Vehicle: List<Vehicle>
)
data model
data class Vehicle(
val Angle: String,
val Date: String,
val Ignition: String,
val Imei: String,
val Lat: String,
val Location: String,
val Long: String,
val Speed: String,
val Tempr: String,
val VehicleNo: String
)
finally calling here
private fun getLatLngs() {
val call: Call<ResponseLoc?>? = RetrofitClient.instance!!.myApi.getLatLngs()
call!!.enqueue(object : Callback<ResponseLoc?> {
override fun onResponse(call: Call<ResponseLoc?>, response: Response<ResponseLoc?>) {
val responseLoc: List<ResponseLoc> = response.body() as List<ResponseLoc>
//Creating an String array for the ListView
val data = arrayOfNulls<String>(responseLoc.size)
for (i in responseLoc.indices) {
data[i] = responseLoc[i].Vehicle.toString()
Log.d("apiii", data[i].toString())
}
}
override fun onFailure(call: Call<ResponseLoc?>, t: Throwable) {
Log.d("apii", t.message.toString())
}
})
}
JSON values
{"Vehicle":[{"VehicleNo":"Test","Imei":"354019","Location":"Tamil Nadu-India","Date":"2021-03-17 19:27:12.000","Tempr":"0","Ignition":"","Lat":"13.11","Long":"80.282","Speed":"0","Angle":"0"}]}
I have tried many stacks none of them helped
Is there any other ways available except Retrofit ???
anybody please help me to get the api results
I have tried many stacks none of them helped
Is there any other ways available except Retrofit ???
anybody please help me to get the api results

How to test api requests and fill classes with fake data?

I can not find a solution to this problem on the Internet. Or is my code so bad?
Interface
interface GetWeatherService {
#GET("/data/2.5/forecast?")
fun getAllWeather(#Query("q")cityName: String, #Query("APPID")app_id: String, #Query("units")units: String="imperial"): Call<ListWeather>
#GET("/data/2.5/weather?")
fun getCurrentWeather(#Query("q")cityName: String, #Query("APPID")app_id: String, #Query("units")units: String="imperial"): Call<MainWeather>
#GET("/data/2.5/weather?")
fun getWeatherDataFromLocation(#Query("lat")lat: String, #Query("lon")lon: String, #Query("APPID") app_id: String): Call<DataFromLocation>
}
Client
object RetrofitClientInstance {
private var retrofit: Retrofit? = null
private var BASE_URL = "https://api.openweathermap.org/"
val retrofitInstance: Retrofit?
get(){
if(retrofit == null){
retrofit = retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit
}
}
MainActivity()
class MainActivity: AppCompatActivity {
fun getDataFromApi(cityName: String) {
val service = RetrofitClientInstance.retrofitInstance!!.create(GetWeatherService::class.java)
// Service 1
service.getCurrentWeather(cityName, APP_ID).enqueue(object: Callback<MainWeather>{
override fun onFailure(call: Call<MainWeather>, t: Throwable) {
}
override fun onResponse(call: Call<MainWeather>, response: Response<MainWeather>) {
val weather = response.body()
val currentWeather = CurrentWeather(
weather!!.main.temp,
weather.weather[0].description,
weather.name,
weather.weather[0].main
)
updateCurrentWeatherUI(currentWeather)
}
})
service.getAllWeather(cityName, APP_ID).enqueue(object: Callback<ListWeather>{
override fun onFailure(call: Call<ListWeather>, t: Throwable) {
}
override fun onResponse(call: Call<ListWeather>, response: Response<ListWeather>) {
val weather = response.body()!!.list
for(item in weather){
val weatherList = NextFiveDayWeather(
item.dt,
item.main.temp,
item.weather[0].description
)
weatherArray.add(weatherList)
updateUI(weatherArray)
}
}
})
}
}
Data class
data class MainWeather (
var dt: Long,
var main: MainDTO,
var weather: List<WeatherDTO>,
var dt_txt: String,
var name: String
)
data class WeatherDTO (var main: String, var description: String, var icon: String)
data class ListWeather (#SerializedName("list") var list: List<MainWeather>)
I can’t test the request to api and I can’t fill the classes with fake data?
Tell me please, what should I do?
What should I study? And if you are not difficult can give advice for the future?
You can download the software Postman to test api endpoints: https://www.getpostman.com/

GSON is not mapping values with data class in kotlin

Hi I am using gson library to map values of response to model. I am doing it like this but it is not mapping values of response to model. I am getting list of 50 models but values in it is zero.
#Provides
#Singleton
fun provideRestApiHelper(
okHttpClient: OkHttpClient,
gson: Gson,
rxJava2CallAdapterFactory: RxJava2CallAdapterFactory): RestApi {
val builder = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addCallAdapterFactory(rxJava2CallAdapterFactory)
.addConverterFactory(GsonConverterFactory.create(gson))
val retrofit = builder.client(okHttpClient).build()
return retrofit.create(RestApi::class.java)
}
RestApi.kt
interface RestApi {
#GET(ApiEndPoint.ENDPOINT_GITHUB_JOBS)
fun getJobsApiCall(): Observable<List<JobsResponse>>
}
ApiHelperImpl.kt
class ApiHelperImpl #Inject constructor(private val restApi: RestApi) : ApiHelper {
override fun getJobsApiCall(): Observable<List<JobsResponse>> {
return restApi.getJobsApiCall()
}
}
JobsResponse.kt
data class JobsResponse(
#field:SerializedName("company_logo")
val companyLogo: String?,
#field:SerializedName("how_to_apply")
val howToApply: String?,
#field:SerializedName("created_at")
val createdAt: String?,
#field:SerializedName("description")
val description: String?,
#field:SerializedName("location")
val location: String?,
#field:SerializedName("company")
val company: String?,
#field:SerializedName("company_url")
val companyUrl: String?,
#field:SerializedName("id")
val id: String?,
#field:SerializedName("title")
val title: String?,
#field:SerializedName("type")
val type: String?,
#field:SerializedName("url")
val url: String?
) : BaseResponse()
I am calling this API https://jobs.github.com/positions.json. Does anyone know what could be the issue ?
That's because you rely on auto-converted java code
remove #field:SerializedName changed it to #SerializedName
don't put them in to primary constructor
define them like this:
data class JobsResponse(){
#SerializedName("company_logo")
val companyLogo: String? = null
....
}

Categories

Resources