RxJava выполняет вызов для каждого элемента списка

У меня проблема с RxJava. У меня есть метод getAll(), он возвращается со списком. Он обслуживает данные из базы данных Room.

@Query("SELECT * from decks ORDER BY id ASC")
fun getAll(): Flowable<List<DeckEntity>>

DeckEntity имеют поля id и name.

Я создал другой класс, назвав его 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 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>>

Вот и все его работы! Спасибо за ответ. Это мне очень помогло !


person vihkat    schedule 24.07.2018    source источник


Ответы (1)


Вам следует подумать о том, чтобы бросить тяжелые вещи в базу данных.

Рассмотрим следующие объекты:

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. По строкам:

ВЫБЕРИТЕ D.id как id, D.name как имя, count (C.id) как count

Он определяет, из каких именно столбцов мы ожидаем, что запрос вернет значения из базы данных. Особенно:

  • D.id as id - из столбца будет возвращено id с псевдонимом D (колоды),
  • D.name as name - он вернет name из столбца с псевдонимом D (колоды),
  • count(C.id) as count - он вернет количество идентификаторов, полученных из столбца с псевдонимом C (карты).

ИЗ колод D ВНУТРЕННИЕ СОЕДИНЯЙТЕ карты C ГДЕ C.deck_id = D.id

Он определяет, что мы хотели бы получать значения из таблиц decks с псевдонимом D и cards с псевдонимом C, и определяет отношение deck_id deck_id CardEntity к id столбцам DeckEntity.

ГРУППА ПО deck_id

Благодаря группировке мы можем легко сделать count(C.id), чтобы получить количество карт, возвращаемых для каждой отдельной колоды.

Поскольку мы также использовали аннотацию @Embedded в DeckWithCardsView - соответствующие столбцы (с псевдонимами) будут сопоставлены с правильными полями во встроенном поле DeckEntity deck.

Менеджер базы данных

Подготовив такие сущности, проекции и дао - менеджер баз данных можно реализовать очень просто:

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);
}

Он создает список идентификаторов и названий колод и создает продукт из таких таблиц. Плоское отображение такого продукта приводит к полному списку из пяти сущностей колоды.

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 дает доступ к коллекции колод. С полным набором карточек и количеством карточек (рассчитывается по базе).

введите описание изображения здесь

Помните - так как связь с таблицей с карточками была сделана, вам не нужно использовать метод count sqlite. Вы можете просто использовать getCards().getSize() в DeckWithCardsView проекции. Это значительно упростит код.

person Tomasz Dzieniak    schedule 24.07.2018
comment
Спасибо за такой длинный ответ. Это полностью помогло мне - person vihkat; 24.07.2018
comment
Но меня очень интересует RX, как с ним делать - person vihkat; 25.07.2018