Android Architecture Component – Phần 3: Tìm Hiểu Về Lifecycle Aware Components

Posted by

Chào mừng các bạn đã đến với bài viết về Android Architecture Component – Phần 3. Đây là bài viết trong các bài bổ sung cho chương trình học Android của Yellow Code Books.

Ở các phần trước, chúng ta đã cùng nhau nói đến cách sử dụng hai thành phần cơ bản trong bộ kiến trúc Android Architecture Component mà Google mang tới, đó là ViewModelLiveData. Hai thành phần này là gì? Chúng ta cùng ôn lại một tí như sau.

ViewModel thực chất chỉ là một lớp được cung cấp bởi hệ thống, nó được tạo ra nhằm mục đích giúp bạn quản lý các dữ liệu của UI, đảm bảo các dữ liệu này luôn được bảo toàn trong quá trình sống của UI đó.

Xem thêm ở mục ViewModel Là Gì?

LiveData là một lớp, nó dùng để truyền tải các thông điệp về dữ liệu, dựa trên mô hình của Observer.

Xem thêm ở mục LiveData Là Gì?

Quả thật hai thành phần ViewModelLiveData cũng đã giúp ích chúng ta giải quyết rất nhiều yêu cầu cơ bản từ các ứng dụng hiện nay rồi. Thế nhưng bấy nhiêu đó thôi vẫn chưa nói hết sức mạnh của Android Architecture Component mang lại. Chúng ta còn phải nói nhiều hơn vể công dụng của kiến trúc mới này. Và hôm nay mình xin trình bày một khía cạnh khác nữa của nó, khía cạnh này có cái tên Lifecycle-Aware Components.

Lifecycle-Aware Components Là Gì?

Cơ bản thì Lifecycle-Aware Components được hiểu đúng như những gì cái tên của nó mang lại. Đó chính là một công cụ giúp bạn có thể xây dựng nên các đối tượng có khả năng nhận biết được sự thay đổi về các trạng thái (lifecycle aware) của các thành phần giao diện (components), như là Activity hay Fragment.

Để dễ hiểu hơn là, đôi khi bạn mong muốn xây dựng các logic cho đối tượng của bạn sao cho nó bám sát vào các trạng thái (hay vòng đời) của các thành phần giao diện. Có thể kể ra đây như, đối tượng giúp kết nối với server để download ảnh chẳng hạn, hay đối tượng lấy tọa độ của người dùng, đối tượng chơi nhạc,… Bạn mong muốn các đối tượng này chỉ được thực thi khi Activity hay Fragment hiển thị ra cho người dùng, và phải ngưng lại, hoặc giải phóng tài nguyên hệ thống khi Activity hay Fragment đó không còn hiển thị nữa. Bạn xem, nếu đối tượng của bạn không quan tâm đến các trạng thái của Activity hay Fragment này, nó có thể gây ra các lỗi nghiêm trọng, nhiều khả năng sẽ crash app, khi mà các thành phần giao diện này đã không còn sống nữa nhưng đối tượng vẫn gọi đến nó.

Vậy thôi, chỉ với ví dụ mong muốn được biết các trạng thái hiển thị, hay còn sống của thành phần giao diện thôi, cũng đủ thấy vai trò của Lifecycle-Aware Components rồi đúng không nào.

Tại Sao Phải Sử Dụng Lifecycle-Aware Components?

Có thể tổng hợp lại là, Lifecycle-Aware Components sẽ giúp cho các đối tượng mà bạn tạo ra, có sự bám sát vào các trạng thái của các thành phần ứng dụng hiệu quả hơn, tránh xảy ra lỗi hơn, nhưng với số lượng code ít hơn, mà lại dễ đọc và dễ bảo trì hơn là việc bạn không áp dụng thành phần này vào trong code của đối tượng.

Để chứng minh cho ý trên, chúng ta cùng đi đến một ví dụ cụ thể để thấy được vai trò của Lifecycle-Aware Components này nhé.

