StateFlow
StateFlow
được mô tả là một flow đại diện cho trạng thái chỉ đọc với một giá trị dữ liệu có thể cập nhật duy nhất phát ra các bản cập nhật giá trị cho bộ thu (flow collectors) của nó. StateFlow
là một data-model hữu ích để đại diện cho bất kỳ loại trạng thái nào, nó được thiết kế để sử dụng trong các trường hợp yêu cầu quản lý trạng thái trong thực thi bất đồng bộ với Kotlin Coroutines. StateFlow
được nâng lên stable API kể từ phiên bản Kotlin Coroutines 1.4.0
StateFlow gồm hai biến thể: StateFlow và MutableStateFlow :
StateFlow<T>
interface chỉ cung cấp quyền truy cập giá trị.MutabaleStateFlow<T>
interface cung cấp khả năng sửa đổi giá trị.
1 2 3 4 5 6 7 8 9 | <span class="token keyword">public</span> <span class="token keyword">interface</span> StateFlow<span class="token operator"><</span><span class="token keyword">out</span> T<span class="token operator">></span> <span class="token operator">:</span> SharedFlow<span class="token operator"><</span>T<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">val</span> value<span class="token operator">:</span> T <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">interface</span> MutableStateFlow<span class="token operator"><</span><span class="token keyword">out</span> T<span class="token operator">></span><span class="token operator">:</span> StateFlow<span class="token operator"><</span>T<span class="token operator">></span><span class="token punctuation">,</span> MutableSharedFlow<span class="token operator"><</span>T<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">override</span> <span class="token keyword">var</span> value<span class="token operator">:</span> T <span class="token keyword">public</span> <span class="token keyword">fun</span> <span class="token function">compareAndSet</span><span class="token punctuation">(</span><span class="token keyword">expect</span><span class="token operator">:</span> T<span class="token punctuation">,</span> update<span class="token operator">:</span> T<span class="token punctuation">)</span><span class="token operator">:</span> Boolean <span class="token punctuation">}</span> |
Trạng thái của StateFlow
được biểu thị bằng giá trị thông qua thuộc tính value
. Thuộc tính value
luôn có giá trị ban đầu do đó có thể đọc một cách an toàn bất cứ lúc nào. Mọi cập nhật đối với giá trị sẽ được phản ánh trong tất cả các bộ thu bằng cách phát ra một giá trị với các cập nhật trạng thái. Trong Android, StateFlow
rất phù hợp cho các lớp cần duy trì trạng thái có thể thay đổi quan sát được.
Một ví dụ đơn giản về cách sử dụng State flow:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <span class="token keyword">class</span> MainViewModel <span class="token operator">:</span> <span class="token function">ViewModel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">val</span> _countState <span class="token operator">=</span> <span class="token function">MutableStateFlow</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">val</span> countState<span class="token operator">:</span> StateFlow<span class="token operator"><</span>Int<span class="token operator">></span> <span class="token operator">=</span> _countState <span class="token keyword">fun</span> <span class="token function">incrementCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> viewModelScope<span class="token punctuation">.</span><span class="token function">launch</span> <span class="token punctuation">{</span> _countState<span class="token punctuation">.</span>value<span class="token operator">++</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">fun</span> <span class="token function">decrementCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> viewModelScope<span class="token punctuation">.</span><span class="token function">launch</span> <span class="token punctuation">{</span> _countState<span class="token punctuation">.</span>value<span class="token operator">--</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Đoạn này khá giống cách sử dụng của LiveData
:
- Khai báo
_countState
là MutableStateFlow để khởi tạo giá trị và cung cấp khả năng cập nhật giá trị. - Khai báo một thể hiện của StateFlow là
countState
được để lộ cho Views (read-only field).
Khi bắt đầu thu giá trị từ StateFlow
, bộ thu sẽ nhận được trạng thái cuối cùng trong luồng và mọi trạng thái tiếp theo. Các giá trị trong StateFlow
được kết hợp bằng cách sử dụng so sánh Any.equals theo cách tương tự với toán tử distinctUntilChanged
. Nó được sử dụng để kết hợp các bản cập nhật value
trong MutableStateFlow và để ngăn chặn việc phát ra các giá trị tới bộ thu khi giá trị mới bằng với giá trị được phát ra trước đó.
Ví dụ views lắng nghe trạng thái StateFlow
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <span class="token keyword">class</span> MainActivity <span class="token operator">:</span> <span class="token function">AppCompatActivity</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">val</span> viewModel <span class="token keyword">by</span> lazy <span class="token punctuation">{</span> <span class="token function">ViewModelProvider</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">[</span>MainViewModel<span class="token operator">::</span><span class="token keyword">class</span><span class="token punctuation">.</span>java<span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">onCreate</span><span class="token punctuation">(</span>savedInstanceState<span class="token operator">:</span> Bundle<span class="token operator">?</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">onCreate</span><span class="token punctuation">(</span>savedInstanceState<span class="token punctuation">)</span> <span class="token function">setContentView</span><span class="token punctuation">(</span>R<span class="token punctuation">.</span>layout<span class="token punctuation">.</span>activity_main<span class="token punctuation">)</span> button_plus<span class="token punctuation">.</span><span class="token function">setOnClickListener</span> <span class="token punctuation">{</span> viewModel<span class="token punctuation">.</span><span class="token function">incrementCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> button_minus<span class="token punctuation">.</span><span class="token function">setOnClickListener</span> <span class="token punctuation">{</span> viewModel<span class="token punctuation">.</span><span class="token function">decrementCount</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> viewLifecycleOwner<span class="token punctuation">.</span>lifecycleScope<span class="token punctuation">.</span><span class="token function">launchWhenStarted</span> <span class="token punctuation">{</span> viewModel<span class="token punctuation">.</span>countState<span class="token punctuation">.</span><span class="token function">collect</span> <span class="token punctuation">{</span> value <span class="token operator">-></span> textview_count<span class="token punctuation">.</span>text <span class="token operator">=</span> <span class="token string">"<span class="token interpolation variable">$value</span>"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Ở ví dụ sử dụng viewLifecycleOwner.lifecycleScope.launchWhenStarted
để ràng buộc với lifecycle của views.
StateFlow và LiveData
StateFlow
và LiveData
có những điểm tương đồng. Cả hai đều là các lớp lưu trữ dữ liệu có thể quan sát được và cả hai đều tuân theo một mẫu tương tự khi được sử dụng trong kiến trúc ứng dụng.
Tuy nhiên, StateFlow
và LiveData
có những hành vi khác nhau:
StateFlow
yêu cầu một trạng thái ban đầu được chuyển vào hàm tạo trong khiLiveData
thì không.StateFlow
ngăn chặn việc phát ra các giá trị tới bộ thu khi giá trị mới bằng với giá trị được phát ra trước đó.LiveData.observe()
tự động hủy đăng ký consumer khi UI chuyển sang trạng tháiSTOPPED
, trong khi collect từ mộtStateFlow
hoặc bất kỳ luồng nào khác thì không.
Ngoài ra, StateFlow
còn sử dụng được nhiều operators rất xịn của Flow (zip, combine …) trong khi LiveData
rất hạn chế.
Trong ví dụ ở bên trên có sử dụng viewLifecycleOwner.lifecycleScope.launchWhenStarted
để nhận biết vòng đời khi thu thập từ flow, tránh lãng phí tài nguyên. Ngoài ra, cũng có thể xử lý khi thay đổi trạng thái vòng đời (Lifecycle states) theo cách thủ công, như trong ví dụ sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <span class="token keyword">class</span> MainActivity <span class="token operator">:</span> <span class="token function">AppCompatActivity</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">..</span><span class="token punctuation">.</span> <span class="token keyword">private</span> <span class="token keyword">var</span> uiStateJob<span class="token operator">:</span> Job<span class="token operator">?</span> <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">onStart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">onStart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> uiStateJob <span class="token operator">=</span> lifecycleScope<span class="token punctuation">.</span><span class="token function">launch</span> <span class="token punctuation">{</span> viewModel<span class="token punctuation">.</span>uiState<span class="token punctuation">.</span><span class="token function">collect</span> <span class="token punctuation">{</span> uiState <span class="token operator">-></span> <span class="token comment">// Handle UI state</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">onStop</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> uiStateJob<span class="token operator">?</span><span class="token punctuation">.</span><span class="token function">cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">onStop</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Một cách khác để ngừng lắng nghe các uiState
thay đổi khi UI không hiển thị là sử dụng hàm asLiveData()
trong thư viện lifecycle-livedata-ktx
để chuyển flow về LiveData
:
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">class</span> LatestNewsActivity <span class="token operator">:</span> <span class="token function">AppCompatActivity</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">..</span><span class="token punctuation">.</span> <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">onCreate</span><span class="token punctuation">(</span>savedInstanceState<span class="token operator">:</span> Bundle<span class="token operator">?</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token operator">..</span><span class="token punctuation">.</span> viewModel<span class="token punctuation">.</span>uiState<span class="token punctuation">.</span><span class="token function">asLiveData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">observe</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> state <span class="token operator">-></span> <span class="token comment">// Handle UI state</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Tham khảo
https://developer.android.com/kotlin/flow/stateflow-and-sharedflow
https://scalereal.com/android/2020/05/22/stateflow-end-of-livedata.html
https://cesarmorigaki.medium.com/replace-livedata-with-stateflow-4f3c89214b04