11

I have some problem in nested fragment in Kotlin. I have nested fragment with ViewModel. After resuming fragment from back button press all observers on viewModel LiveData triggers again although my data does not changed.

First i googled and tried for define observer in filed variable and check if it is initialized then do not observer it again: lateinit var observer: Observer

fun method(){
        if (::observer.isInitialized) return
        observer = Observer{ ... }
        viewModel.x_live_data.observe(viewLifecycleOwner ,observer)
}

So at first enter to fragment it works fine and also after resume it does not trigger again without data change but it does not trigger also on data change! What is going on?

Mahdi
  • 6,139
  • 9
  • 57
  • 109

3 Answers3

13

LiveData always stores the last value and sends it to each Observer that is registered. That way all Observers have the latest state.

As you're using viewLifecycleOwner, your previous Observer has been destroyed, so registering a new Observer is absolutely the correct thing to do - you need the new Observer and its existing state to populate the new views that are created after you go back to the Fragment (since the original Views are destroyed when the Fragment is put on the back stack).

If you're attempting to use LiveData for events (i.e., values that should only be processed once), LiveData isn't the best API for that as you must create an event wrapper or something similar to ensure that it is only processed once.

ianhanniballake
  • 191,609
  • 30
  • 470
  • 443
  • 3
    It is odd that without any change data on resume fragment all observers trigged again. – Mahdi Dec 06 '19 at 05:26
  • When you put the Fragment on the back stack, your previous `observe` is removed. You call `observe` again, which means that you get sent the current state. That's how it is supposed to work as otherwise your new View would not actually be filled with any data. – ianhanniballake Dec 06 '19 at 05:36
  • 1
    Maybe you right i use some liveData for observe a user event. For example click, after resume i think it is remained the data click happens again. Am i right? – Mahdi Dec 06 '19 at 05:39
  • 1
    That's what the last paragraph of my answer talked about, yes. – ianhanniballake Dec 06 '19 at 05:39
  • Ok let me check. – Mahdi Dec 06 '19 at 05:41
  • My actions are repeated because of saved in LiveData. So i create a consumable live data! – Mahdi Dec 06 '19 at 05:57
2

After knowing what happen I decide to go with customized live data to trigger just once. ConsumableLiveData. So I will put answer here may help others.

class ConsumableLiveData<T>(var consume: Boolean = false) : MutableLiveData<T>() {

    private val pending = AtomicBoolean(false)

    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        super.observe(
            owner,
            Observer<T> {
                if (consume) {
                    if (pending.compareAndSet(true, false)) observer.onChanged(it)
                } else {
                    observer.onChanged(it)
                }
            }
        )
    }

    override fun setValue(value: T) {
        pending.set(true)
        super.setValue(value)
    }
}

And for usage just put as bellow. It will trigger just once after any update value. This will great to handle navigation or listen to click or any interaction from user. Because just trigger once!

//In viewModel
val goToCreditCardLiveData = ConsumableLiveData<Boolean>(true)

And in fragment:

viewModel.goToCreditCardLiveData.observe(viewLifecycleOwner) {
        findNavController().navigate(...)

    }
Dharman
  • 30,962
  • 25
  • 85
  • 135
Mahdi
  • 6,139
  • 9
  • 57
  • 109
0

If u are using kotlin and for only one time trigger of data/event use MutableSharedFlow

example:

private val data = MutableSharedFlow<String>() // init

data.emit("hello world) // set value

lifecycleScope.launchWhenStarted {
      data.collectLatest { } // value only collect once unless a new trigger come
}

MutableSharedFlow won't trigger for orientation changes or come back to the previous fragment etc

Eldhopj
  • 2,703
  • 3
  • 20
  • 39