Search Like a Boss

Very simple instant search UI

Instant Search Using RxJava2

Purpose: While I was searching for the best practice of instant search implementations with RxJava, I said yea it’s heaven. There are lots of samples. I used one of these examples with a few changes and the result was horrible. PO was sad, I was sad, Users were sad. Those examples weren’t designed for production application and millions of users. Those were just some kind of bootstrap.

What where the problems they didn’t cover ?

  • Retry and Error Handling : You have an smile on your lips and you are typing on your phone to search some shit but an error occurs. What do you expect? the minimum expectation is to retry your query but those samples simply dispose and if you continue to type, you will not see a thing.
  • Interrupt and Cancel Last Requests : We are talking about instant search, While user is typing we may send undesired requests to the server. For example if user wanted to search “Star Wars” if user typed slowly to reach the wait threshold app might send both “Star wa” and ” Star wars” requests. Preferable behavior is to cancel the “Star wa” query request to prevent from unwanted concurrent results. Those samples simply just send a new request per query or funny thing happens : they will throw java.io.InterruptedIOException exception and due to the lack of exception handling, search will break.
  • Clear Text Issue : a Standard search box most of the times contains a “Clear” button which clears all the characters and makes edit text ready to accept new input. This bug will occur when user types a word like “Star Wars” so fast and while the request is sent, user clicks on the clear button. button action makes edit text clean and removes old results but after a while result of “Star Wars” request comes and the UI suddenly shows results of “Star Wars” phrase. Funny? Not in a deadline.

I mentioned bugs I faced into during implementation, so you have a sense about reasons of codes in Rx chain or maybe you offer a better approach. I skip boilerplate codes and I stick to the concept, you can see full sample on GitHub repository.

GitHub Repo Link : https://github.com/alizeyn/RxSearchSample

Here you can see what the whole search code really is, and I describe more details about every operator below.

RxTextView.textChanges(searchEditText)
                .map(CharSequence::toString)
                .doOnNext(this::showSearchViews)
                .filter(string -> string.length() >= MIN_CHAR_TO_SEARCH)
                .debounce(SEARCH_QUERY_DELAY, TimeUnit.MILLISECONDS)
                .subscribeOn(Schedulers.io())
                .switchMap(api::search)
                .retry()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::next, Throwable::printStackTrace);

  • RxTextView.textChanges(searchEditText) RxTextView is an UI componenet from RxAndroid library, emits any change from textView or EditText (which is a TextView).
  • map(CharSequence::toString) RxTextView emits value as CharSequence and by map operation it changes the CharSequence to the Observable type we need which is a String.
  • doOnNext(this::showSearchViews) This is how we handle “Clear Text Issue”
    private void showSearchViews(String s) {
        if (s.length() > MIN_CHAR_TO_SEARCH) {
            searchRecyclerView.setVisibility(View.VISIBLE);
        } else {
            adapter.updateData(Collections.emptyList());
            searchRecyclerView.setVisibility(View.GONE);
        }
    }

Before filtering observable with length less than two characters, we should handle UI state. However we don’t need to request for these phrases but we may change UI corresponding to the phrase length. Imagine the situation which user clicks on the clear button just after last request is sent. Now when the invalid result comes out, the recyclerView has gone visibility.

  • filter(string -> string.length() >= MIN_CHAR_TO_SEARCH) Searching for queries less than two characters is not acceptable. You may prefer to search for one character too.
  • debounce(SEARCH_QUERY_DELAY, TimeUnit.MILLISECONDS) User is typing and typing takes time. debounce with a reasonable delay waits for user to finish typing then requests if this delay is not completed and user continue to type last request is cancelled so it reduces pressure from server and seems necessary to keep your server up.
  • subscribeOn(Schedulers.io()) SubscribeOn tells RxJava to create our observable in io thread. Read more about subscribeOn and observeOn.
  • switchMap(api::search)
@GET("api/v1/movies")
Observable<SearchResponse> search(@Query("q") String name);

Maps user query string to result from server. The reason for selecting SwitchMap over flatMap (or Map) is that in swtichMap if observable emits something new, previous emissions are no longer produce mapped observables this is an effective way to avoid stale results.

  • retry() When you’re implementing a service which user is interacting to it for a long time, Error handling is fucking important. If something went wrong user should still be able to interact to application. To approach this purpose it’s enough to call retrty(). Keep in mind which in RxJava onError is not something that you may do something after that. Actually when you get an Error and onError is called your observable is disposed and you’re not getting any emission. You should handle errors before that error happens.

I want to clear up something that many RxJava beginners get wrong: onError is an extreme event that should be reserved for times when sequences cannot continue. It means that there was a problem in processing the current item such that no future processing of any items can occur.

