依赖
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
封装步骤
1.配置Retrofit、okhttp
/**
* retrofit 封装
*/
object RetrofitManager {
private const val READ_OUT_TIME = 10_000L
private const val WRITE_OUT_TIME = 10_1000L
private const val CONNECT_OUT_TIME = 10_1000L
private val mRetrofit by lazy {
Retrofit.Builder().baseUrl(BASE_URL)
.client(mClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
/**
* 配置client
*/
private val mClient by lazy {
OkHttpClient.Builder().readTimeout(READ_OUT_TIME, TimeUnit.SECONDS)
.writeTimeout(WRITE_OUT_TIME, TimeUnit.SECONDS)
.connectTimeout(CONNECT_OUT_TIME, TimeUnit.SECONDS)
.addInterceptor(mLogInterceptor)
.build()
}
/**
* 日志打印拦截器
*/
private val mLogInterceptor by lazy {
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
}
/**
* 创建接口Api
*/
fun <T> createApi(service: Class<T>): T {
return mRetrofit.create(service)
}
}
2.请求数据转换
2.1 创建请求接口apiService
interface UserApi {
@POST("user/login")
suspend fun reqLogin(
@Query("username") username: String,
@Query("password") password: String
): BaseResult<UserBean>
}
2.1返回数据封装
/**
* 通用网络请求返回格式
*/
data class BaseResult<T>(val data:T?,val errorCode:Int,val errorMsg:String)
2.2 返回数据转换
真正的数据请求放在Repository(官方的方案还会有一层)
/**
* 数据转换
* BaseResult --> ApiResult
* 根据errorCode 转换成对应的Data 或者 转换成错误信息
*/
rivate suspend fun <T> covert(
result: BaseResult<T>
: ApiResult<T> {
val code = result.errorCode
val errorMsg = result.errorMsg
return when (code) {
//正常状态
ApiCode.RESULT_OK -> {
val data = result.data
if (data != null) {
ApiResult.Success(data)
} else {
ApiResult.Error(ErrorType.EMPTY, errorMsg, errorCode = code)
}
}
//需要登录状态
ApiCode.RESULT_LOGIN -> {
ApiResult.Error(ErrorType.NEED_LOGIN, errorMsg, errorCode = code)
}
//接口错误状态
else -> {
ApiResult.Error(ErrorType.NET_ERROR, errorMsg, errorCode = code)
}
}
BaseResult可能是失败,也可能是成功,所以要对这两种情况做区分。这里转换规则是把BaseResult<T> 转换成ApiResult,ApiResult是密封类,只有两个子类一个是Success,一个是Error。
/**
* 网络请求数据转换类型
* Success、Error类型
*/
sealed class ApiResult<out T> {
data class Success<out T>(val data: T) : ApiResult<T>()
data class Error(
val errorType: ErrorType,
val errorMsg: String? = null,
val error: Throwable? = null,
val errorCode: Int = 0
) : ApiResult<Nothing>()
}
请求异常状态封装
比如出现链接超时等非接口问题,kotlin中需要用异常捕获来处理。
private suspend fun <T> safeApiCall(call: suspend () -> ApiResult<T>): ApiResult<T> {
if (!NetState.isOk()) {
return ApiResult.Error(ErrorType.NET_ERROR, NET_ERROR_SHOW)
}
return try {
call()
} catch (e: Throwable) {
e.printStackTrace()
if (!NetState.isOk()) {
ApiResult.Error(ErrorType.NET_ERROR, NET_ERROR_SHOW, e)
} else {
ApiResult.Error(ErrorType.ERROR, e.message, e)
}
}
}
data转换
可以将接口返回的data数据重新组装成想要的类,比如data+请求的参数。
suspend fun <R, T> request(
converter: ((R) -> T)? = null,
api: suspend () -> BaseResult<R>
): ApiResult<T> {
val apiResult = safeApiCall {
covert(api())
}
return if (apiResult is ApiResult.Error) {
apiResult
} else {
if (converter != null) {
val result = converter((apiResult as ApiResult.Success<R>).data)
if (null != result) {
ApiResult.Success(result)
} else {
ApiResult.Error(ErrorType.CONVERTER_ERROR, CONVERTER_CONTENT_ERROR)
}
} else {
apiResult as? ApiResult.Success<T> ?: ApiResult.Error(
ErrorType.CONVERTER_ERROR,
CONVERTER_CONTENT_ERROR
)
}
}
}
调用
class UserRepository : BaseRepository() {
val api by lazy { RetrofitManager.createApi(UserApi::class.java) }
suspend fun reqData() =
request(converter = { it }, api = { api.reqLogin("13511112222", "1112") })
}
viewmodel 调用
val repo by lazy { UserRepository() }
private val _toastLd by lazy { MutableLiveData<String>() }
val toastLd : LiveData<String> = _toastLd
private val _uiLd by lazy { MutableLiveData<Boolean>() }
val uiLd : LiveData<Boolean> = _uiLd
fun reqData() {
viewModelScope.launch {
val result = withContext(Dispatchers.IO){
repo.reqData()
}
checkResult(result,
success = {
_uiLd.value = true
// saveUser()
},
error = {
it?.apply {
_toastLd.value = this
}
},
isEmpty = {
it.token == null
},
empty = {
_toastLd.value = "数据是空"
})
}
}
checkResult 主要对封装的数据判断,并通知UI更新。
fun <T> checkResult(
result: ApiResult<T>,
isEmpty: ((T) -> Boolean)? = null,
empty: (() -> Unit)? = null,
error: ((String?) -> Unit)? = null,
netError: ((String?) -> Unit)? = null,
needLogin: ((String?) -> Unit)? = null,
success: ((T) -> Unit)? = null
) {
when (result) {
is ApiResult.Success<T> -> {
if (isEmpty?.invoke(result.data) == true) {
empty?.invoke()
} else {
success?.invoke(result.data)
}
}
is ApiResult.Error -> {
val errorMsg = result.errorMsg
when (result.errorType) {
ErrorType.NET_ERROR -> {
netError?.invoke(errorMsg)
}
ErrorType.ERROR -> {
error?.invoke(errorMsg)
}
ErrorType.NEED_LOGIN -> {
needLogin?.invoke(errorMsg)
}
ErrorType.CONVERTER_ERROR -> {
error?.invoke(errorMsg)
}
ErrorType.EMPTY -> {
empty?.invoke()
}
}
}
}