Typing Indicator using RxJava

Typing indicator is a common feature we find in chat applications. Usually, after text someone we expect a response. As soon a person starts typing we can see that little typing indicator (that can be a text, a little bubble, etc). Then, suddenly that indicator disappears. After a second or so it appears again, disappears, appears, and finally a message is received.

In order to develop it, we could add a TextWatcher as a listener for an EditText view and use its methods to control what is going on to that view. This involves creating and stopping timers whenever user types something, and when those timers expire, take appropriate actions. But, what if we want to do it in reactive way? Which operators should we use?

So, the goal of this article it to demonstrate how to develop a typing indicator by using reactive paradigm. We will run into some different approaches using different operators combinations. The first two, window and buffer will not fit our requirements and we will go through them to show why they did not work for us. Then we will present a third option by using a combination of publishambWithtimer operators that will work exactly as we expect. 

For this article these are our requirements:

  1. User types one or two characters. Nothing happens
  2. User types a third (or more) character. Typing indicator is shown
  3. User keeps typing with no more than 5 seconds of time between each character. Typing indicator keeps visible.
  4. User stops typing for more than 5 seconds. Typing indicator disappears. This is an important step. We want our typing indicator to be gone exactly after 5 seconds of the last typed character. Neither more nor less than that.
  5. User types something else and keeps typing with no more than 5 seconds between each character. Typing indicator is visible again and keeps visible.
  6. User stops typing for more than 5 seconds. Typing indicator disappears.

In short, basically we will only show the typing indicator if there are at least three characters and user keeps typing each character within five seconds.

You can find here a demo app which implements all examples demonstrate on this article. So, let’s see some code!

RxBinding

Before we get into our three examples, let’s just demonstrate how we implemented our second requirement: “showing typing indicator only when user typed at least 3 characters”.

In order to explore reactive programming a little more, we decided to use RxBinding library to listen for a TextView changes. Basically we just use RxTextView::textChanges(…) which will emit an observable whenever its content changes. Then, just filter value out in case of less than 2 characters.

window() operator

Now, let’s see our first example by using window operator. It contains many variations and we decided to use the one which accepts a timespan and a count parameters. Let’s check out our code:

This is what docs say about this variant: “This variant of window opens its first window immediately. It closes the currently open window and opens another one every timespan period of time … or whenever the currently open window has emitted count items…”.

This means that it will emit an observable either when nothing happen within 5 seconds or if user types something (since we are using count = 1). So, we rely on the emitted observable. If it contains any data, it means user typed something, so typing indicator message is shown. But when it emits an empty observable, it means nothing happened during 5 seconds, so we hide typing indicator message.

So far so good. But… let’s review our requirements. On the fourth one we said we want to make typing indicator to be gone EXACTLY after 5 seconds user stops typing. Let’s take a closer look on how window() operator works upon our solution:

  • At time 0: a new window is started (which is expected to be opened for 5 seconds if nothing happens)
  • At time 1: user types letters ‘aaa’. Current window is closed and those items are emitted. This will make typing indicator message to appear.
  • At time 5: another window is opened
  • At time 10: window is closed: Only now typing indicator message is gone, since now window operator emitted an empty observable. Here typing indicator message was visible for 9 seconds!
  • At time 14: user types letter ‘b’. Current window is closed and that item is emitted. This makes typing indicator message to appear.
  • At time 15: another window is opened
  • At time 20: window is closed: Only now typing indicator message is gone. Now typing indicator message was visible for 7 seconds!

This step by step clearly shows us that, depends on the period of time user types something, typing indicator message can be visible for more than 5 seconds.

Let’s dissect it into small pieces:

As mentioned before, window() has many variants, and we are using one that accepts a counter parameter and a timestamp, meaning that it will emit an observable either when it collects “count” number of items (in our case “count” is 1 meaning it will emit as soon as user types something) or when the window expires (i.e.: 5 seconds). Which comes first.

