1

I currently have an EditText for the user to enter a search. I'm trying to use RxJava with debounce to only search every so often, instead of each character. However, I'm getting an InterruptedIOException while I'm testing, which kills the stream.

private val subject = BehaviorSubject.create<String>()

init {
    configureAutoComplete()
}

private fun configureAutoComplete() {
    subject.debounce(200, TimeUnit.MILLISECONDS)
        .flatMap {
            getSearchResults(query = it)
        }
        .subscribe({ result ->
            handleResult(result)
        }, { t: Throwable? ->
            Logger.e(t, "Failed to search")
        })
}

fun getSearchResults(query: String): Observable<List<MyObject>> {
    val service = NetworkService.create() // get retrofit service
    return service.search(query)
}


fun search(text: String) {
    subject.onNext(text)
}

As you can see, I'm creating a BehaviorSubject, and within init I'm setting it up with debounce.

getSearchResult returns an Observable and does my network request.

But as I'm testing, if I type at a specific rate ( usually quick-ish, like typing another character while the request is ongoing ) it'll throw an Exception.

 Failed to search : java.io.InterruptedIOException
    at okhttp3.internal.http2.Http2Stream.waitForIo(Http2Stream.java:579)
    at okhttp3.internal.http2.Http2Stream.takeResponseHeaders(Http2Stream.java:143)
    at okhttp3.internal.http2.Http2Codec.readResponseHeaders(Http2Codec.java:125)

I was looking at this, https://stackoverflow.com/a/47276430/3106174, and it seems like I'm doing everything correctly.

advice
  • 5,778
  • 10
  • 33
  • 60
  • Can you add the source code for `getSearchResult()`? – Danail Alexiev Nov 09 '18 at 20:19
  • @DanailAlexiev I've added some details for `getSearchResult`, it's just making a network call via Retrofit, returning an `Observerable` that contains a `List`. – advice Nov 09 '18 at 20:58
  • I would start by increasing the debounce interval and add `distinctUntilChanged()` and `filter()` to remove any empty input. I think your problem may be related to unsubscribing before the HTTP request completes, causing an interruption. – Danail Alexiev Nov 09 '18 at 21:14
  • @DanailAlexiev I tried the 3 suggestions, increasing the debounce interval did make it harder to reproduce, but it still happens if I time it right. It does seem to be related to the HTTP request. Should I be cancelling the in-flight HTTP request if the user types again? – advice Nov 09 '18 at 21:25
  • Also, I realized that if I remove the `debounce`, I'll get a `NetworkOnMainThreadException`. That doesn't seem right. I feel like I have something wrong with my setup. This is in a `ViewModel`, btw. – advice Nov 09 '18 at 21:38
  • You can try using `service.search(query).subscribeOn(Schedulers.io).observeOn(AndroidShedulers.mainThread()`. This will execute the network call on the main thread and deliver it's result on the main thread. – Danail Alexiev Nov 09 '18 at 22:14
  • @DanailAlexiev I actually managed to figure it out by forcing the subject onto `Scheduler.io`. I've thrown down my answer below. Thanks for your help! – advice Nov 09 '18 at 22:27

1 Answers1

0

After more testing, I realized that the network request was on the main thread.

You can test this by replacing your network call with Observerable.create{ ... } and throwing a Thread.sleep(1000) inside.

I was following this tutorial, https://proandroiddev.com/building-an-autocompleting-edittext-using-rxjava-f69c5c3f5a40, and one of the comments mention this issue.

"But I think one thing is misleading in your code snippet, and it’s that subjects aren’t thread safe. And the thread that your code will run on will be the thread that you emitting on (in this case the main thread). "

To solve this issue, you need to force it to run on Schedulers.io(). Make sure it's after the debounce or it won't work.

private fun configureAutoComplete() {
    subject.debounce(200, TimeUnit.MILLISECONDS)
        .observeOn(Schedulers.io()) // add this here
        .distinctUntilChanged()
        .switchMap {
            getSearchResults(query = it)
        }
        .subscribe({ result ->
            handleResult(result)
        }, { t: Throwable? ->
            Logger.e(t, "Failed to search")
        })
}
advice
  • 5,778
  • 10
  • 33
  • 60