Debugging log · Threading
"Only the original thread that created a view hierarchy can touch its views"
A network call finished, I set the result on a TextView, and the app crashed with CalledFromWrongThreadException and that long sentence about the original thread. The data was right there; updating the view should have been trivial. The catch: I was still on the background thread the network call ran on, and Android only lets the main thread touch views.
The short version: you updated a view from a background thread. Android's UI toolkit isn't thread-safe, so only the main (UI) thread may touch views. Do the background work off-thread, then switch back to main — with runOnUiThread, a main-thread Handler, or a coroutine on the main dispatcher — before updating the UI.
The error
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Why Android is strict about this
The view system isn't designed to be touched from multiple threads at once. If two threads modified the same view, you'd get race conditions and corrupted UI state that are nearly impossible to debug. Rather than make every view thread-safe (slow and complex), Android enforces a simple rule: all UI work happens on one thread, the main thread. The crash is the framework catching you breaking that rule early, instead of letting it cause intermittent glitches later. The flip side is the companion rule — don't do slow work on the main thread — so the correct shape is always "work off-thread, update on-thread."
Fix with a coroutine (modern Kotlin)
Switch back to the main dispatcher before touching the view:
lifecycleScope.launch {
val data = withContext(Dispatchers.IO) {
fetchData() // background
}
textView.text = data // back on main — safe
}
Because the launch runs on the main dispatcher by default, the line after withContext is already back on the UI thread. This is the cleanest version and the reason coroutines are so common for this pattern.
Fix with runOnUiThread (from an Activity)
someBackgroundWork {
runOnUiThread {
textView.text = result
}
}
When you're in an Activity and not using coroutines, runOnUiThread posts the UI update to the main thread. Simple and effective for a one-off.
Fix with a Handler (anywhere)
Handler(Looper.getMainLooper()).post {
textView.text = result
}
A Handler bound to the main looper works from any context — a plain thread, a callback, a place with no Activity reference. It's the most general tool when the others don't fit.
The mental model that prevents this entirely: compute off the main thread, display on it. Any line that sets text, changes visibility, or otherwise touches a view must run on main. If you're not sure which thread you're on, you're probably on the wrong one — switch explicitly before the UI line.
Common places it bites
- Network/database callbacks that run on a background thread, then update UI directly.
- Timers and background threads you started yourself.
- Third-party SDK callbacks that don't promise to call you back on main.
- A Toast or dialog shown from a worker thread — same rule applies.
View and method names above are illustrative — apply the pattern to your own code.