How to handle both success and fail responses in Retrofit and RxJava2? - android

I have the following request:
#POST("devices/link/")
fun linkDevice(
#Body deviceInfo: DeviceInfo
): Single<Response<UserDevice>>
I want to achieve the following:
If request was successful, get DeviceInfo and, depending on status code, report event to analytics
If request failed, get exception, transform it and rethrow.
So, the code is:
fun linkDevice(): Single<UserDevice> {
val deviceInfo = deviceUtil.getDeviceInfo()
return devicesService
.linkDevice(deviceInfo)
.onErrorResumeNext { e ->
val transformedException = transformRequestException(e) { httpException ->
return#transformRequestException when (httpException.code()) {
409 -> DeviceAlreadyLinkedException()
else -> null
}
}
Single.error(transformedException)
}
.flatMap { response ->
if (response.isSuccessful) {
val userDevice = response.body()
userDevice?.let {
userPreferences.setDeviceId(it.id)
}
when (response.code()) {
200 -> {
// TODO: Analytics
}
201 -> {
// TODO: Analytics
}
}
Single.just(userDevice)
} else {
throw ApiException()
}
}
}
/**
* Transform exception that came from request
* By default, handles only network and HTTP 401 exceptions, but can contain custom logic, passed with specialTransformer
*/
fun transformRequestException(
e: Throwable,
specialTransformer: ((httpException: HttpException) -> Exception?)? = null
): Exception {
return if (e is HttpException) {
if (e.code() == 401) {
NotAuthenticatedException()
} else {
specialTransformer?.invoke(e) ?: ApiException()
}
} else {
NetworkException()
}
}
But this code does not work, if I operate with Response object, it will work only with Single<UserDevice> in my Retrofit service.
But, with Single<UserDevice> I can't get 2XX codes.
So, how to implement the desired behavior?

So, as for now, I've solved as following:
data class RequestResult<T>(
val data: T,
val code: Int
)
fun <T> transformResponse(response: Response<T>): Single<RequestResult<T>> {
if (response.isSuccessful) {
return Single.just(RequestResult(response.body()!!, response.code()))
} else {
throw HttpException(response)
}
}
fun <T> transformEmptyResponse(response: Response<T>): Completable {
if (response.isSuccessful) {
return Completable.complete()
} else {
throw HttpException(response)
}
}
And:
fun linkDevice(): Single<UserDevice> {
val deviceInfo = deviceUtil.getDeviceInfo()
return devicesService
.linkDevice(deviceInfo)
.flatMap { transformResponse(it) }
.flatMap { requestResult ->
requestResult.data.let { userDevice ->
userPreferences.setDeviceId(userDevice.id)
when (requestResult.code) {
200 -> {
// TODO: Analytics
}
201 -> {
// TODO: Analytics
}
}
Single.just(userDevice)
}
}
.onErrorResumeNext { e ->
val transformedException = transformRequestException(e) { httpException ->
return#transformRequestException when (httpException.code()) {
409 -> DeviceAlreadyLinkedException()
else -> null
}
}
Single.error(transformedException)
}
}
Looks like hack, but I did not find any better solution, and it works.

Related

How should i reduce these boilerplates to handel Network Response in Android using retrofit?

This is my ViewModle class methods
private fun handleResponse(response: Response<OtpSentDataClass>): Resource<OtpSentDataClass> {
if (response.isSuccessful) {
response.body()?.let { otpSentResponse ->
return Resource.Success(otpSentResponse)
}
} else {
val body =
Gson().fromJson(response.errorBody()?.charStream(), ErrorBodyResponse::class.java)
when {
response.code() == 400 -> {
return (Resource.Failure(Constants.error400, body?.message))
}
response.code() == 401 -> {
return (Resource.Failure(Constants.error401, body?.message))
}
response.code() == 403 -> {
return (Resource.Failure(Constants.error403, body?.message))
}
...
response.code() == 408 -> {
return (Resource.Failure(Constants.error408, body?.message))
}
...
...
response.code() == 413 -> {
return (Resource.Failure(Constants.error413, body?.message))
}
...
response.code() == 511 -> {
return (Resource.Failure(Constants.error511, body?.message))
}
else -> {
return Resource.Failure("Something went wrong ${body?.message}")
}
}
}
val body = Gson().fromJson(
response.errorBody()?.charStream(),
ErrorBodyResponse::class.java
)
return Resource.Failure("Something went wrong ${body?.message}")
}
similarly i have other methods in this same viewmodle
like
private fun handleDeviceDetailResponse(response: Response<DeviceDetailResponse>): Resource<DeviceDetailResponse>
private fun handleVerifyResponse(response: Response<VerifyOtpResponse>): Resource<VerifyOtpResponse>
Now the problem is I have many other viewmodels which has these kind of handle response methods, which creates lots of boilerplate.
How should I keep my code clean?
I would put your error codes in a map, so you can retrieve them by number. I don't know what type your constants are, so I'm just treating them as Strings as an example.
object Constants {
//...
private val errorCodes = mapOf(
400 to "bad_request",
401 to "unauthorized",
// etc.
)
const val UNSPECIFIED_ERROR_CODE_STRING
fun errorCodeToString(code: Int): String = errorCodes[code] ?: UNSPECIFIED_ERROR_CODE_STRING
}
Then you can use your function in you application code. Also, your code is tripping over nullability because you separately check isSuccessful and then get your body(). body() is non-null if and only if isSuccessful is true, so there's no reason to check isSuccessful. Your code as written forces you to write a redundant branch of failure code at the bottom that will never be reached.
private fun handleResponse(response: Response<OtpSentDataClass>): Resource<OtpSentDataClass> {
response.body()?.let { otpSentResponse ->
return Resource.Success(otpSentResponse)
}
// We can assert non-null errorBody, because body and errorBody are never both null
val errorBody = Gson().fromJson(response.errorBody()!!.charStream(), ErrorBodyResponse::class.java)
val errorCode = Constants.errorCodeToString(response.code())
return when (errorCode) {
UNSPECIFIED_ERROR_CODE_STRING -> Resource.Failure("Something went wrong ${errorBody?.message}")
else -> Resource.Failure(errorCode, errorBody?.message)
}
}

