Public number: byte array, hope to help you 😇😇

Back in 2019, I wrote two articles describing how I encapsulated and implemented a web request framework step by step, which can be viewed as version 1.0 and version 2.0 😇😇

  • Step by step encapsulation implements its own Network request framework 1.0
  • Step by step encapsulation implements its own Network request framework 2.0

The 1.0 version uses Java + Jetpack + RxJava + Retrofit, and the 2.0 version uses Kotlin + Jetpack + RxJava + Retrofit. One of the major changes in version 2.0 is the replacement of the implementation language, which relies on the brevity and semantic expressiveness of Kotlin, making the entire web request framework much shorter. At this point, RxJava’s relevance to Android developers has been eroded by the advent of Kotlin coroutines: Kotlinx. coroutines

In the world of technology, new technologies are always coming out, and articles from that time are a little out of date now, which is why I wrote this article, version 3.0

A, ReactiveHttp

The concept of coroutines has been around for years, but Kotlin coroutines were only released in version 1.0 in 2018, and will be familiar to Android developers for a bit longer. What coroutines mean is not what this article is supposed to discuss, but if you look at the benefits of coroutines, I’m sure you’ll like it

With that said, here’s the GitHub 3.0 link: ReactiveHttp

Version 3.0 of the technology stack has been updated to Kotlin + Jetpack + Coroutines + Retrofit, hosted to jitpack. IO, where interested readers can import dependencies directly and remotely

  allprojects {
   	repositories {
       maven { url 'https://jitpack.io' }
    }
  }
	
  dependencies {
      implementation 'com.github.leavesC:ReactiveHttp:latest_version'
  }
Copy the code

Two, can bring you what

What can ReactiveHttp do for you?

  • More modern technology stack. Kotlin + Jetpack + Retrofit is probably the most basic component that most Android developers use right now. Kotlin coroutines are relatively untouchable, but I think coroutines are one of the main directions of the future. After all, even Retrofit supports Kotlin coroutines natively, so this library will be more in line with the real-world needs of most developers
  • Minimalist design concept. ReactiveHttp currently contains only twelve Kotlin files and is designed to be more convention than configuration, with most configuration items customizable in the form of method overwrites
  • Minimalist use. By holding a RemoteDataSource object, developers can make asynchronous and synchronous requests anywhere. In addition, performing network request callbacks is naturally essential, and ReactiveHttp provides multiple event callbacks: OnStart, onSuccess, onSuccessIO, onFailed, onFailToast, onCancelled, onFinally, etc., can be declared as needed, or even not implemented at all
  • Support universal automation behavior. For network requests, behaviors such as showLoading, dismissLoading, showToast and so on are universal. We definitely do not want each network request to be triggered by manually invoking methods, but rather to be automatically completed during the process of initiating network requests. ReactiveHttp provides the ability to automate the above generic UI actions, and each action is tied to a lifecycle, avoiding common memory leaks and NPE problems, and providing an entry point for external users to customize various other actions
  • Extremely low access costs. ReactiveHttp does not enforce that externals must inherit from anyBaseViewModelOr is itBaseActivity/BaseFragmentExternal as long as through implementationIUIActionEventObserver,IViewModelActionEventBoth interfaces enjoy the benefits of ReactiveHttp. Of course, you can implement no interface at all if you don’t need the automated behavior of ReactiveHttp
  • Supports concurrent requests from multiple interfaces (two or three interfaces) at the same time, and synchronously calls back when the network request is successful. Therefore, the total time of multiple interfaces depends on the interface that consumes the longest time in theory, thus shortening the waiting time of users and improving user experience

What doesn’t ReactiveHttp give you?

  • The application domain of ReactiveHttp itself is focused on interface requests, so it has mandatory constraints on the form of interface return values, and does not encapsulate file download, file upload and other functions
  • I’m sure there is, but I haven’t thought of it yet

ReactiveHttp has been running stably in our project for more than a year, in this process I am also gradually optimized, so that it can be more suitable for the needs of different external environments, so far I think it is stable enough, I hope to help you 😇😇

Iii. Architecture description

Right now, more than 90 percent of web requests from Android clients rely directly or indirectly on OkHttp? The Web request framework referred to in this article is a layer of encapsulation on top of OkHttp. The native OkHttp is not easy to use, even a little cumbersome. Retrofit is an improvement in ease of use, but it’s not that neat when used directly. So we tend to wrap OkHttp or Retrofit around our own project architecture, which is the starting point for ReactiveHttp

