Когда я начал использовать Room для работы с БД, я задавался вопросом, как же более правильно сформировать подписку на изменения данных в таблице БД, чтобы если при подписке не было данных, то наш репозиторий сразу возвращал пустой список, а не ждал появления данных.

Под подпиской я имею в виду получение текущих данных, а так-же отслеживание изменений в таблице БД и при изменении данных получение их в нашей подписке.

Как вы знаете, для создания подписки возвращающей новые данные при изменении данных в таблице, нужно использовать Flowable из RxJava2.

Примерно так будет выглядеть Dao интерфейс в коде:

@Dao
interface DataDao {
    @Query("SELECT * FROM DataTable")
    fun getData(): Flowable<List<Data>>
}

Но, теперь если в нашем приложении мы попытаемся получить список Data из таблицы DataTable, а в таблице нет записей, то наша Flowable подписка будет ждать появления данных в этой таблице, а нам не нужно ждать этого, нам нужно показать пользователю экран с текстом "Нет данных".

dataDao.getData()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { data ->
        //если в таблице нет записей, то наша Flowable подписка будет ждать появления данных в этой таблице
    }

Для получения требуемого поведения можно сделать следующе.

Добавляем в Dao функцию получения кол-ва записей в таблице:

@Query("SELECT COUNT(*) FROM DataTable")
fun getDataCount(): Flowable<Int>

Данная функция сразу при вызове вернет значение 0, если в таблице нет данных, а так-же каждый раз при изменении данных в таблице будет иметь новое значение кол-ва записей.

Напишем вот такой код в нашем репозитории для получения необходимого поведения:

class DataRepository(private val dataDao: DataDao) {
		fun getData(): Flowable<List<Data>> {
    		return dataDao.getDataCount().switchMap {
                  if (it > 0)
                      dataDao.getData()
                  else
                      Flowable.just(emptyList())
              }
              .subscribeOn(Schedulers.io())
              .observeOn(AndroidSchedulers.mainThread())
    }
}

В итоге мы получаем репозиторий с функцией getData() который при отсутствии данных в таблице вернет пустой список, а при появлении данных вернет новый список с данными.

Пример использования:

dataRepository.getData()
    .subscribe { data ->
        //репозиторий вернет либо пустой список, либо список с данными        	
    }

p.s. В данном случае можно еще подумать над тем, что можно у функции getData в Dao поменять тип Flowable на Maybe (чтобы внутри нашей подписки не было двух Flowable ожидающих изменения данных в таблице) либо сделать dataDao.getData().take(1), но и в приведенном коде репозитория нет ничего криминального:) ведь используется switchMap.

Комментарии (4)


  1. Vest
    26.11.2021 16:30

    Мне кажется, что ваш метод немного дороговат для решаемой задачи. Во-первых, мне не понятно как часто каждый клиент будет опрашивать количество строк (каждую секунду, две и т.д.). Понятно, что посчитать записи тоже надо уметь. Давно была статья на Хабре, где читались системные вьюхи для этого. Возможно, вашу задачу можно было бы решить на триггерах (UPDATE/INSERT/DELETE), но лучше бы уведомить сервер приложений, а не каждого клиента в отдельности. А если есть сервер приложений, то проще сделать как слветуют на SO — некоторая шина данных, сообщения, подписка на изменение и т.д.

    Но это моё мнение…


    1. ilnar_93 Автор
      26.11.2021 18:55

      Каждый клиент сам не будет каждые N времени запрашивать кол-во строк, это будет происходит по триггеру при обновлении данных в таблице. Клиент лишь подписывается один раз, а далее уже работает триггер(на UPDATE/INSERT/DELETE), который уведомляет об изменениях в таблице. При срабатывании триггера происходит повторный запрос кол-ва данных в таблице и если кол-во больше 0, то и самих данных, далее клиенту(подписчику) возвращаются актуальные данные.




  1. GMaksym
    03.12.2021 21:19

    Я бы Publisher использовал и уже на него бы подписался, чтобы покрыть ваш кейс с пустым списком.


    1. ilnar_93 Автор
      03.12.2021 21:31

      Реализовать можно и так. Но в свое время я остановился на решении которую описал. Ну и хотелось всегда быть уверенным, что в UI отображается то, что сейчас в таблице в БД и при этом использовать возможность Room + Flowable.