LiveData,how to avoid the first callback when register observer

Viewed 3885

I want to load tasks in a fragment, in fragment's onViewCreated,I register the LiveData observer, in fragment's onResume,I load the task asynchronously, when first enter the fragment,it works fine,but when I navigate to other fragments then back to the task fragment, the callback onChanged() will be called twice.

I know If LiveData already has data set, it will be delivered to the observer, so when back to the task fragment, onChanged will be triggered when registering the observer in onViewCreated, and in onResume, will trigger onChanged the second time, I want to know how to avoid this. I have searched a lot, I know there is an EventWrapper, which can mark the content consumed when onChanged triggered the first time. but I think this approach is too heavy. Sorry for my poor English...

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle 
   savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    //...
    mainViewModel = ViewModelProviders.of(getActivity()).get(MainViewModel.class);
    mainViewModel.increaseTaskList.observe(getViewLifecycleOwner(), new 
    Observer<List<Task>>() {
        @Override
        public void onChanged(@Nullable List<Task> tasks) {
            Log.d("ZZZ","data changed,IncreaseTaskListAdapter setData");
            adapter.setData(tasks);
        }
    });
}

@Override
public void onResume() {
    super.onResume();
    mainViewModel.loadIncreasePointTaskList();
}
5 Answers

You could use SingleLiveEvent which won't be triggered as long as the content hasn't changed.

This is recommended by Google, though.

I found a simple solution,check the livedata value before load

@Override
public void onResume() {
    super.onResume();
    if (mainViewModel.increaseTaskList.getValue()==null) {
        Log.d("ZZZ","IncreaseFragment loadTaskAsync");
        mainViewModel.loadIncreasePointTaskList();
    }
}

My simple solution is that declare one boolean variable as isFisrtCalled = false, then change it true inside your callback as the first time

isFirstCalled = false;

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle 
   savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    //...
    mainViewModel = ViewModelProviders.of(getActivity()).get(MainViewModel.class);
    mainViewModel.increaseTaskList.observe(getViewLifecycleOwner(), new 
    Observer<List<Task>>() {
        @Override
        public void onChanged(@Nullable List<Task> tasks) {
            if (!isFirstCalled) {
              isFirstCalled = true;
               return;
             } // this will ensure, you will discard fisrt callback
            Log.d("ZZZ","data changed,IncreaseTaskListAdapter setData");
            adapter.setData(tasks);
        }
    });
}

@Override
public void onResume() {
    super.onResume();
    mainViewModel.loadIncreasePointTaskList();
}

My simple solution is that extends your MutableliveData class, add your custom observer methods that take mutable live data parameters and one extra parameter type of boolean, this boolean will help to bypass the first call back of observer, My solution will prevent you to manually handle the boolean every time for every observer,

public class CustomMutableLiveData<T> extends MutableLiveData<T> {

    private final AtomicBoolean byPass = new AtomicBoolean(false);
    private LifecycleOwner owner;
    @NonNull
    private Observer<? super T> observer;

    public CustomMutableLiveData() {
        byPass.set(false);
    }

    @MainThread
    @Override
    public void observe(@NonNull LifecycleOwner owner, @NonNull final Observer<? super T> observer) {
        super.observe(owner, observer);
    }

    @MainThread
    public void setValue(T value) {
        super.setValue(value);

        if (this.byPass.get()) {
            observe(owner, observer);
            this.byPass.set(false);
        }
    }

    public void addObserver(@NonNull LifecycleOwner owner, @NonNull final Observer<? super T> observer) {
        addObserver(owner, observer, false);
    }

    public void addObserver(@NonNull LifecycleOwner owner, @NonNull final Observer<? super T> observer, boolean byPassMode) {
        this.owner = owner;
        this.observer = observer;
        byPass.set(byPassMode);
        if (!byPass.get()) {
            observe(this.owner, this.observer);
        }
    }

}

Add observer with custom observer method like:


mViewModel.name.addObserver(this, name -> {
            mBinding.tvName.setGreetingTextText(name);
        },true);
mViewModel.name.setValue("Zeeshan 1")

mViewModel.name.addObserver(this, name -> {
        mBinding.tvName.setGreetingTextText(name);
    },true); // true for byPass call back

mViewModel.name.setValue("Zeeshan 2")

Above sample code only prints 'Zeeshan 2'

I hope this will help you.

You can now use SingleLiveEvent instead of MutableData.
So change this:

private val _biometricAuthenticationStatus = MutableLiveData(BiometricAuthenticationStatus.WAITING)

Into this:

private val _biometricAuthenticationStatus = SingleLiveEvent<BiometricAuthenticationStatus>()
Related