Reputation: 4127
Reading the Google docs I found (sort of) an example of using a selectedItem
in order to propagate an event being fired to other observers, this is my current implementation:
ViewModel
public void onListItemClicked(Item item) {
if (selectedItem.getValue() == item) {
return;
}
selectedItem.postValue(item);
}
public LiveData<Item> getSelectedItem() {
if (selectedItem == null) {
selectedItem = new MutableLiveData<>();
}
return selectedItem;
}
View
ListViewModel viewModel = ViewModelProviders.of(this).get(ListViewModel.class);
viewModel.getSelectedItem().observe(this, new Observer<Item>() {
@Override
public void onChanged(@Nullable Item item) {
if (item != null) {
openDetailActivity(item);
}
}
});
And when the user clicks the list:
@Override
public void onItemClicked(Item item) {
viewModel.onListItemClicked(item);
}
All good and all it works, the problem is when the user rotates the screen and the ListActivity
is re-created detects a change and will open the DetailActivity
when subscribing.
I found a workaround which is adding selectedItem.postValue(null);
on the getSelectedItem()
but it's a little hacky.
Ofc one could argue that the opening the details activity and propagating the even should be separate, but I was wondering if someone has a better implementation/suggestion.
Upvotes: 0
Views: 832
Reputation: 4127
EDIT
Using the SingleLiveEvent
is the way to go. This makes sure your ViewModel
only fires the event once.
Here's the reference article:
LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)
Java class can be found in the article
I've created a gist with the Kotlin
class.
I've been using with success for these use-cases:
I'll keep the gist up-to-date, but I'll leave the code here also (FYI this might be outdated as I won't be editing this answer every time I make a change to the gist):
package YOUR_PACKAGE
import androidx.annotation.MainThread
import androidx.annotation.Nullable
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean
/**
* 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.
*/
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
// 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
}
}
Old answer:
So after researching quite a bit and getting with contact with a Google dev. the recommended solution is to have separate responsibilities.
Opening an activity should the response to the click event and not the actual change, this type of selectedItem
scenarios is especially useful for decoupled communication to other listening Views.
e.g another fragment in the same activity
Upvotes: 1