Search code examples
androidrx-java2

RxJava: getting "Only the original thread that created a view hierarchy can touch its views" even after subscribing on main thread


I am using an extensing function on autocomplete textview to use debounce strategy and subscribing on mainthread.

 binding.autoCompleteTextView2.addRxTextWatcher()
         .observeOn(AndroidSchedulers.mainThread())
         .subscribeOn(AndroidSchedulers.mainThread())
         .debounce(400, TimeUnit.MILLISECONDS)
         .subscribe {
            if (!TextUtils.isEmpty(it)) {
                        viewModel.searchTeacher(viewModel.meditationDetails?.schoolId,it)
                        binding?.etEmailOfTeacher?.visible()   //Crash at this point
                        binding?.tvEmailOfYourTeacher?.visible()
                }
       }

Extension Function :-

fun EditText.addRxTextWatcher(): Observable<String?> {
val flowable = Observable.create<String?> {
    addTextChangedListener(object : TextWatcher {
        override fun afterTextChanged(s: Editable?) {
        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            it.onNext(s?.toString())
        }

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        }
    })
}

I am getting crash when I trying to update the view visibility that "Only the original thread that created a view hierarchy can touch its views" . But I am already subscribed and observing on mainThread . What am I doing wrong here ?

Complete Log:

2019-09-27 11:51:06.878 9490-9575/com.example.example E/ACRA: ACRA caught a CalledFromWrongThreadException for com.example.example
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7959)
        at android.view.ViewRootImpl.focusableViewAvailable(ViewRootImpl.java:3866)
        at android.view.ViewGroup.focusableViewAvailable(ViewGroup.java:917)
        at android.view.ViewGroup.focusableViewAvailable(ViewGroup.java:917)
        at android.view.ViewGroup.focusableViewAvailable(ViewGroup.java:917)
        at android.view.ViewGroup.focusableViewAvailable(ViewGroup.java:917)
        at android.view.ViewGroup.focusableViewAvailable(ViewGroup.java:917)
        at android.view.ViewGroup.focusableViewAvailable(ViewGroup.java:917)
        at android.view.ViewGroup.focusableViewAvailable(ViewGroup.java:917)
        at android.view.ViewGroup.focusableViewAvailable(ViewGroup.java:917)
        at android.view.ViewGroup.focusableViewAvailable(ViewGroup.java:917)
        at android.view.ViewGroup.focusableViewAvailable(ViewGroup.java:917)
        at android.view.View.setFlags(View.java:14121)
        at android.view.View.setVisibility(View.java:10007)
        at in.eightfolds.utils.ExtentionFunctionsKt.visible(ExtentionFunctions.kt:345)
        at com.example.example.fragment.OnBordingCreateMeditationAdditionalDetailsFragment$setAutoCompleteTextView2$2.accept(OnBordingCreateMeditationAdditionalDetailsFragment.kt:283)
        at com.example.example.fragment.OnBordingCreateMeditationAdditionalDetailsFragment$setAutoCompleteTextView2$2.accept(OnBordingCreateMeditationAdditionalDetailsFragment.kt:38)
        at io.reactivex.internal.observers.LambdaObserver.onNext(LambdaObserver.java:59)
        at io.reactivex.observers.SerializedObserver.onNext(SerializedObserver.java:111)
        at io.reactivex.internal.operators.observable.ObservableDebounceTimed$DebounceTimedObserver.emit(ObservableDebounceTimed.java:142)
        at io.reactivex.internal.operators.observable.ObservableDebounceTimed$DebounceEmitter.run(ObservableDebounceTimed.java:167)
        at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:59)
        at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:51)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:764)

If I write visibility inside activity?.runOnUiThread { } then its working fine . So why am I getting exception even though I am subscribed on mainthread ?


Solution

  • The debounce operator will subscribe on the computation scheduler. Reorder your function like this:

    binding.autoCompleteTextView2.addRxTextWatcher() <-- text watch is on the main thread
             .debounce(400, TimeUnit.MILLISECONDS) <-- debounced throw the result on Computation Scheduler
             .observeOn(AndroidSchedulers.mainThread()) <-- ObserveOn throw the result on MainThread
             .subscribe {
                if (!TextUtils.isEmpty(it)) {
                            viewModel.searchTeacher(viewModel.meditationDetails?.schoolId,it)
                            binding?.etEmailOfTeacher?.visible()   //Crash at this point
                            binding?.tvEmailOfYourTeacher?.visible()
                    }
           }
    

    Ref: RxJava Debounce doc

    debounce(long, TimeUnit) is by default on the computation Scheduler

    Bonus: you can use the operator doOnNext after each operation and log which thread is used with Thread.currentThread().getName()

    ex:

    ...
    .debounce(...)
    .doOnNext{ Log.d(TAG, "Debounce on: " +  Thread.currentThread().getName()) } <-- This will show RxComputationScheduler-N
    .observeOn(AndroidSchedulers.mainThread())
    .doOnNext{ Log.d(TAG, "Debounce on: " +  Thread.currentThread().getName()) } <-- This will show main
    ...