記得我們說的特性吧,stateflow會在舊值和新值相同的情況下不做更新,但有時我們需要在每次retry某些動作,比如重新連線、重新載入等等
這時,我們就需要用shareFlow了,我這邊直接跟別人借例子
class Biller (
private val context: Context,
) : PurchasesUpdatedListener, BillingClientStateListener {
private var billingClient: BillingClient =
BillingClient.newBuilder(context)
.setListener(this)
.enablePendingPurchases()
.build()
private val billingClientStatus = MutableSharedFlow<Int>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
override fun onBillingSetupFinished(result: BillingResult) {
billingClientStatus.tryEmit(result.responseCode)
}
override fun onBillingServiceDisconnected() {
billingClientStatus.tryEmit(BillingClient.BillingResponseCode.SERVICE_DISCONNECTED)
}
// ...
// Suspend until billingClientStatus == BillingClient.BillingResponseCode.OK
private suspend fun requireBillingClientSetup(): Boolean =
withTimeoutOrNull(TIMEOUT_MILLIS) {
billingClientStatus.first { it == BillingClient.BillingResponseCode.OK }
true
} ?: false
init {
billingClientStatus.tryEmit(BillingClient.BillingResponseCode.SERVICE_DISCONNECTED)
billingClientStatus.observe(ProcessLifecycleOwner.get()) {
when (it) {
BillingClient.BillingResponseCode.OK -> with (billingClient) {
updateSkuPrices()
handlePurchases()
}
else -> {
delay(RETRY_MILLIS)
billingClient.startConnection(this@Biller)
}
}
}
}
private companion object {
private const val TIMEOUT_MILLIS = 2000L
private const val RETRY_MILLIS = 3000L
}
}
這邊是以 Google's Billing Client library作為範例,那直接切重點出來講,在when 判斷時,如果result不是ok,那就會先延遲幾秒再連線,很重要,這邊是需要扗失敗的情況下做某些事情,而這個某些事情會觸發下一個result
我直接借例子,因為我想不到好例子,socket會自動重連,ui不用更新,webview不用reload,如果你們有好的例子或情境也可以告訴我
jast有寫一篇關於singleLiveEvent轉換到stateflow的文章,開門
來個取代liveData吧,這篇的範例和前一篇一樣,沒甚麼好講的
唯一要注意的就是stateflow不會更新和舊值一樣的新值,其餘細節,就看前一篇吧
//viewModel
private val _postStateFlow = MutableStateFlow(Post(0,0,"",""))
val postStateFlow = _postStateFlow
init {
viewModelScope.launch {
repo.postFlow.collect {
_postStateFlow.value = it
}
}
}
//fragment
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED){
viewModel.postStateFlow.collect {
Timber.d(it.toString())
}
}
}
讓stateflow能夠大放厥詞說livedata已經被取代的功能,無非就是databinding了,儘管我自己也什麼再用databinding的功能,但或許是某些開發者的首選,這邊就當作棒各位開個門,寫個簡單的範例
首先layout
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="viewModel"
type="com.kenny.androidplayground.databinding.DBindingViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".databinding.DataBindingFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{viewModel.name}"
android:textSize="36sp"
android:background="#333"
app:layout_constraintTop_toTopOf="parent"
android:id="@+id/name_field"
/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/typing_field"
android:text="@={viewModel.newTypingName}"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
class DataBindingFragment : Fragment() {
private lateinit var binding:FragmentDataBindingBinding
private val viewModel by viewModels<DBindingViewModel>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_data_binding, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding = FragmentDataBindingBinding.bind(view)
binding.viewModel = viewModel
binding.lifecycleOwner = viewLifecycleOwner
}
}
到這邊都還是一般databinding的寫法(根據我幾個月前的記憶),解說一下,在xml裡面,我把editText和Textview的text設置為viewModel裏面的變數
且在fragment這邊指定了viewModel的instance給xml,那麼最後完成viewModel吧
class DBindingViewModel: ViewModel() {
private val _name = MutableStateFlow<String?>(null)
val name: StateFlow<String?> = _name
val newTypingName = MutableStateFlow<String?>(null)
init {
viewModelScope.launch {
newTypingName
.map {
"edtext enter: $it"
}
.collect {
_name.value = it
}
}
}
}
should-we-choose-kotlins-stateflow-or-sharedflow-to-substitute-for-android-s-livedata
LiveData:还没普及就让我去世?我去你的 Kotlin 协程