2

I saw a whole lot of posts for having Rx delaying each emission of an event : How to make countdown timer with RxJS Observables?, How to use RxJava Interval Operator, Adding delay between Observable Items RxJava, RxJava delay for each item of list emitted, and others.

Though, I didn't see any for chaining with different delays.

Basically, I have a Textview and a list of letters, and I'd like to :

  • set the text to the first letter
  • wait 1500ms
  • set the text to null
  • wait 500ms
  • set the text to the second letter
  • wait 1500ms
  • set the text to null
  • wait 500ms
  • repeat for the entire list

A code implementation could maybe look somehow like this (but I guess doThing() is nonsense in Rx, and delay() is not meant for this) :

Observable.fromArray(new String[]{"A", "B", "C", "D", "E"})
        .subscribeOn(Schedulers.newThread())
        .observeOn(AndroidSchedulers.mainThread())
        .delay(500L, TimeUnit.MILLISECONDS)
        .doThing((String i) -> {
            textView.setText("");
            Log.d("XXX", "MainActivity :: onCreate (110): letter="+ i);
        })
        .delay(1500L, TimeUnit.MILLISECONDS)
        .doThing((String i) -> {
            textView.setText(i);
            Log.d("XXX", "MainActivity :: onCreate (110): letter="+ i);
        });

How can I achieve this with Rx ?

EDIT : I could use the answer from rxjava delay: How to get variable delay on each item emitted from a list? with a list of letters where one letter on two is special (null maybe ?), but it seems overcomplicated.

Dan Chaltiel
  • 7,811
  • 5
  • 47
  • 92
  • you can try using zip or concat map. basically you have two observables, one for your data, one for your delay (alternating 500/1500 delays). then you combine them together. (yup, that's the answer from your last link) – Angel Koh May 06 '18 at 16:09
  • @AngelKoh You mean my last link in my edit or in my intro ? If in my edit, I'd think such a common task were doable in a less tedious way. If in my intro, could you provide a little example as an answer ? That would help me a lot ! – Dan Chaltiel May 06 '18 at 16:57

1 Answers1

2

Sequence: A (1500ms) null (500ms) B (1500ms) null (500ms) C (500ms) null (1500ms)

textAnimationDisposable = Observable.fromArray("A", "B", "C")
        .concatMap(string ->
              Observable.merge(
                 Observable.just(string),
                 Observable.just("").delay(1500, TimeUnit.MILLISECONDS)
              )
              .concatWith(Observable.<String>empty().delay(500, TimeUnit.MILLISECONDS))
        )
        .observeOn(AndroidSchedulers.mainThread())
        .subscribeOn(Schedulers.newThread())
        .subscribe(string -> textView.setText(string));

The last solution you linked is quite useful for controlling the delay of each item separatly.

aruh
  • 396
  • 6
  • 10
  • This seems very promising, thanks ! I clarified my sequence, it corresponds to your 2nd code. Unfortunately, your code doesn't work : as is, I get a `ViewRootImpl$CalledFromWrongThreadException`, and with some more `.observeOn(AndroidSchedulers.mainThread())` inside the `concatMap()`, I never see the `null` text. – Dan Chaltiel May 06 '18 at 17:24
  • Ah yeah, the created observable within `concatMap()` ran on the background thread and tried to change the layout from there. That caused the `ViewRootImpl$CalledFromWrongThreadException`. I tried it out within an android project, updating the answer... – aruh May 06 '18 at 18:13
  • The sequence still has an isseu: The empty string comes after the text. After the last item, the textview will be empty. – aruh May 06 '18 at 18:26
  • The sequence could be restarted when completed to eliminate this. Also, one could check within `concatMap` if `string` is the last item in the list and not emit the empty string. Then the last string would stay in the text view. – aruh May 06 '18 at 18:37
  • Great, this works like a charm ! Actually, I added a `Action()` as `onComplete`, so after the last, the "game" ends. – Dan Chaltiel May 06 '18 at 19:00