Mình đã học kotlin như thế nào?: Phần 1: từ 1 dev IOS nhảy sang dev android

Bài viết được sự cho phép của tác giả Lê Xuân Quỳnh

Mình làm việc với IOS, cụ thể là dev objective-c và swift cũng được một thời gian, nên các kiến thức về IOS cũng kha khá. Một ngày đẹp trời giữa bão COVID-19, ở nhà làm việc remote, nên có nhiều thời gian để ngồi đọc và ngâm cứu các công nghệ khác hơn, mình quyết định “giao lưu” với Android 1 chút, cụ thể là Kotlin. Lý do chọn Kotlin là vì nó hao hao giống Swift, nên việc tiếp cận có vẻ dễ dàng hơn khi dùng java. Tất nhiên java vẫn được, mình cũng đã code nó trước đây.

  Có nên học Kotlin?
  Phát triển lập trình Android cùng Kotlin

Đầu tiên là thằng bạn nhờ dev hộ 1 con App trên mobile, và mình tự thiết kế giao diện bằng figma như sau:

Trông khá “sexy” với 1 người không chuyên designer đúng không

App thì có cả IOS và android, và sau đó mình quyết định làm cho android trước. Vì cơ bản mình muốn thử sức với cái “chưa biết gì”, như tờ giấy trắng trước. Cuộc sống mà, tính mình lại thích cái mới mẻ.

Vậy bên android công nghệ cái gì là “ngon” nhất bây giờ? Sau một hồi nghiên cứu, google, mình phát hiện ra “Ồ, ông google này chăm sóc dev kỹ thế 🤩” Cụ thể như sau:

Mô hình kiến trúc như sau:

Nhìn vào hình, ta chuyển thành cấu trúc thư mục tương ứng(thật ra là khá mơ hồ nếu chúng ta không đặt tay lên bàn phím và code:

Mình lấy cái ví dụ màn hình login ở trên cho dễ hiểu nhé:

Đầu tiên các bạn cài các thư viện cần thiết để code:

def lifecycle_version = "2.2.0"
    def room_version = "1.1.1"

    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
    implementation "android.arch.persistence.room:runtime:$room_version"
    annotationProcessor "android.arch.persistence.room:compiler:$room_version"

    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
    annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"

    implementation "androidx.recyclerview:recyclerview:1.1.0"
    implementation "androidx.cardview:cardview:1.0.0"


    def retrofit_version = "2.6.2"
    implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
    implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"

    implementation 'com.google.android.material:material:1.1.0'
    implementation 'com.google.android.gms:play-services-vision:19.0.0'

    def preference_version = "1.1.0"
    implementation "androidx.preference:preference-ktx:$preference_version"
  • Trên cùng là activity/fragment(cụ thể mình tạo lớp LoginActivity).

Trông hơi khác giao diện 1 xíu nhỉ, tại mình cũng lười sửa nên cứ để vậy.

Trong đó lớp LoginActivity sẽ phải nối tới lớp Viewmodel (màu xanh biển thứ 2 trên xuống), và trong lớp ViewModel này phải có các LiveData. Vậy LiveData là gì vậy ta?

Mình cũng như bạn, mới đầu chả hiểu nó là cái gì nên cứ code, sau đó ngộ ra nó là “data sống dịch theo nghĩa bình dân là thế! Data nó sống nghĩa là khi có 1 sự thay đổi nào đó về data, thì nó sẽ báo cho tất cả các thằng liên quan đang dùng tới nó, cụ thể là lớp LoginActivity sẽ biết khi nào data thay đổi để mà cập nhật lại dữ liệu.

Trong ví dụ mình thiết kế lớp LoginActivityViewModel:

class LoginActivityViewModel(application: Application): AndroidViewModel(application) {
    private val repository  = LoginActivityRepository(application)
    val loginResponse: LiveData<LoginResponse>
    val showProgress : LiveData<Boolean>

    init {
        this.showProgress = repository.showProgress
        this.loginResponse = repository.loginResponse
    }

    fun login(email: String, password: String) {
        this.repository.login(email, password)
    }
}

Ở đây có 2 cái LiveData đó là loginResponse và showProgress. loginResponse là dữ liệu trả về từ server, khi nó thay đổi thì báo cho view biết thông qua viewmodel. showProgress dùng để show cái loading khi người dùng bấm vào nút login, đẩy data lên thì mình show cái loading chặn người dùng không cho thao tác trên UI nữa.

Quên chưa đưa code của Activity:

class LoginActivity : AppCompatActivity() {
    private lateinit var viewModel: LoginActivityViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)

        edit_user.showKeyboard()

        viewModel = ViewModelProvider(this).get(LoginActivityViewModel::class.java)
        val dialog = ProgressDialogUtil.setProgressDialog(this, "Đang yêu cầu...")

        //handle show loading
        viewModel.showProgress.observe(this, Observer {
            if (it)
                dialog.show()
            else
                dialog.hide()
        })

..... nhiều code sau này nữa, đây chỉ là code trích dẫn không chạy được!
}

