Debugging log · Threading
NetworkOnMainThreadException
A quick test build crashed the instant it tried to fetch data, with android.os.NetworkOnMainThreadException. I'd written the network call inline where it was convenient — right in a click handler — and Android refused to run it there. This is one of the first threading lessons the platform teaches, and it teaches it with a hard crash rather than a warning.
The short version: Android forbids network calls on the main (UI) thread because they block the screen. The fix is to run the network work on a background thread and deliver the result back to the UI — with coroutines on Dispatchers.IO, or a background executor. Do not defeat the check with a thread policy override.
The error
android.os.NetworkOnMainThreadException at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork
Why Android blocks this
The main thread is the one that draws your UI and handles taps. A network call can take hundreds of milliseconds to several seconds, and while it runs, that thread can do nothing else — the screen freezes, taps pile up, and if it drags on, the system shows an "Application Not Responding" dialog. Rather than let apps ship that experience, Android throws immediately on any network on the main thread, turning a janky release into an obvious crash in development. It's a guardrail, not an obstacle.
The fix with coroutines (modern Kotlin)
Run the network work on the IO dispatcher and come back to the main thread with the result:
lifecycleScope.launch {
val data = withContext(Dispatchers.IO) {
api.fetchData() // runs off the main thread
}
textView.text = data // back on main, safe to touch UI
}
The withContext(Dispatchers.IO) block moves just the blocking call off the UI thread; when it returns, you're back on main and can update views safely. This is the standard shape, and libraries like Retrofit integrate with it directly via suspend functions.
The fix without coroutines (executor)
On a Java codebase or older project, use a background executor and post the result back:
Executors.newSingleThreadExecutor().execute(() -> {
String data = api.fetchData(); // background
runOnUiThread(() -> textView.setText(data)); // UI
});
Same principle: the fetch happens off-thread, and the UI update is posted back to the main thread.
Never "fix" this with StrictMode.ThreadPolicy permitAll. It silences the exception by allowing network on the main thread — which brings back the exact UI freezes and ANRs the check exists to prevent. It's the most-copied bad answer for this error. Move the work off the thread instead; it's barely more code.
Things that trip people up
- It's not just HTTP. Opening a socket, resolving DNS, or any blocking I/O on main can trigger it.
- Touch UI only on main. After the background call, switch back before updating views — touching a view off the main thread is its own crash.
- Don't leak the scope. Use a lifecycle-aware scope (
lifecycleScope,viewModelScope) so the work is cancelled when the screen goes away. - Database work belongs off-main too. The same reasoning applies to disk I/O; Room will warn or crash for main-thread queries.
API and view names above are illustrative — apply the pattern to your own code.