Reputation: 406
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
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
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