Because the background returns uniform data structure, such as code, data, message; Those of you who have used Retrofit have defined classes like BaseResponse, but the logic of BaseResponse is pretty much the same, and it’s pretty annoying to write it all the time. Is there a good way to fix this? This article presents an elegant way to solve this problem.

background

When we opened Retrofit’s official documentation, the official example looked like this:

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}
Copy the code

When it comes to our own projects, this is true for the most part:

public interface UserService {
  @GET("/users")
  Call<BaseResponse<List<User>>> getAllUsers();
}
Copy the code

Different companies have different data structures, but they are almost the same, such as code, data, message or status, data, message. Every time you have to write code == 0 and so on.

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

public interface UserService {
  @GET("/users")
  Call<List<User>> getAllUsers();
}
Copy the code

If it’s Kotlin, it’s even more fun to go directly to the suspend method.

interface UserService {
  @GET("/users")
  suspend fun getAllUsers() : List<User>
}
Copy the code

1. Ignored arguments in convert. Factory

public interface Converter<F, Annotations public @Nullable Converter<ResponseBody,? > responseBodyConverter( Type type, Annotation[] annotations, Retrofit retrofit) { return null; }}} Public final class GsonConverterFactory extends Converter.Factory {// Retrofit official Converter.Factory is not used Annotations @override public Converter<ResponseBody,? > responseBodyConverter( Type type, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<? > adapter = gson.getAdapter(TypeToken.get(type)); return new GsonResponseBodyConverter<>(gson, adapter); }}Copy the code

As you can see from the code above, the official Retrofit implementation does not use the parameter Annotations when implementing convert.Factory, which is the key to removing BaseResponse.

2. Implementation method

In fact, the implementation is very simple, which is to add a custom annotation to the Method, and then implement a corresponding Converter.Factory class to determine whether the Method has our custom annotation, if so, convert the data. Finally, it is handed over to Gson for parsing.

Project address: Convex

Github.com/ParadiseHel…

Implementation details

Data conversion interface:

Interface ConvexTransformer {// Convert the original InputStream to the specific business data InputStream // equivalent to get code, data, @throws (IOException::class) Fun Transform (Original: InputStream): InputStream}Copy the code

Custom annotations:

@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) annotation class Transformer(val value: KClass<out ConvexTransformer>) with this custom annotation, Interface UserService {@get (" XXX ") @transformer (XXXTransformer::class) fun xxxMethod() : Any}Copy the code

Convert.Factory implementation class:

class ConvexConverterFactory : Converter.Factory() { override fun responseBodyConverter( type: Type, annotations: Array<Annotation>, retrofit: Retrofit ): Converter<ResponseBody, *>? {// Get Transformer annotations on Method // to determine which specific ConvexTransformer class to use, return Annotations. FilterIsInstance <Transformer>() .firstOrNull()? .value ? .let {transformerClazz -> // Get Converter // ConvexConverterFactory that can process the current returned value, Specific data serialization / / to similar GsonConverterFactory retrofit. NextResponseBodyConverter < Any > (this, the type, annotations)? .let {converter -> // If there is a converter that can process the returned value, create an agent's ConvexConveter ConvexConverter<Any>(transformerClazz, converter) } } } } class ConvexConverter<T> constructor( private val transformerClazz: KClass<out ConvexTransformer>, private val candidateConverter: Converter<ResponseBody, *> ) : Converter<ResponseBody, T? > { @Suppress("UNCHECKED_CAST") @Throws(IOException::class) override fun convert(value: ResponseBody): T? {// Get ConvexTransformer from Convex // Convex is a ServiceRegistry, To store ConvexTransformer // If there is no ConvexTransformer, the corresponding ConvexTransformer will be created by reflection and saved to the ConvexTransformer until the next return is used Convex. GetConvexTransformer (transformerClazz. Java). Let {transformer - > / / conversion data stream, So this is code, data, Transformer. Transform (value.bytestream ()). Let {responseStream -> ResponseBody.create(value.contentType(), responseStream.readBytes()) } }? . Let {responseBody - > / / using specific Converter will data data stream into specific data candidateConverter. Convert (responseBody) as T? }}}Copy the code

3. Use Convex

Add the dependent

Dependencies {implementation "org. Paradisehell. Convex: convex: 1.0.0"}Copy the code

Implement ConvexTransformer

private class TestConvexTransformer : ConvexTransformer {
    @Throws(IOException::class)
    override fun transform(original: InputStream): InputStream {
        TODO("Return the business data InputStream.")
    }
}
Copy the code

Add ConvexConverterFactory to Retrofit

Retrofit.builder ().baseurl ("https://test.com/") // ConvexConverterFactory must be at the front .addConverterFactory(ConvexConverterFactory()) .addConverterFactory(GsonConverterFactory.create()) .build()Copy the code

Defining the Service Interface

Interface XXXService {@get ("/users") // use Transformer; Add specific ConvexTransformer implementation class @ Transformer (TestConvexTransformer: : class) suspend fun getAllUsers () : a List < User >}Copy the code

Example 4.

First of all, thank you WanAndroid for the free API.

www.wanandroid.com/blog/show/2

BaseResponse = BaseResponse; // Data class BaseResponse<T>(@serializedName ("errorCode") val errorCode: Int = 0, @SerializedName("errorMsg") val errorMsg: String? = null, @SerializedName("data") val data: T? = null) / / 2. Implement ConvexTransformer / / users will return to the background data flow for a specific data data class WanAndroidConvexTransformer: ConvexTransformer { private val gson = Gson() @Throws(IOException::class) override fun transform(original: InputStream): BaseResponse val Response = gson.fromjson <BaseResponse<JsonElement>>(original.reader(), object : TypeToken<BaseResponse<JsonElement>>() {}.type) If (Response.errorCode == 0 && Response.data! = null) { return response.data.toString().byteInputStream() } throw IOException( "errorCode : " + response.errorCode + " ; errorMsg : " + response.errorMsg ) } } // 3. @serializedName ("id") val id: Int = 0, @serializedName ("link") val link: String? = null, @SerializedName("author") val author: String? = null, @SerializedName("superChapterName") val superChapterName: String? Service interface WanAndroidService {@get ("/article/top/json") // Specify a ConvexTransformer for the change method, So you can convert BaseResponse into data the @ Transformer (WanAndroidConvexTransformer: : class) suspend fun getTopArticles () : List<Article> } // 5. Define Retrofit private val Retrofit by lazy {retrofit.builder ().baseurl ("https://wanandroid.com/") // must be ConvexConverterFactory comes before all Converter.factory. AddConverterFactory (ConvexConverterFactory()) .addConverterFactory(GsonConverterFactory.create()) .build() } private val wanAndroidService by lazy { retrofit.create(WanAndroidService::class.java) } // 6. LifecycleScope. Launch (Main) {val result = withContext(IO) {// A try catch is required. Throws an exception because of his failure to request runCatching {wanAndroidService. GetTopArticles ()}. The onSuccess} {return @ withContext it. The onFailure { It. PrintStackTrace () return@withContext it}}Copy the code

summary

BaseResponse can now be used to define a Method.

It also supports a variety of different data types, since different methods can specify different ConvexTransformer, regardless of how the BaseResponse is handled. Because the specific business code is given specific Data Data.

Annotations I have to admire the design of Retrofit. The Converter Factory uses the parameter Annotations to represent a large amount of electricity. Salute to Square, Salute~~

Android Advanced Development System Advanced notes, the latest interview review notes PDF,My lot

At the end of the article

Your likes collection is the biggest encouragement to me! Welcome to follow me, share Android dry goods, exchange Android technology. If you have any comments or technical questions about this article, please leave a comment in the comments section.