If I understand your architecture correctly, updateUsers is inside of an AsyncTask or similar.
This is my proposed approach, which involves tweaking your Dao for maximum effectiveness. You wrote a lot of code to make decisions you could ask your database to make.
This is also not tight or efficient code, but I hope it illustrates more effective use of these libraries.
Background thread (IntentService, AsyncTask, etc.):
/*
* assuming this method is executing on a background thread
*/
private void updateUsers(/* from API call */List<User> serverUsers) {
for(User serverUser : serverUsers){
switch(appDatabase.userDao().userExistsSynchronous(serverUser.getId())){
case 0: //doesn't exist
saveShiftForUser(serverUser);
case 1: //does exist
UserWithShifts localUser = appDatabase.userDao().getOldUserSynchronous(serverUser.getId(), serverUser.getUpdatedAt());
if(localUser != null){ //there is a record that's too old
deleteEventsAndShifts(serverUser.getId());
saveShiftForUser(serverUser);
}
default: //something happened, log an error
}
}
}
If running on the UI thread (Activity, Fragment, Service):
/*
* If you receive the IllegalStateException, try this code
*
* NOTE: This code is not well architected. I would recommend refactoring if you need to do this to make things more elegant.
*
* Also, RxJava is better suited to this use case than LiveData, but this may be easier for you to get started with
*/
private void updateUsers(/* from API call */List<User> serverUsers) {
for(User serverUser : serverUsers){
final LiveData<Integer> userExistsLiveData = appDatabase.userDao().userExists(serverUser.getId());
userExistsLiveData.observe(/*activity or fragment*/ context, exists -> {
userExistsLiveData.removeObservers(context); //call this so that this same code block isn't executed again. Remember, observers are fired when the result of the query changes.
switch(exists){
case 0: //doesn't exist
saveShiftForUser(serverUser);
case 1: //does exist
final LiveData<UserWithShifts> localUserLiveData = appDatabase.userDao().getOldUser(serverUser.getId(), serverUser.getUpdatedAt());
localUserLiveData.observe(/*activity or fragment*/ context, localUser -> { //this observer won't be called unless the local data is out of date
localUserLiveData.removeObservers(context); //call this so that this same code block isn't executed again. Remember, observers are fired when the result of the query changes.
deleteEventsAndShifts(serverUser.getId());
saveShiftForUser(serverUser);
});
default: //something happened, log an error
}
});
}
}
You'll want to modify the Dao for whatever approach you decide to use
@Dao
public interface UserDao{
/*
* LiveData should be chosen for most use cases as running on the main thread will result in the error described on the other method
*/
@Query("SELECT * FROM users")
LiveData<List<UserWithShifts>> getAllUsers();
/*
* If you attempt to call this method on the main thread, you will receive the following error:
*
* Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long periods of time.
* at android.arch.persistence.room.RoomDatabase.assertNotMainThread(AppDatabase.java:XXX)
* at android.arch.persistence.room.RoomDatabase.query(AppDatabase.java:XXX)
*
*/
@Query("SELECT * FROM users")
List<UserWithShifts> getAllUsersSynchronous();
@Query("SELECT EXISTS (SELECT * FROM users WHERE id = :id)")
LiveData<Integer> userExists(String id);
@Query("SELECT EXISTS (SELECT * FROM users WHERE id = :id)")
Integer userExistsSynchronous(String id);
@Query("SELECT * FROM users WHERE id = :id AND updatedAt < :updatedAt LIMIT 1")
LiveData<UserWithShifts> getOldUser(String id, Long updatedAt);
@Query("SELECT * FROM users WHERE id = :id AND updatedAt < :updatedAt LIMIT 1")
UserWithShifts getOldUserSynchronous(String id, Long updatedAt);
}
Does this solve your problem?
NOTE: I did not see your saveShiftForUser
or deleteEventsAndShifts
methods. Insert, Save and Update are performed synchronously by Room. If you are running either method on the main thread (I'm guessing this is where your error is coming from), you should create a daoWrapper that is returned from appDatabase like so:
public class UserDaoWrapper {
private final UserDao userDao;
public UserDaoWrapper(UserDao userDao) {
this.userDao = userDao;
}
public LiveData<Long[]> insertAsync(UserWithShifts... users){
final MutableLiveData<Long[]> keys = new MutableLiveData<>();
HandlerThread ht = new HandlerThread("");
ht.start();
Handler h = new Handler(ht.getLooper());
h.post(() -> keys.postValue(userDao.insert(users)));
return keys;
}
public void updateAsync(UserWithShifts...users){
HandlerThread ht = new HandlerThread("");
ht.start();
Handler h = new Handler(ht.getLooper());
h.post(() -> {
userDao.update(users);
});
}
public void deleteAsync(User... users){
HandlerThread ht = new HandlerThread("");
ht.start();
Handler h = new Handler(ht.getLooper());
h.post(() -> {
for(User e : users)
userDao.delete(e.getId());
});
}
}