Also, aren’t most projects now using Jetpack as a set of components to implement the MVVM architecture? ReactiveHttp links Jetpack with Retrofit to make the network request process more responsive and provide more reliable lifecycle security and automated behavior

ReactiveHttp is different from most web request frameworks, which only focus on completing the web request and transmitting the result. ReactiveHttp also implements the function of binding the web request with ViewModel and Activity/Fragment. What ReactiveHttp wants to do is to achieve as many common behaviors as possible, without each layer being strongly dependent on a specific parent class

Google has an official guide to the best application architectures. Where each component depends only on the component at the level below it. ViewModel is a concrete implementation of the principle of separation of concerns. It exists as a carrier and processor of user data. Activities/fragments only rely on ViewModel, which is used to respond to the input of the interface layer and drive the changes of the interface layer. Repository is used to provide a single data source and data storage domain for the ViewModel. Repository can rely on both the persistent data model and remote server data sources

The design of ReactiveHttp is similar to the Best Application Architecture Guide recommended by Google

  • The BaseRemoteDataSource is the lowest level data provider and only serves to provide data to the upper layer. It provides multiple synchronous and asynchronous request methods and relies on the IUIActionEvent interface to communicate with the BaseReactiveViewModel
  • As the carrier and handler of user data, BaseReactiveViewModel contains multiple LiveData related to network request events to drive UI changes at the interface layer. The IViewModelActionEvent interface is used to connect BaseReactiveActivity and BaseReactiveActivity
  • BaseReactiveActivity contains the logic that interacts with the system and the user, responds to changes in the data in the BaseReactiveViewModel, and provides a way to bind to the BaseReactiveViewModel

As mentioned above, ReactiveHttp provides the ability to automatically complete showLoading, dismissLoading, showToast and other actions during network requests. First, the BaseRemoteDataSource uses the IUIActionEvent interface to inform the BaseReactiveViewModel of the action that needs to be triggered during the network request. In this way, the value of ShowLoadingLiveData, DismissLoadingLiveData and ShowToastLiveData changes in chain. BaseReactiveActivity performs uI-level operations by listening for changes in LiveData values

Iv. Common practices

The following steps should be the norm for most applications today when making network requests

The data returned by the server to the mobile terminal is communicated with Json with a specific format. The integer status is used to indicate whether the request is successful or not. In case of failure, it is directly showToast(MSG), and data needs to be declared with generics. Similar to HttpWrapBean

{
    "status":200."msg":"success"."data":""
}

data class HttpWrapBean<T>(val status: Int.val msg: String, val data: T)
Copy the code

Then declare the Api in the interface, which is also common usage with Retrofit. Depending on the situation in the project, the developer might use Call or Observable as the outermost data wrapper class for each interface return value, and then use HttpWrapBean as the wrapper class for the concrete data class

interface ApiService {

    @POST("api1")
    fun api1(a): Observable<HttpWrapBean<Int>>
    
    @GET("api2")
    fun api2(a): Call<HttpWrapBean<String>>

}
Copy the code

