how to get response headers using Retrofit in Kotlin? - android

Hello I am working on async using retrofit and rxjava2
and I have to get the value from the header while talking to the server developer.
However, I don't know how to get the header from the method I use. I know how to get it from Call Response, but I don't know how to bring the header because the method used is different.
my retrofit2 class
private val retrofit: Retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(client())
.baseUrl(serverIp)
.build()
val userApi: UserAPI = retrofit.create(UserAPI::class.java)
my model class
#POST("user")
fun login(
#Body loginRequest : LoginRequest
) : Single<UserResponse>
data class LoginRequest(
val phone: String?,
#SerializedName("gender")
val gender: String?,
#SerializedName("age")
val age: String?,
#SerializedName("email")
val email: String?
)
data class UserResponse (
override var status: Int,
override var message: String,
override var timestamp: String,
var data: Data
) : CommonResponse() {
data class Data(
var username: String?,
var token: String?
)
}
my viewModel ( rx )
addDisposable(
model.loginBody(loginRequest)
.subscribeOn(Schedulers.io())
.subscribe({
_loginResult.postValue(it)
}, {
Timber.d("response error, message : ${it.localizedMessage}")
})
)
My current situation is as follows. I need the headers returned by the server after login,
I can see it from the okhttp log, but I don't know how to get a specific header

Word of advice rather than a solution
Not a question of solving your problem but even if it's late, as you're
using Kotlin, a better solution would be to migrate from rxJava to Kotlin Flow. It allows you to use suspend functions to do your retrofit calls, then use Kotlin Flow to do the rxJava's job on the IO thread.
It also allows you to use the Response<T> retrofit object in a simpler way.
Example:
The retrofit request
#POST(YOUR_ROAD) // Retrofit road
suspend fun connect(
#Body credentials: CredentialsObjectBody
): Response<ConnectionObjectResponse>
The repository call to the retrofit request
// The function is suspending the thread with suspend keyword
suspend fun yourFunction(): Flow<DataState<TypeForEmitter>> = flow {
try {
body.clientId = sessionPrefs.deviceName!!
val connectionResponse =
majorApi.connect(
// what you need to put in the body
)
.apply {
if (isSuccessful) {
body()?.let {
// Here you can use what's in the request body
emit(DataState.Success(it))
} ?: throw Exception("Request body was null")
} else {
throw Exception(headers()["message"]) // use throw to handle the error in the catch
}
}
} catch (e: Exception) {
Log.e(TAG, "error: ${e.message}")
emit(DataState.Error(e))
}
}
DataState:
DataState is a sealed class that allows to differentiate emitted status
sealed class DataState<out R> {
data class Success<out T>(val data: T) : DataState<T>()
data class Error(val exception: Exception) : DataState<Nothing>()
object Loading : DataState<Nothing>()
}
How to call the Kotlin flow to launch it on IO thread to prevent blocking the Main (or UI) thread
withContext(Dispatchers.IO) {
yourFunction().onEach {
/*
onEach is like onNext, but emits every type of error, compared to rxJava
that differentiates next/success, complete and error events
*/
}
}

To retreive response headers and other usefull information you can use the Response type from retrofit2 package. To use this change the return type of your login method to Single<retrofit2.Response<UserResponse>>
#POST("user")
fun login( #Body loginRequest : LoginRequest): Single<retrofit2.Response<UserResponse>>
Now to retrieve headers in your ViewModel
addDisposable(
model.loginBody(loginRequest)
.subscribeOn(Schedulers.io())
.subscribe({
val headers = it.headers() // do something with headers
val data = it.body()
_loginResult.postValue(data)
}, {
Timber.d("response error, message : ${it.localizedMessage}")
})
)

Related

How to properly use Retrofit to call a JSON API in Kotlin

I've tried following several tutorials on calling an API and receiving a JSON response in Kotlin, and this is what I have so far:
interface APIService {
#GET("cursor/popular/10")
fun listRepos(): Call<Any?>
// #Path("user") user: String?
}
fun getURL(url: String): Call<Any?> {
val retrofit = Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(APIService::class.java)
val data: Call<Any?> = service.listRepos()
Log.d("PRINT_JSON_HERE", "HERE: ${data}")
return data
}
When I call getURL("api_here") nothing happens, no error either.
Just wondering what I am doing wrong. I know it says the data class is "Any" but when I start getting a response I'll replace it with a proper data class.
This code is inside a class/ViewModel(). What am I doing wrong?
Api call returns a response and you need to implement onResponse and onFailure override method in your program after
val service = retrofit.create(APIService::class.java)
this put this code
service.enqueue(object : Callback<List<userItem>> {
override fun onResponse(
call : Call<List<userItem>>,
response: Response<List<userItem>>
) {
var data = response.body()
Log.d("data", data.toString)
}
override fun onFailure(call : Call<List<userItem>> , t : Throwable) {
"print toast if an error occurred"
}
)}
}
}
in this code userItem is a data class that can get data from the api.
I hope this can help you.
):

