Android Kotlin Coroutines + Retrofit + MVVM 簡單實現

前言

  • 這是一篇隨記,想嘗試下寫文章。
  • 這裏基本不會深刻討論各個知識點,如需瞭解更多能夠參考 Benny's BlogKotlin官方文檔

爲何使用協程

  • 協程比線程小

我: 爲何已經有Rx了還要在這裏用協程?
某同事: 由於協程比線程小,能夠開不少個。
我:...... 爲何比線程小?html

  我維基了一下,確實有說比線程更小。
  可是看了一些源碼,也是線程池 + 線程實現的,這時就開始有了疑惑,爲何一樣是線程,怎麼就說是比線程小的東西呢?
  直到看到了Benny大佬的文章 協程爲何被稱爲『輕量級線程』解釋,我清晰了。經過測驗,確實啓動成千上萬個協程也不會出現OOM或者其餘問題。android

  • 固然最主要的仍是代碼上的體驗 如今Kotlin愈來愈廣泛,各類inline函數,操做符也都基本能夠替換Rx的經常使用操做符了。因此在寫代碼上體驗仍是相對比較好的。
    (備註:我的以爲協程小不小對於Android開發真的沒多大區別,最主要仍是寫代碼和代碼美觀性)

1、添加依賴

implementation"com.squareup.retrofit2:retrofit:2.6.2"
implementation"com.squareup.retrofit2:converter-gson:2.6.2"

// Coroutines 
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2'
複製代碼

記得使用retrofit 2.6.0 或以上git

2、準備網絡請求接口

  • 建立接口
interface GitApi {
    @GET("/users/{username}/{module}")
    suspend fun repos( @Path("username") username: String, @Path("module") module: String, @Query("page") currPage: Int ): List<RepoInfo>

    @GET("/search/repositories")
    suspend fun searchRepos( @Query("q") key: String, @Query("sort") sort: String? = "updated", @Query("order") order: String? = "desc", @Query("page") currPage: Int ): SearchResponse
}
複製代碼
  • 建立Retrofit實例
fun buildRetrofit(): Retrofit {
        builder.addInterceptor(HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
            override fun log(message: String) {
                Timber.d(message)
            }
        }).apply {
            level = HttpLoggingInterceptor.Level.BODY
        }).addInterceptor(object : Interceptor {
            override fun intercept(chain: Interceptor.Chain): Response {
                val userCredentials = "$username:$password"
                val basicAuth =
                    "Basic ${String(Base64.encode(userCredentials.toByteArray(), Base64.DEFAULT))}"
                val original = chain.request()
                val requestBuilder = original.newBuilder()
                    .header("Authorization", basicAuth.trim { it <= ' ' })
                val request = requestBuilder.build()
                return chain.proceed(request)
            }
        })
        return Retrofit.Builder()
            .client(builder.build())
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
複製代碼

3、 在ViewModel中使用

kotlinx.coroutines.CoroutineScope 接口中的註釋有這麼一句話
github

image.png
因此啓動一個協程必定須要一個 Scope,也就是說每一個suspend函數都有一個 CoroutineScope,若是沒有必定會報錯。而Android的ViewModel中有擴展了一個 viewModelScope,而且跟 lifecycle綁定了,因此在ViewModel的onCleared方法上會自動幫咱們cancel掉這個viewModelScope的全部Jobs。
圖1
爲了方便使用,封裝了一個BaseViewModel

open class BaseViewModel : ViewModel() {
    fun <T> request( onError: (error: Throwable) -> Unit = {}, // 不須要處理Error能夠不傳
        execute: suspend CoroutineScope.() -> T
    ) {
        viewModelScope.launch(errorHandler { onError.invoke(it) }) {
            launch(Dispatchers.IO) {
                execute()
            }
        }
    }

    private fun errorHandler(onError: (error: Throwable) -> Unit): CoroutineExceptionHandler {
        return CoroutineExceptionHandler { _, throwable ->
            Timber.d(throwable)
            onError.invoke(throwable)
        }
    }
}
複製代碼

使用繼承 BaseViewModel 調用 request 方法便可。網絡

class RepoViewModel(
    private val userRepo: UserDataSource,
    private val gitApi: GitApi
) : BaseViewModel() {
    private val _reposResult = BaseLiveData<List<RepoInfo>>()
    val repoResult: BaseLiveData<List<RepoInfo>>
        get() = _reposResult

    fun fetchRepos(module: String) {
        request {
            userRepo.currUser()?.let {
                val result = gitApi.repos(it.nickname, module, 1)
                _reposResult.update(result)
            }
        }
    }
}
複製代碼

ORapp

fun fetchRepos(module: String) {
        request(
            onError = {
                // handle error
            },
            execute = {
                userRepo.currUser()?.let {
                    val result = gitApi.repos(it.nickname, module, 1)
                    _reposResult.update(result)
                }
            }
        )
    }
複製代碼

剩下的基本是LiveData和Fragment之間的訂閱上的邏輯實現了。ide

結語

最近在學習,瞭解也不是很深,歡迎評論補充和提建議。 學習項目地址 Dithub函數

相關文章
相關標籤/搜索