Then, using RxJava in your project, you need to complete the network request as follows

    val retrofit = Retrofit.Builder()
        .baseUrl("https://xxx.com")
        .addConverterFactory(GsonConverterFactory.create())
        .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
        .build()
    val service = retrofit.create(ApiService::class.java)
    val call: Observable<HttpWrapBean<Int>> = service.api1()
    call.subscribe(object : Consumer<HttpWrapBean<Int> > {override fun accept(userBean: HttpWrapBean<Int>?{}},object : Consumer<Throwable> {
        override fun accept(t: Throwable?).{}})Copy the code

Five, simple introduction

ReactiveHttp is much simpler to use than the examples given above. Let’s look at how network requests can be completed using ReactiveHttp

ReactiveHttp needs to know the result of the network request, but it does not know what field names will be used externally to identify the three values in the HttpWrapBean, so it requires an external implementation of the IHttpWrapBean interface to do so. For example, you can do this:

data class HttpWrapBean<T>(val status: Int.val msg: String, val data: T) : IHttpWrapBean<T> {

    override val httpCode: Int
        get() = status

    override val httpMsg: String
        get() = msg

    override val httpData: T
        get() = data

    // Whether the network request was successful
    override val httpIsSuccess: Boolean
        get() = status == 200

}
Copy the code

Suspend is used to modify interface methods, and no additional wrapper classes are required. Suspend was introduced by the Kotlin coroutine, which is used internally by Retrofit to complete the network request when the interface method is decorated with this keyword

interface ApiService {

    @GET("config/district")
    suspend fun getProvince(a): HttpWrapBean<List<DistrictBean>>

}
Copy the code

ReactiveHttp provides the RemoteExtendDataSource to be externally inherited. The RemoteExtendDataSource contains all of the network request methods, and only three necessary fields and methods need to be implemented externally based on the actual situation

  • ReleaseUrl. The BaseUrl of the application
  • CreateRetrofit. Used to create Retrofit, where developers can customize OkHttpClient
  • ShowToast. When a network request fails, this method is used to inform the user of the failure reason

For example, you can implement your own DataSource for your project like this, which contains the developer’s global network request configuration for the entire project

class SelfRemoteDataSource(iActionEvent: IUIActionEvent?) : RemoteExtendDataSource<ApiService>(iActionEvent, ApiService::class.java) {

    companion object {

        private val httpClient: OkHttpClient by lazy {
            createHttpClient()
        }

        private fun createHttpClient(a): OkHttpClient {
            val builder = OkHttpClient.Builder()
                    .readTimeout(1000L, TimeUnit.MILLISECONDS)
                    .writeTimeout(1000L, TimeUnit.MILLISECONDS)
                    .connectTimeout(1000L, TimeUnit.MILLISECONDS)
                    .retryOnConnectionFailure(true)
                    .addInterceptor(FilterInterceptor())
                    .addInterceptor(MonitorInterceptor(MainApplication.context))
            return builder.build()
        }

    }

    /** * Subclasses implement this field to get the interface BaseUrl */ in the release environment
    override val releaseUrl: String
        get() = "https://restapi.amap.com/v3/"

    /** * Allow subclasses to implement their own logic for creating Retrofit * there is no need to cache Retrofit instances externally, ReactiveHttp is already cached internally * but externally it needs to determine whether OKHttpClient needs to be cached *@param baseUrl
     */
    override fun createRetrofit(baseUrl: String): Retrofit {
        return Retrofit.Builder()
                .client(httpClient)
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
    }

    override fun showToast(msg: String) {
        Toast.makeText(MainApplication.context, msg, Toast.LENGTH_SHORT).show()
    }

}
Copy the code

After that, we can rely on SelfRemoteDataSource to make network requests anywhere, declaring Callback methods as needed. In addition, since the extension function is used, the interface methods in The ApiService can be called directly from SelfRemoteDataSource without having to reference or guide the package

	private val remoteDataSource = SelfRemoteDataSource(null)

    val provinceLiveData = MutableLiveData<List<DistrictBean>>()

    fun reqProvince(a) {
        // The enqueueLoading method will pop up a loading box when a network request is initiated
        // Enqueue does not pop up the loading box
        remoteDataSource.enqueueLoading({
            getProvince()
        }) {
            /** * execute */ after Loading is displayed and before the network request is started
            onStart {

            }
            /** * Callback */ when the network request succeeds
            onSuccess {
                provinceLiveData.value = it
            }
            /** * This method is called when a network request is cancelled
            onCancelled {

            }
            /** * this method is called when a network request fails, before onFinally is called
            onFailed {
                val httpException = it
                val realException = httpException.realException
                val errorCode = httpException.errorCode
            }
            /** * Used to control whether the network request fails. Toast failure reason * default to true, that is, the Toast prompt */
            onFailToast {
                true
            }
            /** * execute */ after the network request ends (whether the request is successful or not) and before hiding the Loading
            onFinally {

            }
        }
    }
Copy the code

Six, advanced use

The enqueueLoading method is used to initiate a network request using SelfRemoteDataSource, but the loading box is not actually displayed. RemoteDataSource, ViewModel, and Activity are required to complete UI actions such as ShowLoading, DismissLoading, and ShowToast. The SelfRemoteDataSource needs to be associated with the other two to feed back the UI behavior that needs to be triggered to the Activity

This can be done by directly inheriting from The BaseReactiveActivity and BaseReactiveViewModel, or by implementing the corresponding interfaces. Of course, you don’t have to make any of these changes if you don’t need the various automation behaviors of ReactiveHttp

Overall, ReactiveHttp has extremely low access costs

1, BaseReactiveActivity

BaseReactiveActivity is a default BaseActivity provided by ReactiveHttp. It implements the IUIActionEventObserver interface and provides some default parameters and behaviors. Examples are CoroutineScope and showLoading. But in most cases, our own projects will not inherit external activities, but will have a globally unified BaseActivity that we implement ourselves, so if you don’t want to inherit BaseReactiveActivity, You can implement the IUIActionEventObserver interface yourself, as shown below

@SuppressLint("Registered")
abstract class BaseActivity : AppCompatActivity(), IUIActionEventObserver {

    protected inline fun <reified VM> getViewModel(
        factory: ViewModelProvider.Factory? = null.noinline initializer: (VM. (lifecycleOwner: LifecycleOwner) - >Unit)? = null
    ): Lazy<VM> where VM : ViewModel, VM : IViewModelActionEvent {
        return getViewModel(VM::class.java, factory, initializer)
    }

    override val lifecycleSupportedScope: CoroutineScope
        get() = lifecycleScope

    override val lContext: Context?
        get() = this

    override val lLifecycleOwner: LifecycleOwner
        get() = this

    private var loadDialog: ProgressDialog? = null

    override fun showLoading(job: Job?). {
        dismissLoading()
        loadDialog = ProgressDialog(lContext).apply {
            setCancelable(true)
            setCanceledOnTouchOutside(false)
            // Cancel the network request when the popover is destroyed
// setOnDismissListener {
// job? .cancel()
/ /}
            show()
        }
    }

    override fun dismissLoading(a){ loadDialog? .takeIf { it.isShowing }? .dismiss() loadDialog =null
    }

    override fun showToast(msg: String) {
        if (msg.isNotBlank()) {
            Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
        }
    }

    override fun finishView(a) {
        finish()
    }

    override fun onDestroy(a) {
        super.onDestroy()
        dismissLoading()
    }

}
Copy the code

