Android Room + RxJava 查询记录不存在的处理方法

  Android 的 Room Persistence Library 提供了一层对 SQLite 的抽象,通过 Room RxJava,Room 可以直接返回 RxJava 的 PublisherFlowable 对象,极大地方便了对数据库的访问。

  使用 Room RxJava 时,常用的返回类型有 FlowableSingleMaybe 。Flowable 用于获取并持续监听数据变化,不会结束,当数据变化时,会再次通过 onNext() 传递变化后的新数据。Single 用于一次性的数据获取,获取到数据后会直接结束,不会监听数据变化。Maybe 用于一次性的数据获取,如果数据不存在,则直接结束。除了数据监听上的差异,当数据不存在时,这三者也具有不同的行为,下面通过一个例子进行详细说明。

0. 所使用的 Entity

  后续例子中使用的 Entity 如下所示:

@Entity(tableName = "movies")
public class MovieEntity {
    @PrimaryKey
    private long id;
    private String title;
    // getter and setter
}

它有两个字段:long 型的 id 为唯一 ID,作为主键;String 型的 title 为名称。上面的代码中省略了 getter 和 setter,实际代码中不要忘了,Room 要求 Entity 各字段必须具有 getter 和 setter。

1. Flowable

  如果在 DAO 中使用 Flowable,以 id 查询指定 MovieEntity,即:

@Query("SELECT * FROM movies WHERE id = :movieId")
Flowable<MovieEntity> getMovieById(long movieId);

此时调用 getMovieById(1234),如果 id 为 1234 的 MovieEntity 不存在,则该 Flowable 一直不会发送(Emit)任何数据,即 Observer 的 onNext() 一直不会被调用。实际上,此时 Room 正在监听数据库,当向数据库中插入了 id 为 1234 的 MovieEntity 后,该 Flowable 会 Emit 刚插入的 id 为 1234 的 MovieEntity,并在每次 id 为 1234 的 MovieEntity 发生改变时,再次 Emit 最新的记录。

  所查询记录不存在时,Flowable 不会 Emit 任何数据,由此带来一个问题:如果所查询的记录不存在,则下游永远也不会知道这一情况。

  如果程序是纯响应式的,这可能问题不大。但如果一定要使用 Flowable,且想要明确地知道数据不存在这一事实,则可以使用如下的方法:

@Query("SELECT * FROM movies WHERE id = :movieId")
Flowable<List<MovieEntity>> getMovieById(long movieId);

在使用 List<MovieEntity> 时,如果所查询 id 的 MovieEntity 不存在,则会 Emit 一个空列表(onNext()),下游由此可以得知记录不存在,并进行合适的处理(如 UI 显示数据不存在的通知等)。

2. Single

  对于 Single 的情况:

@Query("SELECT * FROM movies WHERE id = :movieId")
Single<MovieEntity> getMovieById(long movieId);

RxJava2 的流中不能传递 null,如果记录不存在,则该 Single 会抛出 EmptyResultSetException

  如果一定要使用 Single,且不想在记录不存在时抛出异常,也可以使用前面以列表形式查询的方法,即:

@Query("SELECT * FROM movies WHERE id = :movieId")
Single<List<MovieEntity>> getMovieById(long movieId);

此时如果记录不存在,则该 Single 会 Emit 一个空列表(onSuccess()),不会抛出 EmptyResultSetException,下游可以通过判断列表是否为空来判断所查询的记录是否存在。

3. Maybe

  对于数据可能不存在的场景,使用 Maybe 更符合语义:

@Query("SELECT * FROM movies WHERE id = :movieId")
Maybe<MovieEntity> getMovieById(long movieId);

如果记录不存在,则该 Maybe 会直接结束(onComplete())。

  如果需要显式地通知下游记录是否存在,可以进一步地使用:

getMovieById(movieId)
    .switchIfEmpty(Single.defer(() -> Single.just(MovieEntity.EMPTY)));

由此把 Maybe<MovieEntity> 转换为一个 Single<MovieEntity>,如果所查询的记录不存在,则 Emit 一个 MovieEntity.EMPTY,即一个预先定义好的特定数据,下游如果遇到 MovieEntity.EMPTY,即可得知所查记录不存在。

  此外,如果只关心记录是否存在,还可以这样:

getMovieById(movieId)
    .map(movie -> Boolean.TRUE)
    .switchIfEmpty(Single.defer(() -> Single.just(Boolean.FALSE)));

上面把 Maybe<MovieEntity> 转换为 Single<Boolean>Boolean 表示记录是否存在。如果无法直接修改 DAO 来提供查询方法,则可以这样来查询记录是否存在。