How to minimize the number of webservice calls using Kotlin coroutines?

Viewed 1341

In my Android Kotlin project, I call a webservice in a coroutine (myWebservice is just a custom class that manages webservice calls):

fun searchForItems(userInput: String)
{
    CoroutineScope(Dispatchers.IO + Job()).launch {
        val listOfItems = myWebService.call(userInput)
    }
}

That method is called everytime a user types a character in an EditText, so the app calls a webservice that returns a list of items matching his request. But I want to optimize that.

Let's say that the user types the word: "apple". In order to minimise the number of webservice calls, here is what I want to achieve:

  • when the user types the first letter (a), the webservice is called
  • when the user types the next letters, there is no new webservice call as long as the first called hasn't returned (let's assume that he has enough time to type the next letters (pple))
  • when the first webservice call is done, a new call is done automatically with the new user input (apple)

What would be the best practices to achieve that? Or is there a better way to minimize the number of webservice calls?

Thanks.

3 Answers

Using Kotlin coroutines I solved it like this:

class SomeViewModel : ViewModel() {
    private var searchJob: Job? = null

    fun search(userInput: String) {
        searchJob?.cancel() // cancel previous job when user enters new letter
        searchJob = viewModelScope.launch {
            delay(300)      // add some delay before search, this function checks if coroutine is canceled, if it is canceled it won't continue execution
            val listOfItems = myWebService.call(userInput)
            ...
        }
    }  
}

When user enters first letter search() function is called, coroutine is launched and Job of this coroutine is saved to searchJob. Then delay(300) function is called to wait for another user input before calling the WebService. If user enters another letter before 300 milliseconds expire search() function will be called again and previous coroutine will be cancelled using searchJob?.cancel() function and WebService will not be called in the first coroutine.

You need debouncing. Coroutines or not, you shouldn't call the web service with each letter for every active user. That will eventually DDOS the web service if your app is used by many people at once.

Since you are using Kotlin, instead of coroutines you can use Flow. It comes with a built in debounce method. Also the stream of letters is easily modelled as a flow. It would be something like this (I'm not sure this even runs, but you get the idea):

textFlow = flow {
    myTextView.doOnTextChanged { text, start, count, after -> emit(text)}
}.debounce(1000)

The more complex alternative is RxJava's debounce operator.

You can also try LiveData with lupajz's debounce extension

Or you can also roll your own solution.

You can achieve the same functionality using Rx Java's debounce operator. every time you will enter a text in Edit Text Rx Java's debounce will call the webservice and quickly will produce result and if Again user enter another text it will then call the web service.

Please refer the below link for batter understaing , So you can modify your code and achieve same functinolaity

https://blog.mindorks.com/implement-search-using-rxjava-operators-c8882b64fe1d

RxSearchObservable.fromView(searchView)
        .debounce(300, TimeUnit.MILLISECONDS)
        .filter(new Predicate<String>() {
            @Override
            public boolean test(String text) {
                if (text.isEmpty()) {
                    textViewResult.setText("");
                    return false;
                } else {
                    return true;
                }
            }
        })
        .distinctUntilChanged()
        .switchMap(new Function<String, ObservableSource<String>>() {
            @Override
            public ObservableSource<String> apply(String query) {
                return dataFromNetwork(query)
                        .doOnError(throwable -> {
                            // handle error
                        })
                        // continue emission in case of error also
                        .onErrorReturn(throwable -> "");
            }
        })
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Consumer<String>() {
            @Override
            public void accept(String result) {
                textViewResult.setText(result);
            }
        });
Related