Android Room:更新插入的 LiveData 回调?

Android Room : LiveData callback of update insert?

我有一个 Simple DAO 包括 CRUD 功能

FeedEntryDAO.java

@Dao
public interface FeedEntryDAO {

  @Query("SELECT * FROM feedEntrys")
  LiveData<List<FeedEntry>> getAll();

  @Query("SELECT * FROM feedEntrys WHERE uid = :uid LIMIT 1")
  LiveData<FeedEntry> findByUid(int uid);

  @Insert
  void insertAll(FeedEntry... feedEntries);

  @Delete
  void delete(FeedEntry feedEntry);

  @Update
  int update(FeedEntry feedEntry);

}

对于select,可以return LiveData类型。

在 Activity 中,代码非常适合选择

viewModel.getFeedEntrys().observe(this,entries -> {...});

但是,当我尝试插入、更新、删除数据时。代码看起来有点难看,而且每次都创建一个异步任务。

new AsyncTask<FeedEntry, Void, Void>() {
                @Override
                protected Void doInBackground(FeedEntry... feedEntries) {
                  viewModel.update(feedEntries[0]);
                  return null;
                }
}.execute(feedEntry);

我有 2 个问题:

  1. 我可以使用 LiveData 来包装删除、插入、更新功能吗?
  2. 为删除、插入、更新维护此类异步任务 class 的更好方法?

感谢任何建议和意见。谢谢。

  1. Can i use LiveData to wrap Delete, Insert, Update calls?

不,你不能。我写了一个答案给issue。原因是,LiveData 用于通知更改。插入、更新、删除不会触发更改。它将 return 删除的行、插入的 ID 或受影响的行。即使它看起来很糟糕,不让 LiveData 包裹在你的东西上也是有意义的。无论如何,在调用周围设置类似 Single 的东西以让操作在 RX-Java 操作上触发和操作是有意义的。

如果你想触发这些调用,你会观察到一个选择查询,它通知你的 LiveData onec 你已经更新、插入或删除了 some/any 数据。

  1. Better way to maintain such asynctask class for delete, insert , update?

查看您的示例后,您似乎误用了 (Model/View/)ViewModel-Pattern。您永远不应该在您的视图中访问您的存储库。我不确定您是否正在这样做,因为它在您的示例中不可见。无论如何,在观察您的 LiveData 并获得结果后,无需将数据更新包装在 AsyncTask 中的 viewModel 中。这意味着,您应该始终照顾

a) view <-> viewmodel <-> repository 而不是 view <-> repository and view <-> viewmodel

b) 不要尝试使用不需要的线程。默认情况下(如果未使用@MainThread 注释),您在后台线程 (@WorkerThread) 上观察 LiveData,并在 ui-thread (@MainThread).

中获取值

你也可以在摘要中使用@Dao注解classes,所以:

  1. 使用抽象方法 @Insert insert(entities) 和具体方法 insert(entities, callback) 创建一个抽象 @Dao BaseDao class 来完成丑陋的 AsyncTask 工作,调用onBackground 上的摘要 @Insert insert(entities)onPostExecute 上的回调。
  2. 让你的 FeedEntryDAO 也抽象扩展 BaseDao@Query 方法抽象。

Kotlin 中的结果用法非常漂亮:

database.entityDao().insert(entities) { ids ->
    // success
}

要让应用程序 UI 在数据更改时自动更新,请在查询方法描述中使用 LiveData 类型的 return 值。当数据库更新时,Room 会生成所有必要的代码来更新 LiveData。

@Dao
interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    fun loadUsersFromRegionsSync(regions: List<String>): LiveData<List<User>>
}

注意:从 1.0 版开始,Room 使用在 决定是否更新 LiveData 实例的查询。

对于第二个问题,还有一个比 AsyncTask; which is using java Executor 更简洁的替代方法,好消息是您可以使用 Executor 的单个实例而不是 AsyncTask 的多个实例对于所有 CRUD 操作。