Ví dụ chúng ta mong muốn xây dựng một ứng dụng, ứng dụng này sẽ lấy thông tin tọa độ của người dùng và hiển thị chúng lên màn hình. Lưu ý là chỉ hiển thị dạng tọa độ (longitude & latitude, tức là kinh độ & vĩ độ) lên màn hình thôi nhé. Ứng dụng sẽ không hiển thị bản đồ rồi gắn pin vị trí của người dùng lên bản đồ đâu. Chúng ta xây dựng ứng dụng nhẹ và nhanh nhất có thể thôi. Và, để làm theo yêu cầu của bài học, chúng ta sẽ chỉ lấy thông tin tọa độ của người dùng khi mà màn hình chính của ứng dụng hiển thị, còn khi người dùng qua màn hình khác, hoặc chuyển qua sử dụng ứng dụng khác, thì đối tượng lấy thông tin này sẽ dừng lại.

Ở mục này, chúng ta sẽ xây dựng hoàn chỉnh ứng dụng, nhưng theo cách thức cũ, cái cách mà trước khi Lifecycle-Aware Components ra đời. Để các bạn có thể so sánh và hiểu lý do tại sao nên chuyển qua dùng Lifecycle-Aware Components ở mục sau.

Để bắt đầu, chúng ta cùng tạo mới một project Android với tùy chọn Empty Activity (xem hình dưới). Nếu bạn là người mới tiếp cận Android, hãy đọc bài này để biết cách thức tạo mới một project Android nhé.

Tạo mới project

Sau khi đã tạo một project, chúng ta nên khai báo 2 thẻ sau vào Manifest để có thể xin người dùng quyền được sử dụng GPS. Nếu bạn có trắc trở nào ở phần này thì có thể tham khảo source code hoàn chỉnh mình ở link cuối bài.

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />

Tuy đã khai báo quyền được sử dụng GPS ở Manifest như trên, nhưng từ Android 6 (API level 23), chúng ta phải xây dựng thêm chức năng xin quyền người dùng ở runtime nữa. Để làm điều này, bạn hãy thêm các dòng code vào trong file MainActivity.kt như sau. Bạn nên nhớ kiến thức xin quyền này không phải là chủ đề của bài học hôm nay, nên mình xin không nói nhiều về các dòng code này, nếu bạn muốn tìm hiểu về nó, hãy đọc trên trang Android Developer này nhé.

class MainActivity : AppCompatActivity() {

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // TODO: Bắt đầu sử dụng GPS sau khi xin xong quyền
        } else {
            Toast.makeText(this, "Bạn chưa có quyền truy cập vào GPS của thiết bị", Toast.LENGTH_LONG).show()
        }
    }

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

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_LOCATION_PERMISSION_CODE)
        } else {
            // TODO: Bắt đầu sử dụng GPS sau khi xin xong quyền
        }
    }

    companion object {
        const val REQUEST_LOCATION_PERMISSION_CODE = 1
    }
}

Những chỗ được đánh dấu TODO ở code trên chính là những chỗ chúng ta có thể lấy thông tin tọa độ từ người dùng được rồi đấy. Các dòng code còn lại chủ yếu là để hiển thị popup xin quyền mà thôi. Bạn có thể thử thực thi ứng dụng để xem popup xin quyền này nhé.

Lifecycle Aware Components - Popup xin quyền
Màn hình xin quyền người dùng khi thực thi ứng dụng

Bước đầu suôn sẻ rồi. Giờ chúng ta tạo một lớp mới có tên MyLocationManager.kt. Lớp này đảm nhiệm vai trò sử dụng các đối tượng LocationManagerLocationListener từ hệ thống để lấy về thông tin tọa độ của người dùng cho ứng dụng của chúng ta. Một lần nữa, kiến thức về location cũng không phải là chủ đề của bài học hôm nay, nên mình cũng không nói nhiều về các dòng code dưới đây. Chi tiết của các dòng code bạn có thể xem thêm trên trang Android Developer này nhé.

Lớp MyLocationManager.kt sẽ như sau, bạn có thể xem các comment của mình ở trong code để dễ hiểu hơn.

