12/08/2018, 17:40

Kotlin - API Control with Retrofit and RxJava

Giới thiệu Hầu hết chúng ta khi lập trình các ứng dụng cho mobile đều phải gọi api tới server để lấy hay cập nhật dữ liệu. Đối với những người mới như mình, mình mất khá nhiều thời gian để xem nên sử dụng cái gì, dùng như thế nào để lấy dữ liệu từ trên server xuống một cách dễ dàng và đơn ...

Giới thiệu

Hầu hết chúng ta khi lập trình các ứng dụng cho mobile đều phải gọi api tới server để lấy hay cập nhật dữ liệu.

Đối với những người mới như mình, mình mất khá nhiều thời gian để xem nên sử dụng cái gì, dùng như thế nào để lấy dữ liệu từ trên server xuống một cách dễ dàng và đơn giản nhất. Sau khi vật lộn với google mình quyết định sử dụng bộ đôi Retrofit và RxJava để tạo ApiService cho project của mình.

Thông tin chi tiết về 2 thằng này, mọi người có thể tìm trên viblo có khá nhiều bài viết về nó rồi nhé.

Cài đặt

Retrofit

Retrofit là một thư viện HTTP Client cho Android và Java. Retrofit giúp dễ dàng kết nối tới một REST web service bằng cách dịch API thành các Java interface. Thư viện mạnh mẽ này giúp chúng ta làm việc dễ dàng với dữ liệu JSON hay XML sau đó phân tích thành các đối tượng object cơ bản Plain Old Java Objects (POJOs). Nó cũng cung cấp đầy đủ các phương thức GET, POST, PUT, PATCH, và DELETE.

Download the latest JAR or grab via Maven:

<dependency>
  <groupId>com.squareup.retrofit2</groupId>
  <artifactId>retrofit</artifactId>
  <version>2.4.0</version>
</dependency>

or Gradle:

implementation 'com.squareup.retrofit2:retrofit:2.4.0'

RxJava

ReactiveX API tập trung vào đồng bộ dữ liệu, là kết hợp tốt nhất từ các pattern Observer, Iterator và ngôn ngữ lập trình hàm. Lấy dữ liệu theo thời gian thực là vấn đề thông dụng đòi hỏi giải pháp rạch ròi, tối ưu, và có khả năng mở rộng. Sử dụng Observables và các toán tử, ReactiveX cung cấp một tổ hợp các API linh hoạt để tạo và thao tác trên dòng dữ liệu giải quyết vấn đề đồng bộ.

RxJava là thư viện mã nguồn mở implement ReactiveX trên Java. Có 2 lớp chính là Observable và Subscriber:

  • Observable là một lớp đưa ra dòng dữ liệu hoặc sự kiện (event). Flow của Observable là đưa ra một hoặc nhiều các items, sau đó gọi kết thúc thành công hoặc lỗi.
  • Subscriber lắng nghe flow, thực thi các hành động trên dòng dữ liệu hoặc sự kiện được đưa ra bởi Observable

Cài đặt thông qua Gradle:

compile "io.reactivex.rxjava2:rxjava:2.x.y"

(Please replace x and y with the latest version numbers: Maven Central )

Sử dụng

Để dễ dàng hơn trong việc gọi api, mình tạo 1 interface ApiService chứa thông tin về các request lên server mà mình sử dụng. Ngoài ra mình sử dụng thêm HttpLoggingInterceptor để debug các request.

const val BASE_URL = "https://YOUR.BASE.URL/API"
const val LOGIN: String = "login"
const val GET_POST_LIST: String = "posts"
const val GET_POST_DETAIL: String = "posts/{post_id}"
const val ATTACH_POST_DATA: String = "posts/{post_id}/upload"

interface ApiService {
    @POST(LOGIN)
    @FormUrlEncoded
    fun getAccessToken(
            @Field("client_id") clientId: String,
            @Field("client_secret") clientSecret: String,
            @Field("grant_type") grantType: String,
            @Field("username") userName: String,
            @Field("password") password: String
    ): Observable<User>

    @GET(GET_POST_LIST)
    fun getPosts(
        @Header("Authorization") token: String
    ) : Observable<PostList>

    @GET(GET_POST_DETAIL)
    fun getPostDetail(
            @Header("Authorization") token: String,
            @Path("Post_id") postId: Int
    ) : Observable<PostDetail>

    @Multipart
    @POST(ATTACH_POST_DATA)
    fun uploadPostData(
            @Header("Authorization") token: String,
            @Part("post_id") postId: Int,
            @Part file: MultipartBody.Part
    ): Observable<PostDetail>

    companion object {
        private val logging: HttpLoggingInterceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
        private val client: OkHttpClient = OkHttpClient.Builder().addInterceptor(logging).build()

        fun create(): ApiService {
            val retrofit = Retrofit.Builder()
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create())
                    .baseUrl(BASE_URL)
                    .client(client)
                    .build()

            return retrofit.create(ApiService::class.java)
        }
    }
}

Như các bạn có thể thấy, ApiService của mình khá là đơn giản. Mình đặt phương thức create() trong companion object để khi sử dụng mình có thể dễ dàng tạp một instance của service, chẳng hạn như

companion object {
    private val apiService by lazy { ApiService.create() }
    private var disposable: Disposable? = null
}

Ứng với mỗi phương thức, mình sử dụng Observable cùng với data class chứa dữ liệu. Dưới đây là ví dụ về cách sử dụng kết hợp giữa Retrofit và RxJava để login, get list, upload file

Login

disposable = apiService.getAccessToken(CLIENT_ID, CLIENT_SECRET, GRANT_TYPE, input_email.text.toString(), input_password.text.toString())
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(
                {result -> onLoginSuccess(result)},
                {error -> Toast.makeText(this, error.message, Toast.LENGTH_LONG).show()},
                {Log.i(TAG, "Login Completed")}
        )

Get Post list

val header = "Bearer abcdefghijklmnopqrstuvwxyz"
disposable = apiService.getPostList(header)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(
                {result -> showList(result)},
                {error -> Toast.makeText(this, error.message, Toast.LENGTH_SHORT).show()}
        )

Upload file

val header = "Bearer abcdefghijklmnopqrstuvwxyz"

val file = File(path)
val uploadFile = RequestBody.create(MediaType.parse(FileHelper.getMimeType(path)), file)
val filePart = MultipartBody.Part.createFormData("thumbnail", file.name, uploadFile)

disposable = apiService.uploadSingleFile(header, postId, filePart)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(
                {result -> onUploadSuccess(result)},
                {error -> Toast.makeText(context, error.message, Toast.LENGTH_SHORT).show()}
        )
0