- 原文地址:Android Networking in 2019 — Retrofit with Kotlin’s Coroutines
- 原文做者:Navendra Jha
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:feximin
2018 年,Android 圈發生了許多翻天覆地的變化,尤爲是在 Android 網絡方面。穩定版本的 Kotlin 協程的發佈極大地推進了 Android 在處理多線程方面從 RxJava 到 Kotlin 協程的發展。 本文中,咱們將討論在 Android 中使用 Retrofit2 和 Kotlin 協程 進行網絡 API 調用。咱們將調用 TMDB API 來獲取熱門電影列表。html
若是你在 Android 網絡方面有經驗而且在使用 Retrofit 以前進行過網絡調用,但可能使用的是 RxJava 而不是 Kotlin 協程,而且你只想看看實現方式,請查看 Github 上的 readme 文件。前端
簡而言之,Android 網絡或者任何網絡的工做方式以下:java
Android 中,咱們使用:android
顯然這些只是一些熱門的庫,也有其餘相似的庫。此外這些庫都是由 Square 公司 的牛人開發的。點擊 Square 團隊的開源項目 查看更多。ios
Movie Database(TMDb)API 包含全部熱門的、即將上映的、正在上映的電影和電視節目列表。這也是最流行的 API 之一。git
TMDB API 須要 API 密鑰才能請求。爲此:github
獲取 API 密鑰後,按照下述步驟將其在 VCS 中隱藏。編程
//In local.properties
tmdb_api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
//In build.gradle (Module: app)
buildTypes.each {
Properties properties = new Properties()
properties.load(project.rootProject.file("local.properties").newDataInputStream())
def tmdbApiKey = properties.getProperty("tmdb_api_key", "")
it.buildConfigField 'String', "TMDB_API_KEY", tmdbApiKey
it.resValue 'string', "api_key", tmdbApiKey
}
//In your Constants File
var tmdbApiKey = BuildConfig.TMDB_API_KEY
複製代碼
爲了設置項目,咱們首先會將全部必需的依賴項添加到 build.gradle (Module: app) 文件中:後端
// build.gradle(Module: app)
dependencies {
def moshiVersion="1.8.0"
def retrofit2_version = "2.5.0"
def okhttp3_version = "3.12.0"
def kotlinCoroutineVersion = "1.0.1"
def picassoVersion = "2.71828"
//Moshi
implementation "com.squareup.moshi:moshi-kotlin:$moshiVersion"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion"
//Retrofit2
implementation "com.squareup.retrofit2:retrofit:$retrofit2_version"
implementation "com.squareup.retrofit2:converter-moshi:$retrofit2_version"
implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2"
//Okhttp3
implementation "com.squareup.okhttp3:okhttp:$okhttp3_version"
implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
//Picasso for Image Loading
implementation ("com.squareup.picasso:picasso:$picassoVersion"){
exclude group: "com.android.support"
}
//Kotlin Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutineVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutineVersion"
}
複製代碼
//ApiFactory to create TMDB Api
object Apifactory{
//Creating Auth Interceptor to add api_key query in front of all the requests.
private val authInterceptor = Interceptor {chain->
val newUrl = chain.request().url()
.newBuilder()
.addQueryParameter("api_key", AppConstants.tmdbApiKey)
.build()
val newRequest = chain.request()
.newBuilder()
.url(newUrl)
.build()
chain.proceed(newRequest)
}
//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
CallAdapter.Factory
,用於處理 Kotlin 協程中的 Deferred
。調用 /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<TmdbMovie>
)
//A retrofit Network Interface for the Api
interface TmdbApi{
@GET("movie/popular")
fun getPopularMovie(): Deferred<Response<TmdbMovieResponse>>
}
複製代碼
TmdbApi 接口:
建立了數據類後,咱們建立 TmdbApi 接口,在前面的小節中咱們已經將其引用添加至 retrofit 構建器中。在該接口中,咱們添加了全部必需的 API 調用,若有必要,能夠給這些調用添加任意參數。例如,爲了可以根據 id 獲取一部電影,咱們在接口中添加了以下方法:
interface TmdbApi{
@GET("movie/popular")
fun getPopularMovies() : Deferred<Response<TmdbMovieResponse>>
@GET("movie/{id}")
fun getMovieById(@Path("id") id:Int): Deferred<Response<Movie>>
}
複製代碼
接着,咱們最終發出一個用以獲取所需數據的請求,咱們能夠在 DataRepository 或者 ViewModel 或者直接在 Activity 中進行此調用。
這是用來處理網絡響應的類。它可能成功返回所需的數據,也可能發生異常而出錯。
sealed class Result<out T: Any> {
data class Success<out T : Any>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
複製代碼
open class BaseRepository{
suspend fun <T : Any> safeApiCall(call: suspend () -> Response<T>, errorMessage: String): T? {
val result : Result<T> = 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<T>, errorMessage: String) : Result<T>{
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"))
}
}
複製代碼
class MovieRepository(private val api : TmdbApi) : BaseRepository() {
fun getPopularMovies() : MutableList<TmdbMovie>?{
//safeApiCall is defined in BaseRepository.kt (https://gist.github.com/navi25/67176730f5595b3f1fb5095062a92f15)
val movieResponse = safeApiCall(
call = {api.getPopularMovie().await()},
errorMessage = "Error Fetching Popular Movies"
)
return movieResponse?.results.toMutableList();
}
}
複製代碼
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<ParentShowList>>()
fun fetchMovies(){
scope.launch {
val popularMovies = repository.getPopularMovies()
popularMoviesLiveData.postValue(popularMovies)
}
}
fun cancelAllRequests() = coroutineContext.cancel()
}
複製代碼
class MovieActivity : AppCompatActivity(){
private lateinit var tmdbViewModel: TmdbViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_movie)
tmdbViewModel = ViewModelProviders.of(this).get(TmdbViewModel::class.java)
tmdbViewModel.fetchMovies()
tmdbViewModel.popularMovies.observe(this, Observer {
//TODO - Your Update UI Logic
})
}
}
複製代碼
本文是 Android 中一個基礎但卻全面的產品級別的 API 調用的介紹。更多示例,請訪問此處。
祝編程愉快!
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。