[Android][Kotlin][Coroutines] Giới thiệu StateFlow, thay thế LiveData với StateFlow

Để hiểu rõ hơn về bài viết này, trước hết bạn cần hiểu được 1 chút kiến thức về Kotlin Coroutines. Trong bài viết này mình sẽ giới thiệu về StateFlow, cách sử dụng nó như thế nào, và thay thế LiveData bằng StateFlow như thế nào nhé. 1. Introduce State Follow Trong qá trình

Để hiểu rõ hơn về bài viết này, trước hết bạn cần hiểu được 1 chút kiến thức về Kotlin Coroutines. Trong bài viết này mình sẽ giới thiệu về StateFlow, cách sử dụng nó như thế nào, và thay thế LiveData bằng StateFlow như thế nào nhé.

1. Introduce

State Follow

Trong qá trình phát tiển ứng dụng, chúng ta có thể sẽ cần sử dụng Flow để biểu thị trạng thái có thể cập nhật trong các ứng dụng. Sự thay đổi này giới thiệu StateFlow – một giá trị có thể cập nhật đại diện cho một state và là một flow. Nó được thiết kế linh hoạt để phù hợp với nhiều nhu cầu ví dụ như sau:

  • StateFlow <T> interface là một dạng read-only view cung cấp quyền truy cập vào giá trị hiện tại và triển khai một Flow <T> để thu thập các cập nhật cho các giá trị.
  • MutabaleStateFlow <T> interface cho phép thêm xử lý đó là có thể sửa đổi giá trị.

MutableStateFlow có thể thay đổi được tạo bằng cách sử dụng hàm khởi tạo MutableStateFlow (param) với giá trị ban đầu. Giá trị của mutable state flow có thể thay đổi, có thể được cập nhật bằng cách đặt thuộc tính giá trị của nó. Cập nhật giá trị luôn được tích hợp. Vì vậy, một slow collector sẽ bỏ qua các bản cập nhật nhanh, nhưng luôn thu thập giá trị được emitted gần đây nhất.

StateFlow hữu ích như một lớp mô hình dữ liệu (data-model class) để đại diện cho bất kỳ loại trạng thái nào. Các giá trị có thể được xác định bằng cách sử dụng các toán tử khác nhau trên các follow, với toán tử kết hợp đặc biệt hữu ích để kết hợp các giá trị từ nhiều state flows bằng cách sử dụng các hàm tùy ý.
Ví dụ: lớp sau đóng gói một state số nguyên và tăng giá trị của nó trên mỗi lần gọi đến phương thức inc:

class CounterModel {privateval _counter =MutableStateFlow(0)// private mutable state flowval counter = _counter.asStateFlow()// publicly exposed as read-only state flowfuninc(){
        _counter.update{ count -> count +1}// atomic, safe for concurrent use}}

Có hai instance của lớp CounterModel ở trên, người ta có thể xác định tổng các bộ đếm của chúng như sau:

val aModel =CounterModel()val bModel =CounterModel()val sumFlow: Flow <Int>= aModel.counter.combine(bModel.counter){a, b -> a + b}

Và để thay thế cho cách sử dụng ở trên với hàm khởi tạo MutableStateFlow (…), bất kỳ coldFlow nào cũng có thể được chuyển đổi thành state follow bằng toán tử stateIn.

Và chốt lại, API state follow core có thể được tóm tắt như sau:

package kotlinx.coroutines.flow

interface StateFlow<T>: Flow<T>{val value: T // always availabe, reading it never fails}interface MutableStateFlow<T>: StateFlow<T>{overridevar value: T // can read & write value}fun<T>MutableStateFlow(value: T): MutableStateFlow<T>// constructor fun

StateFlow vs ConflatedBroadcastChannel

Về mặt khái niệm, state follow tương tự như ConflatedBroadcastChannel và được thiết kế để thay thế hoàn toàn nó. Nó có những điểm khác biệt quan trọng sau:

  • StateFlow đơn giản hơn, vì nó không phải triển khai tất cả các API Channel, cho phép triển khai nhanh hơn, không có rác (garbage-free), không giống như triển khai ConflatedBroadcastChannel phân bổ các đối tượng trên mỗi giá trị được phát ra.
  • StateFlow luôn có một giá trị có thể được đọc một cách an toàn bất cứ lúc nào thông qua thuộc tính giá trị. Không giống như ConflatedBroadcastChannel, không có cách nào để tạo state follow mà không có giá trị.
  • StateFlow có sự tách biệt rõ ràng thành interface StateFlow chỉ đọc và MutableStateFlow. (đã giới thiệu ở trên)
  • Sự kết hợp StateFlow dựa trên sự bình đẳng như toán tử DifferentUntilChanged, không giống như sự kết hợp trong ConflatedBroadcastChannel dựa trên tham chiếu tới id (reference identity).
  • StateFlow không thể closed như ConflatedBroadcastChannel và không bao giờ có thể đại diện cho một thất bại. Tất cả các lỗi và tín hiệu hoàn thành phải được cụ thể hóa rõ ràng nếu cần.

StateFlow được thiết kế để bao quát tốt hơn các trường hợp sử dụng điển hình để theo dõi các thay đổi trạng thái theo thời gian, đưa ra các lựa chọn thiết kế thực dụng hơn vì lợi ích của sự thuận tiện.

Chuyển Flow sang StateFlow

Một Flow thông thường là cold. Nó không có khái niệm về giá trị cuối cùng và nó chỉ hoạt động khi được thu thập. Tuy nhiên đã có toán tử stateIn để biến bất kỳ Follow nào thành hot StateFlow. Nó được thiết kế để thay thế cho broadcastIn. Nó sẽ giảm bớt nhu cầu phải có một “state flow builder” riêng vì bạn có thể chỉ cần viết luồng {….} .stateIn (scope) để khởi chạy một chương trình emits các giá trị theo code trong dấu ngoặc nhọn.

2. Replacing LiveData with StateFlow

Với sự gia tăng của KMM (Kotlin multiplatform mobile), làm thế nào để chúng ta dọn dẹp càng nhiều phụ thuộc Android (Android dependencies) càng tốt để tăng platform-independent code?
LiveData được cộng đồng Android sử dụng rộng rãi để quản lý và thể hiện trạng thái màn hình. Để thay thế nó một cách an toàn, chúng ta cần xem xét một số implementation chi tiết.

LiveData

Việc phát triển cho Android rất phức tạp vì một số lý do và một trong số chúng đang làm việc với vòng đời của các thành phần chính của nó: Activity và Fragment. Không xử lý vòng đời đúng cách có thể dẫn đến rò rỉ bộ nhớ, treo máy và các trạng thái không mong muốn. LiveData là một triển khai lifecycle-aware nhận biết vòng đời giúp giảm nỗ lực cần thiết để đối phó với những phức tạp này.

StateFlow

Kotlin là một ngôn ngữ mạnh mẽ và có thể trở nên mạnh mẽ hơn khi làm việc với Coroutines. Cả hai đều không ngừng phát triển và trên phiên bản Coroutines 1.3.6, StateFlow đã được giới thiệu. Nó được thiết kế để xử lý các tình huống state publication scenarios, cộng với các thế mạnh đã được giới thiệu ở trên, khiến nó trở thành một ứng cử viên sáng giá để thay thế LiveData.

Và bây giờ hãy cùng so sánh cách dùng nhé:

Using LiveData

Mình sẽ giải quyết trường hợp sử dụng chính của LiveData: xử lý view states bằng ViewModel và Fragment. Dưới đây, một cách triển khai đơn giản:

// ViewModel managing LiveDataclass MainViewModel :ViewModel(){privateval _count = MutableLiveData<Int>()val count : LiveData<Int>= _count
    
    privatefunincrementCounter(){
        _count.value =(_count.value as Int)+1}}
// Observing LiveData at MainFragmentprivatefunobserveState(binding: MainFragmentBinding){
    viewModel.count.observe(viewLifecycleOwner){
        binding.counter.text ="${it}s"}}

Using StateFlow

Đầu tiên, chúng ta cần thêm các phụ thuộc (dependencies) sau:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'

Và code triển khai StateFlow như sau:

// ViewModel managing StateFlowclass MainViewModel :ViewModel(){privateval _count =MutableStateFlow(0)val count : StateFlow<Int>= _count
    
    privatefunincrementCounter(){
        _count.value++}}
// Observing StateFlow at MainFragmentprivatefunobserveState(binding: MainFragmentBinding){
    viewLifecycleOwner.lifecycleScope.launchWhenStarted{
        viewModel.count.collect{
            binding.counter.text ="${it}s"}}}

Vậy là xong rồi, không hề phức tạp chút nào.

TUY NHIÊN, Một số điểm khác biệt cần lưu ý:

Always has a value:
StateFlow luôn có một giá trị có thể được đọc một cách an toàn bất cứ lúc nào thông qua thuộc tính value. Khi khởi tạo StateFlow, chúng ta phải cung cấp một giá trị ban đầu để đảm bảo rằng nó sẽ bắt đầu với một trạng thái. Thiết kế tốt theo ý kiến của mìh!

lifecycleScope
Bằng cách sử dụng extention viewLifecyleOwner.lifecycleScope, chúng ta làm cho follow consumption lifecycle-aware, giống nhưLiveData. Khi destroy, coroutine context bị hủy.

launchWhenStarted
LiveData chỉ phát ra khi LifecycleOwner ở trạng thái hoạt động. Nó tạm dừng consumption nếu trạng thái vòng đời “thấp hơn” so với Started. Để tái tạo hành vi này, chúng ta cần sử dụng launcherWhenStarted.

CHÚ Ý: Nếu các bạn bị lỗi ko tìm thấy extention collect thì hãy thêm dependencies này nhé:

import kotlinx.coroutines.flow.collect

Cám ơn các bạn đã dành thời gian theo dõi!

TÀI LIỆU THAM KHAO:

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