Error/Loading processing when using Retrofit+LiveData

At present, there are many projects that use Retrofit2+LiveData for API requests. Unlike RxJava, LiveData can easily process Error. Therefore, we want to encapsulate a tool class based on LiveData to uniformly process Error/Loading in API requests

HttpManager

open class HttpManager<T>(context: Context, serviceClass: Class<T>) {

    private val BASE_URL: String = context.getString(R.string.wallet_api_url)
    private var isLoading: MutableLiveData<Boolean>? = null
    private var error: MutableLiveData<ErrorResponse>? = null

    private val service = getRetrofit(
            serviceClass,
            BASE_URL,
            getHttpClient(context)
    )

    fun init(
            isLoading: MutableLiveData<Boolean>? = null,
            error: MutableLiveData<ErrorResponse>? = null
    ):T {
        this.isLoading = isLoading
        this.error = error
        return service
    }

    private fun createHeader(context: Context, request: Request): Request {
        return request.newBuilder()
                .addHeader("Accept", "application/json")
                .build()
    }

    private fun getHttpClient(context: Context) : OkHttpClient {
        val interceptor = Interceptor { chain ->
            val response =  chain.proceed(createHeader(context, chain.request()))
            if(response.code() != ErrorCode.HTTP_OK_200.httpErrorCode) {
                try {
                    val source =  response.body()?.source()
                    source?.request(java.lang.Long.MAX_VALUE)
                    val bodyString = source?.buffer?.clone()?.readString(Charset.forName("UTF-8")).toString()
                    val errorBase = Gson().fromJson(bodyString, ErrorBase::class.java)
                    error?.postValue(ErrorResponse(response.code(), errorBase))
                } catch (e: Exception) {
                    error?.postValue(ErrorResponse(response.code(), ErrorBase("Unknown Error", null)))
                }
            }
            response
        }

        return OkHttpClient.Builder()
                .addInterceptor(interceptor)
                .addInterceptor(getLoggingInterceptor())
                .eventListener(object: EventListener(){
                    override fun callStart(call: Call) {
                        isLoading?.postValue(true)
                        super.callStart(call)
                    }

                    override fun callEnd(call: Call) {
                        isLoading?.postValue(false)
                        super.callEnd(call)
                    }
                })
                .readTimeout(5, TimeUnit.SECONDS)
                .connectTimeout(5, TimeUnit.SECONDS)
                .build()
    }

    private fun getLoggingInterceptor(): HttpLoggingInterceptor {
        val logging = HttpLoggingInterceptor()
        if (BuildConfig.DEBUG) {
            logging.level = HttpLoggingInterceptor.Level.BODY
        } else {
            logging.level = HttpLoggingInterceptor.Level.NONE
        }
        return logging
    }

    private fun getRetrofit(serviceClass: Class<T>, baseUrl: String, httpClient: OkHttpClient) : T {
        val retrofit: Retrofit = Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(getConverter())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(httpClient)
                .build()
        return retrofit.create(serviceClass)
    }

    private fun getConverter() : Converter.Factory {
        return GsonConverterFactory.create(GsonBuilder()
                .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
                .create())
    }
}

The main tasks of HttpManager class are as follows:

  1. Create Retofit based on apiService,
  2. Handling loading status through EventListener
  3. Handling Error through Interceptor

ExampleApi

class ExampleApi(context: Context ) : HttpManager< ExampleApi. ExampleService (
    context, ExampleService::class.java) {
    interface ExampleService {
        @POST("/user/setting")
        fun setting(): Call<ExampleResponse>
    }

    fun setting(responseLiveData: MutableLiveData<ExampleResponse>, 
                isLoading: MutableLiveData<Boolean>?, error: MutableLiveData<ErrorResponse>?) {
        GlobalScope.launch {
            init(isLoading, error)
                    .setting()
                    .enqueue(object: Callback<ExampleResponse> {
                        override fun onFailure(call: Call<ExampleResponse>,
                                                 t: Throwable) {
                             //HttpManager is responsible for error handling, so nothing to do here
                        }
    
                        override fun onResponse(call: Call<ExampleResponse>,
                                            response: retrofit2.Response<ExampleResponse>) {
                            if (response.isSuccessful) {
                                responseLiveData.postValue(response.body())
                            }
                        }
                    })
        }
    }

    data class ExampleResponse(
            val hoge: Boolean,
            val fuga: String
    )
}

The main work of ExampleApi:

  1. Provide the calling method of the API request and accept the LiveData parameter
  2. Inherit HttpManager and make Api request through Retrofit

Activity

val response = MutableLiveData<ExampleApi.ExampleResponse>()
val error = MutableLiveData<ErrorResponse>()
val isLoading = MutableLiveData<Boolean>()

WalletUserApi(this).setting(response, isLoading, error)

response.observeForever { result ->
    Toast.makeText(this, "response: $result", Toast.LENGTH_SHORT ).show()
}

error.observeForever { result ->
    Toast.makeText(this, "response: $result", Toast.LENGTH_SHORT ).show()
}

isLoading.observeForever { 
    //"Data binding" means "data binding" means "feeling"
}

Activity does simple things. Just call Api class and pass in LiveData.

LIveData can be put into View Model management. All fragments or customized views within the scope of Activity can easily and timely subscribe to the latest status of Api request process.

Published 12 original articles, won praise 1, visited 304
Private letter follow

Tags: Retrofit Java JSON

Posted on Wed, 29 Jan 2020 11:12:25 -0500 by burnside