How to Parse Json in Kotlin Using Retrofit?

i am new to kotlin and i am in learning phase. I have followed many links but didn't able to understand completely.
I want Json response to show in my textview.
Problem: 1
I have tried this code but was unable to get data, but i want to get the items inside data object. Quote and author are coming null.
{
"status": 200,
"message": "Success",
"data": {
"Quote": "The pain you feel today will be the strength you feel tomorrow.",
"Author": ""
},
"time": "0.14 s"
}
Problem: 2
I dont know how to parse this response in textview
object ServiceBuilder {
private val client = OkHttpClient.Builder().build()
private val retrofit = Retrofit.Builder()
.baseUrl("https://url.com.pk/") // change this IP for testing by your actual machine IP
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
fun<T> buildService(service: Class<T>): T{
return retrofit.create(service)
}}
RestApi
interface RestApi{
#Headers("Content-Type: application/json")
#POST("api/getquotes")
abstract fun addUser(#Body userData: UserInfo): Call<UserInfo>}
RestAPiService
class RestApiService
{
fun addUser(userData: UserInfo, onResult: (UserInfo?) -> Unit)
{
val retrofit = ServiceBuilder.buildService(RestApi::class.java)
retrofit.addUser(userData).enqueue(
object : Callback<UserInfo>
{
override fun onFailure(call: Call<UserInfo>, t: Throwable)
{
onResult(null)
}
override fun onResponse( call: Call<UserInfo>, response: Response<UserInfo>)
{
val addedUser = response.body()
Log.d("responsee",""+addedUser)
onResult(addedUser)
}
}
)
}
}
UserInfo
data class UserInfo (
#SerializedName("Quote")
val quote : String,
#SerializedName("Author")
val author : String
)
MainActivity
fun getQuotes() {
val apiService = RestApiService()
val userInfo = UserInfo("","")
apiService.addUser(userInfo) {
Log.d("Error registering user","errter")
/*if ( != null)
{
// it = newly added user parsed as response
// it?.id = newly added user ID
} else {
Log.d("Error registering user","errter")
}*/
}
}
Any help would be appreciated :)
Status, message and data are all part of the response so you need to take care of that. For example this
data class AddUserResponse(
val `data`: UserInfo, //like you defined it
val message: String,
val status: Int,
val time: String
)
This means parameter and response are different so the RestApi needs to be changed to this
abstract fun addUser(#Body userData: UserInfo): Call<AddUserResponse>}
This in turn also change the types in the service like
class RestApiService
{
fun addUser(userData: UserInfo, onResult: (UserInfo?) -> Unit)
{
val retrofit = ServiceBuilder.buildService(RestApi::class.java)
retrofit.addUser(userData).enqueue(
object : Callback<AddUserResponse>
{
override fun onFailure(call: Call<AddUserResponse>, t: Throwable)
{
onResult(null)
}
override fun onResponse( call: Call<AddUserResponse>, response: Response<AddUserResponse>)
{
val addedUser = response.body()
Log.d("responsee",""+addedUser)
onResult(addedUser.data)
}
}
)
}
}
now in getQuotes you will have that it is a UserInfo object
apiService.addUser(userInfo) {
val returnedUserInfo = it
}
just follow my steps :
File->settings->Plugins
search for JSON To Kotlin class and install it
again click on File->New->Kotlin Data class from JSON
paste your json code here and click on generate. It will generate POJO classes and you will good to go.
The first thing I noticed, is that the data in your json is:
"Quote": "The pain you feel today will be the strength you feel tomorrow.",
"Author": ""
While your UserInfo defined #SerializedName("message") for Quote.

Android Retrofit2: Serialize null without GSON or JSONObject

The result of API call in my Android app can be a JSON with configuration which is mapped to SupportConfigurationJson class, or just pure null. When I get a JSON, the app works properly, but when I get null, I get this exception:
kotlinx.serialization.json.internal.JsonDecodingException: Expected start of the object '{', but had 'EOF' instead
JSON input: null
I should avoid using GSON in this project. I also found a solution, where API interface will return Response<JSONObject>, and after that my repository should check if this JSONObject is null and map it to SupportConfigurationJson if not. But in the project we always used responses with custom classes so I wonder, is there any other solution to get response with null or custom data class?
GettSupportConfiguration usecase class:
class GetSupportConfiguration #Inject constructor(
private val supportConfigurationRepository: SupportConfigurationRepository
) {
suspend operator fun invoke(): Result<SupportConfiguration?> {
return try {
success(supportConfigurationRepository.getSupportConfiguration())
} catch (e: Exception) {
/*
THIS SOLUTION WORKED, BUT I DON'T THINK IT IS THE BEST WAY TO SOLVE THE PROBLEM
if (e.message?.contains("JSON input: null") == true) {
success(null)
} else {
failure(e)
}
*/
//I WAS USING THROW HERE TO SEE WHY THE APP ISN'T WORKING PROPERLY
//throw(e)
failure(e)
}
}
}
SupportConfigurationJson class:
#Serializable
data class SupportConfigurationJson(
#SerialName("image_url")
val imageUrl: String,
#SerialName("description")
val description: String,
#SerialName("phone_number")
val phoneNumber: String?,
#SerialName("email")
val email: String?
)
SupportConfigurationRepository class:
#Singleton
class SupportConfigurationRepository #Inject constructor(
private val api: SupportConfigurationApi,
private val jsonMapper: SupportConfigurationJsonMapper
) {
suspend fun getSupportConfiguration(): SupportConfiguration? =
mapJsonToSupportConfiguration(api.getSupportConfiguration().extractOrThrow())
private suspend fun mapJsonToSupportConfiguration(
supportConfiguration: SupportConfigurationJson?
) = withContext(Dispatchers.Default) {
jsonMapper.mapToSupportSettings(supportConfiguration)
}
}
fun <T> Response<T?>.extractOrThrow(): T? {
val body = body()
return if (isSuccessful) body else throw error()
}
fun <T> Response<T>.error(): Throwable {
val statusCode = HttpStatusCode.from(code())
val errorBody = errorBody()?.string()
val cause = RuntimeException(errorBody ?: "Unknown error.")
return when {
statusCode.isClientError -> ClientError(statusCode, errorBody, cause)
statusCode.isServerError -> ServerError(statusCode, errorBody, cause)
else -> ResponseError(statusCode, errorBody, cause)
}
}
SupportConfigurationApi class:
interface SupportConfigurationApi {
#GET("/mobile_api/v1/support/configuration")
suspend fun getSupportConfiguration(): Response<SupportConfigurationJson?>
}
SupportConfigurationJsonMapper class:
class SupportConfigurationJsonMapper #Inject constructor() {
fun mapToSupportSettings(json: SupportConfigurationJson?): SupportConfiguration? {
return if (json != null) {
SupportConfiguration(
email = json.email,
phoneNumber = json.phoneNumber,
description = json.description,
imageUrl = Uri.parse(json.imageUrl)
)
} else null
}
}
I create Retrofit like this:
#Provides
#AuthorizedRetrofit
fun provideAuthorizedRetrofit(
#AuthorizedClient client: OkHttpClient,
#BaseUrl baseUrl: String,
converterFactory: Converter.Factory
): Retrofit {
return Retrofit.Builder()
.client(client)
.baseUrl(baseUrl)
.addConverterFactory(converterFactory)
.build()
}
#Provides
#ExperimentalSerializationApi
fun provideConverterFactory(json: Json): Converter.Factory {
val mediaType = "application/json".toMediaType()
return json.asConverterFactory(mediaType)
}
Everything is explained here (1min read)
Api is supposed to return "{}" for null, If you can't change API add this converter to Retrofit
You are interacting with your repository directly, i will suggest to use
usecases
to interact with data layer.
Because you are not catching this exception over here, your app is crashing
suspend fun getSupportConfiguration(): SupportConfiguration? =
mapJsonToSupportConfiguration(api.getSupportConfiguration().extractOrThrow())
Usecase usually catch these errors and show useful error msg at the ui.

