Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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.
*/
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);
}
}
@kiratheone

This comment has been minimized.

Copy link

commented Sep 5, 2018

"Traditional way"
is there modern way?. Btw thanks for share

@Ikhiloya

This comment has been minimized.

Copy link

commented Jan 5, 2019

Really helpful gist. I noticed that there's no LiveDataCallAdapter.java file (only kotlin) here, so I added it here. Kindly update, thanks!

@chaiwa-berian

This comment has been minimized.

Copy link

commented Jan 6, 2019

Thank you for putting this together! I updated the README.md for Usage so the LiveDataCallAdapterFactory is instantiated with the new keyword as below:

Usage

Set the LiveDataCallAdapterFactory in the Retrofit Builder

Retrofit.Builder()
        .baseUrl(BASE_URL)
        // ....
        .addCallAdapterFactory(new LiveDataCallAdapterFactory())
        // ....
        .build()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.