/**
 * Lớp này phụ trách việc kết nối lấy thông tin location của user, và trả thông tin này về cho lớp gọi
 * context: Context của lớp gọi
 * callback: Trả kết quả về cho callback
 */
@SuppressWarnings("MissingPermission")
class MyLocationManager(private val context: Context, private val callback: (Location) -> Unit) {

    private var mLocationManager: LocationManager? = null

    /**
     * Phương thức bắt đầu kết nối với Google Service để lấy thông tin tọa độ
     */
    fun start() {
        mLocationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager?
        mLocationManager?.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f, locationListener)

        // Force an update with the last location, if available.
        val lastLocation = mLocationManager?.getLastKnownLocation(
            LocationManager.GPS_PROVIDER
        )
        if (lastLocation != null) {
            locationListener.onLocationChanged(lastLocation)
        }

        Toast.makeText(context, "MyLocationManager started", Toast.LENGTH_SHORT).show()
    }
    
    fun stop() {
        if (mLocationManager == null) {
            return
        }
        mLocationManager?.removeUpdates(locationListener)
        mLocationManager = null

        Toast.makeText(context, "MyLocationManager paused", Toast.LENGTH_SHORT).show()
    }

    /**
     * Custom lại LocationListen để có thể trả thông tin location về cho lớp gọi thông qua callback
     */
    val locationListener = object : LocationListener {

        override fun onLocationChanged(location: Location) {
            callback.invoke(location)
        }

        override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {
        }

        override fun onProviderEnabled(provider: String) {
        }

        override fun onProviderDisabled(provider: String) {
        }
    }
}

Đại loại thì với lớp MyLocationManager.kt này mình xây dựng nên hai phương thức chính là start()stop(). Khi bên ngoài muốn lấy thông tin tọa độ, nó chỉ việc khai báo đối tượng này và gọi đến phương thức start(), phương thức này sẽ dựa vào LocationManager để lấy về location. Lấy được location rồi nó sẽ gọi đến LocationListener để trả location ra bên ngoài. Còn phương thức stop() sẽ kết thúc và giải phóng LocationManager.

Chúng ta cần phải quay lại MainActivity.kt để thay đổi chút đỉnh logic cho màn hình này có thể sử dụng MyLocationManager.kt để hiển thị tọa độ lên màn hình.

Chỉnh sửa đầu tiên, tuy rất nhỏ, nhưng mình nghĩ cũng nên liệt kê ra đây. Đó là giao diện của MainActivity, file activity_main.xml. Mình chỉ thêm ID cho TextView có sẵn ở màn hình này để một lát nữa đây có thể hiển thị thông tin tọa độ lên đó mà thôi.

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
		xmlns:android="http://schemas.android.com/apk/res/android"
		xmlns:tools="http://schemas.android.com/tools"
		xmlns:app="http://schemas.android.com/apk/res-auto"
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		tools:context=".MainActivity">

	<TextView
			android:id="@+id/tvContent"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:text="Hello World!"
			app:layout_constraintBottom_toBottomOf="parent"
			app:layout_constraintLeft_toLeftOf="parent"
			app:layout_constraintRight_toRightOf="parent"
			app:layout_constraintTop_toTopOf="parent"/>

</android.support.constraint.ConstraintLayout>

Và đây là chỉnh sửa đối với MainActivity.kt.

class MainActivity : AppCompatActivity() {

    private lateinit var myLocationManager: MyLocationManager

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            setupLocationListener()
        } else {
            Toast.makeText(this, "Bạn chưa có quyền truy cập vào GPS của thiết bị", Toast.LENGTH_LONG).show()
        }
    }

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

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_LOCATION_PERMISSION_CODE)
        } else {
            setupLocationListener()
        }
    }

    public override fun onStart() {
        super.onStart()

        // Bắt đầu lấy thông tin tọa độ
        myLocationManager.start()
    }

    public override fun onStop() {
        super.onStop()

        // Kết thúc lấy thông tin tọa độ
        myLocationManager.stop()
    }

    private fun setupLocationListener() {
        myLocationManager = MyLocationManager(this) { location ->
            tvContent.text = location.latitude.toString() + ", " + location.longitude
        }
    }

    companion object {
        const val REQUEST_LOCATION_PERMISSION_CODE = 1
    }
}

