kotlinkotlin-coroutinesnetworkonmainthread

NetworkOnMainThreadException while using coroutines


I've been trying to validate the network connection status when a button is pressed. Hereby the code

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    findViewById<Button>(R.id.button).setOnClickListener {
        findViewById<TextView>(R.id.logText).setText("Button Pressed"+counter++)
        GlobalScope.launch (Dispatchers.Main) {
            val result  = withContext(Dispatchers.Default){
                isInternetAvailable()
            }
            println(result.toString())
        }
    }
}

suspend fun isInternetAvailable():Boolean {
    return withContext(Dispatchers.Main) {
        try {
            val ipAddr = InetAddress.getByName("google.com")
            //You can replace it with your name
            println("1$ipAddr")
            return@withContext !ipAddr.equals("")
        } catch (e: Exception) {
            println("within exception$e")
            return@withContext false;
        }

    }
}

However, when the button is pressed following output can be seen in the console.

I/System.out: button clicked
I/System.out: within exceptionandroid.os.NetworkOnMainThreadException
I/System.out: false

Can someone please explain the reason for NetworkOnMainThread Exception?

Thanks a lot.


Solution

  • withContext(Dispatchers.Main) means to run the code in the lambda on the Main thread, which is not allowed for blocking code. For network requests, you should use Dispatchers.IO.

    Also, don't use GlobalScope for this, because it will leak your Activity. Use lifecycleScope. lifecycleScope uses the main thread by default, so when you use it, you don't have to specify a dispatcher unless you are making a blocking call.

    And it is convention to make suspend functions switch to the appropriate dispatcher whenever doing blocking calls so you can safely call them from any dispatcher. This means your suspend functions are not blocking calls, and can be called from coroutines without having to wrap them in withContext.

    So with all of the above, your code becomes:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    
        findViewById<Button>(R.id.button).setOnClickListener {
            findViewById<TextView>(R.id.logText).setText("Button Pressed"+counter++)
            lifecycleScope.launch {
                val result = isInternetAvailable()
                println(result.toString())
            }
        }
    }
    
    suspend fun isInternetAvailable(): Boolean {
        return withContext(Dispatchers.IO){
            try {
                val ipAddr = InetAddress.getByName("google.com")
                //You can replace it with your name
                println("1$ipAddr")
                return@withContext !ipAddr.equals("")
            } catch (e: Exception) {
                println("within exception$e")
                return@withContext false;
            }
    
        }
    }