Kotlin + + Retrofit + MVVM coroutine elegant implementation network requests

Kotlin + + Retrofit + MVVM coroutine elegant implementation network requests

Foreword

Recently been practicing Kotlin, to be honest really nice with incense, the company plans to just give me a new project, he intended to build directly Kotlin project. Just overall framework set up is completed, so the first part of this network requests to others. This use to be coroutine + retrofit + mvvm model, I have here the direct use of a simple demo look at the specific implementation of it. Article only describes the realization of ideas, need to jump directly to the text at the end of the demo.

Project Configuration

First introduced first-dependent needed

implementation 'android.arch.lifecycle:extensions:1.1.1'
 //协程
 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1'
 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
 //retrofit + okHttp3
 implementation 'com.squareup.retrofit2:retrofit:2.4.0'
 implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
 implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'

Realization of ideas

Regardless of these design patterns, first to a simple network request, retrofit of the basic realization of what steps need to see

1. Create a retrofit

~~~
 val retrofit = Retrofit.Builder()
 .baseUrl(RetrofitClient.BASE_URL)
 .addConverterFactory(GsonConverterFactory.create())
 .addCallAdapterFactory(CoroutineCallAdapterFactory())
 .build()
~~~

2. Create service interfaces

~~~
 interface RequestService {
 @GET("wxarticle/chapters/json")
 fun getDatas() : Call<DataBean>
 }
~~~

3. initiate a request

~~~
 val service = retrofit.create(RequestService::class.java)
 service.getDatas().enqueue(object : Callback<DataBean> {
 override fun onFailure(call: retrofit2.Call<DataBean>, t: Throwable) {
 TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
 }
 override fun onResponse(call: retrofit2.Call<DataBean>, response: Response<DataBean>) {
 TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
 }
 })
~~~

This embodiment describes only a simple request of a retrofit, the actual project will be used after the package is substantially, but also to improve the readability of the code, reduce the coupling, each portion of the popular point, only to put their duties Okay doing the work, then we will go around one by one to carry out their duties to achieve

Coroutine achieve

Next, the above request into ways to achieve coroutine

1. Create RetrofitClient

object为了使RetrofitClient 只能有一个实例
~~~
 object RetrofitClient {
 val BASE_URL = "https://wanandroid.com/"
 val reqApi by lazy {
 val retrofit = Retrofit.Builder()
 .baseUrl(BASE_URL)
 .addConverterFactory(GsonConverterFactory.create())
 .addCallAdapterFactory(CoroutineCallAdapterFactory())
 .build()
 return@lazy retrofit.create(RequestService::class.java)
 }
 }
~~~

2. Create a service interface class

~~~
interface RequestService {
 @GET("wxarticle/chapters/json")
 fun getDatas() : Deferred<DataBean>
}
~~~

Follow-up because we will use to coroutines, so here will be replaced Deferred Call

3. initiate a request

~~~
 GlobalScope.launch(Dispatchers.Main) {
 withContext(Dispatchers.IO){
 val dataBean = RetrofitClient.reqApi.getDatas().await()
 }
 //更新ui
 }
~~~

The above uses coroutines, here only about his application, the specific venue and official documentation to learn more about. Coroutine network request, scheduling and IO unit, the main thread will block without fear

Coroutine + ViewModel + LiveData implemented

The above is also simply implemented, but is replaced coroutine, in the project may be further packaged, the ease of use mentioned earlier the MVVM, so ViewModel component architecture also uses the newly introduced and Android LiveData, look implement the ViewModel

class ScrollingViewModel : ViewModel() {
 private val TAG = ScrollingViewModel::class.java.simpleName
 private val datas: MutableLiveData<DataBean> by lazy { MutableLiveData<DataBean>().also { loadDatas() } }
 private val repository = ArticleRepository()
 fun getActicle(): LiveData<DataBean> {
 return datas
 }
 private fun loadDatas() {
 GlobalScope.launch(Dispatchers.Main) {
 getData()
 }
 // Do an asynchronous operation to fetch users.
 }
 private suspend fun getData() {
 val result = withContext(Dispatchers.IO){
// delay(10000)
 repository.getDatas()
 }
 datas.value = result
 }
}

View and ViewModel as an intermediary data, full-time data acquisition Repository, Repository look at the following code to initiate network requests to obtain data

 class ArticleRepository {
 suspend fun getDatas(): DataBean {
 return RetrofitClient.reqApi.getDatas().await()
 }
 }

In the following code Activity

 private fun initData() {
 model.getActicle().observe(this, Observer{
 //获取到数据
 toolbar.setBackgroundColor(Color.RED)
 })
 }

Subsequent optimization

1. The memory leak problem solution

