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.
Related
I am having an issue where data that is written to my Room database does not appear in a ViewModel even though I am writing it synchronously.
This is what a log would look like:
com.widget D/WriteActivity: Writing widget data to the database
com.widget D/WriteActivity: Starting the ReadActivity
com.widget D/ReadActivity: Got a new list of 0 objects
Here is the situation:
I have two activities, WriteActivity and ReadActivity. Inside of the ReadActivity I have a ViewModel listening for database changes (that is instantiated in the onCreate method of the Activity):
// observe the widget data
WidgetViewModel widgetViewModel = ViewModelProviders.of(this).get(
WidgetViewModel.class);
widgetViewModel.getAllWidgets().observe(this, new Observer<List<Widget>>() {
#Override
public void onChanged(#Nullable final List<Widget> updatedWidgets) {
Log.d(TAG, "Got a new list of " + updatedWidgets.size() + " objects");
}
});
Inside of the WriteActivity I have code that adds an object to the database on a background thread, then, once it completes, it launches the ReadActivity:
// persist the objects to the room database (doInBackground)
WidgetRepository myObjectRepository = new WidgetRepository(getApplication());
myObjectRepository.insert(myObjects); // myObjects is a list of 5 objects
// load the ReadActivity (onPostExecute)
Intent myIntent = new Intent(WriteActivity.this, ReadActivity.class);
WriteActivity.this.startActivity(myIntent);
Here is my DAO:
#Dao
public interface WidgetDao {
#Query("SELECT * FROM widget_table")
LiveData<List<Widget>> getAll();
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Widget... widgets);
}
My Database:
#Database(entities = {Widget.class}, version = 1, exportSchema = false)
public abstract class WidgetDatabase extends RoomDatabase {
public abstract WidgetDao widgetDao();
private static volatile WidgetDatabase INSTANCE;
static WidgetDatabase getDatabase(final Context context) {
if (null == INSTANCE) {
synchronized (WidgetDatabase.class) {
if (null == INSTANCE) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
WidgetDatabase.class, "widget_database")
.build();
}
}
}
return INSTANCE;
}
}
My repository:
public class WidgetRepository {
private final WidgetDao widgetDao;
private final LiveData<List<Widget>> widgets;
public WidgetRepository(Application application) {
WidgetDatabase db = WidgetDatabase.getDatabase(application);
widgetDao = db.widgetDao();
widgets = widgetDao.getAll();
}
public LiveData<List<Widget>> getWidgets() {
return widgets;
}
public void insert(List<Widget> widgetsToInsert) {
widgetDao.insert(widgetsToInsert.toArray(
new Widget[widgetsToInsert.size()]));
}
My ViewModel:
public class WidgetViewModel extends AndroidViewModel {
private final LiveData<List<Widget>> widgets;
public WidgetViewModel (Application application) {
super(application);
WidgetRepository widgetRepository = new WidgetRepository(application);
widgets = widgetRepository.getWidgets();
}
public LiveData<List<Widget>> getAllWidgets() { return widgets;
}
}
Your issue is that LiveData<List<Widget>> is not being notified.
So, how to update that ?
see below,
Update LiveData objects
LiveData has no publicly available methods to update the stored data.
The MutableLiveData class exposes the setValue(T) and postValue(T)
methods publicly and you must use these if you need to edit the value
stored in a LiveData object. Usually MutableLiveData is used in the
ViewModel and then the ViewModel only exposes immutable LiveData
objects to the observers.
So, changes you can make to your ViewModel:
public class WidgetViewModel extends AndroidViewModel {
private final MutableLiveData<List<Widget>> widgets = new MutableLiveData<List<Widget>>(); // Make it mutable livedata
public WidgetViewModel (Application application) {
super(application);
WidgetRepository widgetRepository = new WidgetRepository(application);
//widgets = widgetRepository.getWidgets();
//use this to update your live data instead,
widgets.setValue(widgetRepository.getWidgets().getValue()); // This will update your live data, use like this for future updates.
}
public LiveData<List<Widget>> getAllWidgets() { return widgets;
}
}
Checkout more from here
I figured out what was happening. It's embarassing... when I asked my question I had a fundamental misunderstanding of how LiveData works. RTFM jeez :)
After I read the documentation I came to a stunning revelation: LiveData is tied to the lifecycle of the activity. In the example I gave I was attempting to access the ViewModel during the onResume of my ReadActivity because I was wanted to make sure that the UI updated properly. Rookie mistake. Like a fool I believed that the Observer callback would only fire when the data encapsulated by the LiveData was modified. In reality, the LiveData callback in the Observer is called when the activity becomes active regardless of whether the data has changed, so there is no need to try to do anything in the onResume lifecycle method manually. Just wait for the onChanged callback of the Observer and update the UI at that time.
App is working great now, thank you to everyone who read my question.
My view Model
declares two live data :
the scenario is getting two methods from the repository so that second live data input arguments dependent on first live data got data
public class ProductViewModel extends AndroidViewModel {
private LiveData<DataWrapper<GetProductQuery.Product>> productLiveData;
private LiveData<DataWrapper<ArrayList<ProductList>>> vendorProductLiveData;
private ProductRepository repository ;
public ProductViewModel(#NonNull Application application) {
super(application);
repository = new ProductRepository();
}
public LiveData<DataWrapper<GetProductQuery.Product>> getProductLiveData(String productId) {
productLiveData = repository.getProduct(productId);
return productLiveData;
}
public LiveData<DataWrapper<ArrayList<ProductList>>> getVendorProductLiveData(int vendorId) {
vendorProductLiveData = repository.getLimitProduct(vendorId);
return vendorProductLiveData;
} }
in Activity i want run second live data after first live data :
viewModel.getVendorProductLiveData(Integer.parseInt(p.getId())).observe(getActivity(), arrayListDataWrapper -> {
ArrayList<ProductList> pList =
arrayListDataWrapper.getData();
});
viewModel.getProductLiveData(id).observe(this, productDataWrapper -> {
p = productDataWrapper.getData();
});
viewModel.getVendorProductLiveData(Integer.parseInt(p.getId())).observe(getActivity(), arrayListDataWrapper -> {
//do logic after get product
});
You should use Transformations.switchMap() to transform your first LiveData to your second LiveData using the former emitted value as the argument. Then you can observe this transformed LiveData to get the final result.
So, I have just started experimenting with LiveData - I am busy with a new project, where I am using ViewModel as well as LiveData - with some of the RESTFul services I use to fetch data, they take no parameters and return some data.
A typical setup of the MVVM paradigm with LiveData looks much like this:
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
}
Now when we leave this activity, and go to a new activity, by using an Intent or some other means, and not pressing the back button (So, finalize is not called) - and then come back to MyActivity - we of course don't fetch the users again, as we should still have that data.
However, what if we did want to fetch them again?
The only way to do this properly, from what I have looked at, seems to call "setValue" on the getUsers() LiveData object
Something like this:
public class MyActivity extends AppCompatActivity {
public void onResume() {
viewModel.setActive(true);
}
}
And the ViewModel would look like this:
private final MutableLiveData<Boolean> activeLiveData = new MutableLiveData<>();
ViewModel(ViewModelRepo repo){
this.repo = repo;
results = Transformations.switchMap(activeLiveData, active ->{
if(active){
return repo.getUsers();
}else {
return AbsentLiveData.create(); //"Null live data"
}
});
}
LiveData<Users>> getUsers() {
return results;
}
//This could be called "update" with no params
void setActive(boolean active) {
activeLiveData.setValue(active);
}
The one reason I have decided to do it like this is because Google does not want us doing this:
class MyViewModel extends ViewModel {
private final PostalCodeRepository repository;
public MyViewModel(PostalCodeRepository repository) {
this.repository = repository;
}
private LiveData<String> getPostalCode(String address) {
// DON'T DO THIS
return repository.getPostCode(address);
}
}
For this reason:
If this is the implementation, the UI would need to unregister from
the previous LiveData and re-register to the new instance each time
they call getPostalCode(). Moreover, if the UI is re-created, it
triggers another call to repository.getPostCode() instead of using the
previous call’s result.
Is there a better way to get the ViewModel to "redo" its repo.getUsers() call? Perhaps I could just make a method that says "Update()" instead of "active" but still - its doing the same thing differently.
Well here you're doing the fetching in the creator of the ViewModel, which locks things in place. Usually they'd advise to fetch the data in the getter, if the data is not there already.
So a good option would be to use the regular pattern first :
private MutableLiveData<Users> users = null;
ViewModel(ViewModelRepo repo){
this.repo = repo;
}
LiveData<Users> getUsers() {
if (users = null) {
fetchUsers();
}
return users;
}
public void fetchUsers() {
users.postValue(repo.getUsers());
}
And then from your Activity/Fragment, whenever you feel necessary to "refresh the users", you'd simply call viewModel.fetchUsers();
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.