Room - LiveData observer does not trigger when database is updated - android

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)
}
}

Related

How to obtain simple List<T> from Android LiveData<List<T>>?

I'm new to Android and learning the same by developing simple app which consists of single Customer table which I'm accessing using android Room database.
The Customer entity class is
#Entity(tableName = "Customers")
public class CustomerEntity {
#PrimaryKey(autoGenerate = true)
private int customerId;
private String customerName;
private String customerAddress;
private String customerZipCode;
private String customerEMailId;
}
The Customer Dao interface is
#Dao
public interface CustomerDao {
#Insert
public void insertCustomer( CustomerEntity customerEntity );
#Update
public void updateCustomer( CustomerEntity customerEntity );
#Delete
public void deleteCustomer( CustomerEntity customerEntity) ;
#Query("SELECT * FROM Customers")
LiveData<List<CustomerEntity>> getAllCustomers();
#Query("SELECT * FROM Customers WHERE customerZipCode == :givenZipCode ")
LiveData<List<CustomerEntity>> getGivenZipCodeCustomer( String givenZipCode);
#Query("SELECT * FROM Customers WHERE customerZipCode == :givenZipCode ")
List<CustomerEntity> getGivenZipCodeCustomerList( String givenZipCode);
#Query("SELECT * FROM Customers WHERE customerZipCode == :givenZipCode ")
Cursor getGivenZipCodeCustomerCursor(String givenZipCode
}
The Customer Repository class is (partly shown)
public List<CustomerEntity> getGivenZipCodeCustomersList(CustomerEntity customerEntity){
CustomerRepository.CustomerZipCodeAsyncTask customerZipCodeAsyncTask ;
customerZipCodeAsyncTask = new CustomerRepository.CustomerZipCodeAsyncTask( customerDao );
givenZipCodeCustomersList = customerZipCodeAsyncTask.doInBackground( customerEntity );
return givenZipCodeCustomersList ;
}
private static class CustomerZipCodeAsyncTask extends AsyncTask< CustomerEntity ,
Void , List < CustomerEntity > > {
private CustomerDao customerDao;
private CustomerZipCodeAsyncTask ( CustomerDao customerDao ){
this.customerDao = customerDao;
}
#Override
protected List < CustomerEntity > doInBackground(CustomerEntity... customerEntities) {
String zipCode = customerEntities[0].getCustomerZipCode();
return ( customerDao.getGivenZipCodeCustomerList( zipCode ) ) ;
}
}
When I try to obtain the Customer List from the other part of app, I get the message
"java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time."
On the other hand, if I try to get Customer List by executing another Async process which returns LiveData List successfully but returns null when I use getValue() on LiveData.
In the part of app where I am doing this task, neither I expect that retrieved list will change nor it is required to be presented to user. So I do not need to observe this list. I need simple list from which I can access list items and process them further.
I am using Android Studio 3.4 Canary 9, androidx room_version = "2.1.0-alpha03", androix lifecycle_version = "2.0.0"
By using getValue() on LiveData, you're not using the full power of what LiveData is capable of. In your CustomerRepository class, change your posted method to:
public LiveData<List<CustomerEntity>> getGivenZipCodeCustomersList(CustomerEntity customerEntity) {
String zipCode = customerEntity.getCustomerZipCode();
return customerDao.getGivenZipCodeCustomerList(zipCode);
}
Then your Activity could look something like
public class MainActivity extends AppCompatActivity {
private CustomerRepository customerRepository;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
customerRepository = ... //init your repo here
CustomerEntity customerEntity = ... //init your customer entity here
customerRepository.getGivenZipCodeCustomersList(customerEntity).observe(this, new Observer<List<CustomerEntity>> {
#Override
public void onChanged(#Nullable List<CustomerEntity> customerEntities) {
//do stuff with your customerEntities here
}
});
//.. more code
}
}
Doing this, every time your customers matching to the zipcode in your CustomerEntity change in your database, onChanged gets trigger so you can handle the changes automatically.
For more info, refer to the documentation of how to use LiveData.
Hope that helps!
You should invoke
#MainThread
public void observe(#NonNull LifecycleOwner owner, #NonNull Observer<T> observer)
#param owner The LifecycleOwner which controls the observer
#param observer The observer that will receive the events
method on your LiveData somewhere in your Activity/Fragment (They implement LifecycleOwner). After your AsyncTask posts the value, MainThread will then get notified with the value.
If you do not wish to observe this LiveData, you can just removeObservers when you get the value.
For more information, I suggest to read Documentation on LivaData.java

Multiple instances of Livedata observers

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.

Android Room database ViewModel does not reflect synchronously inserted data

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.

Room : LiveData from Dao will trigger Observer.onChanged on every Update, even if the LiveData value has no change

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.

AndroidViewModel - Making duplicate calls doesn't return data in observe function

My question is related to ViewModel second time returns null wherein I am not getting callback inobserve function if I make a repeated call to server. Following is the code I am using -
#Singleton
public class NetworkInformationViewModel extends AndroidViewModel {
private LiveData<Resource<NetworkInformation>> networkInfoObservable;
private final APIClient apiClient;
#Inject
NetworkInformationViewModel(#NonNull APIClient apiClient, #NonNull Application application) {
super(application);
this.apiClient = apiClient;
getNetworkInformation();
}
public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() {
return networkInfoObservable;
}
// making API calls and adding it to Observable
public void getNetworkInformation() {
networkInfoObservable = apiClient.getNetworkInformation();
}
}
In Activity, the ViewModel is defined as followed -
final NetworkInformationViewModel networkInformationViewModel =
ViewModelProviders.of(this, viewModelFactory).get(NetworkInformationViewModel.class);
observeViewModel(networkInformationViewModel);
The observeViewModel function is used to add observable on ViewModel.
public void observeViewModel(final NetworkInformationViewModel networkInformationViewModel) {
networkInformationViewModel.getNetworkInfoObservable()
.observe(this, networkInformationResource -> {
if (networkInformationResource != null) {
if (networkInformationResource.status == APIClientStatus.Status.SUCCESS) {
Timber.d("Got network information data");
} else {
final Throwable throwable = networkInformationResource.throwable;
if (throwable instanceof SocketTimeoutException) {
final NetworkInformation networkInformation = networkInformationResource.data;
String error = null;
if (networkInformation != null) {
error = TextUtils.isEmpty(networkInformation.error) ? networkInformation.reply : networkInformation.error;
}
Timber.e("Timeout error occurred %s %s", networkInformationResource.message, error);
} else {
Timber.e("Error occurred %s", networkInformationResource.message);
}
if (count != 4) {
networkInformationViewModel.getNetworkInformation();
count++;
// Uncommenting following line enables callback to be received every time
//observeViewModel(networkInformationViewModel);
}
}
}
});
}
Uncommenting the following line in above function allows the callback to come everytime, but there has to be a proper way of doing this.
//observeViewModel(networkInformationViewModel);
Please note:-
I don't need RxJava implementation for implementing this.
Right now in getNetworkInformation() you are:
Creating a new LiveData
Updating the the LiveData using setValue
Instead, you should have a single LiveData for APIClient created as a member variable, then in getNetworkInformation() just update that member LiveData.
More generally, your APIClient is a data source. For data sources, you can have them contain member LiveData objects that update when the data changes. You can provide getters to those LiveData objects to make them accessible in ViewModels, and ultimately listen to them in your Activities/Fragments. This is similar how you might take another data source, such as Room, and listen to a LiveData returned by Room.
So the code in this case would look like:
#Singleton
public class APIClient {
private final MutableLiveData<Resource<NetworkInformation>> mNetworkData = new MutableLiveData<>(); // Note this needs to be MutableLiveData so that you can call setValue
// This is basically the same code as the original getNetworkInformation, instead this returns nothing and just updates the LiveData
public void fetchNetworkInformation() {
apiInterface.getNetworkInformation().enqueue(new Callback<NetworkInformation>() {
#Override
public void onResponse(
#NonNull Call<NetworkInformation> call, #NonNull Response<NetworkInformation> response
) {
if (response.body() != null && response.isSuccessful()) {
mNetworkData.setValue(new Resource<>(APIClientStatus.Status.SUCCESS, response.body(), null));
} else {
mNetworkData.setValue(new Resource<>(APIClientStatus.Status.ERROR, null, response.message()));
}
}
#Override
public void onFailure(#NonNull Call<NetworkInformation> call, #NonNull Throwable throwable) {
mNetworkData.setValue(
new Resource<>(APIClientStatus.Status.ERROR, null, throwable.getMessage(), throwable));
}
});
}
// Use a getter method so that you can return immutable LiveData since nothing outside of this class will change the value in mNetworkData
public LiveData<Resource<NetworkInformation>> getNetworkData(){
return mNetworkData;
}
}
Then in your ViewModel...
// I don't think this should be a Singleton; ViewModelProviders will keep more than one from being instantiate for the same Activity/Fragment lifecycle
public class SplashScreenViewModel extends AndroidViewModel {
private LiveData<Resource<NetworkInformation>> networkInformationLiveData;
#Inject
SplashScreenViewModel(#NonNull APIClient apiClient, #NonNull Application application) {
super(application);
this.apiClient = apiClient;
// Initializing the observable with empty data
networkInfoObservable = apiClient.getNetworkData()
}
public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() {
return networkInformationLiveData;
}
}
Your activity can be the same as you originally coded it; it will just get and observe the LiveData from the ViewModel.
So what is Transformations.switchMap for?
switchMap isn't necessary here because you don't need to change the underlying LiveData instance in APIClient. This is because there's really only one piece of changing data. Let's say instead your APIClient needed 4 different LiveData for some reason, and you wanted to change which LiveData you observed:
public class APIClient {
private MutableLiveData<Resource<NetworkInformation>> mNetData1, mNetData2, mNetData3, mNetData4;
...
}
Then let's say that your fetchNetworkInformation would refer to different LiveData to observe depending on the situation. It might look like this:
public LiveData<Resource<NetworkInformation>> getNetworkInformation(int keyRepresentingWhichLiveDataToObserve) {
LiveData<Resource<NetworkInformation>> currentLiveData = null;
switch (keyRepresentingWhichLiveDataToObserve) {
case 1:
currentLiveData = mNetData1;
break;
case 2:
currentLiveData = mNetData2;
break;
//.. so on
}
// Code that actually changes the LiveData value if needed here
return currentLiveData;
}
In this case the actual LiveData coming from getNetworkInformation is changes, and you're also using some sort of parameter to determine which LiveData you want. In this case, you'd use a switchMap, because you want to make sure that the observe statement you called in your Activity/Fragment observes the LiveData returned from your APIClient, even if you change the underlying LiveData instance. And you don't want to call observe again.
Now this is a bit of an abstract example, but it's basically what your calls to a Room Dao do -- if you have a Dao method that queries your RoomDatabase based on an id and returns a LiveData, it will return a different LiveData instance based on the id.
I didn't met the same issue, but i came across a similar thing where the number of observers were increasing each time i was saving the data in db. The way i debugged was how many instances or different instances of observers were getting invoked and i came to know that when you are fetching the live data from view model it needs to be checked for non null or you can say only 1 instance is being returned -
private LiveData<T> data;
public LiveData<T> getLiveData(){
if(data ==null){
data = //api call or fetch from db
}
return data;
}
Before i was simply returning the data object and then after checking the source i came to the conclusion that livedata automatically updates your object and everytime without the null check new instance was getting created and new observers were getting attached. Someone can correct me if my understanding regarding livedata is wrong.
I have already updated the linked question's answer. Re-posting here since I have placed a bounty on the question and hopefully someone will verify that this is the proper way to handle the issue.
Following is the updated working solution -
#Singleton
public class SplashScreenViewModel extends AndroidViewModel {
private final APIClient apiClient;
// This is the observable which listens for the changes
// Using 'Void' since the get method doesn't need any parameters. If you need to pass any String, or class
// you can add that here
private MutableLiveData<Void> networkInfoObservable;
// This LiveData contains the information required to populate the UI
private LiveData<Resource<NetworkInformation>> networkInformationLiveData;
#Inject
SplashScreenViewModel(#NonNull APIClient apiClient, #NonNull Application application) {
super(application);
this.apiClient = apiClient;
// Initializing the observable with empty data
networkInfoObservable = new MutableLiveData<Void>();
// Using the Transformation switchMap to listen when the data changes happen, whenever data
// changes happen, we update the LiveData object which we are observing in the MainActivity.
networkInformationLiveData = Transformations.switchMap(networkInfoObservable, input -> apiClient.getNetworkInformation());
}
/**
* Function to get LiveData Observable for NetworkInformation class
* #return LiveData<Resource<NetworkInformation>>
*/
public LiveData<Resource<NetworkInformation>> getNetworkInfoObservable() {
return networkInformationLiveData;
}
/**
* Whenever we want to reload the networkInformationLiveData, we update the mutable LiveData's value
* which in turn calls the `Transformations.switchMap()` function and updates the data and we get
* call back
*/
public void setNetworkInformation() {
networkInfoObservable.setValue(null);
}
}
The Activity's code will be updated as -
final SplashScreenViewModel splashScreenViewModel =
ViewModelProviders.of(this, viewModelFactory).get(SplashScreenViewModel.class);
observeViewModel(splashScreenViewModel);
// This function will ensure that Transformation.switchMap() function is called
splashScreenViewModel.setNetworkInformation();
Watch her droidCon NYC video for more information on LiveData. The official Google repository for LiveData is https://github.com/googlesamples/android-architecture-components/ look for GithubBrowserSample project.
The apiClient.getNetworkInformation() call doesn't need it any parameters to get additional information. Hence, the 'Void' added in MutableLiveData.
public LiveData<Resource<NetworkInformation>> getNetworkInformation() {
final MutableLiveData<Resource<NetworkInformation>> data = new MutableLiveData<>();
apiInterface.getNetworkInformation().enqueue(new Callback<NetworkInformation>() {
#Override
public void onResponse(
#NonNull Call<NetworkInformation> call, #NonNull Response<NetworkInformation> response
) {
if (response.body() != null && response.isSuccessful()) {
data.setValue(new Resource<>(APIClientStatus.Status.SUCCESS, response.body(), null));
} else {
data.setValue(new Resource<>(APIClientStatus.Status.ERROR, null, response.message()));
}
}
#Override
public void onFailure(#NonNull Call<NetworkInformation> call, #NonNull Throwable throwable) {
data.setValue(
new Resource<>(APIClientStatus.Status.ERROR, null, throwable.getMessage(), throwable));
}
});
return data;
}

Categories

Resources