Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Navigation Component default backstack not working when called from a LiveData observer

I'm using Android Navigation component with a navigation drawer (as in Android Studio template). I have fragment A, B, C as top level fragments which are used in navigation drawer and fragment Z which is connected with fragment A in navigation graph. Now I have a button in fragment A. Clicking the button will open the fragment Z using Safe args.

    binding.button.setOnClickListener {
        val action = NewsFragmentDirections.actionNavNewsToNewsDetailsFragment()
        it.findNavController().navigate(action)
    }

When the fragment Z is opened, the app bar icon will automatically change to back button which will allow me to go back to fragment A.

These are working fine, but this issue is, when I use the same safe args code in a live data obsedrver, the back button is not working.

    viewModel.actionNewsDetails.observe(viewLifecycleOwner, {
        val action = NewsFragmentDirections.actionNavNewsToNewsDetailsFragment()
        findNavController().navigate(action)
    })

Here are some additional details

  • Once we are in fragment Z, it will show back navigation as usual, but just clicking it will not do any action
  • When I clicked the back button fast for several time, I noticed the app bar title flickering (changing between fragment A and Z)
  • I'm able to open Nav drawer by swiping when I'm in fragment Z
  • My live data code is written in fragment A's onCreateView()
  • The live data is triggered from a function in ViewModel

I've been struggling with this issue for long. Sorry for my bad English.

like image 939
Haseeb Pavaratty Avatar asked Oct 24 '25 11:10

Haseeb Pavaratty


1 Answers

The next information is very important :

When I clicked the back button fast for several time, I noticed the app bar title flickering (changing between fragment A and Z)

I am pretty sure what happens is that, your back button in fragment Z is working correctly, your fragment A displays and it's liveData get triggered again and navigate once again to fragment Z. This happens very fast but as you indicate, when you do it very fast you can see the delay.

Solution: before navigate to fragment Z in your LiveData observer, change the value of the liveData so when you go back to fragment A it doesn't trigger again.

This problem made me lose one hour some weeks ago.

Edit 26/10/2020:

To solve that problem, implement the SingleLiveEvent class and use it instead the MutableLiveData.

SingleLiveEvent.class

/**
 * A lifecycle-aware observable that sends only new updates after subscription, used for events like
 * navigation and Snackbar messages.
 * <p>
 * This avoids a common problem with events: on configuration change (like rotation) an update
 * can be emitted if the observer is active. This LiveData only calls the observable if there's an
 * explicit call to setValue() or call().
 * <p>
 * Note that only one observer is going to be notified of changes.
 */
public class SingleLiveEvent<T> extends MutableLiveData<T> {

    private static final String TAG = "SingleLiveEvent";

    private final AtomicBoolean mPending = new AtomicBoolean(false);

    @MainThread
    public void observe(LifecycleOwner owner, final Observer<? super T> observer) {

        if (hasActiveObservers()) {
            Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
        }

        // Observe the internal MutableLiveData
        super.observe(owner, new Observer<T>() {
            @Override
            public void onChanged(@Nullable T t) {
                if (mPending.compareAndSet(true, false)) {
                    observer.onChanged(t);
                }
            }
        });
    }

    @MainThread
    public void setValue(@Nullable T t) {
        mPending.set(true);
        super.setValue(t);
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    public void call() {
        setValue(null);
    }
}

Kotlin Version

class SingleLiveEvent<T> : MutableLiveData<T>() {
    val TAG: String = "SingleLiveEvent"

    private val mPending = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {

        if (hasActiveObservers()) {
            Log.w(TAG,"Multiple observers registered but only one will be notified of changes.")
        }

        // Observe the internal MutableLiveData
        super.observe(owner, Observer<T> { t ->
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        })
    }

    @MainThread
    override fun setValue(@Nullable t: T?) {
        mPending.set(true)
        super.setValue(t)
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        value = null
    }
}
like image 171
Bogdan Android Avatar answered Oct 26 '25 01:10

Bogdan Android



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!