2, BaseReactiveViewModel

Similarly, BaseReactiveViewModel is a default BaseViewModel provided by ReactiveHttp that implements the IViewModelActionEvent interface, Used to receive UI-layer behavior initiated by RemoteDataSource. If you don’t want to inherit from The Base ActiveViewModel, you can implement the IViewModelActionEvent interface yourself, as shown below

open class BaseViewModel : ViewModel(), IViewModelActionEvent {

    override val lifecycleSupportedScope: CoroutineScope
        get() = viewModelScope

    override val showLoadingEventLD = MutableLiveData<ShowLoadingEvent>()

    override val dismissLoadingEventLD = MutableLiveData<DismissLoadingEvent>()

    override val showToastEventLD = MutableLiveData<ShowToastEvent>()

    override val finishViewEventLD = MutableLiveData<FinishViewEvent>()

}
Copy the code

3. Relevance

After completing the above two steps, the developer can associate the RemoteDataSource, ViewModel, and Activity as shown below. The WeatherActivity initializes the WeatherViewModel and binds multiple internal UILiveData using the getViewModel method. The data listening for the WeatherViewModel’s internal and business-specific DataLiveData is done in the lambda expression, and all automation behavior is now bound

class WeatherViewModel : BaseReactiveViewModel() {

    private val remoteDataSource by lazy {
        SelfRemoteDataSource(this)}val forecastsBeanLiveData = MutableLiveData<ForecastsBean>()

    fun getWeather(city: String) {
        remoteDataSource.enqueue({
            getWeather(city)
        }) {
            onSuccess {
                if (it.isNotEmpty()) {
                    forecastsBeanLiveData.value = it[0]}}}}}class WeatherActivity : BaseReactiveActivity() {

    private val weatherViewModel by getViewModel<WeatherViewModel> {
        forecastsBeanLiveData.observe(this@WeatherActivity, {
            showWeather(it)
        })
    }

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_weather)
        weatherViewModel.getWeather("adCode")}private fun showWeather(forecastsBean: ForecastsBean){}}Copy the code

Seven, other

1, BaseRemoteDataSource

The RemoteExtendDataSource provides a number of methods that can be copied, both for configuring the individual network request parameters of OkHttp and for external process control. For example, you can implement your project’s BaseRemoteDataSource like this

class BaseRemoteDataSource(iActionEvent: IUIActionEvent?) : RemoteExtendDataSource<ApiService>(iActionEvent, ApiService::class.java) {

