I am using the new paging library for my data. Everything works fine when the ViewModel is created and live data is first initialized. Problem is that I can not update the value of my live data when for example I click on menu Item and want to update it with a different set of data. Then onChanged method in my fragment does not get called. I have read about MutableLiveData and methods like setValue and postValue which can update the live data, but in my case, I am using LivePagedListProvider and cannot return MutableLiveData from a database.
Dao:
#Query("SELECT * FROM teams ORDER BY name ASC")
LivePagedListProvider<Integer, Team> getAllTeams();
Fragment:
mTeamViewModel.mTeamsList.observe(this, new Observer<PagedList<Team>>() {
#Override
public void onChanged(#Nullable PagedList<Team> teams) {
mTeamAdapter.setList(teams);
}
});
ViewModel:
public LiveData<PagedList<Team>> mTeamsList;
#Inject
DatabaseManager mDatabaseManager;
void setTeamViewModel(final DatabaseManager databaseManager) {
mDatabaseManager = databaseManager;
mTeamsList = mDatabaseManager.getAllTeams().create(
0,
new PagedList.Config.Builder()
.setPageSize(50)
.setPrefetchDistance(50)
.setEnablePlaceholders(true)
.build());
}
boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_favorite:
mTeamsList = mDatabaseManager.getFavoriteTeams().create(
0,
new PagedList.Config.Builder()
.setPageSize(50)
.setPrefetchDistance(50)
.build());
return true;
default: return false;
}
}
Finally I found the solution after the struggle of 3 days,
For updating the value of LiveData<PagedList> we can use MediatorLiveData, as follows, inside your ViewModel class:
public LiveData<PagedList<RecipeListPojo>> liveData;
private MediatorLiveData<PagedList<RecipeListPojo>> mediatorLiveData;
public RecipeListViewModel(#NonNull Application application) {
super(application);
mediatorLiveData = new MediatorLiveData<>();
}
public MediatorLiveData<PagedList<RecipeListPojo>> init(RecipeDao recipeDao, RecipeFrom recipeFrom, String orderBy) {
liveData = new LivePagedListBuilder(recipeDao.getAllRecipesList(simpleSQLiteQuery), 6).build();
mediatorLiveData.addSource(liveData, new Observer<PagedList<RecipeListPojo>>() {
#Override
public void onChanged(#Nullable PagedList<RecipeListPojo> recipeListPojos) {
mediatorLiveData.setValue(recipeListPojos);
}
});
return mediatorLiveData;
}
Further details can be found here about MediatorLiveData
Create a isFavourite column in your model. Then:
#Query("SELECT * FROM teams WHERE isFavourite = true")
LivePagedListProvider<Integer, Team> getFavouriteTeams();
Then you should have a ViewModel with a function/field that returns a MutableLiveData<PagedList<Team>> and in the Fragment/Activity you observe to that field. In your Fragment/Activity you tell the ViewModel every change you need to do to de list showed. Then in the ViewModel you assign different values to the live data list.
Transformations.switchMap can help: https://developer.android.com/reference/android/arch/lifecycle/Transformations#switchmap
class SearchViewModel() : ViewModel(){
val teamId = MutableLiveData<Int>()
var teamsList: LiveData<PagedList<Team>>=
Transformations.switchMap(teamId) { id ->
//just example
if (id <= 0) {
//1
searchDbManager.searchWithoutId()
} else {
//2
anotherSearchDbManager.searchWithId(id)
}.setBoundaryCallback(SearchResultBoundaryCallback()).build()
}
}
fun changeTeamId(id : Int){
teamId.postValue(id)
}
If you want to change the search process to search with team ID (or switch to another datasource), just call from Fragment:
viewModel.changeTeamId(2)
Related
I am struggling to find a solution for calling a livedata observer multiple times in one activity and not creating multiple instances of it, this leads to the problem when the database changes I got callbacks from all the instances.
ViewModel
public class RatingsViewModel extends AndroidViewModel {
private RatingsRepository ratingsRepository;
private LiveData<List<Rating>> ratingsList;
public RatingsViewModel(Application application) {
super(application);
ratingsRepository = new RatingsRepository(application);
}
public LiveData<List<Rating>> getRatingsByDate(LocalDate date) {
ratingsList = ratingsRepository.getActivitiesByDate(date);
return ratingsList;
}
Activity
private void getRatingsByDate(LocalDate date) {
ratingsViewModel.getRatingsByDate(date).observe(this, activities -> {
// list populating stuff
});
}
I tried calling hasObserver() but it returns false so I cannot remove the observers.
You should be able to do something like following (in Kotlin but should be easily translatable in to Java if needed)
val dateLiveData: MutableLiveData<Date> = MutableLiveData()
val ratingsList = MediatorLiveData<List<Rating>>().apply {
this.addSource(dateLiveData) {
this.value = ratingsRepository.getActivitiesByDate(dateLiveData.value)
}
}
fun setDate(date: Date) {
dateLiveData.value = date
}
You'd call observe from onCreate() for example in your activity/fragment and then call setDate() when that value changes.
I am trying out Realm along with Android architecture components including LiveData.
I have been following Google's Guide to Application Architecture:
https://developer.android.com/topic/libraries/architecture/guide.html
...substituting Room with Realm.
I have everything working using:
LiveData<RealmResults<CustomModelObject>>
from my repository layer right through ViewModel to View.
I am thinking it might be nicer to only have more generic types coming back from repository so LiveData<List<CustomModelObject>> rather than LiveData<RealmResults<CustomModelObject>>.
Here is a code snippet of where I have got stuck:
#NonNull
#Override
protected LiveData<List<CustomModelObject>> loadFromDb() {
return Transformations.switchMap(customModelObjectsDao.getCustomModelObjects(),
new Function<RealmResults<CustomModelObject>, LiveData<List<CustomModelObject>>>() {
#Override
public LiveData<List<CustomModelObject>> apply(RealmResults<CustomModelObject> data) {
if (data == null) {
return AbsentLiveData.create();
} else {
return customModelObjectsDao.getCustomModelObjects();
}
}
});
}
customModelObjectsDao.getCustomModelObjects() currently returns LiveData<RealmResults<Inspiration>>.
I want to transform it to LiveData<List<Inspiration>>.
I have tried various Transformations.map and Transformations.switchMap etc with no success and I think I have been staring at it too long now :)
Am I on the right path or am I missing something obvious?
Any help is greatly appreciated.
Thanks,
Paul.
UPDATE
DAO:
public RealmLiveData<CustomModelObject> getCustomModelObjects() {
return asLiveData(realm.where(CustomModelObject.class).findAllAsync());
}
asLiveData Impl:
fun <T: RealmModel> RealmResults<T>.asLiveData() = RealmLiveData<T>(this)
fun Realm.CustomModelObjectsDao(): CustomModelObjectsDao = CustomModelObjectsDao(this)
UPDATE 2
public class RealmLiveData<T> extends LiveData<RealmResults<T>> {
private RealmResults<T> results;
private final RealmChangeListener<RealmResults<T>> listener = new RealmChangeListener<RealmResults<T>>() {
#Override
public void onChange(RealmResults<T> results) {
setValue(results);
}
};
public RealmLiveData(RealmResults<T> realmResults) {
results = realmResults;
}
#Override
protected void onActive() {
results.addChangeListener(listener);
}
#Override
protected void onInactive() {
results.removeChangeListener(listener);
}
}
In your case, replacing LiveData<RealmResults<T> with LiveData<List<T>> would be enough to solve your problem.
However, I'd advise trying out the RealmLiveResults class that is available in the official example:
/**
* This class represents a RealmResults wrapped inside a LiveData.
*
* Realm will always keep the RealmResults up-to-date whenever a change occurs on any thread,
* and when that happens, the observer will be notified.
*
* The RealmResults will be observed until it is invalidated - meaning all local Realm instances on this thread are closed.
*
* #param <T> the type of the RealmModel
*/
public class LiveRealmResults<T extends RealmModel> extends LiveData<List<T>> {
private final RealmResults<T> results;
// The listener will notify the observers whenever a change occurs.
// The results are modified in change. This could be expanded to also return the change set in a pair.
private OrderedRealmCollectionChangeListener<RealmResults<T>> listener = new OrderedRealmCollectionChangeListener<RealmResults<T>>() {
#Override
public void onChange(#NonNull RealmResults<T> results, #Nullable OrderedCollectionChangeSet changeSet) {
LiveRealmResults.this.setValue(results);
}
};
#MainThread
public LiveRealmResults(#NonNull RealmResults<T> results) {
//noinspection ConstantConditions
if (results == null) {
throw new IllegalArgumentException("Results cannot be null!");
}
if (!results.isValid()) {
throw new IllegalArgumentException("The provided RealmResults is no longer valid, the Realm instance it belongs to is closed. It can no longer be observed for changes.");
}
this.results = results;
if (results.isLoaded()) {
// we should not notify observers when results aren't ready yet (async query).
// however, synchronous query should be set explicitly.
setValue(results);
}
}
// We should start observing and stop observing, depending on whether we have observers.
/**
* Starts observing the RealmResults, if it is still valid.
*/
#Override
protected void onActive() {
super.onActive();
if (results.isValid()) { // invalidated results can no longer be observed.
results.addChangeListener(listener);
}
}
/**
* Stops observing the RealmResults.
*/
#Override
protected void onInactive() {
super.onInactive();
if (results.isValid()) {
results.removeChangeListener(listener);
}
}
}
This way your dao can expose LiveData<List<T>>, and your Transformations.map() should work.
If you need:
val list : LiveData<List<mRealmObject>>
First: Create this file:
class RealmLiveData<T : RealmModel>(private val results: RealmResults<T>) :
LiveData<RealmResults<T>>() {
private val listener: RealmChangeListener<RealmResults<T>> =
RealmChangeListener { results -> value = results }
override fun onActive() {
results.addChangeListener(listener)
}
override fun onInactive() {
results.removeChangeListener(listener)
}
}
fun <T: RealmModel> RealmResults<T>.asLiveData() = RealmLiveData<T>(this)
Second: Get your new RealmLiveData :
val mRealmLiveData = realm.where(mRealmObject::class.java).findAllAsync().asLiveData()
And Finally, get the list you need like this:
val list: LiveData<List<mRealmObject>> = Transformations.map(mRealmLiveData) {
realmResult ->
realm.copyFromRealm(realmResult)
}
If you use it in a ViewModel:
//get realm instance
val realm: Realm by lazy {
Realm.getDefaultInstance()
}
// get your live data
val list: LiveData<List<mRealmObject>> = Transformations.map(mRealmLiveData) {
realmResult ->
realm.copyFromRealm(realmResult)
}
// Close your realm instance onCleraded
override fun onCleared() {
realm.close()
super.onCleared()
}
I found that the LiveData returned by Dao will call its observer whenever the row is updated in DB, even if the LiveData value is obviously not changed.
Consider a situation like the following example :
Example entity
#Entity
public class User {
public long id;
public String name;
// example for other variables
public Date lastActiveDateTime;
}
Example Dao
#Dao
public interface UserDao {
// I am only interested in the user name
#Query("SELECT name From User")
LiveData<List<String>> getAllNamesOfUser();
#Update(onConflict = OnConflictStrategy.REPLACE)
void updateUser(User user);
}
Somewhere in background thread
UserDao userDao = //.... getting the dao
User user = // obtain from dao....
user.lastActiveDateTime = new Date(); // no change to user.name
userDao.updateUser(user);
Somewhere in UI
// omitted ViewModel for simplicity
userDao.getAllNamesOfUser().observe(this, new Observer<List<String>> {
#Override
public void onChanged(#Nullable List<String> userNames) {
// this will be called whenever the background thread called updateUser.
// If user.name is not changed, it will be called with userNames
// with the same value again and again when lastActiveDateTime changed.
}
});
In this example, the ui is only interested to user name so the query for LiveData only includes the name field. However the observer.onChanged will still be called on Dao Update even only other fields are updated.
(In fact, if I do not make any change to User entity and call UserDao.updateUser, the observer.onChanged will still be called)
Is this the designed behaviour of Dao LiveData in Room? Is there any chance I can work around this, so that the observer will only be called when the selected field is updated?
Edit : I changed to use the following query to update the lastActiveDateTime value as KuLdip PaTel in comment suggest. The observer of LiveData of user name is still called.
#Query("UPDATE User set lastActiveDateTime = :lastActiveDateTime where id = :id")
void updateLastActiveDateTime(Date lastActiveDateTime, int id);
There is simple solution in Transformations method distinctUntilChanged.expose new data only if data was changed.
In this case we get data only when it changes in source:
LiveData<YourType> getData(){
return Transformations.distinctUntilChanged(LiveData<YourType> source));
}
But for Event cases is better to use this:
https://stackoverflow.com/a/55212795/9381524
This situation is known as false positive notification of observer.
Please check point number 7 mentioned in the link to avoid such issue.
Below example is written in kotlin but you can use its java version to get it work.
fun <T> LiveData<T>.getDistinct(): LiveData<T> {
val distinctLiveData = MediatorLiveData<T>()
distinctLiveData.addSource(this, object : Observer<T> {
private var initialized = false
private var lastObj: T? = null
override fun onChanged(obj: T?) {
if (!initialized) {
initialized = true
lastObj = obj
distinctLiveData.postValue(lastObj)
} else if ((obj == null && lastObj != null)
|| obj != lastObj) {
lastObj = obj
distinctLiveData.postValue(lastObj)
}
}
})
return distinctLiveData
}
I stuck with the same problem.
What i did wrong:
1) creating anonimous object:
private LiveData<List<WordsTableEntity>> listLiveData;
// listLiveData = ... //init our LiveData...
listLiveData.observe(this, new Observer<List<WordsTableEntity>>() {
#Override
public void onChanged(#Nullable List<WordsTableEntity> wordsTableEntities) {
}
});
In my case, I called the method several times in which this line was located.
From the docs i supposed, that new Observers take data from LiveData. Because of that, author could receive few onChanged methods from few new anonimous Observers, if he set observe userDao.getAllNamesOfUser().observe(this, new Observer that way.
Its will be better to create named Observer object before LiveData.observe(... and once
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
observer = new Observer<List<WordsTableEntity>>() {
#Override
public void onChanged(#Nullable List<WordsTableEntity> wordsTableEntities) {
adapter.setWordsTableEntities(wordsTableEntities);
progressBar.setVisibility(View.GONE);
}
};
}
and then set it LiveData.observe(observer and we receive data from LieData first time and then, when data will be changed.
2) Observing one Observe object multiple times
public void callMethodMultipleTimes(String searchText) {
listLiveData = App.getRepositoryRoomDB().searchDataExceptChapter(searchText);
listLiveData.observe(this, observer);
}
I calling this method multiple times and debug showed me, that i was adding my observer as many times, as i called callMethodMultipleTimes();
Our listLiveData is a global variable and it lives. It changes the object reference here
listLiveData = App.getRepositoryRoomDB().searchDataExceptChapter(searchText);
, but the old object in memory is not immediately deleted
This will be fixed, if we call listLiveData.removeObserver(observer); before
listLiveData = App.getRepositoryRoomDB().searchDataExceptChapter(searchText);
And returning to 1) - we can not call listLiveData.removeObserver(our anonimous Observer); because we do not have an anonymous object reference.
So, in the result we can do so:
private Observer observer;
private LiveData<List<WordsTableEntity>> listLiveData;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
observer = new Observer<List<WordsTableEntity>>() {
#Override
public void onChanged(#Nullable List<WordsTableEntity> wordsTableEntities) {
adapter.setWordsTableEntities(wordsTableEntities);
progressBar.setVisibility(View.GONE);
}
};
}
public void searchText(String searchText) {
if (listLiveData != null){
listLiveData.removeObservers(this);
}
listLiveData = App.getRepositoryRoomDB().searchDataExceptChapter(searchText);
listLiveData.observe(this, observer);
}
I didn't use distinct functions. In my case it works without distinct.
I hope my case will help someone.
P.S. Version of libraries
// Room components
implementation "android.arch.persistence.room:runtime:1.1.1"
annotationProcessor "android.arch.persistence.room:compiler:1.1.1"
androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
// Lifecycle components
implementation "android.arch.lifecycle:extensions:1.1.1"
annotationProcessor "android.arch.lifecycle:compiler:1.1.1"
Currently there is no way to stop triggering Observer.onChanged which is why I think the LiveData will be useless for most of the queries that are using some joins.
Like #Pinakin mentioned there is a MediatorLiveData but this is just a filter and the data still gets loaded on every change. Imagine having 3 left joins in 1 query where you only need a field or two from those joins. In case you implement PagedList every time any record from those 4 tables (main + 3 joined tables) gets updated, the query will be called again.
This is OK for some some tables with small amount of data, but correct me if I wrong this would be bad in case of bigger tables.
It would be best if we would have some way of setting the query to be refreshed only if the main table is updated or ideally to have a way to refresh only if fields from that query are updated in the database.
Avoid false positive notifications for observable queries
Let’s say that you want to get a user based on the user id in an observable query:
#Query(“SELECT * FROM Users WHERE userId = :id)
fun getUserById(id: String): LiveData<User>
You’ll get a new emission of the User object whenever that user updates. But you will also get the same object when other changes (deletes, updates or inserts) occur on the Users table that has nothing to do with the User you’re interested in, resulting in false-positive notifications. Even more, if your query involves multiple tables, you’ll get a new emission whenever something changed in any of them.
If your query returns a LiveData, you can use a MediatorLiveData that only allows distinct object emissions from a source.
fun <T> LiveData<T>.getDistinct(): LiveData<T> {
val distinctLiveData = MediatorLiveData<T>()
distinctLiveData.addSource(this, object : Observer<T> {
private var initialized = false
private var lastObj: T? = null
override fun onChanged(obj: T?) {
if (!initialized) {
initialized = true
lastObj = obj
distinctLiveData.postValue(lastObj)
} else if ((obj == null && lastObj != null)
|| obj != lastObj) {
lastObj = obj
distinctLiveData.postValue(lastObj)
}
}
})
return distinctLiveData
}
In your DAOs, make method that returns the distinct LiveData public and the method that queries the database protected.
#Dao
abstract class UserDao : BaseDao<User>() {
#Query(“SELECT * FROM Users WHERE userid = :id”)
protected abstract fun getUserById(id: String): LiveData<User>
fun getDistinctUserById(id: String):
LiveData<User> = getUserById(id).getDistinct()
}
See more of the code here and also in Java.
I am trying to find out in the code below, why is it that Room's LiveData observable does not give me new shifts once I populate the database with new data.
This is put on my activity's onCreate method:
shiftsViewModel = ViewModelProviders.of(this).get(ShiftsViewModel.class);
shiftsViewModel
.getShifts()
.observe(this, this::populateAdapter);
This is the populateAdapter method:
private void populateAdapter(#NonNull final List<Shift> shifts){
recyclerView.setAdapter(new SimpleItemRecyclerViewAdapter(shifts));
}
I also have the following code that populates the database (I use RxJava to do the work on an IO thread since Room needs its code to be called outside the main thread):
#Override
public Observable<List<Shift>> persistShifts(#NonNull final List<Shift> shifts){
return Observable.fromCallable(() -> {
appDatabase.getShiftDao().insertAll(shifts);
return shifts;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
The problem I have occurs when I call persistShifts after I start observing my shiftsViewModel. I would expect that my observer (LiveData) would be triggered with all the newly added shifts. It turns out the observer is triggered, but an empty list of shifts is returned instead. The only way to make it "work" is if I leave the activity (therefore destroying the current ViewModel) and enter again. This time the viewModel's LiveData gives me all the shifts previously persisted, as expected.
Here is the rest of the code:
#Entity
public class Shift{
#PrimaryKey
private long id;
private String start;
private String end;
private String startLatitude;
private String startLongitude;
private String endLatitude;
private String endLongitude;
private String image;
...
DAO:
#Dao
public interface ShiftDAO {
#Query("SELECT * FROM shift")
LiveData<List<Shift>> getAll();
#Query("SELECT * FROM shift WHERE id = :id")
LiveData<Shift> getShiftById(long id);
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<Shift> shifts);
}
ViewModel:
public class ShiftsViewModel extends AndroidViewModel{
private final ISQLDatabase sqlDatabase;
private MutableLiveData<Shift> currentShift;
private LiveData<List<Shift>> shifts;
private boolean firstTimeCreated;
public ShiftsViewModel(final Application application){
super(application);
this.sqlDatabase = ((ThisApplication) application).getSQLDatabase();
this.firstTimeCreated = true;
}
public MutableLiveData<Shift> getCurrentlySelectedShift(){
if(currentShift == null){
currentShift = new MutableLiveData<>();
}
return currentShift;
}
public LiveData<List<Shift>> getShifts() {
if(shifts == null){
shifts = sqlDatabase.queryAllShifts();
}
return shifts;
}
public void setCurrentlySelectedShift(final Shift shift){
currentShift = getCurrentlySelectedShift();
currentShift.setValue(shift);
}
public boolean isFirstTimeCreated(){
return firstTimeCreated;
}
public void alreadyUsed(){
firstTimeCreated = false;
}
}
Why am I not getting the list of shifts I persist in the observe() callback straightaway?
I had a similar problem using Dagger 2 that was caused by having different instances of the Dao, one for updating/inserting data, and a different instance providing the LiveData for observing. Once I configured Dagger to manage a singleton instance of the Dao, then I could insert data in the background (in my case in a Service) while observing LiveData in my Activity - and the onChange() callback would be called.
It comes down to the instance of the Dao must be the same instance that is inserting/updating data and providing LiveData for observation.
In my case, it was because I was using a MediatorLiveData to convert the entities returned from the database and forgot to call setValue() with the converted result, so the mediator was only relying requests to the database but never notifying results.
override fun getItems() = MediatorLiveData<List<Item>>().apply {
addSource(itemDao().getItems()) {
// I was previously only converting the items, without calling 'value ='
value = it.map(ItemWithTags::toDto)
}
}
According to LiveData documentation:
The LiveData class provides the following advantages:
...
Always up to date data: If a Lifecycle starts again (like an activity going back to started state from the back stack) it receives the latest location data (if it didn’t already).
But sometimes I don't need this feature.
For example, I have following LiveData in ViewModel and Observer in Activity:
//LiveData
val showDialogLiveData = MutableLiveData<String>()
//Activity
viewModel.showMessageLiveData.observe(this, android.arch.lifecycle.Observer { message ->
AlertDialog.Builder(this)
.setMessage(message)
.setPositiveButton("OK") { _, _ -> }
.show()
})
Now after every rotation old dialog will appear.
Is there a way to clear stored value after it's handled or is it wrong usage of LiveData at all?
Update
There are actually a few ways to resolve this issue. They are summarized nicely in the article LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case). This is written by a fellow Googler who works with the Architecture Components team.
TL;DR A more robust approach is to use an Event wrapper class, which you can see an example of at the bottom of the article.
This pattern has made it's way into numerous Android samples, for example:
Plaid
Architecture Blueprints
IOSched
Why is an Event wrapper preferred over SingleLiveEvent?
One issue with SingleLiveEvent is that if there are multiple observers to a SingleLiveEvent, only one of them will be notified when that data has changed - this can introduce subtle bugs and is hard to work around.
Using an Event wrapper class, all of your observers will be notified as normal. You can then choose to either explicitly "handle" the content (content is only "handled" once) or peek at the content, which always returns whatever the latest "content" was. In the dialog example, this means you can always see what the last message was with peek, but ensure that for every new message, the dialog only is triggered once, using getContentIfNotHandled.
Old Response
Alex's response in the comments is I think exactly what you're looking for. There's sample code for a class called SingleLiveEvent. The purpose of this class is described as:
A lifecycle-aware observable that sends only new updates after
subscription, used for events like navigation and Snackbar messages.
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().
If you need simple solution, try this one:
class SingleLiveData<T> : MutableLiveData<T?>() {
override fun observe(owner: LifecycleOwner, observer: Observer<in T?>) {
super.observe(owner, Observer { t ->
if (t != null) {
observer.onChanged(t)
postValue(null)
}
})
}
}
Use it like a regular MutableLiveData
I`m not sure if it will work in your case, but in my case (increasing/decreasing items amount in Room by click on views) removing Observer and checking if there is active observers let me do the job:
LiveData<MenuItem> menuitem = mViewModel.getMenuItemById(menuid);
menuitem.observe(this, (MenuItem menuItemRoom) ->{
menuitem.removeObservers(this);
if(menuitem.hasObservers())return;
// Do your single job here
});
});
UPDATE 20/03/2019:
Now i prefer this:
EventWraper class from Google Samples inside MutableLiveData
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
public class Event<T> {
private T mContent;
private boolean hasBeenHandled = false;
public Event( T content) {
if (content == null) {
throw new IllegalArgumentException("null values in Event are not allowed.");
}
mContent = content;
}
#Nullable
public T getContentIfNotHandled() {
if (hasBeenHandled) {
return null;
} else {
hasBeenHandled = true;
return mContent;
}
}
public boolean hasBeenHandled() {
return hasBeenHandled;
}
}
In ViewModel :
/** expose Save LiveData Event */
public void newSaveEvent() {
saveEvent.setValue(new Event<>(true));
}
private final MutableLiveData<Event<Boolean>> saveEvent = new MutableLiveData<>();
LiveData<Event<Boolean>> onSaveEvent() {
return saveEvent;
}
In Activity/Fragment
mViewModel
.onSaveEvent()
.observe(
getViewLifecycleOwner(),
booleanEvent -> {
if (booleanEvent != null)
final Boolean shouldSave = booleanEvent.getContentIfNotHandled();
if (shouldSave != null && shouldSave) saveData();
}
});
In my case SingleLiveEvent doesn't help. I use this code:
private MutableLiveData<Boolean> someLiveData;
private final Observer<Boolean> someObserver = new Observer<Boolean>() {
#Override
public void onChanged(#Nullable Boolean aBoolean) {
if (aBoolean != null) {
// doing work
...
// reset LiveData value
someLiveData.postValue(null);
}
}
};
You need to use SingleLiveEvent for this case
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<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 (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
#MainThread
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#MainThread
fun call() {
value = null
}
companion object {
private const val TAG = "SingleLiveEvent"
}
}
And inside you viewmodel class create object like:
val snackbarMessage = SingleLiveEvent<Int>()
I solved it like that. Live data will clear itself when there is no observer
class SelfCleaningLiveData<T> : MutableLiveData<T>(){
override fun onInactive() {
super.onInactive()
value = null
}
}
The best solution I found is live event library which works perfectly if you have multiple observers:
class LiveEventViewModel : ViewModel() {
private val clickedState = LiveEvent<String>()
val state: LiveData<String> = clickedState
fun clicked() {
clickedState.value = ...
}
}
Might be an ugly hack but... Note: it requires RxJava
menuRepository
.getMenuTypeAndMenuEntity(menuId)
.flatMap { Single.fromCallable { menuTypeAndId.postValue(Pair(it.first, menuId)) } }
.flatMap { Single.timer(200, TimeUnit.MILLISECONDS) }
.subscribe(
{ menuTypeAndId.postValue(null) },
{ Log.d(MenuViewModel.TAG, "onError: ${it.printStackTrace()}") }
)
I know It's not the best way or even a professional way but if you do not hav time to do it the right way you can recreate the MutableLiveDataa after you observed it. it would be like :
private void purchaseAllResultFun() {
viewModel.isAllPurchaseSuccess().observe(getViewLifecycleOwner(), isSuccess -> {
if (!isSuccess) {
failedPurchaseToast();
}else {
successfulPurchaseToast();
}
//reset mutableLiveData after you're done
viewModel.resetIsAllSuccessFull();
});
}
//function in viewmodel class
public void resetIsAllSuccessFull(){
purchaseRepository.reSetIsAllSuccessFull();
}
//function in repository class
public void resetIsAllSuccessFull(){
successLiveData = new MutableLiveData<>();
}
In this way if you need to recall purchaseAllResultFun() function it won't give the stored value.