Android Room + RxJava 查询记录不存在的处理方法
Android 的 Room Persistence Library 提供了一层对 SQLite 的抽象,通过 Room RxJava,Room 可以直接返回 RxJava 的 Publisher 和 Flowable 对象,极大地方便了对数据库的访问。
使用 Room RxJava 时,常用的返回类型有 Flowable、Single 和 Maybe 。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 来提供查询方法,则可以这样来查询记录是否存在。