Retrofit magic, reject duplicate code!

Because the background returns a unified data structure, such as code, data, message; Students who have used Retrofit must have defined a category like BaseResponse, but the processing logic of BaseResponse is similar. It's really annoying to write every time. Is there any good way to solve this pain point? This paper introduces an elegant way to solve this problem.


When we open the official document of Retrofit, the official example is as follows:

public interface GitHubService {
  Call<List<Repo>> listRepos(@Path("user") String user);

When it comes to our own projects, most of them do:

public interface UserService {
  Call<BaseResponse<List<User>>> getAllUsers();

Different companies have different data structures, but they are all similar. For example, code, data, message or status, data, message, what code == 0 should be written every time. It's boring. There's no technical content at all...

Wouldn't it be nice if we could get rid of BaseResponse? As defined below:

public interface UserService {
  Call<List<User>> getAllUsers();

If it's Kotlin, it's even better. Go straight to the suspend method.

interface UserService {
  suspend fun getAllUsers() : List<User>

1.Convert. Ignored parameters in factory

public interface Converter<F, T> {
  abstract class Factory {
    // The parameter annotations is the annotation on the Method
    public @Nullable Converter<ResponseBody, ?> responseBodyConverter(
        Type type, Annotation[] annotations, Retrofit retrofit) {
      return null;

public final class GsonConverterFactory extends Converter.Factory {
  // Retrofit official converter Factory does not use the annotations parameter
  public Converter<ResponseBody, ?> responseBodyConverter(
      Type type, Annotation[] annotations, Retrofit retrofit) {
    TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
    return new GsonResponseBodyConverter<>(gson, adapter);

From the above code, it is not difficult to see that in the implementation of convert In factory, the official implementation of Retrofit does not use the parameter annotations, which is the key to removing BaseResponse.

2. Implementation mode

In fact, the implementation scheme is very simple, that is, add a custom annotation to the Method, and then implement a converter Factory, and then this converter In the factory class, judge whether there is a custom annotation for the Method. If so, convert the data and finally submit it to Gson for parsing.

Project address: Convex

Implementation details

Data conversion interface:

interface ConvexTransformer {
    // Convert the original InputStream into an InputStream of specific business data
    // It is equivalent to obtaining the data stream of code, data and message and converting it into a data stream only
    fun transform(original: InputStream): InputStream

Custom annotation:

@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class Transformer(val value: KClass<out ConvexTransformer>)

// With this custom annotation, we can add this annotation to the Method
interface UserService {
    fun xxxMethod() : Any

Convert.Factory implementation class:

class ConvexConverterFactory : Converter.Factory() {
    override fun responseBodyConverter(
        type: Type,
        annotations: Array<Annotation>,
        retrofit: Retrofit
    ): Converter<ResponseBody, *>? {
        // Get Transformer annotation on Method
        // To determine which specific ConvexTransformer class to use
        return annotations
            ?.let { transformerClazz ->
                // Gets the Converter that can handle the current return value
                // Convertconverterfactory only does data flow and serializes specific data
                // Give it to a similar GsonConverterFactory
                    this, type, annotations
                )?.let { converter ->
                    // If there is a Converter that can handle the return value, create a proxy convertor
                    ConvexConverter<Any>(transformerClazz, converter)

class ConvexConverter<T> constructor(
    private val transformerClazz: KClass<out ConvexTransformer>,
    private val candidateConverter: Converter<ResponseBody, *>
) : Converter<ResponseBody, T?> {

    override fun convert(value: ResponseBody): T? {
        // Get the ConvexTransformer from Convex
        // Convex is a service registry that stores ConvexTransformer
        // If there is no converttransformer, the corresponding converttransformer will be created through reflection
        // And save it in Convex for next use
        return Convex.getConvexTransformer(
            .let { transformer ->
                // Convert data stream, here is to convert code, data and message data stream into data stream
                transformer.transform(value.byteStream()).let { responseStream ->
                    ResponseBody.create(value.contentType(), responseStream.readBytes())
            }?.let { responseBody ->
                // Use the concrete Converter to convert the data stream into concrete data
                candidateConverter.convert(responseBody) as T?

3. Use of converse

Add dependency

dependencies {
    implementation "org.paradisehell.convex:convex:1.0.0"

Implement converttransformer

private class TestConvexTransformer : ConvexTransformer {
    override fun transform(original: InputStream): InputStream {
        TODO("Return the business data InputStream.")

Add converterfactory to Retrofit

    // Convertconverterfactory must be at the top

Define Service interface

interface XXXService {
    // Use the Transformer annotation to add a specific ConvexTransformer implementation class
    suspend fun getAllUsers() : List<User>

4. Examples

First of all, thank you very much for the free API provided by WanAndroid.

// 1. Define BaseResponse
// It is used to process the data returned from the background, deserialize and get the final data
data class BaseResponse<T>(
    val errorCode: Int = 0,
    val errorMsg: String? = null,
    val data: T? = null

// 2. Implement converttransformer
// The user transfers the data returned from the background into specific data
class WanAndroidConvexTransformer : ConvexTransformer {
    private val gson = Gson()

    override fun transform(original: InputStream): InputStream {
        // Deserialize to BaseResponse first
        val response = gson.fromJson<BaseResponse<JsonElement>>(
            object : TypeToken<BaseResponse<JsonElement>>() {
        // Judge whether the Response is successful
        // If successful, the data will be converted into InputStream, and finally processed by the specific Converter
        if (response.errorCode == 0 && != null) {
        throw IOException(
            "errorCode : " + response.errorCode + " ; errorMsg : " + response.errorMsg

// 3. Define model data
data class Article(
    val id: Int = 0,
    val link: String? = null,
    val author: String? = null,
    val superChapterName: String? = null

// 4. Define Service
interface WanAndroidService {
    // Specify ConvexTransformer for the method, so that BaseResponse can be converted to data
    suspend fun getTopArticles(): List<Article>

// 5. Define Retrofit
private val retrofit by lazy {
        // Be sure to put convertconverterfactory in all converters Front of factory

private val wanAndroidService by lazy {

// 6. Execution method
lifecycleScope.launch(Main) {
    val result = withContext(IO) {
        // A try catch operation is required because an exception will be thrown if the request fails
        runCatching {
        }.onSuccess {
            return@withContext it
        }.onFailure {
            return@withContext it
    // print data
    // Successfully print data list, failed to print Exeception


Perfect. There is no need to write BaseResponse for defining Method in the future. BaseResponse can finally be handled uniformly.

Moreover, this scheme also supports a variety of different Data types, because different methods can specify different ConvexTransformer, and it doesn't matter how BaseResponse is processed for specific business processing, because specific business codes get specific Data.

I have to admire the design of Retrofit, converter The factory has set aside the parameter annotations, which is extremely scalable. Pay tribute to square and salute~~

Advanced notes of Android advanced development system, latest interview review notes PDF, My GitHub

end of document

Your favorite collection is my greatest encouragement!
Welcome to follow me, share Android dry goods and exchange Android technology.
If you have any opinions on the article or any technical problems, please leave a message in the comment area for discussion!

Keywords: Android Back-end

Added by amandas on Sun, 26 Dec 2021 21:19:48 +0200