How to chain flows with different return types based on emitted values and collect their results?

I have a situation where I have to execute 3 network requests one after the other collect their results (which are of different types).
Following is the relevant part of the code :
Resource.kt
sealed class Resource<T>(val data: T? = null, val message: String? = null) {
class Loading<T>(data: T? = null): Resource<T>(data)
class Success<T>(data: T?): Resource<T>(data)
class Error<T>(message: String, data: T? = null): Resource<T>(data, message)
}
Repository.kt
override fun getReportData(profileId: Int): Flow<Resource<ProfileReport>> =
flow {
emit(Resource.Loading<ProfileReport>())
var report: ProfileReport? = null
try {
// Api is available as a retrofit implementation
report = api.getReport(profileId).toProfileReport()
} catch (e: HttpException) {
emit(
Resource.Error<ProfileReport>(
message = "An unknown http exception occured"
)
)
}
if (report!= null) {
emit(Resource.Success<ProfileReport>(data = report))
}
}
Say I have 3 such flows to fetch data in my repository and they have different return types (ex: ProfileReport, ProfileInfo, ProfileStatus).
Now in my viewmodel I have a function to execute these flows and perform actions on the values emitted such as :
ViewModel.kt
fun getProfileData(profileId: Int) {
getReportData(profileId)
.onEach { result ->
when (result) {
is Resource.Loading -> {
_loading.value = true
}
is Resource.Error -> {
_loading.value = false
// UI event to display error snackbar
}
is Resource.Success -> {
_loading.value = false
if (result.data != null) {
_report.value = _report.value.copy(
// Use result here
)
}
}
}
}.launchIn(viewModelScope)
}
This works ok for one flow but how can I execute 3 flows one after the other.
That is, execute first one and if its successful, execute second one and so on, and if all of them are successful use the results.
I did it like this :
fun getProfileData(profileId: Int) {
getReportData(profileId)
.onEach { result1 ->
when (result1) {
is Resource.Loading -> {/*do stuff*/}
is Resource.Error -> {/*do stuff*/}
is Resource.Success -> {
getProfileStatus(profileId)
.onEach { result2 ->
is Resource.Loading -> {/*do stuff*/}
is Resource.Error -> {/*do stuff*/}
is Resource.Success -> {
getProfileInfo(profileId)
.onEach { result3 ->
is Resource.Loading -> {/*do stuff*/}
is Resource.Error -> {/*do stuff*/}
is Resource.Success -> {
/*
Finally update viewmodel state
using result1, result2 and result3
*/
}
}.launchIn(viewModelScope)
}
}.launchIn(viewModelScope)
}
}
}.launchIn(viewModelScope)
}
But, this feels too cumbersome and probably there is a better way to chain flows based on success condition and collect results at the end. I checked some ways that use combine() or flatMapMerge() but was unable to use them in this situation.
Is there a way to achieve this? Or is this approach itself wrong from a design perspective maybe?
I think this could be modeled much more cleanly using imperative coroutines than with flows. Since you're overriding functions, this depends on you being able to modify the supertype abstract function signatures.
This solution doesn't use Resource.Loading, so you should remove that to make smart casting easier.
suspend fun getReportData(profileId: Int): Resource<ProfileReport> =
try {
val report = api.getReport(profileId).toProfileReport()
Resource.Success<ProfileReport>(data = report)
} catch (e: HttpException) {
Resource.Error<ProfileReport>(
message = "An unknown http exception occured"
)
}
//.. similar for the other two functions that used to return flows.
fun getProfileData(profileId: Int) {
viewModelScope.launch {
// do stuff to indicate 1st loading state
when(val result = getReportData(profileId)) {
Resource.Error<ProfileReport> -> {
// do stuff for error state
return#launch
}
Resource.Success<ProfileReport> -> {
// do stuff with result
}
}
// Since we returned when there was error, we know first
// result was successful.
// do stuff to indicate 2nd loading state
when(val result = getProfileStatus(profileId)) {
Resource.Error<ProfileStatus> -> {
// do stuff for error state
return#launch
}
Resource.Success<ProfileStatus> -> {
// do stuff with result
}
}
// do stuff to indicate 3rd loading state
when(val result = getProfileInfo(profileId)) {
Resource.Error<ProfileInfo> -> {
// do stuff for error state
return#launch
}
Resource.Success<ProfileInfo> -> {
// do stuff with result
}
}
}
}
If you want to keep your current Flows, you could collect your flows this way to avoid the deep nesting. This works because your source flows are designed to be finite (they aren't repeatedly emitting new values indefinitely, but have only one final result).
fun getProfileData(profileId: Int) = viewModelScope.launch {
var shouldBreak = false
getReportData(profileId).collect { result ->
when (result) {
is Resource.Loading -> { /*do stuff*/ }
is Resource.Error -> {
/*do stuff*/
shouldBreak = true
}
is Resource.Success -> { /*do stuff*/ }
}
}
if (shouldBreak) return#launch
getProfileStatus(profileId).collect { result ->
when (result) {
is Resource.Loading -> { /*do stuff*/ }
is Resource.Error -> {
/*do stuff*/
shouldBreak = true
}
is Resource.Success -> { /*do stuff*/ }
}
}
if (shouldBreak) return#launch
getProfileInfo(profileId).collect { result ->
when (result) {
is Resource.Loading -> { /*do stuff*/ }
is Resource.Error -> { /*do stuff*/ }
is Resource.Success -> { /*do stuff*/ }
}
}
}

Generics and MutableLiveData in Kotlin

I have two quite similar functions and I'm trying to avoid duplication in my code by the use of generics. The functions have both a try catch block and notify its observers with two MutableLiveData of two different types:
val noWasteRecipesPosts: MutableLiveData<List<Recipe>> = MutableLiveData()
val lastArticlesPosts: MutableLiveData<List<Article>> = MutableLiveData()
fun getNoWasteRecipesPosts() {
makeCall(service.getRecipes(), noWasteRecipesPosts)
scope.launch {
try {
val response = service.getRecipes().await()
when (response.isSuccessful) {
true -> {
response.body()?.let {
noWasteRecipesPosts.postValue(ArrayList(response.body()))
} ?: run {
errorLiveData.postValue(response.message())
}
}
false -> errorLiveData.postValue(response.message())
}
} catch (e: Exception) {
noConnectionLiveData.postValue(true)
}
}
}
fun getLastArticlesPosts(excludeRecipes: Boolean) {
scope.launch {
try {
val response = when (excludeRecipes) {
true -> service.getLastArticles(categoriesToExclude = arrayListOf(BlogCategories.NO_WASTE_RECIPES.id))
.await()
false -> service.getLastArticles()
.await()
}
when (response.isSuccessful) {
true -> {
response.body()?.let {
lastArticlesPosts.postValue(ArrayList(response.body()))
} ?: run {
errorLiveData.postValue(response.message())
}
}
false -> errorLiveData.postValue(response.message())
}
} catch (e: Exception) {
noConnectionLiveData.postValue(true)
}
}
}
To avoid code repeating I'm trying to use generics, but probably in the wrong way. I've defined a function that takes the Deferred api response as first parameter and I would like to pass a MutableLiveData to notify observers as the second parameter:
fun makeCall(function: Deferred<Response<*>>, successLiveData: MutableLiveData<*>) {
scope.launch {
try {
val response = function.await()
when (response.isSuccessful) {
true -> {
response.body()?.let {
successLiveData.postValue(it) // Compile error here
} ?: run {
errorLiveData.postValue(response.message())
}
}
false -> errorLiveData.postValue(response.message())
}
} catch (e: Exception) {
noConnectionLiveData.postValue(true)
}
}
}
Unfortunately I'm missing something and the IDE is giving me a Type mismatch error trying to post the LiveData value:
Type mismatch: Required : Nothing! Found: Any.
I'm quite confused, do you have any suggestion to make about MutableLiveData and Generics in kotlin?
The response.body() type and the MutableLiveData type must match. The function signature should be something like this:
fun <T> makeCall(function: Deferred<Response<T>>, successLiveData: MutableLiveData<T>)

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