iT邦幫忙

2021 iThome 鐵人賽

DAY 24
0
Mobile Development

解鎖kotlin coroutine的各種姿勢-新手篇系列 第 24

day24 stateflow和shareflow是如何取代livedata的,聊聊use case吧!!

記得我們說的特性吧,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的文章,開門

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 with databinding

讓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

jast

LiveData:还没普及就让我去世?我去你的 Kotlin 协程


上一篇
day23 stateFlow狀態流,又是沒梗的一天
下一篇
day25 矮額是callback,把它變成flow好了 簡單的callbackFlow
系列文
解鎖kotlin coroutine的各種姿勢-新手篇30

尚未有邦友留言

立即登入留言