Я считаю, что это больше вопрос архитектуры, но позвольте мне сначала попытаться ответить на некоторые из ваших вопросов.
Моя проблема заключается в том, что при использовании потока по умолчанию для комнаты у вас есть только данные поэтому, если бы я подписался на поток, я бы только получил данные
Если есть ошибка с Flow
, возвращаемым Room, вы можете обработать ее через catch()
Что мне нужно сделать, так это каким-то образом уведомить о состоянии приложения, например о загрузке или ошибке.
Я согласен с вами, что наличие State
объект - это хороший подход. На мой взгляд, ViewModel
является обязанностью представить State
объект View
. У этого State
объекта должен быть способ выявлять ошибки.
В настоящий момент я могу думать только о том, как объект «ответа», содержащий состояние, но я не могу найти способ реализовать его.
Я обнаружил, что легче иметь объект State
, чтобы элементы управления ViewModel
отвечали за ошибки, а не объект, всплывающий из Service
layer.
Теперь, когда эти вопросы не нужны, позвольте мне попытаться предложить одно конкретное «решение» вашей проблемы.
Как вы упоминаете, это обычная практика Repository
, который обрабатывает получение данных из нескольких источников данных. В этом случае Repository
будет принимать DAO
и объект, представляющий получение данных из сети, назовем его Api
. Я предполагаю, что вы используете FirebaseFirestore
, поэтому сигнатура класса и метода будет выглядеть примерно так:
class Api(private val firestore: FirebaseFirestore) {
fun getUsers() : Flow<List<UserApiPojo>
}
Теперь возникает вопрос, как превратить API на основе обратного вызова в Flow
. К счастью, мы можем использовать callbackFlow()
для этого. Тогда Api
становится:
class Api(private val firestore: FirebaseFirestore) {
fun getUsers() : Flow<List<UserApiPojo> = callbackFlow {
val data = Gson().toJson(filters)
functions.getHttpsCallable("users").call(data).addOnSuccessListener {
try {
val type = object : TypeToken<List<UserApiPojo>>() {}.type
val users = Gson().fromJson<List<UserApiPojo>>(it.data.toString(), type)
offer(users.toMutableList())
} catch (e: java.lang.Exception) {
cancel(CancellationException("API Error", e))
}
}.addOnFailureListener {
cancel(CancellationException("Failure", e))
}
}
}
Как вы можете видеть, callbackFlow
позволяет нам отменить поток, когда что-то идет не так, и заставить кого-то donwnstream обработать ошибку.
Переход к Repository
Теперь мы хотели бы сделать что-то вроде:
val users: Flow<List<User>> = Flow.concat(userDao.getUsers().toUsers(), api.getUsers().toUsers()).first()
Здесь есть несколько предостережений. first()
и concat()
- это операторы, которые вам придётся придумать. Я не видел версию first()
, которая возвращает Flow
; это оператор терминала (Rx имел версию first()
, которая вернула Observable
, Дэн Лью использует ее в этой записи). Flow.concat()
, похоже, тоже не существует. Цель users
- вернуть Flow
, который испускает первое значение, испускаемое любым источником Flows
. Также обратите внимание, что я сопоставляю пользователей DAO и пользователей Api с общим User
объектом.
Теперь мы можем поговорить о ViewModel
. Как я уже говорил, у ViewModel
должно быть что-то, что содержит State
. Это State
должно представлять данные, ошибки и состояния загрузки. Один из способов сделать это - использовать класс данных.
data class State(val users: List<User>, val loading: Boolean, val serverError: Boolean)
Поскольку у нас есть доступ к Repository
, ViewModel
может выглядеть следующим образом:
val state = repo.users.map {users -> State(users, false, false)}.catch {emit(State(emptyList(), false, true)}
Пожалуйста, имейте в виду, что это грубое объяснение, указывающее вам направление, есть много способов выполнить sh управление состоянием, и это ни в коем случае не полная реализация. Может даже не иметь смысла превращать вызов API в Flow
, например.