安卓MVVM—kotlin協程與Retrofit

本文將給大家演示如何在安卓項目使用協程+Retrofit進行網絡請求,項目使用MVVM架構,引用了架構組件ViewModel和LiveData,包含了異常的封裝處理,可以用在一個商用產品架構上。

1.引入依賴

//使用協程
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
//OkHttp
implementation 'com.squareup.okhttp3:okhttp:4.2.2'
//retrofit2
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
//retrofit協程適配器
implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2"
//Moshi(Json解析)
implementation 'com.squareup.moshi:moshi-kotlin:1.9.2'
kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.9.2'
implementation 'com.squareup.retrofit2:converter-moshi:2.6.2'   //retrofit-moshi解析器

2.創建實體類

/**
 * 封裝的網絡請求結果實體類
 *
 * @author king
 * @date 2019-11-29 09:53
 */
sealed class DataResult<out T : Any> {
    //成功
    data class Success<out T : Any>(val result: T?) : DataResult<T>()
    //失敗
    data class Error(val errorCode:String?,val errorMessage: String?) : DataResult<Nothing>()
}
@JsonClass(generateAdapter = true)
data class MenuResult(val resultcode: String,
                      val reason: String,
                      val result: ResultBean,
                      val error_code: Int)

@JsonClass(generateAdapter = true)
data class ResultBean(val totalNum: String,
                      val pn: String,
                      val rn: String,
                      val data: List<FoodMenu>)

@JsonClass(generateAdapter = true)
data class FoodMenu(val id: String,
                    val title: String,
                    val tags: String,
                    val imtro: String,
                    val ingredients: String,
                    val burden: String,
                    val albums: List<String>,
                    val steps: List<StepsBean>)

@JsonClass(generateAdapter = true)
data class StepsBean(val img: String,
                     val step: String)

2.創建接口,用於請求網絡接口

interface NetworkApi {

    @GET("/cook/query?key=${AppConfig.JUHE_KEY}")
    fun getFoodMenuAsync(@Query("menu") menu:String,
                         @Query("pn") pn:Int, @Query("rn") rn:Int) : Deferred<Response<MenuResult>>

}

因爲要用到協程,所以返回了一個Deferred,而Response是Retrofit的網絡請求結果,MenuResult爲實體類

3.創建retrofit請求工具類

object ApiService {


    //構建OkHttpClient
    private val okHttpClient = OkHttpClient().newBuilder()
            .connectTimeout(60, TimeUnit.SECONDS)
            .proxy(Proxy.NO_PROXY)
            .readTimeout(60, TimeUnit.SECONDS)
            .writeTimeout(60, TimeUnit.SECONDS)
            .build()

    //構建retrofit
    private fun retrofit(): Retrofit = Retrofit.Builder()
            .client(okHttpClient)
            .baseUrl(AppConfig.BASE_API_URL)
            .addConverterFactory(MoshiConverterFactory.create())//moshi解析json適配器,自動將json解析爲對象
            .addCallAdapterFactory(CoroutineCallAdapterFactory())//支持kotlin協程
            .build()

    //創建接口對象
    val api: NetworkApi = retrofit().create(NetworkApi::class.java)

    /**
     * 執行網絡請求操作,並處理異常,這一方法也可忽略,在具體調用位置可以自己處理
     *
     * suspend聲明只能在協程內執行
     */
    suspend fun <T : Any> execute(call: suspend () -> Response<T>): T? {
        val data = parseApiResult(call)
        var result: T? = null
        when (data) {
            is DataResult.Success -> result = data.result
            is DataResult.Error -> {
                //請求失敗
                LogUtil.e(data.errorMessage)
            }
        }
        return result
    }

    /**
     * 執行並處理網絡請求結果(包含異常處理)
     */
    private suspend fun <T : Any> parseApiResult(call: suspend () -> Response<T>): DataResult<T> {
        try {
            val response = call.invoke()
            if (response.isSuccessful) {
                return DataResult.Success(response.body())
            }
            return DataResult.Error(response.code().toString(), response.message())
        }catch (e:Exception){
            LogUtil.e(e)
            return DataResult.Error("", e.message)
        }
    }

}

4.發起調用,並處理結果

class HomeViewModel : ViewModel() {

    val menuData = MutableLiveData<List<FoodMenu>>()
    val error = MutableLiveData<DataResult.Error>()

    /**
     * 獲取數據
     *
     * @param name
     * @param page
     * @param count
     */
    fun getMenuData(menu: String, page: Int, count: Int) {
        viewModelScope.launch(Dispatchers.Default) {
            //使用Retrofit接口
            val res = ApiService.execute(
                    call = { ApiService.api.getFoodMenuAsync(menu, page, count).await() })

//            val result = ApiService.execute (call= {
//               ApiService.api.getFoodMenuStr(menu, page, count).await()
//            })
//            LogUtil.i("結果$result")
//            val moshi = Moshi.Builder()
//                    .add(KotlinJsonAdapterFactory())
//                    .build()
//            val adapter = moshi.adapter(MenuResult::class.java)
//            val res = adapter.fromJson(result)

            if(res == null){
                //爲空,說明請求失敗了
                error.postValue(DataResult.Error("400", "Network Error"))
                return@launch
            }

            //請求成功
            if (res?.resultcode == "200") {
                //接口數據正常
                menuData.postValue(res?.result?.data)
            } else {
                error.postValue(DataResult.Error(res?.resultcode, res?.reason))
            }
        }
    }
}

以上就是全部使用示例,詳情代碼請參考:項目源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章