Các bạn nhìn lên trên code, giải thích như sau:

Google cung cấp ViewModelProvider để hỗ trợ mình tạo viewmodel trong View(activity). Sau đó, khi showProgress thay đổi giá trị, nó sẽ bắn sự kiện đó qua hàm viewModel.showProgress.observe..

thằng View lúc này biết mà điều chỉnh giao diện cho phù hợp.

Tiếp theo là lớp LoginActivityRepository, màu vàng cam ở trên hình. Lớp này làm nhiệm vụ là lấy thông tin từ viewmodel sau đó bắn xuống cho model để xử lý. Có 2 nhánh xử lý:

  • 1 là lấy dữ liệu local(cái này mình chưa đủ thời gian code. sẽ update sau 😪)
  • 2 là dùng retrofit để gọi data lên server và chờ đợi dữ liệu trả về

Cụ thể code như sau:

class LoginActivityRepository(val application: Application) {
    val showProgress = MutableLiveData<Boolean>()
    val loginResponse = MutableLiveData<LoginResponse>()

    fun changeState() {
        showProgress.value = !(showProgress.value != null && showProgress.value!!)
    }

    fun login(email: String, password: String) {
        showProgress.value = true

        val retrofit =
            Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create())
                .build()
        val service = retrofit.create(ZentNetwork::class.java)
        val loginRequest = LoginRequest(email, password, "2")

        service.login(loginRequest).enqueue(object :Callback<LoginResponse> {
            override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
                showProgress.value = false
                loginResponse.value = null
                Log.d("login", "login failed ${t.localizedMessage}")
            }

            override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) {
                showProgress.value = false
                loginResponse.value = response.body()
                Log.d("login", "login success ${response.body()}")
            }

        })
    }
}

Ở đây các bạn chú ý hàm fun login(email: String, password: String), cùng tên với hàm trong Viewmodel. Khi viewmodel gọi tới nó, nó sẽ dùng retrofit gọi dữ liệu từ server. Khi có data trả về, nó tiến hành thay đổi trạng thái của 2 biến showProgress, loginResponse của nó. Và bắn lên viewmodel biết sự thay đổi đó. Code trên đang thiếu mất phần lưu data vào local nhé(mình tính xài realm cho nó chất 😎, sẽ update phần sau).

Code phần API dùng retrofit như sau:

Đầu tiên là tạo 1 cái interface đúng như thằng retrofit bắt làm:

interface ZentNetwork {
    @Headers("Content-Type: application/json")
    @POST("login")
    fun login(@Body body: LoginRequest)
    :Call<LoginResponse>
}

Còn response trả về có dạng:

{
    "status": {
        "code": 0,
        "message": "success"
    },
    "data": {
        "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjE1MzZlMWY1NzIxYTQzZjk2N2I2NDYyYjA5YTBhMjMwZjcxNzkHLQBL6zA45BqHUnzhqSvjaM12vIaxHfw8j1AP1Hu3tpL4_7O8KHm4gj8Fu303Gzm9s",
        "user_info": {
            "name": "Trần Nga",
            "email": "[email protected]",
            "address": null,
            "mobile": "0972612601"
        }
    }
}

Từ data như này làm sao để viết được Model. Ồ! đơn giản lắm các bạn ơi, các bạn dùng cái này nó support tận răng, như hình:

Cách cài plugin thì các bạn xem ở link sau: https://www.youtube.com/watch?v=ku9l-CXHX00

Sau khi có model LoginResponse rồi thì các bạn đưa vào phần retrofit như đoạn code service.login(loginRequest).enqueue(object :Callback<LoginResponse>

Cơ bản MVVM trong android chỉ thế thôi các bạn nhé. Khá là đơn giản đúng không? Về cơ bản nếu bạn nắm chắc được việc sử dụng Android studio, các thư viện như retrofit, thì mấy việc trên đều khá dễ làm và nhanh nữa.

Mình sẽ viết tiếp trong các bài tiếp theo. Còn bây giờ thì vừa code vừa ngẫm nghĩ tiếp.

Bài viết gốc được đăng tải tại codetoanbug.com

Có thể bạn quan tâm:

Xem thêm tuyển dụng android, tuyển ios hấp dẫn trên TopDev