Chà chà nhiều code quá nhỉ. Tuy nhiên nếu nhìn lại theo các khối sau đây thì bạn sẽ dễ hiểu hơn.

  • Đầu tiên, ở phương thức setupLocationListener(). Phương thức này bắt đầu khởi tạo MyLocationManager sau khi xin quyền thành công, hoặc khi màn hình được tạo ở onCreate(). Trong khi khởi tạo MyLocationManager, thì chúng ta khai báo và truyền vào callback. Khi callback này được gọi có nghĩa là LocationListener bên MyLocationManager đã nhận được thông tin vị trí hiện tại của người dùng. Thế thì vị trí đó sẽ được hiển thị lên TextView tvContent thôi.
  • Phương thức onStart(). Đây là callback được gọi tự động trong vòng đời của Activity. Vì yêu cầu của ứng dụng là chỉ cho phép lấy thông tin tọa độ của người dùng khi ứng dụng được mở, do đó callback này rất thích hợp để chúng ta bắt đầu lấy vị trí của người dùng thông qua lời gọi myLocationManager.start().
  • Cuối cùng là phương thức onStop(). Khi Activity không còn hiển thị với người dùng nữa, callback này sẽ được gọi, bạn có thể tham khảo thêm thông tin phương thức này ở đây. Và đây cũng là callback thích hợp để chúng ta kết thúc việc lấy vị trí của người dùng.

Đến đây bạn có thể thực thi chương trình, và hãy chú ý các thông báo trên Toast để xem khi nào ứng dụng bắt đầu lấy thông tin người dùng, khi nào kết thúc nhé. À, tọa độ của người dùng sẽ hiển thị ở giữa màn hình đấy, khi người dùng di chuyển, tọa độ này cũng được cập nhật và thay đổi thường xuyên.

Trên đây là cách cũ mà trước đây chúng ta hay thực thi. Xét về mặt logic thì nó hoàn toàn đúng. Đúng ở chỗ nó (MyLocationManager) vẫn theo sát các trạng thái của các thành phần giao diện như Activity hay Fragment. Nhưng nói đúng hơn, thì không phải MyLocationManager “theo sát” các trạng thái của Activity hay Fragment đâu, mà là Activity hay Fragment đang “điều khiển” MyLocationManager sao cho có thể theo sát các trạng thái của chúng, thông qua các callback onStart()onStop() ở các dòng code trên. Nói như vậy là, nếu chúng ta muốn cho MyLocationManager hay các đối tượng khác sau này của bạn càng theo sát các trạng thái của thành phần giao diện hơn nữa, thì bạn cần phải khai báo nhiều hơn các callback từ Activity hay Fragment này. Điều này dẫn đến một khuyết điểm, là code của bạn sẽ phình lên, làm cho việc bảo trì sẽ trở nên khó quản lý hơn.

Trầm trọng hơn, cách cũ trên đây tồn tại một kẻ hở, kẻ hở này có thể gây ra các lỗi nghiêm trọng trên ứng dụng của chúng ta, có thể dẫn đến crash ứng dụng. Giả sử ứng dụng của chúng ta có thêm chức năng quản lý người dùng, đòi hỏi người dùng phải login trước khi có thể nhìn thấy tọa độ của họ trên màn hình. Và, bạn xây dựng thêm chức năng login ở đâu đó, ở lớp Util chẳng hạn, trong lớp này bạn xây dựng phương thức userLogin() để gọi API đến server. Code ví dụ như sau (code này chỉ là ví dụ thôi, không có trên Github, do đó bạn đừng code thử).

class MainActivity : AppCompatActivity() {

    // ...

