Комната с LiveData: сопоставление "многие ко многим" в адаптере

Давайте возьмем следующий пример:

Существует множество сопоставлений для ПРОДУКТОВ и ЗАКАЗОВ. Таким образом, продукт может быть на несколько заказов, а заказ может иметь несколько продуктов. В комнате у меня есть сущность, у которой в качестве внешних ключей есть и идентификатор продукта, и идентификатор заказа, поэтому я могу сохранить отношения. Теперь очень легко получить все заказы на конкретный продукт, а также все продукты для конкретного заказа.

Теперь вот беда. Насколько я знаю, нет способа получить объект заказа со всеми его продуктами в 1 запрос / сущность. Подробнее об этом можно прочитать в этом посте. В большинстве мест я могу обойти это, просто выполнив два запроса. Первый, чтобы получить интересующий меня заказ, и второй, чтобы получить продукты на основе идентификатора заказа.

Теперь я хочу отобразить комбинацию заказа с его продуктами в адаптере. Для этого мне нужно объединить все мои заказы с их продукцией. Я не знаю, как решить эту проблему с LiveData.

На мой взгляд, лучшим решением было бы создать один запрос, который выбирает OrderWithProducts непосредственно из базы данных. Этот пост предполагает, что это должно быть возможно, но мне не удалось заставить это работать. Также отсутствует самая важная часть в этом примере: класс OrderItem.

Если это решение невозможно, должен быть какой-то способ получить список LiveData OrderWithProducts с двумя запросами и каким-то образом объединить их.

РЕДАКТИРОВАТЬ После предложений @Demigod теперь у меня есть следующее в моей ViewModel:

// MediatorLiveData can observe other LiveData objects and react on their emissions.
var liveGroupWithLights = MutableLiveData<List<GroupWithLights>>()

fun createOrdersWithProducts() {
        appExecutors.diskIO().execute {
            val ordersWithProducts = mutableListOf<OrderWithProducts>()
            val orders = orderRepository.getGroupsSync()
            for (order in orders) {
                val products = productRepository.getProductsSync(order.id)
                val orderWithProducts = OrderWithProducts(order, products)
                ordersWithProducts.add(orderWithProducts)
            }

            liveGroupWithLights.postValue(ordersWithProducts)
        }
    }

Функция внутри моего фрагмента для отправки данных адаптеру:

private fun initRecyclerView() {
    orderListViewModel.getOrdersWithProducts().observe(this, Observer { result ->
        adapter.submitList(result)
    })
}

Так что теперь я могу иметь объект OrderWithProduct в качестве элемента для моего адаптера. Это здорово, я могу использовать продукты для каждого заказа в моем адаптере. Теперь у меня возникают проблемы с обновлением этих элементов при изменении значений в базе данных. Есть идеи для этой части?

Edit2: недействительный трекер

db.invalidationTracker.addObserver(object : InvalidationTracker.Observer("orders", "products", "order_product_join") {
        override fun onInvalidated(tables: MutableSet<String>) {
            createOrdersWithProducts()
        }
    })

Проблема, с которой я столкнулся, состоит в том, что трекер валидации получает много уведомлений об одном изменении.

1 ответ

Решение

Насколько я знаю, это невозможно в настоящее время с одним запросом. Чтобы решить эту проблему, вам нужно выполнить несколько запросов здесь. Сначала - получить список заказов с помощью одного запроса, а после этого получить список товаров для каждого заказа. Чтобы добиться этого, я могу придумать несколько вариантов:

  1. Сделать свой собственный OrdersWithProductsProvider, который вернет этот объединенный объект (Order с List<Porduct>), и он подпишется на изменения database испускать новые объекты, используя LiveData на каждом orders или же products смена стола.
  2. Вы можете использовать MediatorLiveData заполнить список Orderс их Productс, но я не думаю, что это лучший подход, так как вам нужно будет выполнить запрос в фоновом потоке, возможно, использование Rx здесь удобнее.

Лично я бы выбрал первый вариант, так как, вероятно, я хочу получить актуальный список заказов с их продуктами, что означает, что обновление должно сработать при изменении трех таблиц (products, orders, products_to_orders), что можно сделать через Room.InvalidationTracker, Внутри этого провайдера я бы использовал Rx (который может работать с LiveData с помощью LiveDataReactiveStreams).

Дополнение о том, как этого добиться:

Как добиться этого, на самом деле не имеет значения, единственное - запустите весь этот запрос в фоновом потоке и отправьте его на LiveData, Ты можешь использовать Executor, Rxили простой Thread, Так это будет выглядеть примерно так:

private val database : Database // Get the DB
private val executor = Executors.newSingleThreadExecutor()
private val liveData = MutableLiveData<List<OrderWithProducts>>()

fun observeOrdersWithProducts():LiveData<List<OrderWithProducts>> {
    return liveData
}

private fun updateOrdersWithProducts() {
    executor.post {
        val ordersWithProducts = mutableListOf<OrderWithProducts>()
        val orders = db.orders()
        for (order : orders) {
            val products = database.productsForOrder(order)
            val orderWithProducts = OrderWithProducts(order, products)
            ordersWithProducts.add(orderWithProducts)
        }
        liveData.post(ordersWithProducts)
    }
}

Воспринимайте это как не полный рабочий код, а скорее как пример реализации. Вызов updateOrdersWithProducts при инициализации / первом вызове и каждый раз InvalidationTracker сообщит об изменении дБ.

Другие вопросы по тегам