Reputation: 23384
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 ?
Upvotes: 3
Views: 1147
Reputation: 6703
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
...
Upvotes: 2
Reputation: 6988
@xiaomi's is correct regarding the debounce
operating on the computation Scheduler by default, but in order to fix your issue, you have to switch back to main thread using observeOn
operator after the debounce
:
binding.autoCompleteTextView2.addRxTextWatcher()
.debounce(400, TimeUnit.MILLISECONDS) // debounce will operate on computation scheduler
.observeOn(AndroidSchedulers.mainThread()) // change all the operators below to operate on main thread
.subscribe {
// code here will be executed on main thread
}
}
From the docs:
ObserveOn, affects the thread that the Observable will use below where that operator appears.
Upvotes: 1