Getting Internal Server Error 500 on Graphql Mutation - android

viewModelScope.launch {
val req = RequestCodeMutation(phoneNumber)
val response = try {
ApolloClientManager
.apolloClient
.suspendMutate(req)
}
catch (e: ApolloException) {
println(e.message)
isError.value = true
null
}
finally {
loading.value = false
}
val requestCode = response?.data?.requestCode
println(response?.errors)
}
suspend fun <D : Operation.Data, T, V : Operation.Variables> ApolloClient.suspendMutate(mutation: Mutation<D, T, V>): Response<T> =
mutate(mutation).toDeferred().await()
This is my validator on server side. It is shown correctly on Graphiql, however, I am unable to receive this message on client side.
requestCode = async (resolve, source, args, context, info) => {
let { phoneNumber } = args;
phoneNumber = validator.trim(phoneNumber);
Object.assign(args, { phoneNumber });
if (!validator.isMobilePhone(phoneNumber)) {
throw new UserInputError('Invalid phone number provided!');
}
return resolve(source, args, context, info);
}
ApolloException.message is showing Internal Server Error and response?.errors is null.
response?.errors is not supposed to be null and show the proper error message that is being shown on GraphiQL.

In your catch block, you can build a new Response, based on the ApolloCall.
So consider the following:
import com.apollographql.apollo.api.Response
import com.apollographql.apollo.api.Error
fun <T> executeCall(call: ApolloCall<T>): Response<T> = try {
apolloCall.toDeferred().await()
} catch (apolloException: ApolloException) {
val error = Error(apolloException.message ?: "Unknown Error")
Response.builder(apolloCall.operation()).errors(listOf(error)).build()
}
Note: I assume that ApolloClientManager.apolloClient.suspendMutate(req) returns an instance of ApolloCall<T>.
You can then use this function like:
val call = ApolloClientManager.apolloClient.suspendMutate(RequestCodeMutation(phoneNumber))
val response = executeCall(call)
// check the response status code

Related

return value from volley response

Before in other random languages I always returned values from functions and I was so surprised now when I try do like below but got error:
fun getChannels(): List<TblChannel> {
val stringRequest = JsonObjectRequest(
Request.Method.GET, "$baseUrl/api/json/channel_list.json",
null,
{ response ->
try{
val gson = Gson()
val token = TypeToken.getParameterized(ArrayList::class.java,TblChannel::class.java).type
val channels1:JSONArray = response.getJSONArray("groups").getJSONObject(0).getJSONArray("channels")
//got "return isn't allowed here" error
return gson.fromJson(channels1.toString(),token)
} catch (e:Exception){
Log.e(tag,"DkPrintError on getChannels: $e")
}
},
{ error ->
Log.e(tag, "DkPrintError on getChannels: $error")
})
requestQueue.add(stringRequest)
}
How can I convert response body to my class and return them?
This isn't really a kotlin problem, we do have functions that return values, however you cannot return a value from asynch function (which is the case here):
If you perform some calculation asynchronously, you cannot directly return the value, since you don't know if the calculation is finished yet. You could wait it to be finished, but that would make the function synchronous again. Instead, you should work with callbacks
source
what you could do tho (as suggested in the quote), is use callbacks, as shown here
That post will be so helpfull to solve that problem.
In that case I solved the problem with callback method and my code was like below:
fun getChannels(onDataReadyCallback: OnDataReadyCallBack){
val stringRequest = JsonObjectRequest(
Request.Method.GET, "$baseUrl/api/json/channel_list.json",
null,
{ response ->
try{
val gson = Gson()
val token = TypeToken.getParameterized(ArrayList::class.java,TblChannel::class.java).type
val channels1:JSONArray = response.getJSONArray("groups").getJSONObject(0).getJSONArray("channels")
onDataReadyCallback.onDataReady(gson.fromJson(channels1.toString(),token))
} catch (e:Exception){
Log.e(tag,"DkPrintError on getChannels: $e")
}
},
{ error ->
Log.e(tag, "DkPrintError on getChannels: $error")
})
requestQueue.add(stringRequest)
}
and I called that fun like:
private fun getChannels(){
viewModelScope.launch {
channelsLiveData.value=roomRepository.getAllChannels
if (channelsLiveData.value.isNullOrEmpty()){
remoteRepository.getChannels(object :OnDataReadyCallBack{
override fun onDataReady(data: List<TblChannel>) {
viewModelScope.launch {
channelsLiveData.value=data
}
}
})
}
}
}

Android, Retrofit2, get request string

