Ricardo Rodrigues
Ricardo Rodrigues

Reputation: 406

When I go back to a Fragment the observer is immediately called

I have an observer than when it's called changes fragments.

The problem is when i go back the observer is called immediately and my app crashes with a

java.lang.IllegalArgumentException: navigation destination com.superapps.ricardo.tablepro:id/action_searchFragment_to_yourGameList2 is unknown to this NavController.

I don't understand why it is being called.

this is the only method that changes the list

override fun onSuccess(gamePair: Pair<Int, List<BggGame>>) {
        CoroutineScope(Main).launch{
            //goToList(gamePair.second, binding.input.text.toString())
            viewModel.setGameList(gamePair.second)
        }
    }

and this is the viewmodel creation and change fragment code

override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(SearchViewModel::class.java)
        viewModel.gameList.observe(viewLifecycleOwner, Observer {
            goToList(it, binding.input.text.toString())
        })
    }


    private fun goToList(games: List<BggGame>, user: String) {
        val action = SearchFragmentDirections.actionSearchFragmentToYourGameList2(user)
        val gameList = GameList()
        gameList.gameList = games
        action.gameList = gameList

        try {
            Navigation.findNavController(view!!).navigate(action)
            viewModel.gameList.removeObservers(viewLifecycleOwner)
        } catch (e: Exception){
            Log.e("a0,","a..", e)
        }
        progressDialog.dismiss()

    }

Upvotes: 3

Views: 1749

Answers (2)

milad salimi
milad salimi

Reputation: 1660

In your viewModel use SingleLiveEvent instead of MutableLiveData or LiveData.

This is SingleLiveEvent class , you can use it in util package :

import android.util.Log;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import java.util.concurrent.atomic.AtomicBoolean;

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

    private static final String TAG = "SingleLiveEvent";

    private final AtomicBoolean mPending = new AtomicBoolean(false);

    @Override
    public void observe(@NonNull LifecycleOwner owner, @NonNull 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);
    }
}

Upvotes: 3

pdegand59
pdegand59

Reputation: 13029

LiveData keeps the last value that have been set. When calling observe() on a LivaData, if the LiveData has a value, the observer is instantly called with the value previously set.

If you want to use LiveData for "events" like your usecase, your live data should expose an Event object that can be consumed only once.

Here is an example of a good implementation of such an Event class.

From the article :

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
}

Usage in ViewModel :

val gameList = MutableLiveData<Event<List<BggGame>>()
fun setGameList(gameList: List<BggGame>) {
    gameList.value = Event(gameList)
}

Usage in view :

viewModel.gameList.observe(this, Observer {
    it.getContentIfNotHandled()?.let { // Only proceed if the event has never been handled
        goToList(it, binding.input.text.toString())
    }
})

Upvotes: 5

Related Questions