演示示例

public class Repository {

    private static Repository instance;
    private MyDatabase mDatabase;
    private Executor mExecutor = Executors.newSingleThreadExecutor();

    private Repository(Application application) {
        mDatabase = MyDatabase.getInstance(application.getApplicationContext());
    }

    public static Repository getInstance(Application application) {
        if (instance == null) {
            instance = new Repository(application);
        }
        return instance;
    }


    public void insert(final MyModel model) {

        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                mDatabase.getMyModelDAO().insert(model);
            }
        });
    }

    public void update(final MyModel model) {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                mDatabase.getMyModelDAO().update(model);
            }
        });
    }

    public void delete(final MyModel model) {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                mDatabase.getMyModelDAO().delete(model);
            }
        });
    }

}

关于问题2:

对于 Kotlin 用户来说,现在有一个非常好的方法来实现这一点, 因为从 Room 2.1 开始就直接支持协程。给出了一个简洁的例子 here.

您可以直接在 DAO 中使用 "suspend function",这样可以确保在主线程上不执行任何操作:

@Dao
 interface BarDao {

   @Query("SELECT * FROM bar WHERE groupId = 2")
   fun getAllBars(): LiveData<MutableList<Bar>>

   @Query( "SELECT * FROM bar WHERE groupId = 0 LIMIT 1")
   fun getRecentBar(): LiveData<Bar>

   @Insert
   suspend fun insert(bar: Bar)

   @Update
   suspend fun update(bar: Bar)

   @Delete
   suspend fun delete(bar: Bar)

}

然后在您的 viewModel 中,您只需:

    fun insert(bar: Bar) = viewModelScope.launch {
        barDao.insert(bar)
    }

    fun update(bar: Bar) = viewModelScope.launch {
        barDao.update(bar)
    }

    fun delete(bar: Bar)= viewModelScope.launch {
        barDao.delete(bar)
    }

解决您的第二个问题:

Google 发布了一个 Android Room Codelab here,它为在 Android:

中实现 Room 制定了简洁的 MVVM 架构

这里的建议是让数据库操作由数据库 class 中的 public 静态 ExecutorService 处理。 ExecutorService class 的位置可能会有所不同,请记住这个想法是 在 MVVM 中,您的视图并不关心数据实际上是如何被 CURD 处理的,这是 ViewModel 的责任,而不是 View

github repository for this code lab

简而言之,要将类似的想法应用到您的代码中,应该是这样的:

class YourRepository {
    private FeedEntryDAO mFeedEntryDAO;

    YourRepository(Application application) {
        YourDatabase db = YourDatabase.getDatabase(application);
        mFeedEntryDAO = db.feedEntryDAO();
        mAllWords = mWordDao.getAlphabetizedWords();
    }


    void update(FeedEntry feedEntry) {
        Database.databaseExecutor.execute(() - > {
            mFeedEntryDAO.update(feedEntry);
        });
    }
}

class YourViewModel extends ViewModel {
    private YourRepository mRepository;
    void update(FeedEntry feedEntry) {
        mRepository.update(feedEntry)
    }
}

通过这样做,您的视图可以直接调用 viewModel.update(feedEntries[0]).

需要提及的一件重要事情是mFeedEntryDAO.update(feedEntry) 将自动触发您的观察者在 getFeedEntrys LiveData 上的 onChanged 回调。

这对您来说非常方便。您可以阅读有关触发器如何发生的更多信息 here.

列出了每个受支持库的受支持 return 类型 here。为了方便起见,我在此处包含了 table。

Query type Kotlin language features RxJava Guava Jetpack Lifecycle
One-shot write Coroutines (suspend) Single<T>, Maybe<T>, Completable ListenableFuture<T> N/A
One-shot read Coroutines (suspend) Single<T>, Maybe<T> ListenableFuture<T> N/A
Observable read Flow<T> Flowable<T>, Publisher<T>, Observable<T> N/A LiveData<T>

(Web archive link)