使用 LiveData 或 MutableLiveData 观察数据库的单个条目
Observe single entry of database using LiveData or MutableLiveData
我目前正在开发一个应用程序,它需要观察数据库中一个条目的变化。该应用程序的结构如下:
我的 MainActivity 是一个 RecyclerView,它列出了 DB 中的所有好友。单击 item/friend 时,朋友的个人资料 (ProfileActivity) 将启动,包括一个按钮,用于打开编辑器以编辑姓名、生日、图像等。当用户完成编辑后,新数据将保存到DB 和 EditActivity 已关闭,应用 returns 到 ProfileActivity。在这里,我希望显示更改。
我尝试了 LiveData 和 MutableLiveData。在这两种情况下,我都使用了 Activity-ViewModel-Repository-Dao 的通用结构。使用 LiveData 我的 Dao.findBy(friendId) returns LiveData 和使用 MutableLiveData Dao.findBy(friendId) returns FriendEntity。
MutableLiveData 的问题在于无法直接观察到数据库中的更改。对于 LiveData,问题是,必须以某种方式初始化我的存储库中的 LiveData 字段,以便 Activity 观察它。但是 LiveData 不能手动初始化 - 所以我得到一个 NullPointerException。
我尝试在 onCreate 调用 Dao.findBy(-1) 来初始化我的 LiveData,当我知道实际的 friendId 时,我再次调用它,但不再触发 onChanged。
这个问题有好的解决办法吗?
还有其他方法可以初始化 LiveData 吗?
为什么我第二次调用 Dao.findBy(friendId) 时没有触发 onChanged(调用 Dao.findBy(-1))
Activity:
public class ProfileActivity extends AppCompatActivity {
MyViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mutable_live_data);
viewModel = new ViewModelProvider(this).get(MyViewModel.class);
viewModel.getFoundFriend().observe(this, new Observer<FriendEntity>() {
@Override
public void onChanged(FriendEntity friendEntity) {
if (friendEntity != null){
FriendEntity friend = friendEntity.getName();
textViewData.setText(name);
}
}
});
}
}
视图模型:
public class MyViewModel extends AndroidViewModel {
NormalRepository repository;
LiveData<FriendEntity> foundFriend;
public NormalViewModel(@NonNull Application application) {
super(application);
repository = new NormalRepository(application);
foundFriend = repository.getFoundFriend();
}
public LiveData<FriendEntity> getFoundFriend() {
return foundFriend;
}
public void find(long id) {
repository.find(id);
}
}
存储库:
public class NormalRepository {
MyDao dao;
LiveData<FriendEntity> foundFriend;
public NormalRepository(Application application) {
Database database = Database.getInstance(application);
this.dao = database.normalDao();
foundFriend = dao.findBy(-1L);
}
public void find(long id) {
new FindAsyncTask(dao).execute(id);
}
private class FindAsyncTask extends AsyncTask<Long, Void, LiveData<FriendEntity>>{
NormalDao dao;
public FindAsyncTask(NormalDao dao) {
this.dao = dao;
}
@Override
protected LiveData<FriendEntity> doInBackground(Long... longs) {
foundFriend = null;
return dao.findBy(longs[0]);
}
@Override
protected void onPostExecute(LiveData<FriendEntity> friendEntityLiveData) {
foundFriend = friendEntityLiveData;
}
}
public LiveData<FriendEntity> getFoundFriend() {
return foundFriend;
}
}
道:
@Dao
public interface MyDao {
@Query("SELECT * FROM friend_table WHERE id=:id")
LiveData<FriendEntity> findBy(long id);
}
为什么我第二次调用 Dao.findBy(friendId) 时没有触发 onChanged(在调用 Dao.findBy(-1))
在您的存储库中,您正在使用 id = -1L
初始化数据库挂钩。现在你只会收到 id=-1 的实体的更新。而ID字段几乎都是数据库的ROWID,从不为负。
解决方案:
现在,我从您的问题中了解到,您的 DBase table 中将只有一个条目。这意味着您期望的值不(因此 不能 )取决于任何参数。这很容易实现:
由于您只有一个不依赖于任何参数的条目,因此您的函数实际上不需要任何参数findBy(long id)
。只需从查询中删除 WHERE 子句:
@Query("SELECT * FROM friend_table LIMIT 1")
LiveData<FriendEntity> findBy();
MutableLiveData的问题是无法直接观察到DB中的变化
由于您没有提供您的实现,我无法猜测可能是什么问题。不过,正确实施应该不会造成任何问题。
编辑
收到需求反馈后,正确的实现方式:
- 道:
@Dao
public interface MyDao {
@Query("SELECT * FROM friend_table WHERE id=:id LIMIT 1")
LiveData<FriendEntity> findBy(long id);
}
- 存储库:
不需要 find(long id) 方法。改为更改提供 LiveData 的方式。正如我之前的回答,也不要用 id=-1L 初始化它。
public LiveData<FriendEntity> getFriendById(long id) {
if(id != foundFriend.getId()) {
//Background thread is not necessary for @Query annotated methods,
//as Room framework handles those by itself
foundFriend = dao.findBy(id);
}
return foundFriend;
}
- 视图模型:
这一步不是绝对必要的,但作为最佳实践,您不要将存储库中的数据直接暴露给视图。由于您的结果取决于 id 参数,因此请使用 Transformations API ,即:Transformations.switchMap():https://developer.android.com/reference/androidx/lifecycle/Transformations(我强烈建议您学习并使用此 API 以及适用的 LiveData)
public class MyViewModel extends AndroidViewModel {
//..................................
private MutableLiveData<Integer> friendId = new MutableLiveData<>();
private LiveData<FriendEntity> foundFriend =
Transformations.switchMap(friendId,
id -> repository.getFriendById(id));
public void updateFriend(int friendId) {
friendId.setValue(friendId);//this will trigger the lambda expression
//we passed to switchMap function, which
//in return will end up re-queryig of dbase
//by repository.
//...........................
所有这些准备就绪后,您只需在视图(Activity、片段等)中调用 getFoundFriend() 一次,并且可以调用视图模型的 updateFriend(long id) 当你需要新的 FriendEntity 时你的视图中的方法。
我对 Java 有点生疏,因为我使用的是 Kotlin,但在我看来:
FindAsyncTask 没用。如果您 return 来自查询的 LiveData,这可以是 运行 在主线程上。您不需要使用后台线程。事实上,函数 returns 立即获取 LiveData(内容为空值),然后 Room 使用后台线程执行查询并更新 LiveData。那时你得到了你的价值。
现在,忘记你的情况下的 MutableLiveData 吧。也就是说,如果您需要更新自己的值,但在这种情况下,Room 会负责。所以回购将是这样的:
public class NormalRepository {
MyDao dao;
// you probably can transform all in a one liner, i bet you need just a context there, not application
public NormalRepository(Application application) {
Database database = Database.getInstance(application);
this.dao = database.normalDao();
}
public LiveData<FriendEntity> find(long id) {
new dao.findBy(id);
}
}
ViewModel 只需要调用您的查询并存储 LiveData
public class MyViewModel extends AndroidViewModel {
NormalRepository repository;
LiveData<FriendEntity> foundFriend;
// When you create the view Model you should already know what is the id
public NormalViewModel(@NonNull Application application, long id) {
super(application);
repository = new NormalRepository(application);
foundFriend = repository.repository.find(id);
}
public LiveData<FriendEntity> getFoundFriend() {
return foundFriend;
}
}
在 activity 上:
public class ProfileActivity extends AppCompatActivity {
MyViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mutable_live_data);
if(savedInstanceState != null)
long id = savedInstanceState.getLong("MyLong")
viewModel = new ViewModelProvider(this,ViewModelFactory(application, id)).get(MyViewModel.class);
viewModel.getFoundFriend().observe(this, new Observer<FriendEntity>() {
@Override
public void onChanged(FriendEntity friendEntity) {
if (friendEntity != null){
String name = friendEntity.getName();
textViewData.setText(name);
}
}
});
}
}
所以此时您只需要了解如何为您的 viewModel 构建工厂,但我相信您会找到大量这样的示例。我想象您使用 Intent 将 id 传递给 activity,因此您从作为 onCreate
参数传递的包中解压该值
我目前正在开发一个应用程序,它需要观察数据库中一个条目的变化。该应用程序的结构如下: 我的 MainActivity 是一个 RecyclerView,它列出了 DB 中的所有好友。单击 item/friend 时,朋友的个人资料 (ProfileActivity) 将启动,包括一个按钮,用于打开编辑器以编辑姓名、生日、图像等。当用户完成编辑后,新数据将保存到DB 和 EditActivity 已关闭,应用 returns 到 ProfileActivity。在这里,我希望显示更改。
我尝试了 LiveData 和 MutableLiveData。在这两种情况下,我都使用了 Activity-ViewModel-Repository-Dao 的通用结构。使用 LiveData 我的 Dao.findBy(friendId) returns LiveData 和使用 MutableLiveData Dao.findBy(friendId) returns FriendEntity。 MutableLiveData 的问题在于无法直接观察到数据库中的更改。对于 LiveData,问题是,必须以某种方式初始化我的存储库中的 LiveData 字段,以便 Activity 观察它。但是 LiveData 不能手动初始化 - 所以我得到一个 NullPointerException。
我尝试在 onCreate 调用 Dao.findBy(-1) 来初始化我的 LiveData,当我知道实际的 friendId 时,我再次调用它,但不再触发 onChanged。
这个问题有好的解决办法吗? 还有其他方法可以初始化 LiveData 吗? 为什么我第二次调用 Dao.findBy(friendId) 时没有触发 onChanged(调用 Dao.findBy(-1))
Activity:
public class ProfileActivity extends AppCompatActivity {
MyViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mutable_live_data);
viewModel = new ViewModelProvider(this).get(MyViewModel.class);
viewModel.getFoundFriend().observe(this, new Observer<FriendEntity>() {
@Override
public void onChanged(FriendEntity friendEntity) {
if (friendEntity != null){
FriendEntity friend = friendEntity.getName();
textViewData.setText(name);
}
}
});
}
}
视图模型:
public class MyViewModel extends AndroidViewModel {
NormalRepository repository;
LiveData<FriendEntity> foundFriend;
public NormalViewModel(@NonNull Application application) {
super(application);
repository = new NormalRepository(application);
foundFriend = repository.getFoundFriend();
}
public LiveData<FriendEntity> getFoundFriend() {
return foundFriend;
}
public void find(long id) {
repository.find(id);
}
}
存储库:
public class NormalRepository {
MyDao dao;
LiveData<FriendEntity> foundFriend;
public NormalRepository(Application application) {
Database database = Database.getInstance(application);
this.dao = database.normalDao();
foundFriend = dao.findBy(-1L);
}
public void find(long id) {
new FindAsyncTask(dao).execute(id);
}
private class FindAsyncTask extends AsyncTask<Long, Void, LiveData<FriendEntity>>{
NormalDao dao;
public FindAsyncTask(NormalDao dao) {
this.dao = dao;
}
@Override
protected LiveData<FriendEntity> doInBackground(Long... longs) {
foundFriend = null;
return dao.findBy(longs[0]);
}
@Override
protected void onPostExecute(LiveData<FriendEntity> friendEntityLiveData) {
foundFriend = friendEntityLiveData;
}
}
public LiveData<FriendEntity> getFoundFriend() {
return foundFriend;
}
}
道:
@Dao
public interface MyDao {
@Query("SELECT * FROM friend_table WHERE id=:id")
LiveData<FriendEntity> findBy(long id);
}
为什么我第二次调用 Dao.findBy(friendId) 时没有触发 onChanged(在调用 Dao.findBy(-1))
在您的存储库中,您正在使用 id = -1L
初始化数据库挂钩。现在你只会收到 id=-1 的实体的更新。而ID字段几乎都是数据库的ROWID,从不为负。
解决方案: 现在,我从您的问题中了解到,您的 DBase table 中将只有一个条目。这意味着您期望的值不(因此 不能 )取决于任何参数。这很容易实现:
由于您只有一个不依赖于任何参数的条目,因此您的函数实际上不需要任何参数findBy(long id)
。只需从查询中删除 WHERE 子句:
@Query("SELECT * FROM friend_table LIMIT 1")
LiveData<FriendEntity> findBy();
MutableLiveData的问题是无法直接观察到DB中的变化 由于您没有提供您的实现,我无法猜测可能是什么问题。不过,正确实施应该不会造成任何问题。
编辑 收到需求反馈后,正确的实现方式:
- 道:
@Dao
public interface MyDao {
@Query("SELECT * FROM friend_table WHERE id=:id LIMIT 1")
LiveData<FriendEntity> findBy(long id);
}
- 存储库: 不需要 find(long id) 方法。改为更改提供 LiveData 的方式。正如我之前的回答,也不要用 id=-1L 初始化它。
public LiveData<FriendEntity> getFriendById(long id) {
if(id != foundFriend.getId()) {
//Background thread is not necessary for @Query annotated methods,
//as Room framework handles those by itself
foundFriend = dao.findBy(id);
}
return foundFriend;
}
- 视图模型:
这一步不是绝对必要的,但作为最佳实践,您不要将存储库中的数据直接暴露给视图。由于您的结果取决于 id 参数,因此请使用 Transformations API ,即:Transformations.switchMap():https://developer.android.com/reference/androidx/lifecycle/Transformations(我强烈建议您学习并使用此 API 以及适用的 LiveData)
public class MyViewModel extends AndroidViewModel {
//..................................
private MutableLiveData<Integer> friendId = new MutableLiveData<>();
private LiveData<FriendEntity> foundFriend =
Transformations.switchMap(friendId,
id -> repository.getFriendById(id));
public void updateFriend(int friendId) {
friendId.setValue(friendId);//this will trigger the lambda expression
//we passed to switchMap function, which
//in return will end up re-queryig of dbase
//by repository.
//...........................
所有这些准备就绪后,您只需在视图(Activity、片段等)中调用 getFoundFriend() 一次,并且可以调用视图模型的 updateFriend(long id) 当你需要新的 FriendEntity 时你的视图中的方法。
我对 Java 有点生疏,因为我使用的是 Kotlin,但在我看来:
FindAsyncTask 没用。如果您 return 来自查询的 LiveData,这可以是 运行 在主线程上。您不需要使用后台线程。事实上,函数 returns 立即获取 LiveData(内容为空值),然后 Room 使用后台线程执行查询并更新 LiveData。那时你得到了你的价值。 现在,忘记你的情况下的 MutableLiveData 吧。也就是说,如果您需要更新自己的值,但在这种情况下,Room 会负责。所以回购将是这样的:
public class NormalRepository {
MyDao dao;
// you probably can transform all in a one liner, i bet you need just a context there, not application
public NormalRepository(Application application) {
Database database = Database.getInstance(application);
this.dao = database.normalDao();
}
public LiveData<FriendEntity> find(long id) {
new dao.findBy(id);
}
}
ViewModel 只需要调用您的查询并存储 LiveData
public class MyViewModel extends AndroidViewModel {
NormalRepository repository;
LiveData<FriendEntity> foundFriend;
// When you create the view Model you should already know what is the id
public NormalViewModel(@NonNull Application application, long id) {
super(application);
repository = new NormalRepository(application);
foundFriend = repository.repository.find(id);
}
public LiveData<FriendEntity> getFoundFriend() {
return foundFriend;
}
}
在 activity 上:
public class ProfileActivity extends AppCompatActivity {
MyViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mutable_live_data);
if(savedInstanceState != null)
long id = savedInstanceState.getLong("MyLong")
viewModel = new ViewModelProvider(this,ViewModelFactory(application, id)).get(MyViewModel.class);
viewModel.getFoundFriend().observe(this, new Observer<FriendEntity>() {
@Override
public void onChanged(FriendEntity friendEntity) {
if (friendEntity != null){
String name = friendEntity.getName();
textViewData.setText(name);
}
}
});
}
}
所以此时您只需要了解如何为您的 viewModel 构建工厂,但我相信您会找到大量这样的示例。我想象您使用 Intent 将 id 传递给 activity,因此您从作为 onCreate
参数传递的包中解压该值