The problem here is that when user types something, window() operator makes current window to be emitted, but it will only start another window either if user types something else (which makes it to start and close a new window and emit it immediately) or after the latest timespan expires. We can see this behavior above when user typed some characters at time 1 making current window to be emitted immediately, but only at time 5 it started a new window and emitted it only after 5 seconds (i.e.: at time 10)

We can see that by using window() operator, depends on the period of time we stop typing, typing indicator message will be visible from 5 to up to 9 seconds. So, this is not what we want!

buffer() Operator

Our second example uses buffer() operator. It is quite similar to window() operator. For every variant of it, there is a window correspondent method. We will use the same variant method as we did in window() example with the same values. This is how our code looks like:

For the variant we will use, basically the difference is that it will always emit something when it’s timespan elapses, regardless whether it is fewer than count. This means that our buffer() operator will emit each 5 seconds, no matter what happens. Within this period, if user types something, that item will be emitted (since count = 1), and when it elapses 5 seconds since its last bundle emission, it will emit an empty list, making our typing indicator message to be gone.

The problem is that if user types something right before timespan elapses (e.g.: 500ms before), typing indicator message will be visible for a very short period of time.

Let’s see a hypothetical example:

  • At time 0: a new timespan is started (which is expected to be opened for 5 seconds)
  • At time 1: user types letters ‘aaa’. These items are emitted. This will make typing indicator message to appear.
  • At time 5: the first timespan elapses, so an empty list is emitted making typing indicator message to be gone. Notice that the indicator message was visible for only 4 seconds.
  • At time 9: user types letter ‘b’. This item is emitted. This will make typing indicator message to appear.
  • At time 10: the latest timespan elapsed, so an empty list is emitted making indicator message to be gone. It was visible for only 1 second.

We clearly see that by using buffer operator, the effect is the opposite than when using window() operator. Depends on the period of its timespan user types something, typing indicator message will be visible from some milliseconds to up to 5 seconds.

Once again this is not what we want!

publish-ambWith-timer Operators

And now our third example. It is based in an answer from Dávid Karnok on this SO question. It uses a combination of publish-ambWith-timer operators.

Before go into the details, let’s see how our code looks like:

Regarding to amb() operator, docs say: “…given two or more source Observables, emit all of the items from only the first of these Observables to emit an item or notification.”… “Amb will ignore and discard the emissions and notifications of all of the other source Observables.”.

As pointed out on this article also from Dávid Karnok (and I really recommend read it), after amb receives the first emitted item from its sources, it will unsubscribe and ignore the others. So, if user types something, it will unsubscribe timer operator, so we do not have to worry about it. Nice, isn’t it? Also, by using repeat operator downstream, it means that it repeats the sequence emitted by the source Observable (in this case ambWith) as its own sequence, meaning that right after user types something (or when timer expires), a new timer will be started. This will give us a null emitted item exactly after five seconds of idleness. So this is finally what we were looking for!

Additional Notes

For all three examples showed in this post, after user types at least three characters, if he/she keeps typing (within 5 seconds between each character), an observable is emitted for every typed character. Also, if nothing happens for a while, an empty observable will be emitted every 5 seconds.

This means that even if typing indicator message is already being shown, for every typed character, we will call myTextView.setText([your name] + ” is typing”). The same happens when that message is no longer visible. Every 5 seconds of idleness we will call myTextView.setText(“”) method. For our demo project this is not a problem, but in a real application, instead of update a view we would probably trigger a network request to inform the other person that someone is typing. This could be a problem, since we would waste network bandwidth with useless messages.

In order to prevent it, we can use distinctUntilChanged() operator. It will prevent emissions downstream until emitted observable changes its status (i.e. if it is emitting an empty observable every five second, these emissions will not be propagated down until user types something) which will then emit an observable with that value.

Conclusion

The idea behind this article is to demonstrate how easy we can code using reactive programming. Maybe, when we first look at a reactive code, things might seem weird, but as we get used with it, we feel comfortable doing it. There are lots of thing to learn about reactive programming and I hope we can learn all together.

Stay tuned! We are planning to keep talking about reactive programming for the next couple of articles. And of course, if you have any question, please add a comment below.