I'm using Retrofit2 to get data in my Android applications. It looks like this:
interface ApiChatService {
#GET("ncs-chat-web/rest/v1/message")
suspend fun getChatMessages(#Header("Authorization") jwtToken: String, #Query("page") page: Long, #Query("count") count: Int): Response<List<ChatMessageApi>>
}
I call this Retrofit function this way:
override suspend fun getChatMessages(
jwtToken: String,
page: Long
): OperationResult<List<ChatMessageApi>> {
return try {
val response: Response<List<ChatMessageApi>> =
apiChatService.getChatMessages(normalizeJwtToken(jwtToken), page = page, count = MESSAGE_PAGE_SIZE)
if (response.isSuccessful) {
OperationResult(operationResult = Result.OK, resultObject = response.body())
} else {
Log.d("ApiDatasourceImpl.getChatMessages", response.errorBody()?.string()?: "Empty error message")
OperationResult(
operationResult = Result.ERROR,
operationInfo = response.errorBody()?.string()
)
}
} catch (e: Exception) {
Log.d("ApiDatasourceImpl.getChatMessages", e.localizedMessage ?: "Empty error message")
OperationResult(operationResult = Result.ERROR, operationInfo = e.localizedMessage)
}
}
In my Android code I got response code 500 with message "Internal server error"
When I call this request in Postman with such URL
https://my-server.com/ncs-chat-web/rest/v1/message?count=10&page=1
I got 200 code and expected payload.
I'm wondering is there any way to get URL which create Retrofit based on my interface function?

how to handle network exception using coroutine?

I try handling exception using coroutine. I wrote code like this, but didn't work. I can't see any log except for using try-catch. I do not want to use try catch at all function, but want to make clean code handling exception. what should I do for this?
viewmodel
private val handler = CoroutineExceptionHandler { _, exception ->
when (exception) {
is UnknownHostException -> {
showLog("login UnknownHostException : " +exception.message)
}
else -> {
}
}
}
fun login(mobile:String){
viewModelScope.launch(handler) {
try{
var login = apiRepository.login(mobile)
_isLogin.value = login
}catch(e:Exception){
}
}
}
repository
override suspend fun login(mobile: String): LoginResultData {
var result =LoginResultData()
withContext(ioDispatcher){
val request = apiServerModel.login(mobile)
val response = request.await()
result = response
}
return result
}
fun login(mobile:String){
viewModelScope.launch(handler) {
val login = apiRepository.login(mobile)
_isLogin.value = login
}
}

How to get the response of another observable when error occurs?

I just want to ask if it is possible to get the response of another observable after encountering an error from the another observable?
for example I am calling a two api Avatar and Attachment using a combineLatest.
val avatar: Observable<ResponseBody> = api().getAvatar()
val attachment: Observable<ResponseBody> = api().getAttachment()
val obs = Observables.combineLatest(avatar, attachment)
.map { it ->
if (it.first is Exception) {
Log.e(TAG, "getAvatar failed")
} else {
updateAvatar()
}
if (it.second is Exception) {
Log.e(TAG, "getAttachment failed")
} else {
updateAttachment()
}
if (it.first !is Exception && it.second !is Exception) {
Log.i(TAG, "success first=${it.first}, second=${it.second}")
updateAll()
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.onErrorReturn { it }
.subscribe()
disposable.add(obs)
I just want to get the avatar response if the attachment error and I want to get the attachment response if the avatar error.
Thanks.
Yes, my friend. You can handle error for each observable that you combine by calling onErrorReturn() method. You can use empty ResponseBody for detecting error. Final code
val avatar: Observable<Optional<ResponseBody>> = api().getAvatar().onErrorReturn{ Optional.empty }
val attachment: Observable<Optional<ResponseBody>> = api().getAttachment().onErrorReturn{ Optional.empty }
val obs = Observables.combineLatest(avatar, attachment) {avatar, attachment ->
if (!avatar.isPresent()) {
//logic
}
if (!attachment.isPresent()) {
//logic
}
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.onErrorReturn { it }
.subscribe()
If you use java 7 or lower in you project, you can write your own Optional
class Optional<T>(val value: T?) {
companion object {
fun <T> empty(): Optional<T> = Optional(null)
}
fun isPresent() = value != null
}

How to retry Retrofit call on HTTP errors (401) when using RxJava?

My current Android Application is employing Retrofit(2.4.0) and RxJava(2.1.16) to execute my Web Service calls.
Im using Google SignIn for my User Authentication.
I want my Retrofit calls to detect HTTP 401 (UNAUTHORIZED) and attempt to Silently Login with Google Signin
then retry the Retrofit call.
My retrofit calls resemble this
#Headers(HEADER_ACCEPT_JSON)
#GET("resources")
Observable<Response<String>> getResources(#Header(HEADER_AUTHORIZATION) #NonNull final String authenticationToken, #QueryMap(encoded = true) #NonNull Map<String, Object> queryMap);
API_SERVICE.getResources(Login.getAuthorizationToken(), id)
.subscribeOn(Schedulers.io())
.subscribe(Network::manageResource, Network::handle));
From googling I can see that retry/retryWhen will only be triggered when an error occurs in my RxJava chain,
however HTTP 401 errors are not going to raise this condition.
As a newbie to RxJava how can I detect my HTTP 401 code and..
a). Execute Google SignIn Silent login
b). Silent login completes OK, retry my API call?
UPDATE
Ive got closer with the following code
#Headers(HEADER_ACCEPT_JSON)
#GET("resources")
Single<Response<String>> getResources(#Header(HEADER_AUTHORIZATION) #NonNull final String authenticationToken, #QueryMap(encoded = true) #NonNull Map<String, Object> queryMap);
API_SERVICE.getResources(Login.getAuthorizationToken(), id)
.subscribeOn(Schedulers.io())
.flatMap(new Function<Response<Article>,
SingleSource<Response<Article>>>() {
#Override
public SingleSource<Response<Article>> apply(final Response<Article> response) {
Log.d(TAG, "apply() called with: response = [" + response + "]");
if (response.isSuccessful()) {
return Single.just(response);
} else {
return Single.error(new RuntimeException());
}
}
})
.retryWhen(errors -> errors.take(1).flatMap(new Function<Throwable, Publisher<?>>() {
#Override
public Publisher<?> apply(final Throwable throwable) {
Log.d(TAG, "apply() called with: throwable = [" + throwable + "]");
Login.loginSilently().subscribe();
return Flowable.just("DELAY").delay(10, TimeUnit.SECONDS);
}
}))
.subscribe(Network::manageResource, Network::handle));
I do not like the Flowable.just("DELAY").delay() call and also even though I am now catching the exception and silently login in OK I get this exception
09-10 16:39:29.878 7651-7718/research.android E/Network: handle:
java.util.NoSuchElementException
at io.reactivex.internal.operators.flowable.FlowableSingleSingle$SingleElementSubscriber.onComplete(FlowableSingleSingle.java:116)
at io.reactivex.subscribers.SerializedSubscriber.onComplete(SerializedSubscriber.java:168)
at io.reactivex.internal.operators.flowable.FlowableRepeatWhen$WhenReceiver.onComplete(FlowableRepeatWhen.java:119)
at io.reactivex.internal.operators.flowable.FlowableFlatMap$MergeSubscriber.drainLoop(FlowableFlatMap.java:426)
at io.reactivex.internal.operators.flowable.FlowableFlatMap$MergeSubscriber.drain(FlowableFlatMap.java:366)
at io.reactivex.internal.operators.flowable.FlowableFlatMap$InnerSubscriber.onComplete(FlowableFlatMap.java:673)
at io.reactivex.subscribers.SerializedSubscriber.onComplete(SerializedSubscriber.java:168)
at io.reactivex.internal.operators.flowable.FlowableDelay$DelaySubscriber$OnComplete.run(FlowableDelay.java:139)
at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
09-10 16:39:29.878 7651-7678/research.android D/OkHttp: <-- HTTP FAILED: java.io.IOException: Canceled
How can I get the retrywhen to wait for the silentLogin to complete?
and
Whats causing the NoSuchElementException?
As far as I remember if you have error code > 300 then onError() will be called with Throwable which can ba cast to HttpException from where you can get error code returned by server so then you can call other function to make some "silent call"
When you initialize client:
Retrofit.Builder()
.baseUrl(baseUrl)
.client(createClient())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(ApiHandler(Schedulers.io()))
.build()
Error handler:
class ApiHandler(scheduler: Scheduler) : CallAdapter.Factory() {
private val original: RxJava2CallAdapterFactory
= RxJava2CallAdapterFactory.createWithScheduler(scheduler)
override fun get(returnType: Type, annotations: Array<Annotation>, retrofit: Retrofit): CallAdapter<*, *>?
= original.get(returnType, annotations, retrofit)?.let { Wrapper(it) }
private class Wrapper<R>(private val wrapped: CallAdapter<R, *>) : CallAdapter<R, Any> {
override fun adapt(call: Call<R>?): Any? {
call ?: return null
val result = wrapped.adapt(call)
return when (result) {
is Maybe<*> -> result.onErrorResumeNext(Function { Maybe.error(wrap(it)) })
is Single<*> -> result.onErrorResumeNext { Single.error(wrap(it)) }
is Completable -> result.onErrorResumeNext { Completable.error(wrap(it)) }
is Flowable<*> -> result.onErrorResumeNext(Function { Flowable.error(wrap(it)) })
is Observable<*> -> result.onErrorResumeNext(Function { Observable.error(wrap(it)) })
else -> result
}
}
override fun responseType(): Type = wrapped.responseType()
private fun wrap(throwable: Throwable) = when (throwable) {
is HttpException -> {
val exception = ApiException.http(throwable)
toLog("ex - ${exception.message}")
exception
} // We had non-200 http error
is JsonSyntaxException -> ApiException.parse(throwable) // We had json parsing error
is SocketTimeoutException -> ApiException.timeout(throwable) // A network error happened
is IOException -> ApiException.network(throwable) // A network error happened
else -> ApiException.unknown(throwable) // We don't know what happened. We need to simply convert to an unknown error
}
}
}
Api exception class:
class ApiException internal constructor(message: String,
/** Response object containing status code, headers, body, etc. */
val response: ErrorResponse?,
/** The event kind which triggered this error. */
#ApiError val error: Int,
exception: Throwable?) : RuntimeException(message, exception) {
companion object {
fun http(exception: HttpException): ApiException {
val response = exception.response()
var errorResponse: ErrorResponse? = null
val message = if (response == null) {
if (exception.message().isEmpty()) exception.code().toString() else exception.message()
} else {
// here you can check error code and throw needed exception
val errorBody = response.errorBody()?.string().toString()
if (errorBody.isNotEmpty()) {
toLog("ApiException: $errorBody")
}
try {
errorResponse = GsonBuilder().create().fromJson(errorBody, ErrorResponse::class.java)
errorResponse?.toString() ?: errorBody
} catch (e: Exception) {
e.printStackTrace()
response.raw().message()
}
}
return ApiException(message, errorResponse, ApiError.HTTP, exception)
}
fun network(exception: IOException): ApiException {
return ApiException(exception.message ?: "network", null, ApiError.NETWORK, exception)
}
fun parse(exception: JsonSyntaxException): ApiException {
return ApiException(exception.message ?: "parse", null, ApiError.CONVERSION, exception)
}
fun unknown(exception: Throwable): ApiException {
return ApiException(exception.message ?: "unknown", null, ApiError.UNKNOWN, exception)
}
fun timeout(exception: SocketTimeoutException): ApiException {
return ApiException("Connection timed out", null, ApiError.TIMEOUT_EXCEPTION, exception)
}
}
}
And when calling request
yourRequest.compose { observable ->
observable.retryWhen { flow ->
flow.ofType(ApiException::class.java).flatMap {
when {
it.error == ApiError.TIMEOUT_EXCEPTION -> Flowable.empty<T>()
it.error == ApiError.NETWORK -> getSnackBarFlowable().flatMap { if (it) Flowable.just(it) else Flowable.empty<T>() }
else -> Flowable.error(it)
}
}
}
}.subscribe({}, {})
getSnackBarFlowable() is get from fragment. you can use something else
fun getSnackBarFlowable(): Flowable<Boolean> = Flowable.create({ subscriber ->
if (view == null) {
subscriber.onNext(false)
} else {
val snackBar = Snackbar.make(activity!!.currentFocus, R.string.error_connection_fail, Snackbar.LENGTH_INDEFINITE)
snackBar.setAction("Retry") { subscriber.onNext(true) }
snackBar.show()
}
}, LATEST)
I know, quite enough of code. But this solution is really helpful for me in different projects
To solve 401 Unauthorized Error try to implement AuthInterceptor to your OkHttpClient.
BasicAuthInterceptor interceptorAuth = new BasicAuthInterceptor(yourToken);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptorAuth)
.build();
builder.client(client);
If your authToken is expired or bad try to gain new.
public class BasicAuthInterceptor implements Interceptor {
private String yourToken;
public BasicAuthInterceptor(String token) {
this.yourToken = token;
}
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request authenticatedRequest = request.newBuilder()
.header("Authorization", format("token %s", yourToken)).build();
Response response = chain.proceed(authenticatedRequest);
boolean unauthorized = response.code() == 401;
if (unauthorized) {
Request modifiedRequest = request.newBuilder()
.header("Authorization", format("token %s", getNewToken())).build();
response = chain.proceed(modifiedRequest);
}
return response;
}
}

Categories

Resources