Retrofit and Kotlin Post Request 400 error

I am trying to make a simple post request to googles dialogflow in retrofit using kotlin. I am modeling my code off of this site. However, I keep getting 400 errors when trying to make a search so there must be something wrong with my interface creating the message body I believe. I have working python code that does the same functionality as shown here:
url = "https://api.dialogflow.com/v1/query?v=20170712"
headers = {
'Authorization': 'Bearer ' + my_key ,
'Content-Type' : 'application/json'
}
body = {
'lang': 'en',
'query': 'id like to fix my wire c1000 stocks',
'sessionId': 'me'
}
resp = r.post(url,headers=headers,data=json.dumps(body))
I have set this up in android studio as 3 classes:
1) Message.kt
The body of the post request
object Message {
data class MsgBody(val lang: String, val query: String, val sesId: String)
}
2) Model.kt
The response from dialogflow
object Model {
data class Response(val resp: Result)
data class Result(val fulfillment: Fulfillment)
data class Fulfillment(val speech: String)
}
3) DialogFlowService.kt
The interface that has the post request enpoint
interface DialogFlowService {
#Headers(
"Authorization: Bearer {MY API KEY}",
"Content-Type: application/json"
)
#POST("query")
fun getAiMessage(#Body msg: Message.MsgBody,
#Query("v") v: String): Observable<Model1.Response>
companion object {
fun create(): DialogFlowService {
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://api.dialogflow.com/v1/")
.build()
return retrofit.create(DialogFlowService::class.java)
}
}
}
All of this is then used in my main activity as seen below:
class MainActivity : AppCompatActivity() {
private var disposable: Disposable? = null
private val dialogFlowService by lazy {
DialogFlowService.create()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
if (editText.text.toString().isNotEmpty()) {
sendMessage(editText.text.toString())
}
}
}
private fun sendMessage(msg: String){
disposable = dialogFlowService.getAiMessage(Message.MsgBody("en",msg,"me"),"20170712")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> textView.text = "DialogFlow says: ${result.resp.fulfillment.speech}" },
{ error -> Toast.makeText(this, error.message, Toast.LENGTH_SHORT).show() }
)
}
override fun onPause() {
super.onPause()
disposable?.dispose()
}
}
I tried to follow the tutorial as close as possible and am very confused as to what I did wrong. Like I said above I think this is related to my DialogFlowService.kt file. Thanks for any help in advance.
Error in post request since Model variable name sesId did not equal the actual key sessionId. As Raghunandan said a logging interceptor is very useful

