简体   繁体   中英

Rewinding an Observable in RxJava?

I've been applying RxJava and reactive programming to command line applications. I've been discovering some pretty awesome design patterns that really simplify the code for a command line interface.

However, there is one thing I'm struggling with. Often I am emitting values down a chain of operators, and I concatMap() an Observable.create() to solicit user input on the emitted value.

This is easy enough, until I want to provide a means to go BACK to the previous emission. It starts to get very messy and despite my effort I know this does not work 100% correctly. I want a user input of "BACK" to try and rewind by one emission by requesting it from a cache. Is there a better operator or transformer to rewind the emissions at a previous point in the chain and re-send them back up?

public class Test {

    public static void main(String[] args) {

        final Scanner scanner = new Scanner(System.in);
        final List<String> cache = new ArrayList<>();
        final AtomicInteger cursorIndex = new AtomicInteger(-1);


        Observable.just(
                "What is your name?",
                "What is your quest?",
                "What is your favorite color?",
                "What is the capital of Assyria?",
                "What is the velocity of an unladen swallow?"
        )
        .doOnNext(cache::add)
        .concatMap(q -> {
            int cursor = cursorIndex.get();
            if (cursor >= 0 && cursor < cache.size() - 2) {
                return Observable.just(cache.get(cursor));
            } else {
                cursorIndex.incrementAndGet();
                return Observable.just(q);
            }
        })
        .doOnNext(System.out::println)
        .concatMap(q -> Observable.create(s -> {
                    String answer = scanner.nextLine().trim();

                    if (answer.toUpperCase().equals("BACK")) {
                        cursorIndex.decrementAndGet();
                    }
                    s.onNext(answer);
                    s.onCompleted();
                })
        ).filter(s -> !s.equals("BACK"))
         .subscribe(q -> System.out.println("You said: " + q));

    }
}

    What is your name?
    Sir Lancelot
    You said: Sir Lancelot
    What is your quest?
    To seek the holy grail
    You said: To seek the holy grail
    What is your favorite color?
    BACK
    What is your quest?

What I would do is to have the index as a BehaviorSubject and call onNext on it whenever I have to switch between questions:

Scanner scanner = new Scanner(System.in);

String[] questions = {
        "What is your name?",
        "What is your quest?",
        "What is your favorite color?",
        "What is the capital of Assyria?",
        "What is the velocity of an unladen swallow?"
};

String[] cache = new String[questions.length];

BehaviorSubject<Integer> index = BehaviorSubject.create(0);

index
.observeOn(Schedulers.trampoline())
.concatMap(idx -> {
    if (idx == questions.length) {
        index.onCompleted();
        return Observable.empty();
    }

    System.out.println(questions[idx]);
    String answer = scanner.nextLine().trim();

    if ("BACK".equals(answer)) {
        index.onNext(Math.max(0, idx - 1));
        return Observable.empty();
    } else
    if ("QUIT".equals(answer)) {
        index.onCompleted();
        return Observable.empty();
    }

    cache[idx] = answer;
    index.onNext(idx + 1);
    return Observable.just(answer);
})
.subscribe(v -> System.out.println("You said: " + v));

I suppose there is one way if the Observable is finite and has a manageable number of emissions (which should be the case if each one requires a user input). You could collect it into a toList() and create a new Observable off that list. You can then control the iteration and use a boolean flag to signal a rewind at any moment.

public class Test {

    public static void main(String[] args) {

        final Scanner scanner = new Scanner(System.in);
        final AtomicBoolean rewind = new AtomicBoolean();

        Observable.just(
                "What is your name?",
                "What is your quest?",
                "What is your favorite color?",
                "What is the capital of Assyria?",
                "What is the velocity of an unladen swallow?"
        ).toList().concatMap(l -> Observable.create(s -> {
            for (int i = 0; i < l.size(); i++) {
                s.onNext(l.get(i));
                if (rewind.getAndSet(false) &&  i >= 1) {
                    i = i - 2;
                }
            }
            s.onCompleted();
        }))
        .doOnNext(System.out::println)
        .concatMap(q -> Observable.create(s -> {
                    String answer = scanner.nextLine().trim();

                    if (answer.toUpperCase().equals("BACK")) {
                        rewind.set(true);
                    }
                    s.onNext(answer);
                    s.onCompleted();
                })
        ).filter(s -> !s.equals("BACK")).subscribe(q -> System.out.println("You said: " + q));

    }
}

What is your name?
Sir Lancelot of Camelot
You said: Sir Lancelot of Camelot
What is your quest?
To seek the Holy Grail
You said: To seek the Holy Grail
What is your favorite color?
Blue
You said: Blue
What is the capital of Assyria?
Asher
You said: Ashur
What is the velocity of an unladen swallow?
BACK
What is the capital of Assyria?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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