RxJava 调用每个列表项
RxJava make a call for every single list item
我对 RxJava 有疑问。
我有一个 getAll()
方法,它返回一个列表。它提供来自 Room 数据库的数据。
@Query("SELECT * from decks ORDER BY id ASC")
fun getAll(): Flowable<List<DeckEntity>>
DeckEntity
有一个 id
和一个 name
字段。
我创建了另一个 class 并将其命名为 PrepareItem
,因为我想用更多参数将其装箱。 (这将是一个 Adapter
模型)检查它:
data class PrepareItem (
var deckEntity: DeckEntity,
var countsOfCards: Int
)
所以,我想调用 getAll()
并将其映射到 PrepareItem。它还在工作。
deckRepository.getAll()
.map {
it.map {
PrepareItem(it,0)
}
}
但是,还有countsOfCards is equal 0
。我想进行另一个存储库调用,以获取值并进行设置。重要的!每个值都需要对存储库进行一次调用。因此,如果我有 5 个项目,那么我需要等到另一个调用完成 5 个。
我试过了,但我很困惑。
(代码已更新)
fun mapper(item: DeckEntity) : Single<PrepareItem> {
return cardRepository.getDueDatedCardsFromDeck(deckId = item.id!! /*TODO !!*/)
.map {
PrepareItem(item, it.size)
}
}
val call = deckRepository.getAll()
.flatMapIterable { item->item }
.flatMapSingle {
mapper(it)
}.toList()
.toObservable()
...
onError
或 onComplete
从未调用过。为什么?
任何人都知道怎么做?我希望它远离存储库。
谢谢!
更新:
解法:
新建一个class
class DeckWithCards {
@Embedded
lateinit var deckEntity: DeckEntity
@Relation(
entity = CardEntity::class,
entityColumn = "deckId",
parentColumn = "id")
lateinit var cards: List<CardEntity>
}
为DeckDao
增添新的乐趣
@Query("SELECT * from decks ORDER BY id ASC")
fun getAllWithCards(): Flowable<List<DeckWithCards>>
这就是它的全部作品!谢谢你的回答。这对我帮助很大!
你应该考虑把硬东西扔到数据库上。
考虑以下实体:
CardEntity
包含有关特定卡的信息。它还确保在单副牌中可以存在一张给定名称的牌。
@Entity(
tableName = "cards",
indices = {
@Index(value = { "name", "deck_id" }, unique = true)
}
)
public class CardEntity {
//region Column Definitions
@PrimaryKey
@ColumnInfo(name = "id")
private Long id;
@NonNull
@ColumnInfo(name = "name")
private String name = "";
@NonNull
@ColumnInfo(name = "deck_id")
private Long deckId = 0L;
//endregion
//region Getters and Setters
(...)
//endregion
}
DeckEntity
包含有关特定牌组的信息。
@Entity(tableName = "decks")
public class DeckEntity {
//region Column Definitions
@PrimaryKey
@ColumnInfo(name = "id")
private Long id;
@NonNull
@ColumnInfo(name = "name")
private String name = "";
//endregion
//region Getters and Setters
(...)
//endregion
}
DeckWithCardsView
是一个投影,它结合了 DeckEntity
、CardEntity
的集合和一些元数据(在本例中 - 这副牌中的许多牌)。
public class DeckWithCardsView {
//region Deck
@Embedded
private DeckEntity deck;
public DeckEntity getDeck() {
return deck;
}
public void setDeck(DeckEntity deck) {
this.deck = deck;
}
//endregion
//region Cards
@Relation(
entity = CardEntity.class,
entityColumn = "deck_id",
parentColumn = "id")
private List<CardEntity> cards = new ArrayList<>();
public List<CardEntity> getCards() {
return cards;
}
public void setCards(List<CardEntity> cards) {
this.cards = cards;
}
//endregion
//region Cards count
private Integer count = 0;
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
//endregion
}
完整的 Dao
代码如下所示:
@Dao
public abstract class DeckDao {
//region Deck
@Insert(onConflict = OnConflictStrategy.IGNORE)
public abstract void insertDeck(DeckEntity entity);
@Insert(onConflict = OnConflictStrategy.IGNORE)
public abstract void insertDecks(List<DeckEntity> entities);
//endregion
//region Card
@Insert(onConflict = OnConflictStrategy.IGNORE)
public abstract void insertCard(CardEntity entity);
@Insert(onConflict = OnConflictStrategy.IGNORE)
public abstract void insertCards(List<CardEntity> cards);
//endregion
//region Deck with Cards
@Transaction
@Query(
"SELECT D.id as id, D.name as name, count(C.id) as count "
+ "FROM decks D "
+ "INNER JOIN cards C "
+ "WHERE C.deck_id = D.id "
+ "GROUP BY deck_id")
public abstract Flowable<List<DeckWithCardsView>> getAll();
//endregion
}
它实现了以下功能:
- 正在将单副牌添加到数据库,
- 正在将一组牌组添加到数据库,
- 正在将单张卡添加到数据库中,
- 正在将卡片集合添加到数据库中,
- 正在从数据库中检索上述投影列表。
最重要的部分在Sqlite查询代码中。逐行:
SELECT D.id as id, D.name as name, count(C.id) as count
它定义了我们期望查询将从数据库中 return 值中提取的确切列。特别是:
D.id as id
- 它将 return id
来自别名为 D
(甲板), 的列
D.name as name
- 它将 return name
来自别名为 D
(甲板), 的列
count(C.id) as count
- 它将 return 从别名为 C
(卡片)的列中检索到的一些 ID。
FROM decks D INNER JOIN cards C WHERE C.deck_id = D.id
它定义了我们希望从别名为 D
的表 decks
和别名为 C
的 cards
中检索值,并定义了 CardEntity
上的关系deck_id
到 DeckEntity
的 id
列。
GROUP BY deck_id
多亏了分组,我们可以轻松地 count(C.id)
检索为每一副牌 return 编辑的许多卡片。
因为我们还在 DeckWithCardsView
中使用了 @Embedded
注释 - 相应的(别名)列将与嵌入的 DeckEntity
甲板字段中的正确字段相匹配。
数据库管理器
准备好这样的实体、投影和 dao - 数据库管理器可以简单地实现为:
public Completable saveAllDecks(@Nullable List<DeckEntity> decks) {
return Completable.fromAction(
() -> database
.deckDao()
.insertDecks(decks)
).subscribeOn(Schedulers.io());
}
public Completable saveAllCards(@Nullable List<CardEntity> cards) {
return Completable.fromAction(
() -> database
.deckDao()
.insertCards(cards)
).subscribeOn(Schedulers.io());
}
public Flowable<List<DeckWithCardsView>> getDecksWithCards() {
return database
.deckDao()
.getAll()
.subscribeOn(Schedulers.io());
}
示例数据:
我还准备了一个示例代码,它创建了五个完整的套牌(每个套牌中的所有花色和等级)。
private static final Long[] DECK_IDS = {
1L, 2L, 3L, 4L, 5L
};
private static final String[] DECK_NAMES = {
"Deck 1", "Deck 2", "Deck 3", "Deck 4", "Deck 5"
};
Completable prepareDecks() {
final Observable<Long> ids = Observable.fromArray(DECK_IDS);
final Observable<String> names = Observable.fromArray(DECK_NAMES);
final List<DeckEntity> decks = ids.flatMap(id -> names.map(name -> {
final DeckEntity entity = new DeckEntity();
entity.setId(id);
entity.setName(name);
return entity;
})).toList().blockingGet();
return cinemaManager.saveDecks(decks);
}
它创建一个 id 和 deck 名称的列表,并创建这些表的产品。平面映射此类产品会产生包含五个甲板实体的完整列表。
private static final String[] CARD_SUITS = {
"diamonds", "hearts", "spades", "clubs"
};
private static final String[] CARD_RANKS = {
"Two of ",
"Three of ",
"Four of ",
"Five of ",
"Six of ",
"Seven of",
"Eight of ",
"Nine of ",
"Ten of ",
"Jack of ",
"Queen of ",
"King of ",
"Ace of "
};
Completable prepareCards() {
final Observable<Long> decks = Observable.fromArray(DECK_IDS);
final Observable<String> suits = Observable.fromArray(CARD_SUITS);
final Observable<String> ranks = Observable.fromArray(CARD_RANKS);
final List<CardEntity> cards = decks.flatMap(deck -> suits.flatMap(suit -> ranks.map(rank -> {
final CardEntity entity = new CardEntity();
entity.setName(String.format("%s %s", rank, suit));
entity.setDeckId(deck);
return entity;
}))).toList().blockingGet();
return cinemaManager.saveCards(cards);
}
使用类似的方法,我已经为之前准备好的牌组创建了一个完整的卡片集合。
最后润色:
辛苦了。剩下容易的部分:
private Completable prepareData() {
return prepareDecks().andThen(prepareCards());
}
void observeDecksWithCards() {
disposables.add(
prepareData()
.andThen(deckManager.getDecksWithCards())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
this::handleObserveDecksWithCardsSuccess,
this::handleObserveDecksWithCardsError));
}
private void handleObserveDecksWithCardsSuccess(@NonNull List<DeckWithCardsView> decks) {
Timber.v("Received number of decks: %d", decks.size());
}
private void handleObserveDecksWithCardsError(@NonNull Throwable throwable) {
Timber.e(throwable.getLocalizedMessage());
}
结果:
因此我们有两个表。对于甲板和卡片。订阅 getDecksWithCards
可以访问一组牌组。有完整的卡片收集和卡片数量(由数据库计算)。
Remember - since the relation to table with cards has been made, you do not need to use the count
sqlite method. You can just use getCards().getSize()
in DeckWithCardsView
projection. This will simplify the code significantly.
我对 RxJava 有疑问。
我有一个 getAll()
方法,它返回一个列表。它提供来自 Room 数据库的数据。
@Query("SELECT * from decks ORDER BY id ASC")
fun getAll(): Flowable<List<DeckEntity>>
DeckEntity
有一个 id
和一个 name
字段。
我创建了另一个 class 并将其命名为 PrepareItem
,因为我想用更多参数将其装箱。 (这将是一个 Adapter
模型)检查它:
data class PrepareItem (
var deckEntity: DeckEntity,
var countsOfCards: Int
)
所以,我想调用 getAll()
并将其映射到 PrepareItem。它还在工作。
deckRepository.getAll()
.map {
it.map {
PrepareItem(it,0)
}
}
但是,还有countsOfCards is equal 0
。我想进行另一个存储库调用,以获取值并进行设置。重要的!每个值都需要对存储库进行一次调用。因此,如果我有 5 个项目,那么我需要等到另一个调用完成 5 个。
我试过了,但我很困惑。 (代码已更新)
fun mapper(item: DeckEntity) : Single<PrepareItem> {
return cardRepository.getDueDatedCardsFromDeck(deckId = item.id!! /*TODO !!*/)
.map {
PrepareItem(item, it.size)
}
}
val call = deckRepository.getAll()
.flatMapIterable { item->item }
.flatMapSingle {
mapper(it)
}.toList()
.toObservable()
...
onError
或 onComplete
从未调用过。为什么?
任何人都知道怎么做?我希望它远离存储库。 谢谢!
更新:
解法:
新建一个class
class DeckWithCards {
@Embedded
lateinit var deckEntity: DeckEntity
@Relation(
entity = CardEntity::class,
entityColumn = "deckId",
parentColumn = "id")
lateinit var cards: List<CardEntity>
}
为DeckDao
增添新的乐趣
@Query("SELECT * from decks ORDER BY id ASC")
fun getAllWithCards(): Flowable<List<DeckWithCards>>
这就是它的全部作品!谢谢你的回答。这对我帮助很大!
你应该考虑把硬东西扔到数据库上。
考虑以下实体:
CardEntity
包含有关特定卡的信息。它还确保在单副牌中可以存在一张给定名称的牌。
@Entity(
tableName = "cards",
indices = {
@Index(value = { "name", "deck_id" }, unique = true)
}
)
public class CardEntity {
//region Column Definitions
@PrimaryKey
@ColumnInfo(name = "id")
private Long id;
@NonNull
@ColumnInfo(name = "name")
private String name = "";
@NonNull
@ColumnInfo(name = "deck_id")
private Long deckId = 0L;
//endregion
//region Getters and Setters
(...)
//endregion
}
DeckEntity
包含有关特定牌组的信息。
@Entity(tableName = "decks")
public class DeckEntity {
//region Column Definitions
@PrimaryKey
@ColumnInfo(name = "id")
private Long id;
@NonNull
@ColumnInfo(name = "name")
private String name = "";
//endregion
//region Getters and Setters
(...)
//endregion
}
DeckWithCardsView
是一个投影,它结合了 DeckEntity
、CardEntity
的集合和一些元数据(在本例中 - 这副牌中的许多牌)。
public class DeckWithCardsView {
//region Deck
@Embedded
private DeckEntity deck;
public DeckEntity getDeck() {
return deck;
}
public void setDeck(DeckEntity deck) {
this.deck = deck;
}
//endregion
//region Cards
@Relation(
entity = CardEntity.class,
entityColumn = "deck_id",
parentColumn = "id")
private List<CardEntity> cards = new ArrayList<>();
public List<CardEntity> getCards() {
return cards;
}
public void setCards(List<CardEntity> cards) {
this.cards = cards;
}
//endregion
//region Cards count
private Integer count = 0;
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
//endregion
}
完整的 Dao
代码如下所示:
@Dao
public abstract class DeckDao {
//region Deck
@Insert(onConflict = OnConflictStrategy.IGNORE)
public abstract void insertDeck(DeckEntity entity);
@Insert(onConflict = OnConflictStrategy.IGNORE)
public abstract void insertDecks(List<DeckEntity> entities);
//endregion
//region Card
@Insert(onConflict = OnConflictStrategy.IGNORE)
public abstract void insertCard(CardEntity entity);
@Insert(onConflict = OnConflictStrategy.IGNORE)
public abstract void insertCards(List<CardEntity> cards);
//endregion
//region Deck with Cards
@Transaction
@Query(
"SELECT D.id as id, D.name as name, count(C.id) as count "
+ "FROM decks D "
+ "INNER JOIN cards C "
+ "WHERE C.deck_id = D.id "
+ "GROUP BY deck_id")
public abstract Flowable<List<DeckWithCardsView>> getAll();
//endregion
}
它实现了以下功能:
- 正在将单副牌添加到数据库,
- 正在将一组牌组添加到数据库,
- 正在将单张卡添加到数据库中,
- 正在将卡片集合添加到数据库中,
- 正在从数据库中检索上述投影列表。
最重要的部分在Sqlite查询代码中。逐行:
SELECT D.id as id, D.name as name, count(C.id) as count
它定义了我们期望查询将从数据库中 return 值中提取的确切列。特别是:
D.id as id
- 它将 returnid
来自别名为D
(甲板), 的列
D.name as name
- 它将 returnname
来自别名为D
(甲板), 的列
count(C.id) as count
- 它将 return 从别名为C
(卡片)的列中检索到的一些 ID。
FROM decks D INNER JOIN cards C WHERE C.deck_id = D.id
它定义了我们希望从别名为 D
的表 decks
和别名为 C
的 cards
中检索值,并定义了 CardEntity
上的关系deck_id
到 DeckEntity
的 id
列。
GROUP BY deck_id
多亏了分组,我们可以轻松地 count(C.id)
检索为每一副牌 return 编辑的许多卡片。
因为我们还在 DeckWithCardsView
中使用了 @Embedded
注释 - 相应的(别名)列将与嵌入的 DeckEntity
甲板字段中的正确字段相匹配。
数据库管理器
准备好这样的实体、投影和 dao - 数据库管理器可以简单地实现为:
public Completable saveAllDecks(@Nullable List<DeckEntity> decks) {
return Completable.fromAction(
() -> database
.deckDao()
.insertDecks(decks)
).subscribeOn(Schedulers.io());
}
public Completable saveAllCards(@Nullable List<CardEntity> cards) {
return Completable.fromAction(
() -> database
.deckDao()
.insertCards(cards)
).subscribeOn(Schedulers.io());
}
public Flowable<List<DeckWithCardsView>> getDecksWithCards() {
return database
.deckDao()
.getAll()
.subscribeOn(Schedulers.io());
}
示例数据:
我还准备了一个示例代码,它创建了五个完整的套牌(每个套牌中的所有花色和等级)。
private static final Long[] DECK_IDS = {
1L, 2L, 3L, 4L, 5L
};
private static final String[] DECK_NAMES = {
"Deck 1", "Deck 2", "Deck 3", "Deck 4", "Deck 5"
};
Completable prepareDecks() {
final Observable<Long> ids = Observable.fromArray(DECK_IDS);
final Observable<String> names = Observable.fromArray(DECK_NAMES);
final List<DeckEntity> decks = ids.flatMap(id -> names.map(name -> {
final DeckEntity entity = new DeckEntity();
entity.setId(id);
entity.setName(name);
return entity;
})).toList().blockingGet();
return cinemaManager.saveDecks(decks);
}
它创建一个 id 和 deck 名称的列表,并创建这些表的产品。平面映射此类产品会产生包含五个甲板实体的完整列表。
private static final String[] CARD_SUITS = {
"diamonds", "hearts", "spades", "clubs"
};
private static final String[] CARD_RANKS = {
"Two of ",
"Three of ",
"Four of ",
"Five of ",
"Six of ",
"Seven of",
"Eight of ",
"Nine of ",
"Ten of ",
"Jack of ",
"Queen of ",
"King of ",
"Ace of "
};
Completable prepareCards() {
final Observable<Long> decks = Observable.fromArray(DECK_IDS);
final Observable<String> suits = Observable.fromArray(CARD_SUITS);
final Observable<String> ranks = Observable.fromArray(CARD_RANKS);
final List<CardEntity> cards = decks.flatMap(deck -> suits.flatMap(suit -> ranks.map(rank -> {
final CardEntity entity = new CardEntity();
entity.setName(String.format("%s %s", rank, suit));
entity.setDeckId(deck);
return entity;
}))).toList().blockingGet();
return cinemaManager.saveCards(cards);
}
使用类似的方法,我已经为之前准备好的牌组创建了一个完整的卡片集合。
最后润色:
辛苦了。剩下容易的部分:
private Completable prepareData() {
return prepareDecks().andThen(prepareCards());
}
void observeDecksWithCards() {
disposables.add(
prepareData()
.andThen(deckManager.getDecksWithCards())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
this::handleObserveDecksWithCardsSuccess,
this::handleObserveDecksWithCardsError));
}
private void handleObserveDecksWithCardsSuccess(@NonNull List<DeckWithCardsView> decks) {
Timber.v("Received number of decks: %d", decks.size());
}
private void handleObserveDecksWithCardsError(@NonNull Throwable throwable) {
Timber.e(throwable.getLocalizedMessage());
}
结果:
因此我们有两个表。对于甲板和卡片。订阅 getDecksWithCards
可以访问一组牌组。有完整的卡片收集和卡片数量(由数据库计算)。
Remember - since the relation to table with cards has been made, you do not need to use the
count
sqlite method. You can just usegetCards().getSize()
inDeckWithCardsView
projection. This will simplify the code significantly.