使用RxJava将本地数据与远程(或缓存)数据

时间:2016-02-20 15:04:16

标签: android rx-java kotlin rx-kotlin

这是工作代码,但我有一些问题以及有关改进它的建议请求。我是RxJava的新手,我还没有完全理解如何将这些类型的可观察链连接在一起。

我有两个模型对象ListItemUserInfoListItem存在于本地数据库中,并使用UserInfo提供的ID从服务器获取ListItem

UserInfo Web服务接受一组ID,它将返回UserInfo个对象的列表。

此代码的流程如下:

  1. 从数据库
  2. 加载ListItem
  3. 使用从数据库中提取的ListItem,检查内存缓存,看看我是否已为特定UserInfo提取ListItem
  4. 对于未缓存UserInfo的任何项目,请从网络中提取
  5. 将提取的UserInfo个对象放入缓存
  6. 重新运行第2步(方法为loadCachedUserInfo
  7. 将结果返回给订阅者
  8. 注意:如果列表被视为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数组,并在缓存项与之关联后返回与observable相同的数组。这对我来说不合适。我认为此调用只应返回与其关联的缓存UserInfo的项目。但是,我需要继续将完整的ListItem数组传递给下一个方法
    2. 2。)我是否需要做额外的工作来支持取消订阅?

      3。)这是类似的问题1.我的fetchUserInfoForListItems获取一个列表项数组,并在获取并重新运行缓存方法后返回具有相同列表项数组的observable。这对我来说也不合适。我宁愿这个方法为被提取的对象返回Observable<List<UserInfo>>。我不理解itemsInList如何将ListItem s与新提取的UserInfo相关联,并返回ListItem s的Observable。

      编辑:撰写此帖后,它帮助我实现了一些目标。我可以在一个Observable.create中使用flatMap包装我的调用,它可以包含我想从我的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 个答案:

答案 0 :(得分:3)

  
      
  1. 目前,loadCachedUserInfo接收ListItem数组,并在缓存项与之关联后返回与observable相同的数组。这对我来说不合适。我认为此调用应该只返回具有与之关联的缓存UserInfo的项目。但是,我需要继续将ListItem的完整数组传递给下一个方法
  2.   

我不确定我是否理解正确,但如果您只需要副作用(缓存),则可以使用doOnNext。例如,

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

fun cache(listItems : List<ATListItem>, userIDIndex : Int) {
    // caching
}
  
      
  1. 我是否需要做额外的工作来支持取消订阅?
  2.   

不,AFAIK。

注意:

有关doOnNext的更多信息,请访问What is the purpose of doOnNext(...) in RxJavahere

通常,如果lambda中的最后一个语句是表达式,则不需要return@...。 e.g:

.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)
} 

我没有测试代码。