fix: remove runBlocking(Dispatchers.Main) deadlock — app now starts

- NetworkBinder functions were suspend but never actually suspended
  (ConnectivityManager calls are synchronous) — made them plain fun
- MainActivity.discoverNetworks() now calls them directly, no coroutine
- Wrapped in try/catch so network discovery failure doesn't crash Activity
- Removed unused awaitNetwork() extension + coroutine imports
This commit is contained in:
Mathieu Moran
2026-08-12 00:54:27 -04:00
parent 40f92d8703
commit f1606ad1f6
2 changed files with 16 additions and 49 deletions
@@ -1,30 +1,20 @@
package com.yarisobd.app package com.yarisobd.app
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.yarisobd.app.ui.theme.YarisOBDTheme import com.yarisobd.app.ui.theme.YarisOBDTheme
import com.yarisobd.app.util.NetworkBinder import com.yarisobd.app.util.NetworkBinder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
// Discovered at startup — null if the network isn't available
private var piClient: OkHttpClient? = null private var piClient: OkHttpClient? = null
private var serverClient: OkHttpClient? = null private var serverClient: OkHttpClient? = null
@@ -32,8 +22,6 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
// Discover networks synchronously at startup (fast — just reads
// ConnectivityManager state, no network I/O)
discoverNetworks() discoverNetworks()
setContent { setContent {
@@ -52,17 +40,22 @@ class MainActivity : ComponentActivity() {
} }
private fun discoverNetworks() { private fun discoverNetworks() {
val context = applicationContext try {
val piNetwork = NetworkBinder.findPiHotspotNetwork(applicationContext)
// Blocking is fine here — NetworkBinder discovery just reads
// ConnectivityManager state, no network calls.
runBlocking(Dispatchers.Main) {
val piNetwork = NetworkBinder.findPiHotspotNetwork(context)
piClient = piNetwork?.let { NetworkBinder.createBoundClient(it) } piClient = piNetwork?.let { NetworkBinder.createBoundClient(it) }
Log.d(TAG, "Pi WiFi network: ${if (piClient != null) "bound" else "not found"}")
val cellNetwork = NetworkBinder.findCellularNetwork(context) val cellNetwork = NetworkBinder.findCellularNetwork(applicationContext)
?: NetworkBinder.findDefaultNetwork(context) ?: NetworkBinder.findDefaultNetwork(applicationContext)
serverClient = cellNetwork?.let { NetworkBinder.createBoundClient(it) } serverClient = cellNetwork?.let { NetworkBinder.createBoundClient(it) }
Log.d(TAG, "Cellular/default network: ${if (serverClient != null) "bound" else "not found"}")
} catch (e: Exception) {
Log.e(TAG, "Network discovery failed", e)
// Both clients stay null — screen shows "not found" state
} }
} }
companion object {
private const val TAG = "MainActivity"
}
} }
@@ -4,16 +4,10 @@ import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.Network import android.net.Network
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import android.net.NetworkRequest
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Dns import okhttp3.Dns
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import java.net.InetAddress import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Proxy import java.net.Proxy
import java.net.Socket
import javax.net.SocketFactory
import kotlin.coroutines.resume
/** /**
* Discovers and binds OkHttp clients to specific Android [Network] objects * Discovers and binds OkHttp clients to specific Android [Network] objects
@@ -30,7 +24,7 @@ object NetworkBinder {
* Identified by: TRANSPORT_WIFI + NO NET_CAPABILITY_INTERNET * Identified by: TRANSPORT_WIFI + NO NET_CAPABILITY_INTERNET
* (the Pi hotspot has no internet gateway). * (the Pi hotspot has no internet gateway).
*/ */
suspend fun findPiHotspotNetwork(context: Context): Network? { fun findPiHotspotNetwork(context: Context): Network? {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
return cm.allNetworks.firstOrNull { network -> return cm.allNetworks.firstOrNull { network ->
@@ -44,7 +38,7 @@ object NetworkBinder {
* Finds the cellular network. * Finds the cellular network.
* Identified by: TRANSPORT_CELLULAR + NET_CAPABILITY_INTERNET * Identified by: TRANSPORT_CELLULAR + NET_CAPABILITY_INTERNET
*/ */
suspend fun findCellularNetwork(context: Context): Network? { fun findCellularNetwork(context: Context): Network? {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
return cm.allNetworks.firstOrNull { network -> return cm.allNetworks.firstOrNull { network ->
@@ -98,23 +92,3 @@ object NetworkBinder {
} }
} }
} }
/**
* Waits for a network with the given [transport] to become available.
* Used at app startup to wait for the Pi hotspot to connect before
* making API calls.
*/
suspend fun ConnectivityManager.awaitNetwork(transport: Int): Network {
return suspendCancellableCoroutine { continuation ->
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
continuation.resume(network)
}
}
val request = NetworkRequest.Builder()
.addTransportType(transport)
.build()
registerNetworkCallback(request, callback)
continuation.invokeOnCancellation { unregisterNetworkCallback(callback) }
}
}