如何使用Android MVVM 架構(二)-使用 Retrofit

如何使用Android MVVM 架構(二)-使用 Retrofit

情境

Android MVVM 架構(一) 中架構了基礎的 MVVM,今天來調整讀取 Api 的部分改用 Retrofit 來完善我們的架構,如果對於 Retrofit 不熟悉,可以參考之前所寫的 如何使用 Retrofit 基礎操作

我們基於 如何使用 Retrofit 基礎操作 的例子加進我們原本在 Android MVVM 架構(一) 所寫的架構,兩個看起來結果會相同,但是內部的架構長得不一樣。

完整程式碼

如果需要完整的程式碼,可以到 GitHub 上觀看或者下載。

程式碼說明

一開始我們加入了 ApiService 這個類別,它是 Retrofit 拿來打 Api 的宣告介面,透過 @ 宣告的 Url,就可以輕鬆取得 Api 所提供的資訊。

interface ApiService {  
    @GET("/posts")  
    fun index(): Call<List<Posts>>  
}

接著是我們最終要的 Retrofit 設定,透過這些設定,就可以簡單操作 Retrofit 幫我們打 Api 並且拿到相關資訊。

logging.level = HttpLoggingInterceptor.Level.BODY  
okHttpClient = OkHttpClient().newBuilder().addInterceptor(logging).build()  
retrofit = Retrofit.Builder()  
        .baseUrl(Config.URL)  
        .addConverterFactory(GsonConverterFactory.create())  
        .client(okHttpClient)  
        .build()

上面可以看到,我們設置了一個 Interceptor 來幫助攔截 Api 相關的訊息,接著有客製化一個 OkHttpClient 來加入攔截器,如果沒有客製化 OkHttpClient 就無法加入攔截器了,這邊還有一個工具叫做 Gson,它是一個輔助我們把 Json 跟物件對應的方式相互轉換的工具,如果對這個工具不熟悉,可以參考 如何使用Gson

接著我們到 Repository 調整一下,打 Api 後的 Callback 都在這邊處理,可以看到我們將 loadInfo 從以下程式碼調整。

fun loadInfo(task: OnTaskFinish) {  
    Executors.newSingleThreadExecutor().submit {  
        val userData = UserData()  
        userData.userName = "jake"  
        userData.userAge = 30  
        Thread.sleep(3000)  
        task.onFinish(userData)  
    }  
}

現在因為真的是去 Api 要資料,所以不需要等待 3秒了。

fun loadInfo(task: OnTaskFinish) {  
    val apiService = AppClientManager.client.create(ApiService::class.java)  
    apiService.getPosts().enqueue(object : Callback<List<Posts>> {  
        override fun onResponse(call: Call<List<Posts>>, response: Response<List<Posts>>) {  
            //success   
        }override fun onFailure(call: Call<List<Posts>>, t: Throwable) {  
        //fail  
}  
    })  
}

透過可以看到透過 Retrofit 呼叫 getPosts 方法後,Callback 回來兩種狀態:成功或失敗(跟人生一樣…嗚嗚嗚),我們假裝人生都是成功的(喂~),所以在裡面處理成功回來的資訊,失敗的情況就暫時放著之後再處理。

而之前宣告的 interface 也要進行修正。

interface OnTaskFinish {  
    fun onFinish(data: String)  
}

原本是 UserData 的參數變成 String 的參數。

接著來看一下我們怎麼處理 Response 回來的資訊。

val sb = StringBuffer()
response.body()?.forEach {
    sb.append(it.body)
    sb.append("\n")
    sb.append("---------------------\n")
}
task.onFinish(sb.toString())

這邊的 task 是從外部傳進來的 Callback,用來通知我們 Api 已經拿到資訊並且處理完畢,我們看到從 Response 傳回來的內容會轉換成 List 的型態,所以透過 foreach 的方式將資訊整理成一個字串傳回 ViewModel。

我們回來看一下 ViewModel 的部分,會發現其實只需要改變回傳的資料,這就是 MVVM 的好處。

fun callInfo():LiveData<String>{  
    infoRepository.loadInfo(object : OnTaskFinish {  
        override fun onFinish(data: String) {  
            userInfoLiveData.postValue(data)  
        }  
    })  
    return userInfoLiveData  
}

接著我們回到最主要的 Activity 部分,這邊畫面可能需要加一些 UI 來幫我們呈現回來的資訊,所以 layout 的部分調整成以下。

<Button  
        android:id="@+id/send_data"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="call api" />  
  
<ScrollView  
        android:layout_width="match_parent"  
        android:layout_height="match_parent"  
        android:layout_marginTop="48dp"  
        app:layout_constraintEnd_toEndOf="parent"  
        app:layout_constraintStart_toStartOf="parent"  
        app:layout_constraintTop_toBottomOf="@+id/send_data">  
  
    <TextView  
        android:id="@+id/info"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content" />  
</ScrollView>

上面是一個發送按鈕,下面是我們資訊呈現的部分,由於我們資訊可能很多,所以用一個 ScrollView 包起來方便可以滾動。

send_data.setOnClickListener {
    val dialog = ProgressDialog.show(
        this, "",
        "Loading. Please wait...", true
    )
    dialog.show()
    infoViewModel.callInfo().observe(this, Observer {
        dialog.dismiss()
        info.text = it
    })
}

Activity 的部分沒有變化太大,我們學 Retrofit 操作教學 這篇,放置一個 Loading 的畫面來表示讀取中,等到資料回來以後,我們就用 TextView 來呈現資訊。

最終呈現的效果如下。


這樣就是一個簡單的 MVVM 打 Api 的範例了,後續我們可以加入 ROOM 資料庫套件,來充分應用 Repository 的機制,還可以透過 RxJava 跟 Koin 的套件優化我們整體的架構。