    companion object {

        private val httpClient: OkHttpClient by lazy {
            createHttpClient()
        }

        private fun createHttpClient(a): OkHttpClient {
            val builder = OkHttpClient.Builder()
                    .readTimeout(1000L, TimeUnit.MILLISECONDS)
                    .writeTimeout(1000L, TimeUnit.MILLISECONDS)
                    .connectTimeout(1000L, TimeUnit.MILLISECONDS)
                    .retryOnConnectionFailure(true)
                    .addInterceptor(FilterInterceptor())
                    .addInterceptor(MonitorInterceptor(MainApplication.context))
            return builder.build()
        }
    }

    /** * Subclasses implement this field to get the interface BaseUrl */ in the release environment
    override val releaseUrl: String
        get() = HttpConfig.BASE_URL_MAP

    /** * Allow subclasses to implement their own logic for creating Retrofit * there is no need to cache Retrofit instances externally, ReactiveHttp is already cached internally * but externally it needs to determine whether OKHttpClient needs to be cached *@param baseUrl
     */
    override fun createRetrofit(baseUrl: String): Retrofit {
        return Retrofit.Builder()
                .client(httpClient)
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
    }

    /** * This method can be overridden if the outside world wants to do special processing on the Throwable to change the Exception type * for example, When the token expires, the interface will typically return a specific httpCode indicating that the mobile device needs to update the token * at this point, the external implementation of a BaseException subclass TokenInvalidException and return * This can be done with a strong reminder of the cause of the interface exception, without having to worry about how much httpCode is */
    override fun generateBaseException(throwable: Throwable): BaseHttpException {
        return if (throwable is BaseHttpException) {
            throwable
        } else {
            LocalBadException(throwable)
        }
    }

    /** * Is used by external relay to control whether an onFail callback is called when an exception is thrown, or if true is returned, otherwise no callback *@param httpException
     */
    override fun exceptionHandle(httpException: BaseHttpException): Boolean {
        return true
    }

    /** * is used to report exceptions during network requests externally for recording *@param throwable
     */
    override fun exceptionRecord(throwable: Throwable) {
        Log.e("SelfRemoteDataSource", throwable.message ? :"")}/** * is used to format a BaseException so that Toast prompts an error message * if the request fails@param httpException
     */
    override fun exceptionFormat(httpException: BaseHttpException): String {
        return when (httpException.realException) {
            null -> {
                httpException.errorMessage
            }
            is ConnectException, is SocketTimeoutException, is UnknownHostException -> {
                "Connection timed out. Please check your network Settings."
            }
            else- > {"Request process throws exception:" + httpException.errorMessage
            }
        }
    }

    override fun showToast(msg: String) {
        Toast.makeText(MainApplication.context, msg, Toast.LENGTH_SHORT).show()
    }

}
Copy the code

In addition, developers can declare an instance of the BaseRemoteDataSource variable directly in their BaseViewModel, and all child viewModels use the same DataSource configuration globally. You can also declare a BaseRemoteDataSource if you need to use a different BaseUrl for certain interfaces

open class BaseViewModel : BaseReactiveViewModel() {

    /** * Normally there should be only one RemoteDataSource implementation class in a single project, which uses the same configuration globally * but the parent class should also allow subclasses to use a unique RemoteDataSource, which allows subclasses to override this field */
    protected open val remoteDataSource by lazy {
        BaseRemoteDataSource(this)}}Copy the code

2, BaseHttpException

BaseHttpException is a wrapper class that ReactiveHttp wraps all kinds of exceptions that occur during network requests. Any exception information transmitted to the outside will be encapsulated as BaseHttpException. BaseHttpException has two default subclasses for server and local exceptions

/ * * *@paramErrorCode indicates the errorCode returned by the server or the local errorCode * defined in HttpConfig@paramErrorMessage Indicates the original exception information returned by the server or thrown during a request@paramRealException is used to store real runtime exceptions */ when code is a local error code
open class BaseHttpException(val errorCode: Int.val errorMessage: String, val realException: Throwable?) : Exception(errorMessage) {

    companion object {

        /** * This variable is used to indicate that an exception */ was thrown during the network request process
        const val CODE_ERROR_LOCAL_UNKNOWN = -1024520

    }

    /** * if the server returns the code! = An exception caused by successCode */
    val isServerCodeBadException: Boolean
        get() = this is ServerCodeBadException

