Использование RxJava для объединения локальных данных с удаленными (или кэшированными) данными

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

У меня есть два модельных объекта, ListItem а также UserInfo, ListItems существует в локальной базе данных и UserInfo извлекается с сервера с помощью идентификатора, предоставленного ListItem,

UserInfo веб-сервис принимает массив идентификаторов, для которых он будет возвращать список UserInfo объекты.

Поток этого кода выглядит следующим образом:

  1. нагрузка ListItems из базы данных
  2. С использованием ListItemS извлекается из базы данных, проверьте кэш в памяти, чтобы увидеть, если я уже извлек UserInfo для конкретного ListItem
  3. Для любых предметов, чьи UserInfo не кешируется, извлекай их из сети
  4. Поместите выбранный UserInfo объекты в кеш
  5. Повторите шаг 2 (метод loadCachedUserInfo)
  6. Вернуть результаты подписчику

Обратите внимание 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)

Мои вопросы по поводу этого кода:

  1. В настоящее время loadCachedUserInfo принимает в массиве ListItem и возвращает тот же массив в качестве наблюдаемого после того, как кешированные элементы были связаны с ним. Это неправильно для меня. Я думаю, вместо этого этот вызов должен возвращать только те элементы, которые имеют в кэше UserInfo связано с этим. Тем не менее, мне нужно продолжать передавать полный массив ListItem к следующему методу

2.) Нужно ли выполнять дополнительную работу для поддержки отписки?

3.) Это аналогичный вопрос 1. Мой fetchUserInfoForListItems берет массив элементов списка и возвращает наблюдаемый с тем же массивом элементов списка после того, как они были извлечены и повторно запущены через метод кэширования. Это также кажется мне неправильным. Я предпочел бы этот метод вернуть Observable<List<UserInfo>> для объектов, которые были выбраны. Я не понимаю, как в itemsInList затем связать ListItems с новобрачным UserInfo и вернуть наблюдаемый из тех, ListItems.

Изменить: После написания этого поста он помог мне понять несколько вещей. Я могу 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 ответ

Решение
  1. В настоящее время loadCachedUserInfo принимает массив ListItem и возвращает тот же массив, что и наблюдаемый, после того, как кешированные элементы были связаны с ним. Это неправильно для меня. Я думаю, что вместо этого этот вызов должен возвращать только те элементы, которые связаны с кэшированным UserInfo. Однако мне нужно продолжить передачу полного массива ListItem следующему методу

Я не уверен, что правильно вас понимаю, но если вам нужен только побочный эффект (кеширование), вы можете просто использовать doOnNext, Например,

.doOnNext { listItems ->
    if ( list.isUserList ) {
        cache(listItems, userIDIndex = list.userIDIndex!!)
    }
}

fun cache(listItems : List<ATListItem>, userIDIndex : Int) {
    // caching
}
  1. Нужно ли выполнять дополнительную работу для поддержки отписки?

Нет, 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)
} 

Я не проверял код.

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