使用RxJava从Firebase实时数据库加入扁平化数据

时间:2016-07-01 10:30:27

标签: firebase rx-java firebase-realtime-database

加入展平数据是documentation中描述的常见用例。但文档显示了一个非实时的简单示例,它不会对更改做出反应。我正在寻找一个更强大的实现。我认为RxJava非常适合这种情况。

考虑遵循Firebase结构:

{
  "messages": {
    "group_id_1": {
      "message_id_1": {
        "text": "Hello",
        "author": "uid_1"
      }
    }
  },
  "users": {
    "uid_1": {
      "name": "David"
    }
  },
  "rooms": {
    "room_id_1": {
      "name": "General",
      "members": {
        "uid_1": true
      }
    }
  }
}

我在这里看到两个用例:

  1. 获取具有作者姓名的组中的邮件列表
    • 我想我会得到Observable<Message>,当我订阅它时,依赖项(这些消息的用户)也将在某些缓存中订阅。当我显示消息时,我可以从缓存中获取作者的姓名。
    • 它也是实时的 - 如果作者姓名更改,则observable会发出更改的消息。
    • 当我取消订阅observable时,也依赖取消订阅。
  2. 获取房间成员名单及其名称
    • 我想我会得到Observable<User>,当我订阅它时,它会首先订阅房间的成员,然后订阅个人用户。
    • 这是实时的 - 如果房间成员发生变化,我会收到通知。
    • 当我取消订阅observable时,也依赖取消订阅。
  3. 你知道库/解决方案可以做到吗?

    或者如果我创建了一个会使用它吗?

2 个答案:

答案 0 :(得分:1)

我打算提出这个问题的一个变体,但似乎建立在这个问题之上可能会更好......我将描述什么是希望至少部分地回答上述问题而且还有一个缺点我正在看。

使用上面的数据模型,我们可能会有以下内容来创建围绕firebase查询的RxJava包装,以获取特定空间的成员键列表以及获取特定成员的详细信息(请注意onCompleted()subscriber.getMemberInfo的使用情况} ......稍后再说!)。

public Observable<String> getRoomMembers(String roomId) {
    return Observable.create(subscriber -> {
        databaseReference.child(roomId + "/members").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                for (DataSnapshot childSnapshot: dataSnapshot.getChildren()) {
                    String userId = childSnapshot.getKey()
                    subscriber.onNext(userId);
                }
                subscriber.onCompleted();
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
            }
        });
    });
}

public Observable<Member> getMemberInfo(String memberId) {
    return Observable.create(subscriber -> {
        databaseReference.child(memberId).addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                Member member = dataSnapshot.getValue(Member.class);
                subscriber.onNext(member);
                subscriber.onCompleted();
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
            }
        });
    });
}

然后,我们可以使用以下内容获取特定房间Member的列表(已将isActive属性添加到Member以显示我们如何过滤我们获得的结果)

    getRoomMembers(roomId)
            .flatMap(memberId -> getMemberInfo(memberId))
            .filter(Member::isActive)
            .toList()
            .subscribe(members -> {

            });

因此,上述工作达到了一定程度。问题是,我必须在subscriber.onCompleted()中致电getMemberInfo,以便上述flatMap调用工作....这意味着对Member数据的任何后续更改都不是在上述订阅中触发更新。对RxJavaFirebase来说相对较新,所以可能会遗漏一些明显的东西。

答案 1 :(得分:1)

我最终用这两种方法解决了它(在Kotlin中,Java类似,只是更冗长):

fun <A, B> Observable<List<A>>.mapSubQueries(subQuery: (A) -> Observable<B>): Observable<List<Observable<B>>> {
    return this.flatMap {
        if (it.isNotEmpty()) {
            return@flatMap Observable.from(it).map { subQuery(it) }.toList()
        } else {
            return@flatMap Observable.just(listOf<Observable<B>>())
        }
    }
}

@Suppress("UNCHECKED_CAST")
fun <T> Observable<List<Observable<T>>>.joinSubQueries(): Observable<List<T>> {
    return this.flatMap {
        if (it.isNotEmpty()) {
            return@flatMap Observable.combineLatest(it, {
                val list = mutableListOf<T>()
                it.forEach {
                    list.add(it as T)
                }
                list
            })
        } else {
            return@flatMap Observable.just(listOf<T>())
        }
    }
}

为了让所有消息都能吸引用户,我可以像这样使用它:

fun usersInMessages(roomId): Observable<List<User>> {
    return DatabaseRead.messages(roomId)
            .mapSubQueries { DatabaseRead.user(it.getAuthor()) }
            .joinSubQueries()
}

我决定最好将此代码保存在我的代码库中,并针对各种用例进行稍微修改。使它成为一个库将使其灵活性降低。重点是始终使用Observable.combineLatest()。许多其他Rx参数都是无用的,因为它们需要onComplete()调用,在这里我处理无限的Observable。