I would like to get a simple string from an API.
Usually, i can get everything I want from an API with the following functions :
class OrderRepositoryImpl(val orderService: OrderService) : OrderRepository {
override fun getPaymentMethods(id: String, success: (List<PaymentMode>) -> Unit, failure: (Throwable) -> Unit): Subscription {
return orderService.getPaymentMethods(id)
.subscribeOn(Schedulers.io())
.map { it.entrySet() }
.map { it.map { it.value }.map {it.asJsonObject } }
.map { it.map { PaymentMode().apply { loadFromJson(it) } } }
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ success.invoke(it) }, { failure.invoke(it) })
}
}
And in OrderService :
#GET("api/order/payment/modes/list/{id}")
fun getPaymentMethods(#Path("id") id: String): Observable<JsonObject>
This works perfectly, on an API with regular Json Objects.
But today, I have a problem : I have an API with a unique string, like this :
"validated"
or :
"draft"
So I made the followin function (in OrderRepositoryImpl class) :
override fun getOrderStatus(id: String, success: (String) -> Unit, failure: (Throwable) -> Unit) =
orderService.getOrderStatus(id)
.subscribeOn(Schedulers.io())
.map { it }
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ success.invoke(it.toString()) }, { failure.invoke(it) } )
And in Order Service :
#GET("api/order/checkout/{id}/status")
fun getOrderStatus(#Path("id") id: String): Observable<JsonObject>
I call this method like this :
fun getOrderStatus(id : Long) {
orderRepository.getOrderStatus(id.toString(), {
println("SUCCESS !")
println("STATUS == $it")
}, {
println("FAILURE...")
})
}
But I don't get anything from "success.invoke" line. When I call this method in my code, i always have "FAILURE" in my logs... Even if one of the logs lines is :
D/OkHttp: "validated"
which is exactly what I want to see in case of success.
I know it is pretty strange I can get and parse json objects and not a string, but I kind of learned on the job...
How can I get a simple string from an API ?
Okay, I am SO ashamed.
So, I printed the error in my "FAILURE..." section, and I got something like :
got jsonPrimitive but expected jsonObject
As the API was just returning me a string, and NOT an object, it is call a Json Primitive.
So I just changed the return of my functions in OrderService :
#GET("api/order/checkout/{id}/status")
fun getOrderStatus(#Path("id") id: String): Observable<JsonPrimitive>
Thanks for your helpful comments.
interface ServiceInterFace {
#POST("api/order/checkout/{id}/status")
fun getOrderStatus(#Path("id") id: String): Call<String>
}
// calling from your main class
val id: String? = null
val retrofit = Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.baseUrl("----Your link here-----")
.build()
val scalarService = retrofit.create(ServiceInterFace::class.java!!)
val stringCall = scalarService.getOrderStatus(id)
stringCall.enqueue(object : Callback<String> {
override fun onResponse(call: Call<String>, response: Response<String>) {
if (response.isSuccessful) {
val responseString = response.body()//get response here
}
}
override fun onFailure(call: Call<String>, t: Throwable) {
Toast.makeText(this#Main2Activity, "Failed to connect server",
Toast.LENGTH_SHORT).show()
}
})
//import
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'
Related
I've got the root of the problem of this implmenetation:
How to delete with Body using retrofit with usecase?
In brief:
With this usecase method like underneath I return Unit.
My logs didn't show it directly, but in debugger I have seen that the problem is because of
Service methods cannot return void.
for method NotificationApi.deleteToken
if (e.isSameExceptionAs(fromDownstream) || e.isCancellationCause(coroutineContext)) {
How to change this method deleteToken to work with delete and not return void/unit?
How to provide it with usecase properly?
Thanks.
This is my UseCase
class DeleteTokenSecondUseCase #Inject constructor(private val api: NotificationApi) :
BaseApiRequestUseCase<DeleteTokenSecondUseCase.Params, Unit>() {
override fun create(params: Params): Flow<Unit> = flow {
api.deleteToken(NotificationApi.TokenChangedBody(params.token))
}
data class Params(val token: String)
}
this is my delete
#HTTP(method = "DELETE", path = "account/firebase", hasBody = true)
fun deleteToken(#Body body: TokenChangedBody)
data class TokenChangedBody(val token: String)
fun method in vm
fun notifyNotificationTokenChanged(token: String) {
val params = DeleteTokenSecondUseCase.Params(token)
deleteTokenSecondUseCase.buildWithState(params)
.withSuccess { Log.d("build", "WORKS $params") }
.withError { Log.d("build", "NOT WORKS $params") }
.launchIn(viewModelScope)
}
EDIT:
I have implmeneted it with Call event but still it goes onFailure, guys why?
#HTTP(method = "DELETE", path = "account/firebase", hasBody = true)
fun deleteToken(#Body body: TokenChangedBody) : Call<ResponseBody>
fun notifyNotificationTokenChanged(token: String) {
val params = NotificationApi.TokenChangedBody(token)
val deleteRequest: Call<ResponseBody> = notificationApi.deleteToken(params)
deleteRequest.enqueue(object : Callback<ResponseBody?> {
override fun onResponse(call: Call<ResponseBody?>?, response: Response<ResponseBody?>?) {
Log.d("apitoken", "WORKS")
}
override fun onFailure(call: Call<ResponseBody?>?, t: Throwable?) {
Log.d("apitoken", "DOESNT WORK")
}
})
}
EDIT2:
WORKS!
SOLUTION
fun notifyNotificationTokenChanged(token: String) {
val params = NotificationApi.TokenChangedBody(token)
val deleteRequest: Call<Void> = notificationApi.deleteToken(params)
deleteRequest.enqueue(object : Callback<Void?> {
override fun onResponse(call: Call<Void?>?, response: Response<Void?>?) {
}
override fun onFailure(call: Call<Void?>?, t: Throwable?) {
}
})
}
#HTTP(method = "DELETE", path = "account/firebase", hasBody = true)
fun deleteToken(#Body body: TokenChangedBody) : Call<Void>
Someone told in Kotlin use :Completable, but haven't test it.
stackoverflow.com/questions/35429481/retrofit-2-void-return
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.
I found some Api about Pokemon(https://pokeapi.co). And I try get data from this Api and it's work.
The data look like this
But I try to use name of data from Api to get some image with this path "api/v2/pokemon/{name}"
The problem is how can get name out of onResponse or do the other way to get name and image of Pokemon
MainActivity
val retrofit = Retrofit.Builder()
.baseUrl("https://pokeapi.co/")
.addConverterFactory(GsonConverterFactory.create())
.client(HTTPLogger.getLogger())
.build()
val jsonPlaceholderApi = retrofit.create(pokemonService::class.java)
val myCall: Call<PokemonInGen> = jsonPlaceholderApi.getGen(1)
myCall.enqueue(object : Callback<PokemonInGen> {
override fun onResponse(
call: Call<PokemonInGen>,
response: Response<PokemonInGen>
) {
val DataResponse: PokemonInGen = response.body()!!
Timber.i("on do Respon %s", DataResponse)
}
override fun onFailure(call: Call<PokemonInGen>, t: Throwable) {
Timber.i("on do ERROR")
}
})
My Service
interface pokemonService {
#GET("api/v2/generation/{id}")
fun getGen(
#Path("id") id: Int,
): Call<PokemonInGen>
#GET("api/v2/pokemon/{name}")
fun getArtwork(
#Path("name") name: String,
): Call<PokemonArtwork>
}
My Model Data class
data class PokemonInGen(
val pokemon_species: List<PokemonList>)
data class PokemonList(
val name: String,
val url: String,
)
To fetch Pokemon image you should create additional function in your MainActivity class
fun fetchPokemonArtwork(name: String) {
jsonPlaceholderApi.getArtwork(name).enqueue(object : Callback<PokemonArtwork> {
override fun onResponse(
call: Call<PokemonArtwork>,
response: Response<PokemonArtwork>
) {
// An artwork is successful fetched
val artwork = response.body()!!
}
override fun onFailure(call: Call<PokemonArtwork>, t: Throwable) {
// Handle a failure
}
})
}
You should call this function right after you fetched a Pokemon in generation
val myCall: Call<PokemonInGen> = jsonPlaceholderApi.getGen(1)
myCall.enqueue(object : Callback<PokemonInGen> {
override fun onResponse(
call: Call<PokemonInGen>,
response: Response<PokemonInGen>
) {
val DataResponse: PokemonInGen = response.body()!!
Timber.i("on do Respon %s", DataResponse)
// Extract name
val name = DataResponse.pokemon_species.first().name
// Fetch an artwork
fetchPokemonArtwork(name)
}
override fun onFailure(call: Call<PokemonInGen>, t: Throwable) {
Timber.i("on do ERROR")
}
})
P.S. I proceeded from the assumption that you've implemented PokemonArtwork class. Please let me know if you are facing difficulties in comments below.
P.S.S. It's not recommended to make network calls in Activity or Fragment classes. This guide to app architecture should help you to select correct app structure in your future releases.
You can using #Url to support dynamic link in retrofit. Example below:
interface pokemonService {
#GET
Call<PokemonResponse> getListPokemon(#Url String url);
}
And paste your url to browser to see data format.
I was wondering what is the best way to handle network errors in retrofit requests when using coroutines.
The classic way is handling exception at highest level, when a request is made:
try {
// retrofit request
} catch(e: NetworkException) {
// show some error message
}
I find this solution wrong and it adds a lot of boilerplate code, instead I went with creating an interceptor that returns a error response:
class ErrorResponse : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
return try {
chain.proceed(request)
} catch (e: Exception) {
Snackbar.make(
view,
context.resources.getText(R.string.network_error),
Snackbar.LENGTH_LONG
).show()
Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(599)
.message(e.message!!)
.body(ResponseBody.create(null, e.message!!))
.build()
}
}
}
This solution is a little better, however I think that it can be improved.
So my question is: What is the correct way to handle the cases when user doesn't have internet connection, without a lot of boilerplate code (ideally with a global handler in case of connection errors) ?
Using Result to wrap my response
sealed class Result<out T : Any> {
data class Success<out T : Any>(val value: T) : Result<T>()
data class Failure(val errorHolder:ErrorHolder) : Result<Nothing>()}
ErrorHolder :
sealed class ErrorHolder(override val message):Throwable(message){
data class NetworkConnection(override val message: String) : ErrorHolder(message)
data class BadRequest(override val message: String) : ErrorHolder(message)
data class UnAuthorized(override val message: String) : ErrorHolder(message)
data class InternalServerError(override val message: String) :ErrorHolder(message)
data class ResourceNotFound(override val message: String) : ErrorHolder(message)
}
an extension to handle exeptions
suspend fun <T, R> Call<T>.awaitResult(map: (T) -> R): Result<R> = suspendCancellableCoroutine { continuation ->
try {
enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, throwable: Throwable) {
errorHappened(throwable)
}
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
try {
continuation.resume(Result.Success(map(response.body()!!)))
} catch (throwable: Throwable) {
errorHappened(throwable)
}
} else {
errorHappened(HttpException(response))
}
}
private fun errorHappened(throwable: Throwable) {
continuation.resume(Result.Failure(asNetworkException(throwable)))
}
})
} catch (throwable: Throwable) {
continuation.resume(Result.Failure(asNetworkException(throwable)))
}
continuation.invokeOnCancellation {
cancel()
}}
And this how I make the api call:
suspend fun fetchUsers(): Result<List<User>> {
return service.getUsers().awaitResult { usersResponseDto ->
usersResponseDto.toListOfUsers()
}
}
UPDATE:
Let's say you have an error body like below:
{
"error" : {
"status" : 502,
"message" : "Bad gateway."
}
}
First we should create an data class to model response body
data class HttpErrorEntity(
#SerializedName("message") val errorMessage: String,
#SerializedName("status") val errorCode: Int
)
and here is asNetworkException implementation :
private fun asNetworkException(ex: Throwable): ErrorHolder {
return when (ex) {
is IOException -> {
ErrorHolder.NetworkConnection(
"No Internet Connection"
)
}
is HttpException -> extractHttpExceptions(ex)
else -> ErrorHolder.UnExpected("Something went wrong...")
}
}
private fun extractHttpExceptions(ex: HttpException): ErrorHolder {
val body = ex.response()?.errorBody()
val gson = GsonBuilder().create()
val responseBody= gson.fromJson(body.toString(), JsonObject::class.java)
val errorEntity = gson.fromJson(responseBody, HttpErrorEntity::class.java)
return when (errorEntity.errorCode) {
ErrorCodes.BAD_REQUEST.code ->
ErrorHolder.BadRequest(errorEntity.errorMessage)
ErrorCodes.INTERNAL_SERVER.code ->
ErrorHolder.InternalServerError(errorEntity.errorMessage)
ErrorCodes.UNAUTHORIZED.code ->
ErrorHolder.UnAuthorized(errorEntity.errorMessage)
ErrorCodes.NOT_FOUND.code ->
ErrorHolder.ResourceNotFound(errorEntity.errorMessage)
else ->
ErrorHolder.Unknown(errorEntity.errorMessage)
}
}
By implementing Interceptor, you are in right way. But by a little change, you can this sample class:
class NetworkConnectionInterceptor(val context: Context) : Interceptor {
#Suppress("DEPRECATION")
private val isConnected: Boolean
get() {
var result = false
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
cm?.run {
cm.getNetworkCapabilities(cm.activeNetwork)?.run {
result = when {
hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
else -> false
}
}
}
} else {
cm?.run {
cm.activeNetworkInfo?.run {
if (type == ConnectivityManager.TYPE_WIFI) {
result = true
} else if (type == ConnectivityManager.TYPE_MOBILE) {
result = true
}
}
}
}
return result
}
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
if (!isConnected) {
// Throwing your custom exception
// And handle it on onFailure
}
val builder = chain.request().newBuilder()
return chain.proceed(builder.build())
}
}
Then add it to your OkHttpClient.Builder():
.addInterceptor(NetworkConnectionInterceptor(context));
And in failure you can handle it in onFailure method like this:
override fun onFailure(call: Call<BaseModel>, t: Throwable) {
if (t is NoConnectivityException) {
// Handle it here :)
}
}
I have a call to my service with retrofit in which I get an answer and I need to send it to another class.
I have tried to save the response data in a ContentValues and send them by means of a function but this does not work.
fun dataEmployee(name: String, numEmp: String): ConsultMovResponse? {
var cMov = PersonData(name, numEmp)
var pos: ConsultMovResponse?
RetrofitClient.instMov.consultMov(cMov).enqueue(object : Callback<ConsultMovResponse> {
override fun onResponse(call: Call<ConsultMovResponse>, response: Response<ConsultMovResponse>) {
pos = response?.body()
//return response, this code does not work.
return pos?
}
override fun onFailure(call: Call<ConsultMovResponse>, t: Throwable) {
println("Error : " + t.stackTrace.toString())
println("Error : " + t.message)
}
})
return pos?
}
The way you're using Retrofit, it'll execute the request asynchronously. This means that before it has a chance to finish the request, the function dataEmployee will return an uninitialised pos.
There are different ways to go about this, but an easy one is to propagate the callback. Say you define the function as:
fun dataEmployee(name: String, numEmp: String, callback: (ConsultMovResponse?) -> Unit)
The last argument is a function that should be called when onResponse is called. Something like:
override fun onResponse(call: Call<ConsultMovResponse>, response: Response<ConsultMovResponse>) {
callback(response?.body())
}
The way you can call now the method would be:
dataEmployee("Foo", "1234") {
// Use the implicit parameter `it` which will be the response
}
Edit
For the error you can follow a similar process. Let's change dataEmployee to:
fun dataEmployee(name: String, numEmp: String, onSuccess (ConsultMovResponse?) -> Unit, onFailure: (Throwable) -> Unit)
On failure you can then call:
override fun onFailure(call: Call<ConsultMovResponse>, throwable: Throwable) {
onFailure(throwable)
}
Now you call dataEmployee like so:
dataEmployee("foo", "1234",
onSuccess = { /*handle success*/ },
onFailure = { /*`it` will be the error */ })