Multiple calls to set LiveData is not observed

家住魔仙堡 提交于 2020-03-01 02:05:15

问题


I have recently seen a weird issue that is acting as a barrier to my project. Multiple calls to set the live data value does not invoke the observer in the view.

It seems that only the last value that was set actually invokes the Observer in the view.

Here is the code snippet for a review.

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private lateinit var viewModel: MainViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModel = ViewModelProviders.of(this).get(MainViewModelImpl::class.java)

        viewModel.state().observe(this, Observer {
            onStateChange(it!!)
        })

        viewModel.fetchFirstThree()

    }

    private fun onStateChange(state: MainViewModel.State) {

        when (state) {
            is One -> {
                show(state.data)
            }
            is Two -> {
                show(state.data)
            }
            is Three -> {
                show(state.data)
            }
        }
    }

    private fun show(data: String) {
        Log.d("Response", data)
    }
}

MainViewModel.kt

abstract class MainViewModel : ViewModel() {

    sealed class State {
        data class One(val data: String) : State()
        data class Two(val data: String) : State()
        data class Three(val data: String) : State()
    }

    abstract fun state(): LiveData<State>

    abstract fun fetchFirstThree()
}

MainViewModelImpl.kt

class MainViewModelImpl : MainViewModel() {

    private val stateLiveData: MediatorLiveData<State> = MediatorLiveData()

    override fun state(): LiveData<State> = stateLiveData

    override fun fetchFirstThree() {
        stateLiveData.value = State.One("One")
        stateLiveData.value = State.Two("Two")
        stateLiveData.value = State.Three("Three")
    }
}

Expected output:

Response: One
Response: Two
Response: Three

Actual Output:

Response: Three

As per the output above, the Observer is not being called for the first two values.


回答1:


I did some science, re-implementing LiveData and MutableLiveData to log out some data.

Check the source code here.

setValue value=Test1
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
Returned at !observer.active
setValue value=Test2
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
Returned at !observer.active
setValue value=Test3
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
Returned at !observer.active
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
ITEM: Test3

It looks like the observer hasn't reached an active state when you send the initial values.

private void considerNotify(LifecycleBoundObserver observer) {
    // <-- Three times it fails here. This means that your observer wasn't ready for any of them.
    if (!observer.active) {
        return;
    }

Once the observer reaches an active state, it sends the last set value.

void activeStateChanged(boolean newActive) {
    if (newActive == active) {
        return;
    }
    active = newActive;
    boolean wasInactive = LiveData.this.mActiveCount == 0;
    LiveData.this.mActiveCount += active ? 1 : -1;
    if (wasInactive && active) {
        onActive();
    }
    if (LiveData.this.mActiveCount == 0 && !active) {
        onInactive();
    }
    if (active) {
        // <--- At this point you are getting a call to your observer!
        dispatchingValue(this);
    }
}



回答2:


You could use custom LiveData like this:

class ActiveMutableLiveData<T> : MutableLiveData<T>() {

  private val values: Queue<T> = LinkedList()

  private var isActive: Boolean = false

  override fun onActive() {
      isActive = true
      while (values.isNotEmpty()) {
          setValue(values.poll())
      }
  }

  override fun onInactive() {
      isActive = false
  }

  override fun setValue(value: T) {
      if (isActive) {
          super.setValue(value)
      } else {
          values.add(value)
      }
  }
}



回答3:


You should call viewModel.fetchFirstThree() after Activity's onStart() method. for example in onResume() method.

Because in LiveData the Observer is wrapped as a LifecycleBoundObserver. The field mActive set to true after onStart().

class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {

    @Override
    boolean shouldBeActive() {
        return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);// return true after onStart()
    }
    @Override
    public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
        if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
            removeObserver(mObserver);
            return;
        }
        activeStateChanged(shouldBeActive());// after onStart() change mActive to true
    }
}

When the observer notify the change it calls considerNotify, before onStart it will return at !observer.mActive

 private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {// called in onCreate() will return here.
        return;
    }
    if (!observer.shouldBeActive()) {
        observer.activeStateChanged(false);
        return;
    }
    if (observer.mLastVersion >= mVersion) {
        return;
    }
    observer.mLastVersion = mVersion;
    //noinspection unchecked
    observer.mObserver.onChanged((T) mData);
}


来源:https://stackoverflow.com/questions/50582682/multiple-calls-to-set-livedata-is-not-observed

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!