Handle no network in rxjava with retrofit

I am developing an application which doing network request using retrofit2 and rxjava2. I am doing it using MVVM approach which is in my RestInterface the result of the request returned in Flowable and in my repository I convert the Flowable into livedata so I can make the activity observe it in my viewmodel. But by doing this I got confuse on how to handle if there is no network where I ussually handle this in the rxJava side but since it's in the repository I can't do much thing about it.
Here is the code for the rest :
#GET(NEWS_ARTICLE)
fun getArticlesFromSources(#Query("domains") source: String,
#Query("apiKey") apiKey: String = BuildConfig.NEWS_API_KEY):
Flowable<NewsResponse>
The code for repository
fun getArticleFromSources(source: String) : LiveData<NewsResponse>{
return LiveDataReactiveStreams.fromPublisher(newsRest.getArticlesFromSources(source)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()))
}
and in my viewmodel :
private var mTriggerFetchData = MutableLiveData<String>()
private val article: LiveData<NewsResponse> = Transformations.switchMap(mTriggerFetchData){
newsRepository.getArticleFromSources(it)
}
fun getArticle() = article
fun loadArticle(source: String?){
mTriggerFetchData.value = source
}
and I observe it on my Activity :
getViewModel().getArticle().observe(this, Observer {newsResponse ->
Log.v("test", newsResponse?.articles?.size.toString())
articleList?.clear()
newsResponse?.articles?.let { articleList?.addAll(it) }
articleAdapter.notifyDataSetChanged()
})
getViewModel().loadArticle(sourceUrl)
As you can see, I was thinking to handle it in the activity but I still got confused about it. any help would be much appreciated. thanks!
You can try add onErrorReturn() to the chain
fun getArticleFromSources(source: String) : LiveData<NewsResponse>{
return LiveDataReactiveStreams.fromPublisher(newsRest.getArticlesFromSources(source)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.onErrorReturn((e: Throwable) -> {
return e
})
}
Or, rather than exposing just the NewsResponse through your LiveData object you can wrap the object and error into a wrapper class that can hold the error.
You can do something like this:
LiveDataReactiveStreams.fromPublisher(newsRest.getArticlesFromSources(source)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io()
.map(Result::success)
.onErrorReturn(Result::error))
Where Result class that holds either the error or result is something like this
class Result<T>(val data: T?, val error: Throwable?) {
companion object {
fun <T> success(data: T): Result<T> {
return Result(data, null)
}
fun <T> error(error: Throwable): Result<T> {
return Result(null, error)
}
}
}
You can then check if there's an error from the network

Categories

Resources