如何从 LiveData 获取值?
How can I get a value from LiveData?
我是第一次使用 Room。我正在查看 LiveData 概念。我知道我们可以从 DB 中获取记录到 LiveData 并附加 Observers。
@Query("SELECT * FROM users")
<LiveData<List<TCUser>> getAll();
但我在后台执行同步,我需要从服务器获取数据并将其与名为 "users" 的 RoomDatabase table 中的数据进行比较,然后从中插入、更新或删除用户 table。在采取任何行动之前如何遍历 LiveData 列表?因为如果我把它放在 for 循环中它会出错。
或者我不应该在这种情况下使用 LiveData 吗?
我想我需要打电话给
<LiveData<List<TCUser>> getAll().getValue()
但这样做正确吗?这里有更多代码可以让我了解我正在尝试做什么:
List<User>serverUsers: Is the data received from a response from an API
private void updateUsers(List<User> serverUsers) {
List<UserWithShifts> users = appDatabase.userDao().getAllUsers();
HashMap<String, User> ids = new HashMap();
HashMap<String, User> newIds = new HashMap();
if (users != null) {
for (UserWithShifts localUser : users) {
ids.put(localUser.user.getId(), localUser.user);
}
}
for (User serverUser : serverUsers) {
newIds.put(serverUser.getId(), serverUser);
if (!ids.containsKey(serverUser.getId())) {
saveShiftForUser(serverUser);
} else {
User existingUser = ids.get(serverUser.getId());
//If server data is newer than local
if (DateTimeUtils.isLaterThan(serverUser.getUpdatedAt(), existingUser.getUpdatedAt())) {
deleteEventsAndShifts(serverUser.getId());
saveShiftForUser(serverUser);
}
}
}
其中:
@Query("SELECT * FROM users")
List<UserWithShifts> getAllUsers();
updateUsers() 中的第一行是在插入新数据之前从数据库中获取数据以进行处理的正确方法还是应该改为
<LiveData<List<User>> getAll().getValue()
谢谢,
如果我对您的体系结构的理解正确,updateUsers 在 AsyncTask 或类似的内部。
这是我建议的方法,它涉及调整您的 Dao 以获得最大效率。你写了很多代码来做出你可以让你的数据库做出的决定。
这也不是紧凑或高效的代码,但我希望它能说明如何更有效地使用这些库。
后台线程(IntentService、AsyncTask等):
/*
* 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
}
}
}
如果 运行 在 UI 线程(Activity、片段、服务)上:
/*
* 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
}
});
}
}
无论您决定使用什么方法,您都需要修改 Dao
@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);
}
这是否解决了您的问题?
注意:我没有看到您的 saveShiftForUser
或 deleteEventsAndShifts
方法。插入、保存和更新由 Room 同步执行。如果您是 运行 主线程上的任一方法(我猜这是您的错误来源),您应该创建一个从 appDatabase 返回的 daoWrapper,如下所示:
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());
});
}
}
我是第一次使用 Room。我正在查看 LiveData 概念。我知道我们可以从 DB 中获取记录到 LiveData 并附加 Observers。
@Query("SELECT * FROM users")
<LiveData<List<TCUser>> getAll();
但我在后台执行同步,我需要从服务器获取数据并将其与名为 "users" 的 RoomDatabase table 中的数据进行比较,然后从中插入、更新或删除用户 table。在采取任何行动之前如何遍历 LiveData 列表?因为如果我把它放在 for 循环中它会出错。
或者我不应该在这种情况下使用 LiveData 吗?
我想我需要打电话给
<LiveData<List<TCUser>> getAll().getValue()
但这样做正确吗?这里有更多代码可以让我了解我正在尝试做什么:
List<User>serverUsers: Is the data received from a response from an API
private void updateUsers(List<User> serverUsers) {
List<UserWithShifts> users = appDatabase.userDao().getAllUsers();
HashMap<String, User> ids = new HashMap();
HashMap<String, User> newIds = new HashMap();
if (users != null) {
for (UserWithShifts localUser : users) {
ids.put(localUser.user.getId(), localUser.user);
}
}
for (User serverUser : serverUsers) {
newIds.put(serverUser.getId(), serverUser);
if (!ids.containsKey(serverUser.getId())) {
saveShiftForUser(serverUser);
} else {
User existingUser = ids.get(serverUser.getId());
//If server data is newer than local
if (DateTimeUtils.isLaterThan(serverUser.getUpdatedAt(), existingUser.getUpdatedAt())) {
deleteEventsAndShifts(serverUser.getId());
saveShiftForUser(serverUser);
}
}
}
其中:
@Query("SELECT * FROM users")
List<UserWithShifts> getAllUsers();
updateUsers() 中的第一行是在插入新数据之前从数据库中获取数据以进行处理的正确方法还是应该改为
<LiveData<List<User>> getAll().getValue()
谢谢,
如果我对您的体系结构的理解正确,updateUsers 在 AsyncTask 或类似的内部。
这是我建议的方法,它涉及调整您的 Dao 以获得最大效率。你写了很多代码来做出你可以让你的数据库做出的决定。
这也不是紧凑或高效的代码,但我希望它能说明如何更有效地使用这些库。
后台线程(IntentService、AsyncTask等):
/*
* 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
}
}
}
如果 运行 在 UI 线程(Activity、片段、服务)上:
/*
* 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
}
});
}
}
无论您决定使用什么方法,您都需要修改 Dao
@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);
}
这是否解决了您的问题?
注意:我没有看到您的 saveShiftForUser
或 deleteEventsAndShifts
方法。插入、保存和更新由 Room 同步执行。如果您是 运行 主线程上的任一方法(我猜这是您的错误来源),您应该创建一个从 appDatabase 返回的 daoWrapper,如下所示:
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());
});
}
}