Комната с 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()
        }
    })

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


person Beuz    schedule 31.07.2018    source источник


Ответы (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 будет уведомлять об изменении базы данных.

person Demigod    schedule 01.08.2018
comment
Большое спасибо за ваш ответ. Я боялся, что это невозможно с одним запросом. Надеюсь, это будет добавлено в Room в будущем. Я выберу первый вариант, но я не использую Rx (пока). Итак, я создам этого провайдера, который будет возвращать LiveData ‹List ‹OrdersWithProducts››. Так что сначала мне нужно было получить LiveData ‹List ‹Orders›› и добавить LiveData ‹List ‹Product›› к каждому заказу. Как бы я это сделал? - person Beuz; 01.08.2018
comment
@JeroenBeuzenberg, добавил пример того, как этого добиться. Пример описывает идею ниже, так что это не чисто рабочий код. Используйте то, что вы используете в настоящее время / или использовали для фоновых потоков. - person Demigod; 01.08.2018
comment
Спасибо, что нашли время помочь мне. Будем использовать это для реализации! - person Beuz; 01.08.2018
comment
Я обновил свой ответ, включив в него код на основе вашего предложения. Теперь я могу заполнить свой адаптер объединенными объектами OrderWithProduct, но значения не обновляются, как с другими моими объектами LiveData. - person Beuz; 02.08.2018
comment
@Beuz, опубликуйте свой код. Вы используете трекер аннулирования для отслеживания изменений? - person Demigod; 02.08.2018
comment
В OP вы можете увидеть код, который я использую в своем фрагменте, чтобы наблюдать за изменениями LiveData в модели просмотра (OrderWithProducts LiveDat ‹List›. Какой код вас интересует? - person Beuz; 02.08.2018
comment
Это нормально, опубликуйте код модели представления и / или класса, который обрабатывает эти обновления данных. - person Demigod; 03.08.2018