[译] 2019 年的 Android 网络 —— Retrofit 与 Kotlin 协程(1)

//OkhttpClient for building http request url
private val tmdbClient = OkHttpClient().newBuilder()
.addInterceptor(authInterceptor)
.build()

fun retrofit() : Retrofit = Retrofit.Builder()
.client(tmdbClient)
.baseUrl(“https://api.themoviedb.org/3/”)
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()

val tmdbApi : TmdbApi = retrofit().create(TmdbApi::class.java)

}

看一下我们在 ApiFactory.kt 文件中做了什么。

  • 首先,我们创建了一个用以给所有请求添加 api_key 参数的网络拦截器,名为 authInterceptor
  • 然后我们用 OkHttp 创建了一个网络客户端,并添加了 authInterceptor。
  • 接下来,我们用 Retrofit 将所有内容连接起来构建 Http 请求的构造器和处理器。此处我们加入了之前创建好的网络客户端、基础 URL、一个转换器和一个适配器工厂。 首先是 MoshiConverter,用以辅助 JSON 解析并将响应的 JSON 转化为 Kotlin 数据类,如有需要,可进行选择性解析。 第二个是 CoroutineCallAdaptor,它的类型是 Retorofit2 中的 CallAdapter.Factory,用于处理 Kotlin 协程中的 Deferred
  • 最后,我们只需将 TmdbApi 类(下节中创建) 的一个引用传入之前建好的 retrofit 类中就可以创建我们的 tmdbApi。

探索 Tmdb API

调用 /movie/popular 接口我们得到了如下响应。该响应中返回了 results,这是一个 movie 对象的数组。这正是我们关注的地方。

{
“page”: 1,
“total_results”: 19848,
“total_pages”: 993,
“results”: [
{
“vote_count”: 2109,
“id”: 297802,
“video”: false,
“vote_average”: 6.9,
“title”: “Aquaman”,
“popularity”: 497.334,
“poster_path”: “/5Kg76ldv7VxeX9YlcQXiowHgdX6.jpg”,
“original_language”: “en”,
“original_title”: “Aquaman”,
“genre_ids”: [
28,
14,
878,
12
],
“backdrop_path”: “/5A2bMlLfJrAfX9bqAibOL2gCruF.jpg”,
“adult”: false,
“overview”: “Arthur Curry learns that he is the heir to the underwater kingdom of Atlantis, and must step forward to lead his people and be a hero to the world.”,
“release_date”: “2018-12-07”
},
{
“vote_count”: 625,
“id”: 424783,
“video”: false,
“vote_average”: 6.6,
“title”: “Bumblebee”,
“popularity”: 316.098,
“poster_path”: “/fw02ONlDhrYjTSZV8XO6hhU3ds3.jpg”,
“original_language”: “en”,
“original_title”: “Bumblebee”,
“genre_ids”: [
28,
12,
878
],
“backdrop_path”: “/8bZ7guF94ZyCzi7MLHzXz6E5Lv8.jpg”,
“adult”: false,
“overview”: “On the run in the year 1987, Bumblebee finds refuge in a junkyard in a small Californian beach town. Charlie, on the cusp of turning 18 and trying to find her place in the world, discovers Bumblebee, battle-scarred and broken. When Charlie revives him, she quickly learns this is no ordinary yellow VW bug.”,
“release_date”: “2018-12-15”
}
]
}

因此现在我们可以根据该 JSON 创建我们的 Movie 数据类和 MovieResponse 类。

// Data Model for TMDB Movie item
data class TmdbMovie(
val id: Int,
val vote_average: Double,
val title: String,
val overview: String,
val adult: Boolean
)

// Data Model for the Response returned from the TMDB Api
data class TmdbMovieResponse(
val results: List
)

//A retrofit Network Interface for the Api
interface TmdbApi{
@GET(“movie/popular”)
fun getPopularMovie(): Deferred<Response>
}

TmdbApi 接口:

创建了数据类后,我们创建 TmdbApi 接口,在前面的小节中我们已经将其引用添加至 retrofit 构建器中。在该接口中,我们添加了所有必需的 API 调用,如有必要,可以给这些调用添加任意参数。例如,为了能够根据 id 获取一部电影,我们在接口中添加了如下方法:

interface TmdbApi{

@GET(“movie/popular”)
fun getPopularMovies() : Deferred<Response>

@GET(“movie/{id}”)
fun getMovieById(@Path(“id”) id:Int): Deferred<Response>

}

最后,进行网络调用

接着,我们最终发出一个用以获取所需数据的请求,我们可以在 DataRepository 或者 ViewModel 或者直接在 Activity 中进行此调用。

密封 Result 类

这是用来处理网络响应的类。它可能成功返回所需的数据,也可能发生异常而出错。

sealed class Result {
data class Success(val data: T) : Result()
data class Error(val exception: Exception) : Result()
}

构建用来处理 safeApiCall 调用的 BaseRepository

open class BaseRepository{

suspend fun safeApiCall(call: suspend () -> Response, errorMessage: String): T? {

val result : Result = safeApiResult(call,errorMessage)
var data : T? = null

when(result) {
is Result.Success ->
data = result.data
is Result.Error -> {
Log.d(“1.DataRepository”, “$errorMessage & Exception - ${result.exception}”)
}
}

return data

}

private suspend fun <T: Any> safeApiResult(call: suspend ()-> Response, errorMessage: String) : Result{
val response = call.invoke()
if(response.isSuccessful) return Result.Success(response.body()!!)

return Result.Error(IOException(“Error Occurred during getting safe Api result, Custom ERROR - $errorMessage”))
}
}

构建 MovieRepository

class MovieRepository(private val api : TmdbApi) : BaseRepository() {

fun getPopularMovies() : MutableList?{

//safeApiCall is defined in BaseRepository.kt (https://gist.github.com/navi25/67176730f5595b3f1fb5095062a92f15)
val movieResponse = safeApiCall(
call = {api.getPopu[译] 2019 年的 Android 网络 —— Retrofit 与 Kotlin 协程(1)
larMovie().await()},
errorMessage = “Error Fetching Popular Movies”
)

return movieResponse?.results.toMutableList();

}

}

创建 ViewModel 来获取数据

class TmdbViewModel : ViewModel(){

private val parentJob = Job()

private val coroutineContext: CoroutineContext
get() = parentJob + Dispatchers.Default

private val scope = CoroutineScope(coroutineContext)

private val repository : MovieRepository = MovieRepository(ApiFactory.tmdbApi)

val popularMoviesLiveData = MutableLiveData<MutableList>()

fun fetchMovies(){
scope.launch {
val popularMovies = repository.getPopularMovies()
popularMoviesLiveData.postValue(popularMovies)
}
}

fun cancelAllRequests() = coroutineContext.cancel()

}

在 Activity 中使用 ViewModel 更新 UI

class MovieActivity : AppCompatActivity(){

private lateinit var tmdbViewModel: TmdbViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_movie)

class MovieActivity : AppCompatActivity(){

private lateinit var tmdbViewModel: TmdbViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_movie)

上一篇:[极客大挑战 2019]Http


下一篇:全球及中国低发射率玻璃行业发展动态与前景策略分析报告2022~2027年