Использование RxJava для объединения локальных данных с удаленными (или кэшированными) данными
Это рабочий код, но у меня есть несколько вопросов, а также просьба о совете по его улучшению. Я новичок в RxJava, и я не полностью обдумал, как связать эти типы наблюдаемых вместе.
У меня есть два модельных объекта, ListItem
а также UserInfo
, ListItem
s существует в локальной базе данных и UserInfo
извлекается с сервера с помощью идентификатора, предоставленного ListItem
,
UserInfo
веб-сервис принимает массив идентификаторов, для которых он будет возвращать список UserInfo
объекты.
Поток этого кода выглядит следующим образом:
- нагрузка
ListItem
s из базы данных - С использованием
ListItem
S извлекается из базы данных, проверьте кэш в памяти, чтобы увидеть, если я уже извлекUserInfo
для конкретногоListItem
- Для любых предметов, чьи
UserInfo
не кешируется, извлекай их из сети - Поместите выбранный
UserInfo
объекты в кеш - Повторите шаг 2 (метод
loadCachedUserInfo
) - Вернуть результаты подписчику
Обратите внимание UserInfo
объекты должны быть выбраны только для ListItem
если список был признан isUserList
,
Вот код:
fun itemsInList(list : ATList, parentValue : String? = null, searchString : String? = null, limit : Int = defaultFetchLimit, sortOrder: SortDescriptor? = null) : Observable<List<ATListItem>> {
return Observable.create<List<ATListItem>> { subscriber ->
val listItems = listItemsInList(list, parentValue = parentValue, searchString = searchString, limit = limit, sortOrder = sortOrder)
subscriber.onNext(listItems)
subscriber.onCompleted()
}.flatMap { listItems ->
if ( list.isUserList ) {
return@flatMap loadCachedUserInfo(listItems, userIDIndex = list.userIDIndex!!)
}
return@flatMap Observable.just(listItems)
}.flatMap { listItems ->
if ( list.isUserList ) {
return@flatMap fetchUserInfoForListItems(listItems, list.userIDIndex!!, force = false)
}
return@flatMap Observable.just(listItems)
}
}
fun loadCachedUserInfo(listItems : List<ATListItem>, userIDIndex : Int) : Observable<List<ATListItem>> {
return Observable.create<List<ATListItem>> { subscriber ->
for ( listItem in listItems ) {
listItem.coreUserInfo = coreUserMap[listItem.valueForAttributeIndex(userIDIndex)?.toLowerCase()]
}
subscriber.onNext(listItems)
subscriber.onCompleted()
}
}
fun fetchUserInfoForListItems(listItems : List<ATListItem>, userIDIndex: Int, force: Boolean) : Observable<List<ATListItem>> {
val itemsToFetch = if ( force ) listItems else listItems.filter { it.coreUserInfo == null }
val ids = itemsToFetch.map { it.valueForAttributeIndex(userIDIndex) ?: "" }.filter { !it.isEmpty() }
val records = hashMapOf("records" to ids)
if ( itemsToFetch.count() == 0 ) {
return Observable.just(listItems)
}
return RuntimeDataController.dataService.fetchCoreUserInfo(recordsMap = records)
.map { json ->
val recordsArray = json.arrayValue("records")
for ( i in 0..recordsArray.length() - 1) {
val coreUserInfo = CoreUserInfo(recordsArray.getJSONObject(i))
coreUserMap[coreUserInfo.username.toLowerCase()] = coreUserInfo
coreUserMap[coreUserInfo.userID] = coreUserInfo
coreUserInfo.externalUserID?.let { coreUserMap[it] = coreUserInfo }
}
return@map listItems
}.flatMap { loadCachedUserInfo(listItems, userIDIndex = userIDIndex) }
}
Пользователь инициирует последовательность событий, вызывая:
ListController.itemsInList(list)
Мои вопросы по поводу этого кода:
- В настоящее время
loadCachedUserInfo
принимает в массивеListItem
и возвращает тот же массив в качестве наблюдаемого после того, как кешированные элементы были связаны с ним. Это неправильно для меня. Я думаю, вместо этого этот вызов должен возвращать только те элементы, которые имеют в кэшеUserInfo
связано с этим. Тем не менее, мне нужно продолжать передавать полный массивListItem
к следующему методу
2.) Нужно ли выполнять дополнительную работу для поддержки отписки?
3.) Это аналогичный вопрос 1. Мой fetchUserInfoForListItems
берет массив элементов списка и возвращает наблюдаемый с тем же массивом элементов списка после того, как они были извлечены и повторно запущены через метод кэширования. Это также кажется мне неправильным. Я предпочел бы этот метод вернуть Observable<List<UserInfo>>
для объектов, которые были выбраны. Я не понимаю, как в itemsInList
затем связать ListItem
s с новобрачным UserInfo
и вернуть наблюдаемый из тех, ListItem
s.
Изменить: После написания этого поста он помог мне понять несколько вещей. Я могу flatMap обернуть мои вызовы в Observable.create, который может содержать смарты, которые я хотел извлечь из моего fetchUserInfoForListItems
Позвольте мне ответить на вопрос № 3. Вот обновленный код:
fun itemsInList(list : ATList, parentValue : String? = null, searchString : String? = null, limit : Int = defaultFetchLimit, sortOrder: SortDescriptor? = null) : Observable<List<ATListItem>> {
return Observable.create<List<ATListItem>> { subscriber ->
val listItems = listItemsInList(list, parentValue = parentValue, searchString = searchString, limit = limit, sortOrder = sortOrder)
subscriber.onNext(listItems)
subscriber.onCompleted()
}.flatMap { listItems ->
if ( list.isUserList ) {
return@flatMap loadCachedUserInfo(listItems, userIDIndex = list.userIDIndex!!)
}
return@flatMap Observable.just(listItems)
}.flatMap { listItems ->
if ( list.isUserList ) {
return@flatMap Observable.create<List<ATListItem>> { subscriber ->
fetchUserInfoForListItems(listItems, list.userIDIndex!!, force = false).map { userInfoList ->
for (coreUserInfo in userInfoList) {
coreUserMap[coreUserInfo.username.toLowerCase()] = coreUserInfo
coreUserMap[coreUserInfo.userID] = coreUserInfo
coreUserInfo.externalUserID?.let { coreUserMap[it] = coreUserInfo }
}
}.flatMap {
loadCachedUserInfo(listItems, userIDIndex = list.userIDIndex!!)
}.subscribe {
subscriber.onNext(listItems)
subscriber.onCompleted()
}
}
}
return@flatMap Observable.just(listItems)
}
}
fun loadCachedUserInfo(listItems : List<ATListItem>, userIDIndex : Int) : Observable<List<ATListItem>> {
return Observable.create<List<ATListItem>> { subscriber ->
listItems.forEach { listItem -> listItem.coreUserInfo = coreUserMap[listItem.valueForAttributeIndex(userIDIndex)?.toLowerCase()] }
subscriber.onNext(listItems)
subscriber.onCompleted()
}
}
fun fetchUserInfoForListItems(listItems : List<ATListItem>, userIDIndex: Int, force: Boolean) : Observable<List<CoreUserInfo>> {
val itemsToFetch = if ( force ) listItems else listItems.filter { it.coreUserInfo == null }
val ids = itemsToFetch.map { it.valueForAttributeIndex(userIDIndex) ?: "" }.filter { !it.isEmpty() }
val records = hashMapOf("records" to ids)
if ( itemsToFetch.count() == 0 ) { return Observable.just(ArrayList<CoreUserInfo>()) }
return RuntimeDataController.dataService.fetchCoreUserInfo(recordsMap = records)
.map { json ->
val userInfo = ArrayList<CoreUserInfo>()
json.arrayValue("records").eachObject { userInfo.add(CoreUserInfo(it)) }
return@map userInfo
}
}
1 ответ
- В настоящее время loadCachedUserInfo принимает массив ListItem и возвращает тот же массив, что и наблюдаемый, после того, как кешированные элементы были связаны с ним. Это неправильно для меня. Я думаю, что вместо этого этот вызов должен возвращать только те элементы, которые связаны с кэшированным UserInfo. Однако мне нужно продолжить передачу полного массива ListItem следующему методу
Я не уверен, что правильно вас понимаю, но если вам нужен только побочный эффект (кеширование), вы можете просто использовать doOnNext
, Например,
.doOnNext { listItems ->
if ( list.isUserList ) {
cache(listItems, userIDIndex = list.userIDIndex!!)
}
}
fun cache(listItems : List<ATListItem>, userIDIndex : Int) {
// caching
}
- Нужно ли выполнять дополнительную работу для поддержки отписки?
Нет, AFAIK.
Замечания:
Подробнее о doOnNext
Вы можете найти в разделе Какова цель doOnNext(...) в RxJava и здесь
Обычно вам не нужно return@...
если последнее утверждение в лямбда-выражении. например:
.flatMap { listItems ->
if ( list.isUserList ) {
return@flatMap loadCachedUserInfo(listItems, userIDIndex = list.userIDIndex!!)
}
return@flatMap Observable.just(listItems)
}
можно написать так:
.flatMap { listItems ->
if ( list.isUserList )
loadCachedUserInfo(listItems, userIDIndex = list.userIDIndex!!)
else
Observable.just(listItems)
}
Я не проверял код.