diff --git a/app/build.gradle b/app/build.gradle index f12fb9a..fe54651 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -64,4 +64,16 @@ dependencies { // optional - Test helpers testImplementation "androidx.room:room-testing:$room_version" /*** Room Dependencies End ***/ + + /*** Retrofit Dependencies Start ***/ + // 會連帶引用 OkHttp,因此不必再 implementation + // 這裡使用最新版,若要支援舊版 Android 請注意使用的版本。 + // 詳見:https://github.com/square/retrofit/blob/master/CHANGELOG.md + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + // 因為我們 API 格式為 JSON,因此使用 GSON 來幫忙轉為物件 + implementation "com.squareup.retrofit2:converter-gson:2.9.0" + /*** Retrofit Dependencies End ***/ + + implementation "com.squareup.okhttp3:logging-interceptor:3.14.9" + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.9' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0dec640..7c687f4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,9 @@ + + + { + try { + val response = userApi.listUser() + return if (response.isSuccessful) { + response.body()!!.data + } else { + throw Exception("should success") + } + } catch (e: Exception) { + throw e + } + } + + suspend fun getUser(userId: Int): User { + try { + val response = userApi.getUser(userId) + return if (response.isSuccessful) { + response.body()!!.data + } else { + throw Exception("should success") + } + } catch (e: Exception) { + throw e + } + } + + suspend fun createUser(userName: String, job: String): User { + try { + val response = userApi.createUser(userName, job) + return if (response.isSuccessful) { + response.body()!! + } else { + throw Exception("should success") + } + } catch (e: Exception) { + throw e + } + } +} \ No newline at end of file diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/network/UserApi.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/network/UserApi.kt new file mode 100644 index 0000000..6f67142 --- /dev/null +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/network/UserApi.kt @@ -0,0 +1,24 @@ +package tw.andyang.kotlinandroidworkshop.network + +import retrofit2.Response +import retrofit2.http.* +import tw.andyang.kotlinandroidworkshop.network.entity.Envelope +import tw.andyang.kotlinandroidworkshop.network.entity.User + +interface UserApi { + + companion object { + const val PATH_ARG_USER_ID = "user_id" + } + + // 注意!不要斜線開頭! + @GET("users") + suspend fun listUser(): Response>> + + @GET("users/{$PATH_ARG_USER_ID}") + suspend fun getUser(@Path(PATH_ARG_USER_ID) userId: Int): Response> + + @FormUrlEncoded + @POST("users") + suspend fun createUser(@Field("name") userName: String, @Field("job") job: String): Response +} \ No newline at end of file diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/network/entity/Envelope.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/network/entity/Envelope.kt new file mode 100644 index 0000000..62eadfb --- /dev/null +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/network/entity/Envelope.kt @@ -0,0 +1,5 @@ +package tw.andyang.kotlinandroidworkshop.network.entity + +data class Envelope( + val data: T +) \ No newline at end of file diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/network/entity/User.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/network/entity/User.kt new file mode 100644 index 0000000..06580ff --- /dev/null +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/network/entity/User.kt @@ -0,0 +1,11 @@ +package tw.andyang.kotlinandroidworkshop.network.entity + +import com.google.gson.annotations.SerializedName + +class User( + @SerializedName("id") val id: Int, + @SerializedName("email") var email: String, + @SerializedName("first_name") var firstName: String, + @SerializedName("last_name") var lastName: String, + @SerializedName("avatar") var avatar: String +) \ No newline at end of file diff --git a/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/UserRepository.kt b/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/UserRepository.kt new file mode 100644 index 0000000..c785058 --- /dev/null +++ b/app/src/main/java/tw/andyang/kotlinandroidworkshop/repository/UserRepository.kt @@ -0,0 +1,15 @@ +package tw.andyang.kotlinandroidworkshop.repository + +import tw.andyang.kotlinandroidworkshop.network.ApiClient +import tw.andyang.kotlinandroidworkshop.network.entity.User + +class UserRepository(private val apiClient: ApiClient) { + + suspend fun listUser(): List { + return apiClient.listUser() + } + + suspend fun getUser(userId: Int): User { + return apiClient.getUser(userId) + } +} \ No newline at end of file diff --git a/app/src/test/java/tw/andyang/kotlinandroidworkshop/network/ApiClientTest.kt b/app/src/test/java/tw/andyang/kotlinandroidworkshop/network/ApiClientTest.kt new file mode 100644 index 0000000..b8dec06 --- /dev/null +++ b/app/src/test/java/tw/andyang/kotlinandroidworkshop/network/ApiClientTest.kt @@ -0,0 +1,62 @@ +package tw.andyang.kotlinandroidworkshop.network + +import com.google.gson.Gson +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.newSingleThreadContext +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.After +import org.junit.Before +import org.junit.Test + +class ApiClientTest { + + private val mainThreadSurrogate = newSingleThreadContext("UI thread") + + @Before + fun setUp() { + Dispatchers.setMain(mainThreadSurrogate) + } + + @After + fun tearDown() { + Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher + mainThreadSurrogate.close() + } + + @Test + fun testListUser() { + runBlocking { + launch(Dispatchers.Main) { + val gson = Gson() + val apiClient = ApiClient() + println(gson.toJson(apiClient.listUser())) + } + } + } + + @Test + fun testGetUser() { + runBlocking { + launch(Dispatchers.Main) { + val gson = Gson() + val apiClient = ApiClient() + println(gson.toJson(apiClient.getUser(1))) + } + } + } + + @Test + fun testCreateUser() { + runBlocking { + launch(Dispatchers.Main) { + val gson = Gson() + val apiClient = ApiClient() + println(gson.toJson(apiClient.createUser("morpheus", "leader"))) + } + } + } +} \ No newline at end of file