Вам следует подумать о том, чтобы бросить тяжелые вещи в базу данных.
Рассмотрим следующие объекты:
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