    /** * An exception was thrown during the network request (e.g., the Json returned by the server failed to parse) */
    val isLocalBadException: Boolean
        get() = this is LocalBadException

}

/** * THE API request was successful, but code! = successCode *@param errorCode
 * @param errorMessage
 */
class ServerCodeBadException(errorCode: Int, errorMessage: String) : BaseHttpException(errorCode, errorMessage, null) {

    constructor(bean: IHttpWrapBean<*>) : this(bean.httpCode, bean.httpMsg)

}

/** * The request process throws an exception *@param throwable
 */
class LocalBadException(throwable: Throwable) : BaseHttpException(CODE_ERROR_LOCAL_UNKNOWN, throwable.message? :"", throwable)
Copy the code

Sometimes developers need special handling for exceptions and can implement their own BaseHttpException subclasses. For example, when a token expires, the interface usually returns a specific httpCode indicating that the mobile needs to update the token. To implement a BaseHttpException subclass TokenInvalidException and return it in the BaseRemoteDataSource, use the following method: You don’t have to worry about what the httpCode is

class TokenInvalidException : BaseHttpException(CODE_TOKEN_INVALID, "Token has expired".null)

open class BaseRemoteDataSource(iActionEvent: IUIActionEvent?) : RemoteExtendDataSource<ApiService>(iActionEvent, ApiService::class.java) {

    companion object {

        private val httpClient: OkHttpClient by lazy {
            createHttpClient()
        }

        private fun createHttpClient(a): OkHttpClient {
            val builder = OkHttpClient.Builder()
                    .readTimeout(1000L, TimeUnit.MILLISECONDS)
                    .writeTimeout(1000L, TimeUnit.MILLISECONDS)
                    .connectTimeout(1000L, TimeUnit.MILLISECONDS)
                    .retryOnConnectionFailure(true)
                    .addInterceptor(FilterInterceptor())
                    .addInterceptor(MonitorInterceptor(MainApplication.context))
            return builder.build()
        }
    }

    /** * Subclasses implement this field to get the interface BaseUrl */ in the release environment
    override val releaseUrl: String
        get() = "https://restapi.amap.com/v3/"

    /** * Allow subclasses to implement their own logic for creating Retrofit * there is no need to cache Retrofit instances externally, ReactiveHttp is already cached internally * but externally it needs to determine whether OKHttpClient needs to be cached *@param baseUrl
     */
    override fun createRetrofit(baseUrl: String): Retrofit {
        return Retrofit.Builder()
                .client(httpClient)
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
    }

    /** * This method can be overridden if the outside world wants to do special processing on the Throwable to change the Exception type * for example, When the token expires, the interface will typically return a specific httpCode indicating that the mobile device needs to update the token * at this point, the external implementation of a BaseException subclass TokenInvalidException and return * This can be done with a strong reminder of the cause of the interface exception, without having to worry about how much httpCode is */
    override fun generateBaseException(throwable: Throwable): BaseHttpException {
        if (throwable is ServerCodeBadException && throwable.errorCode == BaseHttpException.CODE_TOKEN_INVALID) {
            return TokenInvalidException()
        }
        return if (throwable is BaseHttpException) {
            throwable
        } else {
            LocalBadException(throwable)
        }
    }

    /** * Is used by external relay to control whether an onFail callback is called when an exception is thrown, or if true is returned, otherwise no callback *@param httpException
     */
    override fun exceptionHandle(httpException: BaseHttpException): Boolean {
        return httpException !is TokenInvalidException
    }

    override fun showToast(msg: String) {
        Toast.makeText(MainApplication.context, msg, Toast.LENGTH_SHORT).show()
    }

}
Copy the code

Eight, the end

This has covered most of the important points of ReactiveHttp, but due to my writing and presentation skills, some readers may be confused, so I recommend clone the code and try it out. ReactiveHttp sample code includes a complete weather forecast function, through the sample code to help you get started faster 😇😇

In addition, the GitHub submission history of ReactiveHttp started in December 2020, while the 1.0 version of the article was published in February 2019, because I felt that the old code had too many redundant resources, which made clone too slow. So I reset the code so that three versions correspond to three branches. The 1.0 and 2.0 versions only contain the last submission at that time, and interested readers can refer to my previous two articles

Click to jump to GitHub: ReactiveHttp