https://blog.danlew.net/2015/12/08/error-handling-in-rxjava/
  • observeOn(AndroidSchedulers.mainThread()) Result (List of Search result) should be emitted in the main thread for UI interaction. For example show the result in RecyclerView or produce the right message to user.
  • subscribe(this::next, Throwable::printStackTrace) As onComplete and onSubscribe is not needed I implemented just onNext and onError.
    private void next(SearchResponse searchResponse) {
        List<Movie> data = searchResponse.getData();
        if (data != null) {
            adapter.updateData(data);

            if (data.size() == 0) {
                Toast.makeText(SearchActivity.this,
                        "no result",
                        Toast.LENGTH_SHORT).show();
            }
        }
    }

Done 🙂

If you have any question or you’ve got a better approach in your mind just let me know.

Rasht Travelogue

A view of the forest from a local cafe window, Masuleh

It was a nice afternoon, low power sun on the leafs of Schefflera was a royal scene. A cup of tea and sound of Saxophone in the background convinced me this is the most relaxing afternoon I’ve ever had. I just started to listening to a Persian podcast (Radio Deev) at the time. I played the 8th episode, it’s name was : “Tomorrow the sky of Rasht is rainy” the seek bar indicator was in the middle of it’s way which I told my self : I’m going to Rasht.

The folk music, sound of local markets , those poems, they all was like a powerful magic. The decision was made, I had to go, so I started to share the idea with my friends, you know a lonely trip does not seem fun (at first). The trip started to teach me stuff at the decision level. I guess there is no better teacher than a trip. My friends who were supposed to be with me in the trip canceled the plan for their own personal reasons, I was expecting myself to cancel the trip but for a moment I thought myself, why? surprisingly I felt deeply inside I need to be alone. I should do it alone. Deep inside human kind is alone. Most of the times we can’t see ourselves in crowd, I strongly believe we all need a time for nothing and nobody.

You should feel it by heart

I traveled to the Rasht, Masuleh, Lahijan and Fuman. All the trip was amazing and fresh but it’s not possible to write down the beauty of it. It’s a feeling at the moment. so true, so present, so beautiful and I guess it’s the soul of travelling. That’s the reason you should be there yourself to feel it. actually I tried but I failed when you write it, it seems super ordinary. “I was feeling the drops of clouds on my skin.” but you can’t feel it. Use your imagination but it’s not fucking enough. It’s impossible. You should experience it yourself. I was walking around in the pure nature a dog started to follow me and she was playing around with joy and she was following me. I never saw a dog that happy. You wouldn’t understand this. You should feel it and you should be there to feel it.

Rasht

Rasht is alive. That’s the best description for this city. You can feel life every corner. Food is amazing. People seem happy, most of the people are so good looking it’s easy to understand they really care about their appearance and clothes. They just live, they don’t care much about future, they buy good cloth, they eat great foods. It’s a lovely texture of everything but to be honest Rasht still is not the heaven some people try to show. You can still see around and understand problems, poverty and irresponsibility.

A closed cinema at Rasht

And don’t ever miss Rasht cafes, be sure you try “beens with Olive oil” for breakfast. Smell of Olive oil makes insane and you can’t believe how delicious beans can be. Oh and remember to keep some cash in your pocket a lot of local stores in Rasht don’t have a POS.

Beans with Olives oil and Olives itself 🙂

Torabi’s House

Torabi’s house is where I met Koroush and Sina. Sina is really cool, so funny and friendly. Koroush is a lovely photographer, he is an art guy. And he read novel every monday and tuesday at 07:00 PM at Torabi’s house. “Where is this Torabi’s house anyway?” you may ask. And I will answer : “They call it Dream House”. A historical cute house which has been saved from destruction.

Torabi’s house yard
Koroush paintings

I met lovely and cool guys at Torabi’s house. We talked about the trip and drank delicious tea together.

Masuleh

Masuleh was the nature I never believed it really exists. I was thinking it’s just in movies. In real life we can not see these forest or clouds among tress on mountains. For me it was a true dream. It was dreamy. I slept one night in Masuleh I walked, I climbed. I touched it to understand it. To accept it. I hugged it to believe it’s true.

Sad part was the reality that tourism was completely fucked the local people life. Locals income was not from the farming or livestock. They rent their houses to the passengers. That’s how they make money. The crowd make lot’s of noise and population so locals may decide to leave and rent their houses for a relax life and money.

Funny story, only with 15 min of walking you can be in a place with no other human being. It seems people just need a place to take their stupid selfies and leave. They don’t explore the environment. They don’t have the soul.

Lahijan

Lahijan is a lost soul which belongs to a wise king. That’s how I call Lahijan. Maybe fame doesn’t just fuck character of humans but also fucks cities. I couldn’t find the coziness and intimacy of Rasht in Lahijan.

I spend a lot of my time in Hotel because I was tired and the entering nigh in Lahijan was depressing. The next morning I went to the Sheykh Zahed Gilani Mosque and it changed everything. Tea farms was around me. Every deep breath was a gift, So refreshing.

I really encourage you for a lonely trip. You will never forget it.