https://kotlinlang.org logo
Title
t

Tobi

07/26/2019, 1:58 PM
In order to address the deprecation of
NetworkInfo#isConnectedOrConnecting
and
ConnectivityManager.CONNECTIVITY_ACTION
in API 28 I wrote the following class. https://developer.android.com/reference/android/net/NetworkInfo#isConnectedOrConnecting() https://developer.android.com/reference/android/net/ConnectivityManager#CONNECTIVITY_ACTION
/**
 * Observes network connectivity by consulting the [ConnectivityManager].
 * Observing can run infinitely or automatically be stopped after the first response is received.
 */
class ConnectivityObserver @JvmOverloads constructor(

        val context: Context,
        val onConnectionAvailable: () -> Unit,
        val onConnectionLost: () -> Unit = {},
        val shouldStopAfterFirstResponse: Boolean = false

) {

    private val connectivityManager
        get() = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    @Suppress("DEPRECATION")
    private val intentFilter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)

    private val broadCastReceiver = object : BroadcastReceiver() {

        @Suppress("DEPRECATION")
        override fun onReceive(context: Context?, intent: Intent?) {
            if (ConnectivityManager.CONNECTIVITY_ACTION != intent?.action) {
                return
            }
            val networkInfo = connectivityManager.activeNetworkInfo
            if (networkInfo != null && networkInfo.isConnectedOrConnecting) {
                onConnectionAvailable.invoke()
            } else {
                onConnectionLost.invoke()
            }
            if (shouldStopAfterFirstResponse) {
                stop()
            }
        }

    }

    private lateinit var networkCallback: ConnectivityManager.NetworkCallback

    init {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            networkCallback = object : ConnectivityManager.NetworkCallback() {

                override fun onAvailable(network: Network) {
                    super.onAvailable(network)
                    onConnectionAvailable.invoke()
                    if (shouldStopAfterFirstResponse) {
                        stop()
                    }
                }

                override fun onLost(network: Network?) {
                    super.onLost(network)
                    onConnectionLost.invoke()
                    if (shouldStopAfterFirstResponse) {
                        stop()
                    }
                }
            }
        }
    }

    fun start() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            // Decouple from component lifecycle, use application context.
            // See: <https://developer.android.com/reference/android/content/Context.html#getApplicationContext()>
            context.applicationContext.registerReceiver(broadCastReceiver, intentFilter)
        } else {
            connectivityManager.registerDefaultNetworkCallback(networkCallback)
        }
    }

    fun stop() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            context.applicationContext.unregisterReceiver(broadCastReceiver)
        } else {
            connectivityManager.unregisterNetworkCallback(networkCallback)
        }
    }

}
It can be used this way:
val onConnectionAvailable = TODO()
val connectivityObserver = ConnectivityObserver(context, onConnectionAvailable)
connectivityObserver.start()
connectivityObserver.stop()
or this way:
val onConnectionAvailable = TODO()
val onConnectionLost = TODO()
ConnectivityObserver(context, 
    onConnectionAvailable, 
    onConnectionLost, 
    shouldStopAfterFirstResponse = true
).start()
What do you think about my implementation?
If you use Activity context to instantiate it, it will behave differently pre and post N when activity is closed - pre N it will stop updating and after N it will keep doing it
@Marko Mitic Can you please explain the difference?
m

Marko Mitic

07/26/2019, 2:07 PM
Sure, if you register broadcast receiver using context of Android component with lifecycle (that can be destroyed like Activity or Service), receiver will be automatically unregistered when component is destroyed
Using application context for registering receiver would keep it registered while app is alive
t

Tobi

07/26/2019, 2:10 PM
Ah. Thank you. I will probably change that in the class.
context
->
context.applicationContext
@Marko Mitic Another topic: I noticed that retrieving the
WifiManager
requires to use the
applicationContext
. https://developer.android.com/reference/android/content/Context.html#WIFI_SERVICE I could not find any indicator that this is true for the
ConnectivityManager
. Do you know? Your topic: Where did you read that the
BroadcastReceiver
must be registered via
applicationContext
for < Android Nougat?
m

Marko Mitic

07/27/2019, 11:10 AM
yes on the last question
BroadcastReceiver doesn't have to be registered using app context, it's just that when using activity context, it might not last as long as you want it to
t

Tobi

07/27/2019, 11:12 AM
Okay. Thank you.
m

Marko Mitic

07/27/2019, 11:13 AM
WiFiManager requiring app context seems to be an implementation detail
(or a bug they first fixed in android N)
t

Tobi

07/27/2019, 11:45 AM
I updated the code. ✔️
d

dave08

08/15/2019, 8:09 AM
You could also wrap the whole thing in a coroutines Flow, which could make it even more practical (depends on your uses of it)...
👍🏻 1
I mean
callbackFlow
A bit like I did for broadcasts:
fun Context.fromBroadcast(filter: IntentFilter) = callbackFlow<Intent> {
	Log.i(TAG, "Registering receiver for $filter")

	val receiver = object : BroadcastReceiver() {
		override fun onReceive(context: Context, intent: Intent) {
			Log.i(TAG, "Got intent: $intent")

			this@callbackFlow.sendBlocking(intent)
		}
	}
	this@fromBroadcast.registerReceiver(receiver, filter)

	awaitClose { this@fromBroadcast.unregisterReceiver(receiver) }
}