    public override fun onStart() {
        super.onStart()

        // Thực hiện login trước khi lấy thông tin tọa độ
        Util.userLogin { result ->
            if (result) {
                // Bắt đầu lấy thông tin tọa độ
                myLocationManager.start()       
            }
        }
    }

    public override fun onStop() {
        super.onStop()

        // Kết thúc lấy thông tin tọa độ
        myLocationManager.stop()
    }

    // ...
}

Vậy kẻ hở ở đây là gì? Giả sử phương thức Util.userLogin() phải thực hiện trong một khoảng thời gian hơi lâu, người dùng đợi không được, họ thoát ứng dụng, hoặc chuyển sang màn hình khác. Tình huống này dẫn đến lời gọi myLocationManager.start() chưa kịp thực thi, thì myLocationManager.stop() đã được gọi. Việc stop() trước start() như vậy khá nguy hiểm, sẽ có những bạn không kiểm tra nullstop() khi giải phóng các đối tượng, trong khi các đối tượng này thậm chí còn chưa được khởi tạo ở start() nữa. Sẽ dẫn đến crash ứng dụng.

Nào, chúng ta cùng qua bước kế tiếp, để xem Lifecycle-Aware Components giúp chúng ta giải quyết các khuyết điểm này như thế nào nhé.

Lifecycle-Aware Components Gồm Có Những Đối Tượng Nào?

Đây mới bắt đầu mục chính của bài học hôm nay. Để có thể bắt tay vào sử dụng thành phần thú vị này, chúng ta phải nắm vững khái niệm của một vài đối tượng mới mẻ mà thành phần này mang lại như sau.

Đối Tượng Lifecycle

Lifecycle là đối tượng đầu tiên mà thành phần mới này mang lại. Lifecycle sẽ nắm giữ thông tin về các trạng thái của các thành phần giao diện như Activity hay Fragment. Lát nữa khi nhìn vào sơ đồ bên dưới bạn sẽ thấy rõ các trạng thái này. Mục đích của các trạng thái này là gì? Đó là, giúp cho các đối tượng khác có thể dựa vào đây mà định nghĩa ra các logic, các logic này sẽ bám sát theo các giá trị đã được định nghĩa này (bằng cách sử dụng annotation trước mỗi phương thức). Một lát đến bài thực hành bên dưới mình sẽ nói rõ hơn.

Còn đây là sơ đồ về các trạng thái mà Lifecycle định nghĩa sẵn.

Các State và Event trong lớp Lifecycle

Với sơ đồ trên, bạn hãy nhớ Lifecycle có định nghĩa 2 enum là EventState. Event sẽ chứa các giá trị diễn đạt cho các callback của Activity hay Fragment, bạn có thể xem ON_CREATE, ON_START,… sẽ khớp với các callback onCreate(), onStart(),… đúng không nào. Còn State là các diễn đạt mới về các trạng thái của Activity hay Fragment.

Nếu bạn vẫn chưa hiểu chuyện gì đang xảy ra, thì cứ tạm biết đến đây nhé, chúng ta sẽ làm sáng tỏ vấn đề một cách từ từ.

Các Interface: LifecycleOwner & LifecyleObserver

Chúng ta cùng nói về hai interface này trong một mục, vì sao vậy? Chỉ cần đọc tên thôi bạn cũng đủ biết câu trả lời rồi.

Cụ thể là vì để cho các thành phần giao diện (Activity hay Fragment) có thể thông báo các trạng thái của chúng đến với các thành phần lắng nghe khác (mình dùng từ “thông báo” chứ không phải “điều khiển” như cách làm kiểu cũ trên kia đâu nhé), thì hệ thống phải dùng đến mô hình Observer. Bạn có thể xem lại phần trước để hiểu rõ về mô hình này, mình trích lại ý sau.

