介绍
学习官方架构过程中网络数据源总结
依赖
- Hilt:依赖注入
- Retrofit+协程
- kotlinx-serialization Json解析
- PersistentCookieJar cookie持久化
文件结构
- app/
- data/
- di/
- DataModule 数据层依赖注入
- local/
- database/ 数据库实现
- entity/
- LocaDataSource.kt 本地数据源
- remote/
- model/
- network/ 网络实现
- RemoteDataSource.kt 远端数据源
- repository/
- DataRepository.kt 数据仓储
定义解析接口统一返回值格式实体
后端API口的Json返回格式
{
"data": {},
"errorCode": 0,
"errorMsg": ""
}
统一的响应数据类
data class ApiResponse<T>(
val data: T?,
val errorCode: Int = -1,
val errorMsg: String = ""
){
val succeeded
get() = (SUCCESS_CODE == errorCode)
companion object {
private const val SUCCESS_CODE = 0
}
}
定义API请求描述接口
interface ApiService {
@POST("/user/login")
@FormUrlEncoded
suspend fun login(
@Field("username") username: String, @Field("password") pwd: String
): ApiResponse<LoggedInUser>
}
调用该请求的代码如下
suspend fun login(username: String, password: String): LoggedInUser? {
return try {
val result = apiService.login(username, password)
return if (result.succeeded) {
result.data
} else {
// 服务器返回的错误
throw ServerException(errorCode, errorMsg)
}
}catch (e:Exception){
//网络请求错误
throw e
}
}
返回类型
HTTP请求返回单个响应,而不是响应流,因此只支持suspend fun。如果我们有机会重新做RxJava适配器,我们可能也不支持他们的可观察对象Observable,而是只支持Single类型。来源Retrofit issues Link
注:如果需要对数据进行过滤、变换、处理也可以转换成Flow。
interface FeedApi {
@GET("/")
suspend fun getPosts(): List<Post>
}
flow {
emit(getPosts())
}.map {
//数据变换
}.flowOn(dispatcher)
异常处理
官方推荐使用自定义异常,比如常见的登录授权失效(UserNotAuthenticatedException)、服务器返回的错误信息(ServerException)可以使用自定义异常公开这些错误。
@Serializable
data class ApiResponse<out T>(
private val data: T? = null,
val errorCode: Int = -1,
val errorMsg: String = ""
) {
val succeeded
get() = (SUCCESS_CODE == errorCode)
fun getOrNull(): T? {
return data
}
fun getOrThrow(): T? = handleResult()
/**
* 解析返回结果
* 可根据code 扩展不同的自定义异常
* UserNotAuthenticatedException 用户未授权异常
* ServerException()服务器返回错误
*/
@Throws(AppException::class)
private fun handleResult(): T? {
return when (errorCode) {
SUCCESS_CODE -> {
this.data
}
UN_AUTHENTICATED_CODE -> {
throw UserNotAuthenticatedException()
}
else -> {
throw ServerException(errorCode, errorMsg)
}
}
}
companion object {
private const val SUCCESS_CODE = 0
private const val UN_AUTHENTICATED_CODE = -1001
}
}
用法
suspend fun login(username: String, password: String): LoggedInUser? {
return apiService.login(username, password).getOrThrow()
}
线程安全
数据源和存储库应该具有主线程安全性(即从Main线程调用是安全的),例如 Room 或 Retrofit 已提供具有主线程安全性的 API。对于主线程安全的我们需要手动切换到IO线程来保证main-safe.
class NewsRemoteDataSource(
private val newsApi: NewsApi,
private val ioDispatcher: CoroutineDispatcher
) {
/**
* Fetches the latest news from the network and returns the result.
* This executes on an IO-optimized thread pool, the function is main-safe.
*/
suspend fun fetchLatestNews(): List<ArticleHeadline> =
// Move the execution to an IO-optimized thread since the ApiService
// doesn't support coroutines and makes synchronous requests.
withContext(ioDispatcher) {
newsApi.fetchLatestNews()
}
}
// Makes news-related network synchronous requests.
interface NewsApi {
fun fetchLatestNews(): List<ArticleHeadline>
}