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.