Как создать LiveData, который генерирует одно событие и уведомляет только последнего подписанного наблюдателя?

Я создал оперативные данные, которые генерируют одно событие, как в этом пример.

Мой вопрос следующий: как уведомить только последнего подписанного наблюдателя, когда значение в LiveData изменится?

Мне приходит в голову хранить наблюдателей в связанном списке в классе SingleLiveData, а затем вызывать super.observe только в том случае, если переданный наблюдатель совпадает с последним элементом списка.

Я не уверен, что это лучший подход.

Я хочу использовать этот механизм для распространения событий кликов FAB от активности к фрагментам, которые отображаются внутри ViewPager. Фрагменты динамически добавляются в просмотр адаптера пейджера, поэтому допустим, что мы знаем порядок фрагментов.


person TheTechWolf    schedule 08.08.2018    source источник
comment
так что, вероятно, MediatorLifeData (или Transformations#switchMap) - это то, что вы могли бы использовать для такого случая   -  person pskink    schedule 09.08.2018
comment
Хм, я не уверен, как это решит мою проблему. Мне нужен способ уведомить последних или всех подписанных наблюдателей и после этого установить значение null. Не могли бы вы уточнить немного больше?   -  person TheTechWolf    schedule 09.08.2018
comment
"last or all subscribed observers" так последнее или все?   -  person pskink    schedule 09.08.2018
comment
Последнее было бы предпочтительнее, но я также мог бы работать с ним, если все получат обновления;)   -  person TheTechWolf    schedule 09.08.2018
comment
если последнее, чем хорошо, возможно, MutableLifeData было бы лучше с переопределенным методом observe, где вы вызываете метод removeObservers, за которым следует super.observe - таким образом вы уверены, что активен не более одного наблюдателя   -  person pskink    schedule 09.08.2018
comment
Это хорошая идея. Тогда мне просто нужно найти способ снова добавить этого наблюдателя, когда один из фрагментов в стеке станет видимым.   -  person TheTechWolf    schedule 09.08.2018


Ответы (3)


В конце концов, я нашел обходной путь для этой проблемы. Мне пришлось отказаться от оперативных данных, которые генерируют одно событие, поскольку они не могли вести себя так, как мне нужно.

Вместо этого я использовал простые изменяемые оперативные данные, которые создают объект события, который оборачивает данные, как в последнем абзаце этого статья Хосе Альсеррека.

Я показываю фрагменты в пейджере, поэтому в данный момент у меня есть только один видимый фрагмент.

Итак, моя модель представления выглядит так:

class ActionViewModel : ViewModel() {
  private val onCreateLiveData: MutableLiveData<Event<String>> = MutableLiveData()

  fun observeOnCreateEvent(): LiveData<Event<String>> = onCreateLiveData

  fun onCreateCollectionClick(message: String) {
    this.onCreateLiveData.value = Event(message)
  }
}

Реализация класса-обертки событий выглядит так:

/*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 = false
    private set // Allow external read but not write

  /**
   * Returns the content and prevents its use again.
  */
  fun getContentIfNotHandled(): T? {
    return if (hasBeenHandled) {
      null
    } else {
      hasBeenHandled = true
      content
    }
  }

  /**
    * Returns the content, even if it's already been handled.
  */
  fun peekContent(): T = content
}

Во фрагментах теперь мы можем наблюдать такие события:

override fun onActivityCreated(savedInstanceState: Bundle?) {
   super.onActivityCreated(savedInstanceState)

   actionViewModel = ViewModelProviders.of(requireActivity()).get(ActionViewModel::class.java)
   actionViewModel.observeOnCreateEvent()
       .observe(this, Observer {
         it?.takeIf { userVisibleHint }?.getContentIfNotHandled()?.let {
           //DO what ever is needed
         }
       })
}

Свойство фрагмента userVisibleHint возвращает значение true, если фрагмент в данный момент виден пользователю. Поскольку мы показываем только один фрагмент за раз, это работает для нас. Это означает, что фрагмент будет получать доступ к данным события только в том случае, если он виден.

Кроме того, реализация оболочки Event позволяет только одно чтение значения, так что каждый следующий раз, когда Observer получает это событие, его значение будет нулевым, и мы его проигнорируем.

Вывод. Таким образом, мы имитируем живые данные одного события, которые уведомляют только последнего подписавшегося наблюдателя.

person TheTechWolf    schedule 09.08.2018
comment
Просто и полезно! Спасибо. - person Hoang Nguyen Huu; 31.10.2019
comment
Если быть точным, это не решает Как уведомить только последнего подписавшегося наблюдателя. Первый в очереди получит уведомление. Помогает только ваша настройка: вероятно, ваше живое количество фрагментов в пейджере равно 1, поэтому все невидимые фрагменты остановлены, поэтому они не могут конкурировать за событие. - person ror; 26.02.2020

Я разработал решение, не стесняйтесь взглянуть на https://github.com/ueen/LiveEvent

person ueen    schedule 18.01.2020

Если вы используете Kotlin, вы можете заменить LiveData на Flow. StateFlow может использоваться вместо обычного LiveData, а SharedFlow можно использовать для событий без сохранения состояния. Это также обеспечит вам нулевую безопасность и все операторы и конфигурации, которые поставляются с Flow.

Миграция описана здесь среди других мест. Вот простой пример:

ViewModel:

interface MyViewModel {
    val myData: StateFlow<MyData>
    val myEvents: SharedFlow<MyEvent>
}

class MyViewModelImpl: MyViewModel {
    override val myData = MutableStateFlow(MyData())
    override val myEvents = MutableSharedFlow<MyEvent>(replay = 0, extraBufferCapacity = 1, BufferOverflow.DROP_OLDEST)

    /*
     * Do stuff 
     */
}

Мероприятия:

lifecycleScope.launch {
    myData.collect {
        // handle stateful data    
    }
}

lifecycleScope.launch {
    myEvents.collect {
        // handle stateless events    
    }
}

Обратите внимание, что для lifecycleScope требуется соответствующая ktx-зависимость.

Герер еще немного читает о Flow в Android.

person Sir Codesalot    schedule 28.06.2021