Nếu bạn nào còn chưa rõ về khái niệm Observer, thì đây chính là một mô hình trong số các mô hình mẫu khác (các mô hình mẫu đều được gọi chung với cái tên Design Pattern). Mô hình Observer này được xây dựng với ý tưởng sẽ có một đối tượng trung tâm (còn được gọi là Subject), đối tượng trung tâm này nắm danh sách các đối tượng quan sát khác (đối tượng quan sát chính là các Observer). Để rồi khi có bất kỳ thay đổi nào với trạng thái của đối tượng trung tâm đó, nó sẽ thông báo ngay cho các đối tượng quan sát được biết.

Xem thêm ở mục LiveData Là Gì?

Với mô hình Observer như vậy. Thì các Activity hay Fragment sẽ implement interface LifecycleOwner. Còn các đối tượng mới mẻ nào đó mà bạn muốn lắng nghe các trạng thái của Activity hay Fragment này (như đối tượng MyLocationManager mà chúng ta đã xây dựng trên kia) sẽ implement interface LifecycleObserver. Hai dẫn xuất của LifecycleOwnerLifecycleObserver này sẽ dùng đến đối tượng Lifecycle trên kia để mà trao đổi các trạng thái với nhau. Đến đây thì các bạn đã có sự gắn kết giữa các khái niệm mới trong bài này chưa nào.

Xây Dựng Lifecycle-Aware Components Như Thế Nào?

Nào, giờ thì chúng ta hãy tận dụng các kiến thức trên đây để làm tốt hơn ứng dụng lấy tọa độ của người dùng bằng cách tận dụng sức mạnh của Lifecycle-Aware Components nhé.

Trước tiên, chúng ta hãy đến với lớp cốt lõi của ứng dụng, lớp MyLocationManager.kt. Như đã nói trên kia, đây là lớp mà chúng ta cần phải lắng nghe các trạng thái của Activity hay Fragment sử dụng đến. Bạn đã biết, với cách cũ trên đây, MyLocationManager rất “bị động”, nó phải chờ Activity hay Fragment gọi đến các phương thức start() hay stop() của nó thì mới thực thi. Nhưng với cách mới này, MyLocationManager sẽ chủ động hơn, bằng cách implement interface LifecycleObserver (bạn hãy xem kỹ lại LifecycleObserver là gì từ mục trên nhé). Mình thêm vào các code được tô sáng như sau. Bạn code đi rồi mình giải thích sau nhé.

/**
 * Lớp này phụ trách việc kết nối lấy thông tin location của user, và trả thông tin này về cho lớp gọi
 * context: Context của lớp gọi
 * callback: Trả kết quả về cho callback
 */
@SuppressWarnings("MissingPermission")
class MyLocationManager(private val context: Context, private val callback: (Location) -> Unit): LifecycleObserver {

    private var mLocationManager: LocationManager? = null

    /**
     * Phương thức bắt đầu kết nối với Google Service để lấy thông tin tọa độ
     */
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun start() {
        mLocationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager?
        mLocationManager?.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f, locationListener)

        // Force an update with the last location, if available.
        val lastLocation = mLocationManager?.getLastKnownLocation(
            LocationManager.GPS_PROVIDER
        )
        if (lastLocation != null) {
            locationListener.onLocationChanged(lastLocation)
        }

        Toast.makeText(context, "MyLocationManager started", Toast.LENGTH_SHORT).show()
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun stop() {
        if (mLocationManager == null) {
            return
        }
        mLocationManager?.removeUpdates(locationListener)
        mLocationManager = null

        Toast.makeText(context, "MyLocationManager paused", Toast.LENGTH_SHORT).show()
    }

    /**
     * Custom lại LocationListen để có thể trả thông tin location về cho lớp gọi thông qua callback
     */
    val locationListener = object : LocationListener {

        override fun onLocationChanged(location: Location) {
            callback.invoke(location)
        }

        override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {
        }

        override fun onProviderEnabled(provider: String) {
        }

        override fun onProviderDisabled(provider: String) {
        }
    }
}

