简体   繁体   English

如何在RxJava 2中结合动态数量的Observable?

[英]How to combine a dynamic number of Observables in RxJava 2?

I'm stuck in a quite weird problem and ask for your help. 我陷入了一个很奇怪的问题,要求您的帮助。

The overall setting is the following: I have a bunch of data providers that can occur or disappear at runtime. 总体设置如下:我有一堆可以在运行时出现或消失的数据提供程序。 These providers provide - big surprise - data, modeled as io.reactivex.Observable s. 这些提供程序提供的数据令人惊讶(建模为io.reactivex.Observable (To be precise: as BehaviorSubject s, remembering the latest data for new subscribers.) (确切地说,作为BehaviorSubject ,记住新订户的最新数据。)

Now, I need to combine the data of all current data providers, so that I get a new "main" Observable which gets updated whenever any data provider's observable changes or when new providers appear (or old ones disappear). 现在,我需要合并所有当前数据提供者的数据,以便获得一个新的“主要” Observable,每当任何数据提供者的可观察变化或新提供者出现(或旧提供者消失)时,该值都会更新。

So far, that sounds like merging, but for the main Observable I need all provider's data combined, on any change, each provider's respective last state. 到目前为止,这听起来像是合并,但是对于主要的Observable,我需要将所有提供者的数据组合在一起,无论发生什么变化,每个提供者各自的最后状态。 This combining works fine for non-dynamic providers, which are known in advance, using Observable.combineLatest . 结合使用Observable.combineLatest ,这种组合对于预先已知的非动态提供者非常有效。

But the problems arise, when I embed that method into a dynamic context, listending for added or removed providers. 但是,当我将该方法嵌入到动态上下文中时,就会出现问题,从而为添加或删除的提供程序添加列表。 Then an update of one of the provider's Observable triggers not only one update as expected, but several updates, some of them only containing partial data. 然后,对提供者的“可观察的”之一的更新不仅触发了预期的一个更新,还触发了多个更新,其中一些更新仅包含部分数据。

Have a look at the following (self-contained, using RxJava 2.1.9) example, wich should clarify on my problem. 看看下面的示例(使用RxJava 2.1.9,功能齐全),该示例应阐明我的问题。 Commenting is done as println(), so that the produced output is readable, too. 注释通过println()完成,因此生成的输出也可读。 The first part is static, without adding or removing providers, and works as expected. 第一部分是静态的,没有添加或删除提供程序,并且按预期工作。 The second part is the weird thing... 第二部分是奇怪的事情...

I'd appreciate any further ideas or assistance to solve this issue - thanks! 感谢您提供解决此问题的其他想法或帮助-谢谢!

import io.reactivex.Observable;
import io.reactivex.functions.Function;
import io.reactivex.subjects.BehaviorSubject;
import io.reactivex.subjects.Subject;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class CombineLatestWithDynamicSources {

    static final Function<Object[], List<String>> STRING_COMBINER = objects -> Arrays.stream(objects)
                                                                                     .map(Object::toString)
                                                                                     .collect(Collectors.toList());

    public static void main(String[] args) {

        System.out.println("*** STATIC ***");
        staticCombineWorksAsExpected();

        System.out.println("\n*** DYNAMIC ***");
        dynamicCombineBehavesWeird();

    }

    static void staticCombineWorksAsExpected() {

        Subject<String> subjectA = BehaviorSubject.createDefault("A.1");
        Subject<String> subjectB = BehaviorSubject.createDefault("B.1");

        List<Subject<String>> subjects = Arrays.asList(subjectA, subjectB); // potentially more...

        Observable<List<String>> combined = Observable.combineLatest(subjects, STRING_COMBINER);

        System.out.println("initial values:");
        combined.subscribe(strings -> System.out.println(">> Combined: " + strings));

        System.out.println("updating A:");
        subjectA.onNext("A.2");

        System.out.println("updating B:");
        subjectB.onNext("B.2");

        System.out.println("\n... works as expected, but adding subjects isn't possible in this setting.");
    }

    static void dynamicCombineBehavesWeird() {

        List<Subject<String>> subjectsList = new ArrayList<>();
        Subject<List<Subject<String>>> subjectsObservable = BehaviorSubject.createDefault(subjectsList);

        System.out.println("subjects are initially empty:");
        subjectsObservable.subscribe(subjects -> System.out.println(">> Subjects: " + subjects));

        Observable<List<String>> combined = subjectsObservable.flatMap(
                subjects -> Observable.combineLatest(subjects, STRING_COMBINER));

        combined.subscribe(strings -> System.out.println(">> Combined: " + strings));

        System.out.println("add Subject A, providing default value 'A.1' - as expected:");
        Subject<String> subjectA = BehaviorSubject.createDefault("A.1");
        subjectsList.add(subjectA);
        subjectsObservable.onNext(subjectsList);

        System.out.println("updating A - also as expected:");
        subjectA.onNext("A.2");

        System.out.println("add Subject B, providing default value 'B.1' - as expected, now both subject's last values show up:");
        Subject<String> subjectB = BehaviorSubject.createDefault("B.1");
        subjectsList.add(subjectB);
        subjectsObservable.onNext(subjectsList);

        System.out.println("updating A again - I'd expect the second result only! Why is there '[A.3]' popping up before the expected result?!");
        subjectA.onNext("A.3");

        System.out.println("This doesn't happen on updating B...:");
        subjectB.onNext("B.2");

        System.out.println("digging deeper, add Subject C, providing default value 'C.1' - as expected again:");
        Subject<String> subjectC = BehaviorSubject.createDefault("C.1");
        subjectsList.add(subjectC);
        subjectsObservable.onNext(subjectsList);

        System.out.println("Now update A - three results pop up, only the last is expected!");
        subjectA.onNext("A.4");

        System.out.println("update B, which now emits also two results - last expected only:");
        subjectB.onNext("B.3");

        System.out.println("update C works as expected:");
        subjectC.onNext("C.2");

        System.out.println("\n... huh? Seems on updating the first source, the combined results gets computed for the first, " + "then for the first and second, then for first, second and third (and so on?) source...");

    }

}

... which produces the following output: ...产生以下输出:

*** STATIC ***
initial values:
>> Combined: [A.1, B.1]
updating A:
>> Combined: [A.2, B.1]
updating B:
>> Combined: [A.2, B.2]

... works as expected, but adding subjects isn't possible in this setting.

*** DYNAMIC ***
subjects are initially empty:
>> Subjects: []
add Subject A, providing default value 'A.1' - as expected:
>> Subjects: [io.reactivex.subjects.BehaviorSubject@4157f54e]
>> Combined: [A.1]
updating A - also as expected:
>> Combined: [A.2]
add Subject B, providing default value 'B.1' - as expected, now both subject's last values show up:
>> Subjects: [io.reactivex.subjects.BehaviorSubject@4157f54e, io.reactivex.subjects.BehaviorSubject@90f6bfd]
>> Combined: [A.2, B.1]
updating A again - I'd expect the second result only! Why is there '[A.3]' popping up before the expected result?!
>> Combined: [A.3]
>> Combined: [A.3, B.1]
This doesn't happen on updating B...:
>> Combined: [A.3, B.2]
digging deeper, add Subject C, providing default value 'C.1' - as expected again:
>> Subjects: [io.reactivex.subjects.BehaviorSubject@4157f54e, io.reactivex.subjects.BehaviorSubject@90f6bfd, io.reactivex.subjects.BehaviorSubject@47f6473]
>> Combined: [A.3, B.2, C.1]
Now update A - three results pop up, only the last is expected!
>> Combined: [A.4]
>> Combined: [A.4, B.2]
>> Combined: [A.4, B.2, C.1]
update B, which now emits also two results - last expected only:
>> Combined: [A.4, B.3]
>> Combined: [A.4, B.3, C.1]
update C works as expected:
>> Combined: [A.4, B.3, C.2]

... huh? Seems on updating the first source, the combined results gets computed for the first, then for the first and second, then for first, second and third (and so on?) source...

My combining-problem kept me going for several days before asking here - but even after my question, it didn't let me off... now finally, I could dig on it and I have a solution. 我的合并问题使我不得不待了好几天才问这里-但是即使在我提出问题之后,它也没有让我离开...现在终于,我可以继续研究,并且有解决方案。 The problem was the not-disposing of the previous subscriptions inside the flatmap. 问题是flatmap 以前订阅的未处置。 gosh! 天哪!

First, I pulled the chaining apart to get insight to the moving parts: 首先,我将链条分开以了解运动部件:

List<Subject<String>> subjectsList = new ArrayList<>();
Subject<List<Subject<String>>> subjectsObservable = BehaviorSubject.createDefault(subjectsList);

final Disposable[] disposable = new Disposable[]{Disposables.empty()};

// combined now is a subject to push updates into
Subject<List<String>> combined = BehaviorSubject.createDefault(Collections.emptyList());

subjectsObservable.subscribe(subjects -> {
    Observable<List<String>> combSubj = Observable.combineLatest(subjects, STRING_COMBINER);

    // here's the key: I remember the previous subscription and dispose it on update.
    disposable[0].dispose();
    disposable[0] = combSubj.subscribe(strings -> combined.onNext(strings));
});

combined.subscribe(strings -> System.out.println(">> Combined: " + strings));

And finally, a little bit neater and closer to my original (not working) solution: 最后,更加整洁,更接近我原来的(无效)解决方案:

// remember the disposables, start with an empty to avoid NullPointerExceptions
Disposable[] prevDisposable = new Disposable[]{Disposables.empty()};
Observable<List<String>> combined = subjectsObservable.flatMap(
        subjects -> Observable.combineLatest(subjects, STRING_COMBINER)
                              // dispose the previous subscription to combineLatest's Observable and remember the new one
                              .doOnSubscribe(disposable -> {
                                    prevDisposable[0].dispose();
                                    prevDisposable[0] = disposable;
                              })
        );

combined.subscribe(strings -> System.out.println(">> Combined: " + strings));

You can use .concatMap oprator 您可以使用.concatMap操作者

val o1 = Observable.just(0,1,2)
val o2 = Observable.just(3,4,5)
val subject = PublishSubject.create<Observable<Int>>()

subject
        .concatMap { it }
        .subscribe { it: Int? ->
            System.out.print("$it,")
        }

subject.onNext(o1)
subject.onNext(o2)

out is 出来是

0,1,2,3,4,5,

Some time since - now I know better... ;-) 从那以后的一段时间-现在我知道了... ;-)

Simple Solution - so simple... :m 简单的解决方案-如此简单...:m

subjectsObservable.switchMap(subjects -> Observable.combineLatest(subjects, STRING_COMBINER))
                  .subscribe(strings -> System.out.println(">> Combined: " + strings));

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM