This article is a simple practice, using the previous learning to write a simple network request framework. If you don’t know much about LiveData, ViewMode, etc., check out: Jetpack 2: Lifecycles Jetpack 3: LiveData Jetpack 4: ViewModel

This article uses Retrofit+ coroutine +LiveData+ViewMode to build a network request component. This is the simplest demo, on this basis can be appropriately encapsulated to meet their needs for business logic.

1. Main contents

  • Import dependence
  • RetrofitManger: Mainly used for Retrofit initialization
  • LiveData: data distribution
  • ViewModel: data retrieval (extension functions using ViewModel coroutines)

2. Import dependencies

Note: the dependencies I use are up to date, not up to date.

Implementation 'Androidx. core:core-ktx:1.3.2' implementation "Org. Jetbrains. Kotlin: kotlin - stdlib: $kotlin_version" implementation 'androidx. Appcompat: appcompat: 1.2.0' / / lifecycle Implementation 'androidx. Lifecycle: lifecycle - common - java8:2.3.0 - alpha01' / / liveData implementation 'androidx. Lifecycle: lifecycle - livedata - KTX: 2.3.0 - alpha01' / / ViewModel implementation 'androidx. Lifecycle: lifecycle - viewmodel - KTX: 2.3.0 - alpha01' / / coroutines implementation "Org. Jetbrains. Kotlinx: kotlinx coroutines - android: 1.3.4"/request/network implementation 'com. Squareup. Retrofit2: retrofit: 2.9.0' Implementation 'com. Squareup. Retrofit2: converter - gson: 2.9.0' implementation 'com. Squareup. Okhttp3: logging - interceptor: 4.7.2'Copy the code

3. Network request related

RetrofitManger we use retrofit2.9.0, retrofit2.6.0 can use the suspend keyword and Ctrip can be used in a good combination. RetrofitManger initializers Retrofit, Okhttp, and so on. My Api uses a singleton to prevent a large number of Retrofit objects from being generated throughout the application, ensuring that only one Api object is generated.

The interface I’m using here is wanAndroid’s public Api.

object RetrofitManger {

    var mApi: AppApi? = null

    private const val CONNECTION_TIME_OUT = 10L
    private const val READ_TIME_OUT = 10L

    var API_URL = "https://www.wanandroid.com"

    fun getApiService(): AppApi {
        if (mApi == null) {
            synchronized(this) {
                if (mApi == null) {
                    val okHttpClient =
                        buildOkHttpClient()
                    mApi =
                        buildRetrofit(
                            API_URL,
                            okHttpClient
                        ).create(AppApi::class.java)
                }
            }
        }
        return mApi!!
    }

    private fun buildOkHttpClient(): OkHttpClient.Builder {
        val logging = HttpLoggingInterceptor()
        logging.level = HttpLoggingInterceptor.Level.BODY
        return OkHttpClient.Builder()
            .addInterceptor(logging)
            .connectTimeout(CONNECTION_TIME_OUT, TimeUnit.SECONDS)
            .readTimeout(READ_TIME_OUT, TimeUnit.SECONDS)
            .proxy(Proxy.NO_PROXY)
    }

    private fun buildRetrofit(baseUrl: String, builder: OkHttpClient.Builder): Retrofit {
        val client = builder.build()

        return Retrofit.Builder()
            .baseUrl(baseUrl)
            .addConverterFactory(GsonConverterFactory.create())
            .client(client).build()
    }
}
Copy the code

Response:

class Response<T>( val data: T? , val info: Int, val msg: String )Copy the code

Api: You can see the method suspend tag used, which is an asynchronous method used by Ctrip.

@get ("article/list/{page}/json") suspend fun getArticleList(@path ("page") page: Int): Response<ArticleListBean> }Copy the code

4. Wrap the ViewModel

The method of getting the data, and the LiveData, are encapsulated in the ViewModel. LiveData is used to observe changes to the data, and when it is retrieved, it is distributed to the Activity using postValue. It also encapsulates the method of exception handling:

class CoroutinesViewModel : ViewModel() {

    val api by lazy { RetrofitManger.getApiService() }
    var articlesLiveData: MutableLiveData<MutableList<ArticleBean>> = MutableLiveData()

    var apiError:MutableLiveData<Throwable> = MutableLiveData()

    fun getArticles(page: Int) {

        val exception = CoroutineExceptionHandler { coroutineContext, throwable ->
            apiError.postValue(throwable)
            Log.i("CoroutinesViewModel",throwable.message!!)
        }

        viewModelScope.launch(exception) {
            val respose = api.getArticleList(page)
            if (respose.info == 0) {
                articlesLiveData.postValue(respose.data?.datas)
            } else {
                articlesLiveData.postValue(mutableListOf())
            }
        }
    }

}

Copy the code
4.1, viewModelScope. Launch

This is the expansion method of ViewModel. Ctrip itself also provides the Launch method, but it is suggested to use the method provided by ViewModel. Canceled when ViewModel will be cleared This scope will be canceled when a ViewModel is destroyed.

/** * [CoroutineScope] tied to this [ViewModel]. * This scope will be canceled when ViewModel will be cleared, i.e [ViewModel.onCleared] is called * * This scope is bound to * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate] */ val ViewModel.viewModelScope: CoroutineScope get() { val scope: CoroutineScope? = this.getTag(JOB_KEY) if (scope ! = null) { return scope } return setTagIfAbsent(JOB_KEY, CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)) } internal class CloseableCoroutineScope(context:  CoroutineContext) : Closeable, CoroutineScope { override val coroutineContext: CoroutineContext = context override fun close() { coroutineContext.cancel() } }Copy the code

5. Activity data display

Initialize the ViewModel to receive the data distributed by LiveData.

class MainActivity : AppCompatActivity() {

    private val viewModel by lazy { ViewModelProvider(this).get(CoroutinesViewModel::class.java) }

    private var textShowData:TextView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initView()
        startObserver()
    }

    private fun initView() {
        val btnGetData = findViewById<Button>(R.id.btnGetData)
        btnGetData.setOnClickListener {
            viewModel.getArticles(1)
        }
        textShowData = findViewById<TextView>(R.id.tvInfo)
    }

    private fun startObserver() {
        viewModel.articlesLiveData.observe(this, Observer {
            it.run {
                if (this.size > 0) {
                    val text = StringBuilder()
                    this.forEach {
                        text.append(it.title+"\n")
                    }
                    textShowData?.text = text
                }
            }
        })

        viewModel.apiError.observe(this, Observer {

        })
    }
}
Copy the code

Running results:

6, the final conclusion

If you have a certain understanding of LiveData, ViewModel, Ctrip and Retrofit, the encapsulation ideas above seem to be clear and simple. Therefore, if you want to better understand this package, you need to have a simple understanding of LiveData, ViewModel, Ctrip and Retrofit components first.

Source code: LibNetwork · Chen Zaifeng /JetpackStudy – Code Cloud – Open Source China (gitee.com)