Sự sửa chữa đối với MyLocationManager không nhiều đúng không nào, nhưng rất hiệu quả. Việc implement interface LifecycleObserver cho phép lớp này có khả năng “lắng nghe” các trạng thái của Activity hay Fragment (chính là LifecycleOwner mà lát chúng ta nói đến). Nhưng lắng nghe như thế nào? Đó chính là dựa vào các định nghĩa annotation trước các phương thức onStart()onStop() mà bạn thấy, có các annotation tương ứng. Như @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) sẽ cho hệ thống biết chỉ nên gọi đến phương thức này khi Activity hay Fragment liên kết với nó được gọi bởi onResume(). Hay @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) sẽ cho hệ thống biết chỉ nên gọi đến phương thức này khi Activity hay Fragment liên kết với nó dược gọi trước khi onPause(). Và các trạng thái khác tương tự.

Chúng ta vừa mới implement LifecycleObserver cho MyLocationManager. Giờ hãy đến với LifecycleOwner, chính là lớp tạo ra các trạng thái mà LifecycleObserver nghe theo. Tuy nhiên có một điều hay là với các Activity hay Fragment trong gói Support Library 26.1.0 trở lên thì sẽ mặc định được implement LifecycleOwner rồi. Có nghĩa là chúng ta không cần phải khai báo gì thêm đối với các thành phần giao diện này. Vậy MainActivity.kt của chúng ta sẽ trông như sau.

class MainActivity : AppCompatActivity() {

    private lateinit var myLocationManager: MyLocationManager

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            setupLocationListener()
        } else {
            Toast.makeText(this, "Bạn chưa có quyền truy cập vào GPS của thiết bị", Toast.LENGTH_LONG).show()
        }
    }

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

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_LOCATION_PERMISSION_CODE)
        } else {
            setupLocationListener()
        }
    }

    private fun setupLocationListener() {
        myLocationManager = MyLocationManager(this) { location ->
            tvContent.text = location.latitude.toString() + ", " + location.longitude
        }
        lifecycle.addObserver(myLocationManager)
    }

    companion object {
        const val REQUEST_LOCATION_PERMISSION_CODE = 1
    }
}

Bạn có thấy khác biệt gì không? Có thể thấy rằng các callback onStart(), onStop() đã không còn nữa. Vì sao? Vì MyLocationManager đã tự nó biết lắng nghe các trạng thái này rồi, MainActivity không cần phải điều khiển nữa để tránh xảy ra lỗi như ví dụ kiểu cũ trên kia. Nhưng, để làm cho MyLocationManager có thể tự nó biết lắng nghe, thì MainActivity (chính là lớp dẫn xuất của LifecycleOwner) phải gọi đến phương thức lifecycle.addObserver(myLocationManager) để đăng ký lắng nghe cho MyLocationManager này. Bạn đừng quên dòng này nhé.

Giờ đây khi thực thi ứng dụng, bạn đã có thể thấy nó hoạt động như như cách cũ trên kia, nhưng code đã ngắn hơn đúng không nào, vì hệ thống đã làm giúp chúng ta vài điều rồi. Trước mắt mình cũng chưa có cơ hội ứng dụng thành phần này vào các project của mình đang quản lý, để xem tính ổn định của nó như thế nào. Nhưng không sao, code ngắn lại là vui rồi. Cảm nhận của các bạn sau bài viết hôm nay như thế nào, hãy chia sẻ nhé.

Kết quả cuối cùng

Bạn vừa xem qua phần 3 của Android Architecture Component. Cảm ơn các bạn đã đọc và ủng hộ blog. Các bạn hãy chờ đón phần 4 của mình.

Download Source Code Mẫu

Bạn có thể download source code mẫu của bài này ở đây.

Cảm ơn bạn đã đọc các bài viết của Yellow Code Books. Bạn hãy ủng hộ blog bằng cách:

Đánh giá 5 sao bên dưới mỗi bài nếu thấy thích.
Comment bên dưới mỗi bài nếu có thắc mắc.
Để lại địa chỉ email của bạn ở thanh bên phải để nhận được thông báo sớm nhất khi có bài viết mới.
Chia sẻ các bài viết của Yellow Code Books đến nhiều người khác.

Advertisements
No votes yet.
Please wait...

Gửi phản hồi