//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
 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)










