使用 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中的变化 由于您没有提供您的实现,我无法猜测可能是什么问题。不过,正确实施应该不会造成任何问题。


编辑 收到需求反馈后,正确的实现方式:

  1. 道:
    @Dao
    public interface MyDao {

        @Query("SELECT * FROM friend_table WHERE id=:id LIMIT 1")
        LiveData<FriendEntity> findBy(long id);
    }

  1. 存储库: 不需要 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;
}

  1. 视图模型:

这一步不是绝对必要的,但作为最佳实践,您不要将存储库中的数据直接暴露给视图。由于您的结果取决于 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

参数传递的包中解压该值