Knot and the bigwigs of the views we will use GlobalScope may be a problem of memory leaks has been optimized. Because the process requested coroutine, if the case ViewModel destruction, which is requesting coroutine, it will not be destroyed, a memory leak, so ViewModel onCleared inside, even if the end of task coroutine, with reference to the following code.

 open class BaseViewModel : ViewModel(), LifecycleObserver{
 private val viewModelJob = SupervisorJob()
 private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
 //运行在UI线程的协程
 fun launchUI( block: suspend CoroutineScope.() -> Unit) {
 try {
 uiScope.launch(Dispatchers.Main) {
 block()
 }
 }catch (e:Exception){
 e.printStackTrace()
 }
 }
 override fun onCleared() {
 super.onCleared()
 viewModelJob.cancel()
 }
}

Of course, the best way is to use viewModelScope, but when I introduced this package, will complain, because of the recent busy being not more urgent to solve, follow-up questions have time I will continue to modify, but also hope you can help big brother pointing

2. The request code optimization

Look at the previous request code

private suspend fun getData() {
 val result = withContext(Dispatchers.IO){
// delay(10000)
 repository.getDatas()
 }
 datas.value = result
 }

Every time the need to write a withContext (), practice, feeling a little inconvenient, and thus be thought for a moment, how to give him the seal into the request which method? code show as below

open class BaseRepository {
 suspend fun <T : Any> request(call: suspend () -> ResponseData<T>): ResponseData<T> {
 return withContext(Dispatchers.IO){ call.invoke()}
 }
}

By BaseRepository written inside a special request method, so that each request simply execute the request on the line reference to the following

class ArticleRepository : BaseRepository() {
 suspend fun getDatas(): ResponseData<List<Data>> {
 return request {
 delay(10000)
 Log.i(ScrollingViewModel::class.java.simpleName,"loadDatas1 run in ${Thread.currentThread().name}")
 RetrofitClient.reqApi.getDatas().await() }
 }
}

Note: This delay (10000) just my test, meaning that the current coroutine sleep, prevent sprouting new add in their own projects, or the need to talk about the

Look at the ViewModel is too simple

class ScrollingViewModel : BaseViewModel() {
 private val TAG = ScrollingViewModel::class.java.simpleName
 private val datas: MutableLiveData<List<Data>> by lazy { MutableLiveData<List<Data>>().also { loadDatas() } }
 private val repository = ArticleRepository()
 fun getActicle(): LiveData<List<Data>> {
 return datas
 }
 private fun loadDatas() {
 launchUI {
 Log.i(TAG,"loadDatas1 run in ${Thread.currentThread().name}")
 val result = repository.getDatas()
 Log.i(TAG,"loadDatas3 run in ${Thread.currentThread().name}")
 datas.value = result.data
 }
 // Do an asynchronous operation to fetch users.
 }
}

Note the request section, just two sentences, a request to initiate val result = repository.getDatas (), then the assignment is for our LiveData, there is no synchronization code looks feeling, this is the coroutine charm, in order to verify our request does not block the main thread, I print the log

06-19 12:26:35.736 13648-13648/huaan.com.mvvmdemo I/ScrollingViewModel: loadDatas start run in main
06-19 12:26:45.743 13648-13684/huaan.com.mvvmdemo I/ScrollingViewModel: request run in DefaultDispatcher-worker-1
06-19 12:26:46.227 13648-13648/huaan.com.mvvmdemo I/ScrollingViewModel: loadDatas end run in main

See, carry out their duties, the effect is great

Exception Handling

So now we get only to find there is no exception handler, when the request fails, the project collapsed, and this is not what we want results, due to the good not think of a better way to deal with only one set of tyr catch Dingyiding out , and refer to the following

open class BaseViewModel : ViewModel(), LifecycleObserver{
 private val viewModelJob = SupervisorJob()
 private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
 private val error by lazy { MutableLiveData<Exception>() }
 private val finally by lazy { MutableLiveData<Int>() }
 //运行在UI线程的协程
 fun launchUI( block: suspend CoroutineScope.() -> Unit) {
 uiScope.launch(Dispatchers.Main) {
 try {
 block()
 }catch (e:Exception){
 error.value = e
 }finally {
 finally.value = 200
 }
 }
 }
 override fun onCleared() {
 super.onCleared()
 viewModelJob.cancel()
 }
 /**
 * 请求失败,出现异常
 */
 fun getError(): LiveData<Exception> {
 return error
 }
 /**
 * 请求完成,在此处做一些关闭操作
 */
 fun getFinally(): LiveData<Int> {
 return finally
 }
}

Epilogue

Above only describes some of the implementation process, we had to use specific reference demo, basically meet the needs of most, if interested small partner, you can download the demo reference, I feel good, then easily point a praise very satisfied. To learn not fine, there may be the use of inappropriate, I hope you can point big brother wrong place, very grateful.

Guess you like

Origin blog.51cto.com/14332859/2428845