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?
Related
I have post method where I am sending login request to server but first I want to save that response using shared preferences in my repository how can save retrofit response in shared preferences
below my interface class I have implemented login post request logic
interface MeloApi {
#Headers("Content-Type: application/json")
#POST("/login")
suspend fun makeLogin(#Body loginModel: LoginModel) : Response<LoginModel>
}
below my loginModel class
data class LoginModel(
val userName:String,
val password:String)
below LoginResponse.kt
data class LoginResponse(
#SerializedName("accessToken")
val accessToken: String,
#SerializedName("refreshToken")
val refreshToken: String,
#SerializedName("status")
val status: String,
#SerializedName("user")
val user: User
)
below my Interceptor
class HeaderInterceptor(
private val tokenManager: TokenManager
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
request = request.newBuilder()
.header("refreshToken", tokenManager.refreshToken.toString())
.header("accessToken", tokenManager.accessToken.toString())
.build()
return chain.proceed(request)
}
}
below my token manager
class TokenManager {
var accessToken: String? = null
var refreshToken: String? = null
}
below my appModule.kt
val apiModule = module {
single {
TokenManager()
}
single {
HeaderInterceptor(get())
}
single {
val httpInterceptor = HttpLoggingInterceptor()
httpInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
Retrofit.Builder()
.client(
OkHttpClient.Builder()
.addInterceptor(HeaderInterceptor(get()))
.addInterceptor(httpInterceptor).build()
)
// .addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.build()
.create(MeloApi::class.java)
}
}
below my LoginRepository
class LoginRepository(
private val meloApi: MeloApi
) {
suspend fun login(loginModel: LoginModel) {
GlobalScope.launch {
val response = meloApi.makeLogin(loginModel)
response.isSuccessful
val userName = response.body()?.userName
val password = response.body()?.password
loginRequest(userName, password)
}
}
private suspend fun loginRequest(userName: String?, password: String?){
}
}
If you want to store as object you have to use serialization/deserialization.
I used gson here.
val loginResponse = LoginResponse("AToken", "RToken", "200", User("Edgar"))
val objToString = Gson().toJson(loginResponse)
val sharedPref = context.getSharedPreferences("loginRes", MODE_PRIVATE)
sharedPref.edit().putString("response", objToString).apply()
val response = sharedPref.getString("response", "null")
val stringToObj = Gson().fromJson(response, LoginResponse::class.java)
You can use proto datastore instead of shared preferences.
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)
}
}
}
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.
I'm using retrofit for web requests and then moshi for JSON parsing,this is api
#POST("/verify_code/send_email")
suspend fun sendEmail(#Body sendEmailRequest: SendEmailRequest): BaseResponse<Unit>
the BaseResponse
#JsonClass(generateAdapter = true)
open class BaseResponse<T> {
#Json(name = "code")
var code: Int? = null
#Json(name = "message")
var message: String? = null
#Json(name = "data")
var data: T? = null
}
JSON String
{
"code": 200,
"message": "Some Message",
"data": null
}
and error log
2021-11-26 09:59:24.166 14288-14288/com.gow E/FlowKtxKt$next: java.lang.IllegalArgumentException: Unable to create converter for com.gow.base.BaseResponse<kotlin.Unit>
for method VerifyApi.sendEmail
I tried adding the following, but it didn't work
object UnitConverterFactory : Converter.Factory() {
override fun responseBodyConverter(
type: Type, annotations: Array<out Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, *>? {
return if (type == Unit::class.java) UnitConverter else null
}
private object UnitConverter : Converter<ResponseBody, Unit> {
override fun convert(value: ResponseBody) {
value.close()
}
}
}
it`s the Moshi bug.
I solved my problem by using Any instead of Unit.
like this:
#POST("/verify_code/send_email")
suspend fun sendEmail(#Body sendEmailRequest: SendEmailRequest): BaseResponse<Any>
I had the same issue.
I fixed it by following the comment on this link.
I do not see how you are adding your UnitConverterFactory but in my case, the order you add it is important.
I am also using MoshiConverterFactory and ApiResultConverterFactory from EitherNet, so my UnitConverterFactory had to be placed after ApiResultConverterFactory and before MoshiConverterFactory:
Retrofit.Builder()
...
.addCallAdapterFactory(ApiResultCallAdapterFactory)
//the order of the converters matters.
.addConverterFactory(ApiResultConverterFactory)
.addConverterFactory(UnitConverterFactory)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
I'm building an app for a company using MVVM & clean architecture so I've created 3 modules, the app module (presentation layer), the data module (data layer) & the domain module (domain/interactors layer). Now, in my data module, I'm using Retrofit and Gson to automatically convert the JSON I'm receiving from a login POST request to my kotlin data class named NetUserSession that you see below. The problem I'm having is that the logging interceptor prints the response with the data in it normally but the response.body() returns an empty NetUserSession object with null values which makes me think that the automatic conversion isn't happening for some reason. Can somebody please tell me what I'm doing wrong here?
KoinModules:
val domainModule = module {
single<LoginRepository> {LoginRepositoryImpl(get())}
single { LoginUseCase(get()) }
}
val presentationModule = module {
viewModel { LoginViewModel(get(),get()) }
}
val dataModule = module {
single { ApiServiceImpl().getApiService() }
single { LoginRepositoryImpl(get()) }
}
}
Api interface & retrofit:
interface ApiService {
#POST("Login")
fun getLoginResult(#Body netUser: NetUser) : Call<NetUserSession>
#GET("Books")
fun getBooks(#Header("Authorization") token:String) : Call<List<NetBook>>
}
class ApiServiceImpl {
fun getApiService(): ApiService {
val logging = HttpLoggingInterceptor()
logging.setLevel(HttpLoggingInterceptor.Level.BODY)
//TODO:SP Remove the interceptor code when done debugging
val client: OkHttpClient = OkHttpClient.Builder()
.addInterceptor(logging)
.build()
val retrofit = Retrofit.Builder().baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
// tell retrofit to implement the interface of our api
return retrofit.create(ApiService::class.java)
}
}
NetUserSession:
data class NetUserSession(
#SerializedName("expires_in")
val expires_in: Int,
#SerializedName("token_type")
val token_type: String,
#SerializedName("refresh_token")
val refresh_token: String,
#SerializedName("access_token")
val access_token: String
) {
fun toUserSession(): UserSession = UserSession(
expiresIn = expires_in,
tokenType = token_type,
refreshToken = refresh_token,
accessToken = access_token
)
}
UserSession in domain:
data class UserSession(
val expiresIn:Int,
val tokenType:String,
val refreshToken:String,
val accessToken:String
)
LoginRepositoryImpl where the error occurs:
class LoginRepositoryImpl(private val apiService: ApiService) : LoginRepository {
override suspend fun login(username:String,password:String): UserSession? = withContext(Dispatchers.IO){
val response = apiService.getLoginResult(NetUser(username,password)).awaitResponse()
println("THE RESPONSE WAS : ${response.body()}")
return#withContext if(response.isSuccessful) response.body()?.toUserSession() else null
}
}
LoggingInterceptor result after the 200-OK:
{"expires_in":3600,"token_type":"Bearer","refresh_token":"T1amGR21.IdKM.5ecbf91162691e15913582bf2662e0","access_token":"T1amGT21.Idup.298885bf38e99053dca3434eb59c6aa"}
Response.body() print result:
THE RESPONSE WAS : NetUserSession(expires_in=0, token_type=null, refresh_token=null, access_token=null)
Any ideas what I'm failing to see here?
After busting my head for hours, the solution was to simply change the model class's members from val to var like so :
data class NetUserSession(
#SerializedName("expires_in")
var expires_in: Int = 0,
#SerializedName("token_type")
var token_type: String? = null,
#SerializedName("refresh_token")
var refresh_token: String? = null,
#SerializedName("access_token")
var access_token: String? = null
) {
fun toUserSession(): UserSession = UserSession(
expiresIn = expires_in,
tokenType = token_type!!,
refreshToken = refresh_token!!,
accessToken = access_token!!
)
}