日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【译】LiveData 在 SnackBar/Navigation 情景下的使用(SingleLiveEvent)

發布時間:2023/12/31 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【译】LiveData 在 SnackBar/Navigation 情景下的使用(SingleLiveEvent) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

本文翻譯自【LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)】,詳細介紹了 liveData 的使用。感謝作者 Jose Alcérreca。水平有限,歡迎指正討論。 前面兩篇介紹 LiveData 的文章(【譯】Android Architecture - ViewModel 與 View 的通信 和 【譯】LiveData 使用詳解)都提到了 SingleLiveEvent,本篇重點來看下它是個什么東西,以及它的使用場景。

正文

LiveData 一般被用于 View 與 ViewModel 的通信。View 通過訂閱 LiveData 的變化來更新 UI,這適用于需要長時間展示在屏幕上的數據。

然而,有些數據可能只需要展示一次,例如 SnackBar 消息,一個 Navigation 事件,或者一個觸發 Dialog 展示/消失的數據。

我們不應該嘗試用 Architecture Components 基礎或擴展庫來解決這個問題,相反這是一個設計問題。我們建議你將這些事件作為數據狀態的一部分。在本文中,我們將展示一些常見錯誤和推薦方法。

? Bad: 1. Using LiveData for events

這種用法是在 LiveData 中保存一個 SnackBar 消息,或一個 Navigation 事件。盡管原則上是 LiveData 的正常使用,但這存在一些問題。 在一個包含首頁和詳情頁的應用中,首頁的 ListViewModel.kt 代碼如下:

// Don't use this for events class ListViewModel : ViewModel {private val _navigateToDetails = MutableLiveData<Boolean>()val navigateToDetails : LiveData<Boolean>get() = _navigateToDetailsfun userClicksOnButton() {_navigateToDetails.value = true} } 復制代碼

MyFragment.kt 代碼如下:

myViewModel.navigateToDetails.observe(this, Observer {if (it) startActivity(DetailsActivity...) }) 復制代碼

這種使用方式的問題是:_navigateToDetails 中的值會永遠為 true,從而導致無法回到首頁。 復現步驟是:

  • 用戶點擊按鈕,啟動詳情頁 DetailsActivity
  • 用戶點擊返回鍵,返回到主界面 MasterActivity
  • 這時 MasterActivity 由非活動狀態恢復到活動狀態
  • 但 myViewModel 觀察到 _navigateToDetails 仍舊為 true,就又跳轉到詳情頁 DetailsActivity
  • 一種看起來沒問題的解決方案是:頁面跳轉后立馬把標志位設為 false,如 ListViewModel.kt 所示:

    fun userClicksOnButton() {_navigateToDetails.value = true_navigateToDetails.value = false // Don't do this } 復制代碼

    然而,需要注意的是:LiveData 不能保證發射它接收到的每個數據值。例如我們在沒有活動的觀察者時設置了一個新值,這個新值不會被發送,此外,在多個子線程中操作 LiveData 可能發生競爭狀況,從而導致觀察者只會收到一次回調。 但這個方案的主要問題是:別人很難看懂這個代碼,并且這種代碼也很丑陋。那么,我們應該怎么確保在導航事件發生后恢復初值呢?

    ? Better: 2. Using LiveData for events, resetting event values in observer

    另一種稍微好點,但仍有問題的方案是:View 告訴 ViewModel,導航事件已經完成,LiveData 應該恢復默認值了。

    Usage

    基于第一節的例子,對觀察者代碼做如下改動即可,MyFragment.kt:

    listViewModel.navigateToDetails.observe(this, Observer {if (it) {myViewModel.navigateToDetailsHandled()startActivity(DetailsActivity...)} }) 復制代碼

    然后在 ListViewModel.kt 中添加一個 navigateToDetailsHandled() 方法:

    class ListViewModel : ViewModel {private val _navigateToDetails = MutableLiveData<Boolean>()val navigateToDetails : LiveData<Boolean>get() = _navigateToDetailsfun userClicksOnButton() {_navigateToDetails.value = true}fun navigateToDetailsHandled() {_navigateToDetails.value = false} } 復制代碼

    Issues

    這種方法的問題是:存在很多樣板代碼,ViewModel 中每添加一個事件都要添加一個對應的方法,并且很容易出錯。此外,觀察者(View)很容易忘記調用 ViewModel 的這個方法。

    ? OK: Use SingleLiveEvent

    一種還可以接受的解決方案是:SingleLiveEvent。這個類是 Google 官方 Demo 中的適用于這種特殊場景的解決方案,它是一個僅發送一次更新的 LiveData。

    public class SingleLiveEvent<T> extends MutableLiveData<T> {private static final String TAG = "SingleLiveEvent";private final AtomicBoolean mPending = new AtomicBoolean(false);@MainThreadpublic void observe(LifecycleOwner owner, final Observer<T> observer) {if (hasActiveObservers()) {Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");}// Observe the internal MutableLiveDatasuper.observe(owner, new Observer<T>() {@Overridepublic void onChanged(@Nullable T t) {if (mPending.compareAndSet(true, false)) {observer.onChanged(t);}}});}@MainThreadpublic void setValue(@Nullable T t) {mPending.set(true);super.setValue(t);}/*** Used for cases where T is Void, to make calls cleaner.*/@MainThreadpublic void call() {setValue(null);} } 復制代碼

    Usage

    ListViewModel.kt 代碼如下:

    class ListViewModel : ViewModel {private val _navigateToDetails = SingleLiveEvent<Any>()val navigateToDetails : LiveData<Any>get() = _navigateToDetailsfun userClicksOnButton() {_navigateToDetails.call()} } 復制代碼

    MyFragment.kt 代碼如下:

    myViewModel.navigateToDetails.observe(this, Observer {startActivity(DetailsActivity...) }) 復制代碼

    Issues

    SingleLiveEvent 的問題在于:它僅限于一個觀察者。如果你無意中添加了多個,則只會有一個收到回調,并且無法保證哪一個會收到。

    ? Recommended: Use an Event wrapper

    推薦的解決方案是:封裝事件。通過這種方式,我們可以明確地管理實踐是否被處理,從而減少錯誤。

    Usage

    Event.kt 封裝了事件,代碼如下:

    /*** Used as a wrapper for data that is exposed via a LiveData that represents an event.*/ open class Event<out T>(private val content: T) {var hasBeenHandled = falseprivate set // Allow external read but not write/*** Returns the content and prevents its use again.*/fun getContentIfNotHandled(): T? {return if (hasBeenHandled) {null} else {hasBeenHandled = truecontent}}/*** Returns the content, even if it's already been handled.*/fun peekContent(): T = content } 復制代碼

    ListViewModel.kt 代碼如下:

    class ListViewModel : ViewModel {private val _navigateToDetails = MutableLiveData<Event<String>>()val navigateToDetails : LiveData<Event<String>>get() = _navigateToDetailsfun userClicksOnButton(itemId: String) {_navigateToDetails.value = Event(itemId) // Trigger the event by setting a new Event as a new value} } 復制代碼

    MyFragment.kt 代碼如下:

    myViewModel.navigateToDetails.observe(this, Observer {// Only proceed if the event has never been handledit.getContentIfNotHandled()?.let {startActivity(DetailsActivity...)} }) 復制代碼

    這種方案的優勢在于:用戶需要調用 Event#getContentIfNotHandled() 方法或 Event#peekContent() 來指定跳轉 Intent。這種方案將事件作為 UI 狀態的一部分:現在它們只是一個已被消費或未被消費的消息。

    總結

    design events as part of your state. 我們可以包裝自己的 Event 來滿足自己的需求。 Bonus! 如果有很多事件,可以使用 EventObserver 避免一些樣板代碼。

    參考

    • LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)

    聯系

    我是 xiaobailong24,您可以通過以下平臺找到我:

    • Github: github.com/xiaobailong…
    • 簡書: www.jianshu.com/u/3dac2ad17…
    • 掘金: juejin.im/user/59413c…

    總結

    以上是生活随笔為你收集整理的【译】LiveData 在 SnackBar/Navigation 情景下的使用(SingleLiveEvent)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。