Android 官方App 架构- 网络数据源搭建

234 阅读2分钟

介绍

学习官方架构过程中网络数据源总结

依赖

  • 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>
}

github.com/square/retr…