Skip to content

Instantly share code, notes, and snippets.

@Ikhiloya
Forked from AkshayChordiya/ApiResponse.java
Last active April 29, 2020 15:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ikhiloya/81ee838f512c6e7279dd08d9841a261c to your computer and use it in GitHub Desktop.
Save Ikhiloya/81ee838f512c6e7279dd08d9841a261c to your computer and use it in GitHub Desktop.
LiveData adapter for Retrofit

LiveData Adapter for Retrofit

Copy the following classes in your project to use LiveData<ApiResponse<T>> instead of Call<T> in Retrofit services. Made this gist so everyone can just copy and paste them in project rather than finding through the Google Samples.

Original Credits: I didn't make these classes, they are from Google Sample

Difference

Traditional way - with Call:

interface SomeService {

    @GET("data")
    fun getData(): Call<T>
}

Traditional way - with LiveData:

interface SomeService {

    @GET("data")
    fun getData(): LiveData<ApiResponse<T>>
}

Usage

Set the LiveDataCallAdapterFactory in the Retrofit Builder

Retrofit.Builder()
        .baseUrl(BASE_URL)
        // ....
        .addCallAdapterFactory(LiveDataCallAdapterFactory())
        // ....
        .build()
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.util.ArrayMap;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import retrofit2.Response;
/**
* Common class used by API responses.
*
* @param <T>
*/
public class ApiResponse<T> {
private static final Pattern LINK_PATTERN = Pattern
.compile("<([^>]*)>[\\s]*;[\\s]*rel=\"([a-zA-Z0-9]+)\"");
private static final Pattern PAGE_PATTERN = Pattern.compile("page=(\\d)+");
private static final String NEXT_LINK = "next";
public final int code;
@Nullable
public final T body;
@Nullable
public final String errorMessage;
@NonNull
public final Map<String, String> links;
public ApiResponse(Throwable error) {
code = 500;
body = null;
errorMessage = error.getMessage();
links = Collections.emptyMap();
}
public ApiResponse(Response<T> response) {
code = response.code();
if (response.isSuccessful()) {
body = response.body();
errorMessage = null;
} else {
String message = null;
if (response.errorBody() != null) {
try {
message = response.errorBody().string();
} catch (IOException ignored) {
Timber.e(ignored, "error while parsing response");
}
}
if (message == null || message.trim().length() == 0) {
message = response.message();
}
errorMessage = message;
body = null;
}
String linkHeader = response.headers().get("link");
if (linkHeader == null) {
links = Collections.emptyMap();
} else {
links = new ArrayMap<>();
Matcher matcher = LINK_PATTERN.matcher(linkHeader);
while (matcher.find()) {
int count = matcher.groupCount();
if (count == 2) {
links.put(matcher.group(2), matcher.group(1));
}
}
}
}
public boolean isSuccessful() {
return code >= 200 && code < 300;
}
public Integer getNextPage() {
String next = links.get(NEXT_LINK);
if (next == null) {
return null;
}
Matcher matcher = PAGE_PATTERN.matcher(next);
if (!matcher.find() || matcher.groupCount() != 1) {
return null;
}
try {
return Integer.parseInt(matcher.group(1));
} catch (NumberFormatException ex) {
Timber.w("cannot parse next page from %s", next);
return null;
}
}
}
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.example.github.util;
import com.android.example.github.api.ApiResponse;
import android.arch.lifecycle.LiveData;
import java.lang.reflect.Type;
import java.util.concurrent.atomic.AtomicBoolean;
import retrofit2.Call;
import retrofit2.CallAdapter;
import retrofit2.Callback;
import retrofit2.Response;
/**
* A Retrofit adapter that converts the Call into a LiveData of ApiResponse.
* @param <R>
*/
public class LiveDataCallAdapter<R> implements CallAdapter<R, LiveData<ApiResponse<R>>> {
private final Type responseType;
public LiveDataCallAdapter(Type responseType) {
this.responseType = responseType;
}
@Override
public Type responseType() {
return responseType;
}
@Override
public LiveData<ApiResponse<R>> adapt(Call<R> call) {
return new LiveData<ApiResponse<R>>() {
AtomicBoolean started = new AtomicBoolean(false);
@Override
protected void onActive() {
super.onActive();
if (started.compareAndSet(false, true)) {
call.enqueue(new Callback<R>() {
@Override
public void onResponse(Call<R> call, Response<R> response) {
postValue(new ApiResponse<>(response));
}
@Override
public void onFailure(Call<R> call, Throwable throwable) {
postValue(new ApiResponse<R>(throwable));
}
});
}
}
};
}
}
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.arch.lifecycle.LiveData
import com.bitfurther.notigo.student.api.ApiResponse
import retrofit2.Call
import retrofit2.CallAdapter
import retrofit2.Callback
import retrofit2.Response
import java.lang.reflect.Type
import java.util.concurrent.atomic.AtomicBoolean
/**
* A Retrofit adapter that converts the Call into a LiveData of ApiResponse.
* @param <R>
*/
class LiveDataCallAdapter<R>(private val responseType: Type) : CallAdapter<R, LiveData<ApiResponse<R>>> {
override fun responseType(): Type {
return responseType
}
override fun adapt(call: Call<R>): LiveData<ApiResponse<R>> {
return object : LiveData<ApiResponse<R>>() {
internal var started = AtomicBoolean(false)
override fun onActive() {
super.onActive()
if (started.compareAndSet(false, true)) {
call.enqueue(object : Callback<R> {
override fun onResponse(call: Call<R>, response: Response<R>) {
postValue(ApiResponse(response))
}
override fun onFailure(call: Call<R>, throwable: Throwable) {
postValue(ApiResponse(throwable))
}
})
}
}
}
}
}
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.arch.lifecycle.LiveData;
import android.support.annotation.NonNull;
import com.bitfurther.notigo.student.api.ApiResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import retrofit2.CallAdapter;
import retrofit2.Retrofit;
public class LiveDataCallAdapterFactory extends CallAdapter.Factory {
@Override
public CallAdapter<?, ?> get(@NonNull Type returnType, @NonNull Annotation[] annotations, @NonNull Retrofit retrofit) {
if (getRawType(returnType) != LiveData.class) {
return null;
}
Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
Class<?> rawObservableType = getRawType(observableType);
if (rawObservableType != ApiResponse.class) {
throw new IllegalArgumentException("type must be a resource");
}
if (!(observableType instanceof ParameterizedType)) {
throw new IllegalArgumentException("resource must be parameterized");
}
Type bodyType = getParameterUpperBound(0, (ParameterizedType) observableType);
return new LiveDataCallAdapter<>(bodyType);
}
}
@renatojobal
Copy link

Thanks for sharing. It is very useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment