118

In Android N, it is mentioned on the official website that "Apps targeting Android N do not receive CONNECTIVITY_ACTION broadcasts". And it is also mentioned that JobScheduler can be used as an alternative. But the JobScheduler doesn't provide exactly the same behavior as CONNECTIVITY_ACTION broadcast.

In my Android application, I was using this broadcast to know the network state of the device. I wanted to know if this state was CONNECTING or CONNECTED with the help of CONNECTIVITY_ACTION broadcast and it was best suited for my requirement.

Now that it is deprecated, can any one suggest me the alternative approach to get current network state?

IAmInPLS
  • 4,051
  • 4
  • 24
  • 57
Raghuram db
  • 1,471
  • 3
  • 13
  • 19
  • 10
    And what if the OP someday wants some behavior that requires upping the `targetSdkVersion` to N or later? – Michael Apr 05 '16 at 10:17
  • 1
    Well , I too know that if I don't target my application to Android N I will receive the broadcast. But my application needs to support Android N. How can I get the same broadcast behavior in Android N? Is there any other approach I can try? @DavidWasser – Raghuram db Apr 06 '16 at 05:25
  • Sometimes I think it makes more sense worry about the future in the future. This is purely a pragmatic approach to programming. Of course, you can always try to make sure that your code doesn't use any deprecated features. On the other hand, deprecated features usually stay around for a long time and it may be that your app will be end-of-lifed before the deprecated features go away. Android N is so new that I wouldn't spend a lot of time worrying about it. Yet. Just my 2 cents. Please note that I wrote a comment to the question and didn't suggest that "don't do that" was a valid answer. – David Wasser Apr 06 '16 at 10:03
  • 2
    @Raghuramdb Your app can run on Android N even if you don't target your app to Android N. You only have to target Android N if you want to use features that are only available in Android N. – David Wasser Apr 06 '16 at 10:19
  • 2
    You can still use the `BroadcastReceiver` with the `android.net.conn.CONNECTIVITY_CHANGE` intent filter even when targeting API29, you just need to register it in `Application.OnCreate`. You will just not get any updates when the app is closed. – Pierre Jun 10 '19 at 12:04
  • @Pierre How to register it in Application.onCreate ? – SimpleGuy Dec 07 '20 at 09:13
  • 1
    @SimpleGuy Implement an Application class `public class MainApplication extends Application implements Application.ActivityLifecycleCallbacks {...}` override `public void onCreate() {` and in there call `IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); registerReceiver(new MyBroadcastReceiver(), intentFilter);` Dont forget in your `AndroidManifest.xml` to add `android:name` to application `` – Pierre Dec 07 '20 at 09:53

15 Answers15

116

What will be deprecated is the ability for a backgrounded application to receive network connection state changes.

As David Wasser said you can still get notified of connectivity changes if:

  • The app or service is in "foreground" state (like app's view being open, or foreground-service's notification being up).
  • And you have registered your receiver programmatically with its context, instead of doing it in the manifest, like:
    IntentFilter filter = new IntentFilter();
    @SuppressWarnings({"deprecation", "RedundantSuppression"})
    String action = ConnectivityManager.CONNECTIVITY_ACTION;
    filter.addAction(action);
    myContext.registerReceiver(myReceiver, filter);
    

Alternative

If you don't like @SuppressWarnings part, or think even programmatically registered receiver may stop working (in some future Android version), then continue reading:

You can use NetworkCallback instead, which requires said foreground state as well. In particular, you will need to override onAvailable for connected state changes.

Let me draft a snippet quickly:

public class ConnectionStateMonitor extends NetworkCallback {
   
   final NetworkRequest networkRequest;
   
   public ConnectionStateMonitor() {
       networkRequest = new NetworkRequest.Builder()
           .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
           .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
           .build();
   }

   public void enable(Context context) {
       ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
       connectivityManager.registerNetworkCallback(networkRequest, this);
   }

   // Likewise, you can have a disable method that simply calls ConnectivityManager.unregisterNetworkCallback(NetworkCallback) too.

   @Override
   public void onAvailable(Network network) {
       // Do what you need to do here,
       // or instead, override `onUnavailable()` or `onLost()`,
       // (to detect absense of network).
   }
}
Top-Master
  • 7,611
  • 5
  • 39
  • 71
Amokrane Chentir
  • 29,907
  • 37
  • 114
  • 158
  • 3
    Since this technique will only able to work, if the app is running in foreground. Does that mean, we no longer have ability to listen to connection event, when app is not running in foreground? Having in manifest.xml no longer has any effect in Android N. – Cheok Yan Cheng Nov 06 '16 at 13:22
  • 3
    @CheokYanCheng AFAIK that's correct. You need to have a process that runs in the foreground to listen for connectivity events. It seems that the assumption that was made by the Android framework engineers was that listening to connectivity events was mostly done in order to know when to start syncing data between client & server. Therefore, JobScheduler is the recommended way for that use case. – Amokrane Chentir Nov 06 '16 at 20:06
  • 54
    lol what the hell, another 10 android updates and all we will be able to write is a hello world app – DennisVA Aug 29 '18 at 13:49
  • 2
    Do I need to unregister NetworkCallback (for example, in activity's onDestroy method)? – Ruslan Berozov Oct 30 '18 at 12:59
  • 2
    @Ruslan yes ofcourse, or you will leak whatever is registered – DennisVA Feb 13 '19 at 12:31
55

I will update Sayem's answer for fix lint issues its showing to me.

class ConnectionLiveData(val context: Context) : LiveData<Boolean>() {

    private var connectivityManager: ConnectivityManager = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager

    private lateinit var connectivityManagerCallback: ConnectivityManager.NetworkCallback

    private val networkRequestBuilder: NetworkRequest.Builder = NetworkRequest.Builder()
        .addTransportType(android.net.NetworkCapabilities.TRANSPORT_CELLULAR)
        .addTransportType(android.net.NetworkCapabilities.TRANSPORT_WIFI)

    override fun onActive() {
        super.onActive()
        updateConnection()
        when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> connectivityManager.registerDefaultNetworkCallback(getConnectivityMarshmallowManagerCallback())
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> marshmallowNetworkAvailableRequest()
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> lollipopNetworkAvailableRequest()
            else -> {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                    context.registerReceiver(networkReceiver, IntentFilter("android.net.conn.CONNECTIVITY_CHANGE")) // android.net.ConnectivityManager.CONNECTIVITY_ACTION
                }
            }
        }
    }

    override fun onInactive() {
        super.onInactive()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            connectivityManager.unregisterNetworkCallback(connectivityManagerCallback)
        } else {
            context.unregisterReceiver(networkReceiver)
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private fun lollipopNetworkAvailableRequest() {
        connectivityManager.registerNetworkCallback(networkRequestBuilder.build(), getConnectivityLollipopManagerCallback())
    }

    @TargetApi(Build.VERSION_CODES.M)
    private fun marshmallowNetworkAvailableRequest() {
    connectivityManager.registerNetworkCallback(networkRequestBuilder.build(), getConnectivityMarshmallowManagerCallback())
    }

    private fun getConnectivityLollipopManagerCallback(): ConnectivityManager.NetworkCallback {
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
           connectivityManagerCallback = object : ConnectivityManager.NetworkCallback() {
               override fun onAvailable(network: Network?) {
                   postValue(true)
               }

               override fun onLost(network: Network?) {
                   postValue(false)
               }
           }
           return connectivityManagerCallback
       } else {
           throw IllegalAccessError("Accessing wrong API version")
       }
    }

    private fun getConnectivityMarshmallowManagerCallback(): ConnectivityManager.NetworkCallback {
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
          connectivityManagerCallback = object : ConnectivityManager.NetworkCallback() {
            override fun onCapabilitiesChanged(network: Network?, networkCapabilities: NetworkCapabilities?) {
                networkCapabilities?.let { capabilities ->
                    if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
                        postValue(true)
                    }
                }
            }
            override fun onLost(network: Network?) {
                postValue(false)
            }
         }
         return connectivityManagerCallback
       } else {
         throw IllegalAccessError("Accessing wrong API version")
       }
    }

    private val networkReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            updateConnection()
        }
    }

    private fun updateConnection() {
        val activeNetwork: NetworkInfo? = connectivityManager.activeNetworkInfo
        postValue(activeNetwork?.isConnected == true)
    }
}

And same usage:

    val connectionLiveData = ConnectionLiveData(context)
    connectionLiveData.observe(this, Observer { isConnected ->
           isConnected?.let {
             // do job
           }
    })

Btw thanks sayem for your solution.

android developer
  • 114,585
  • 152
  • 739
  • 1,270
Kebab Krabby
  • 1,574
  • 13
  • 21
30

The documentation for Android N states:

Apps targeting Android N do not receive CONNECTIVITY_ACTION broadcasts, even if they have manifest entries to request notification of these events. Apps running in the foreground can still listen for CONNECTIVITY_CHANGE on their main thread if they request notification with a BroadcastReceiver.

This means that you can still register a BroadcastReceiver if your app is running in the foreground, in order to detect changes in the network connectivity.

David Wasser
  • 93,459
  • 16
  • 209
  • 274
  • does this mean that the app will stop receiving broadcasts once it's not in the foreground? (so i can't listen for it in a service for example?) – sundie Apr 11 '16 at 08:59
  • 1
    I don't know for sure, I would need to test it to be sure. However, reading the documentation would appear that if your app is not in the foreground, then you wouldn't get the broadcast `Intent`. – David Wasser Apr 11 '16 at 14:50
  • 2
    But to detect connectivity change in background is mandatory for any sip-apps (VoIP) ... those apps are normally running in background for days and jump to foreground only if a call comes in (just like your dialer of the phone)... Those apps *need* to reconnect automatically in background. This kills all those apps (that don't have their own push server) from the android platform as they will be offline. always. – Grisgram Jan 16 '17 at 09:21
  • just use firebase push service. – Pierre Jun 10 '19 at 12:11
  • @Deprecated public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; – Juan Ignacio Barisich Sep 16 '19 at 16:55
  • OMG was this quote confusing! Did search hours for how to listen to "`CONNECTIVITY_CHANGE`" instead of deprecated one, but in the end that is just the value of "`CONNECTIVITY_ACTION`" variable. I hope people stop copy/pasting docs, I mean, if that was helpful we wouldn't be here. – Top-Master Feb 14 '22 at 10:46
25

Please check first @Amokrane Chentir answer for android N support.

For those who wants to support in all api level & observe it in ui, please check bellow code.

LiveData of NetworkConnection:

class ConnectionLiveData(val context: Context) : LiveData<Boolean>(){

    var  intentFilter = IntentFilter(CONNECTIVITY_ACTION)
    private var  connectivityManager = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
    private lateinit var networkCallback : NetworkCallback

    init {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            networkCallback = NetworkCallback(this)
        }
    }

    override fun onActive() {
        super.onActive()
        updateConnection()
        when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> connectivityManager.registerDefaultNetworkCallback(networkCallback)
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> {
                val builder = NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).addTransportType(TRANSPORT_WIFI)
                connectivityManager.registerNetworkCallback(builder.build(), networkCallback)
            }
            else -> {
                context.registerReceiver(networkReceiver, intentFilter)
            }
        }
    }

    override fun onInactive() {
        super.onInactive()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            connectivityManager.unregisterNetworkCallback(networkCallback)
        } else{
            context.unregisterReceiver(networkReceiver)
        }
    }


    private val networkReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            updateConnection()
        }
    }

    fun updateConnection() {
        val activeNetwork: NetworkInfo? = connectivityManager.activeNetworkInfo
        postValue(activeNetwork?.isConnectedOrConnecting == true)
    }

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    class NetworkCallback(val liveData : ConnectionLiveData) : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network?) {
            liveData.postValue(true)
        }

        override fun onLost(network: Network?) {
            liveData.postValue(false)
        }
    }
}

observe in UI (Activity/Fragment):

val connectionLiveData = ConnectionLiveData(context)
    connectionLiveData.observe(this, Observer { 
       // do whatever you want with network connectivity change 
})
JJD
  • 50,076
  • 60
  • 203
  • 339
Sayem
  • 4,891
  • 3
  • 28
  • 43
  • btw, you don't need to define `IntentFilter` explicitly. Like so: `var intentFilter = IntentFilter(CONNECTIVITY_ACTION)` – Ryan Amaral Sep 23 '18 at 16:30
  • thanks for your suggestion. I didn't want to create object everytime in onActive. – Sayem Sep 24 '18 at 01:54
9

Based on @KebabKrabby's answer:

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Context.CONNECTIVITY_SERVICE
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.ConnectivityManager.CONNECTIVITY_ACTION
import android.net.ConnectivityManager.EXTRA_NO_CONNECTIVITY
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.os.Build
import androidx.lifecycle.LiveData

class ConnectivityWatcher(
    private val context: Context
): LiveData<Boolean>() {

    private lateinit var networkCallback: ConnectivityManager.NetworkCallback
    private lateinit var broadcastReceiver: BroadcastReceiver

    override fun onActive() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
            networkCallback = createNetworkCallback()
            cm.registerDefaultNetworkCallback(networkCallback)
        } else {
            val intentFilter = IntentFilter(CONNECTIVITY_ACTION)
            broadcastReceiver = createBroadcastReceiver()
            context.registerReceiver(broadcastReceiver, intentFilter)
        }
    }

    override fun onInactive() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
            cm.unregisterNetworkCallback(networkCallback)
        } else {
            context.unregisterReceiver(broadcastReceiver)
        }
    }

    private fun createNetworkCallback() = object : ConnectivityManager.NetworkCallback() {

        override fun onCapabilitiesChanged(
            network: Network,
            networkCapabilities: NetworkCapabilities
        ) {
            val isInternet = networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)
            val isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)
            postValue(isInternet && isValidated)
        }

        override fun onLost(network: Network) {
            postValue(false)
        }
    }

    private fun createBroadcastReceiver() = object : BroadcastReceiver() {

        override fun onReceive(context: Context?, intent: Intent?) {
            val isNoConnectivity = intent?.extras?.getBoolean(EXTRA_NO_CONNECTIVITY) ?: true
            postValue(!isNoConnectivity)
        }
    }
}

And using of it almost the same as in the original answer (if observe from an Activity, for example):

ConnectivityWatcher(this).observe(this, Observer {
    Log.i("*-*-*", "is internet available? - ${if (it) "Yes" else "No"}")
})
DmitryKanunnikoff
  • 2,226
  • 2
  • 22
  • 35
  • 2
    The most helpful answer so far. The only issue with this solution is that with ConnectivityManager.CONNECTIVITY_ACTION and Broadcast Receiver it was possible to emit a false value when the app was started with an airplane mode on or mobile data / WIFI switched off. The problem is that ConnectivityManager.NetworkCallback() doesn't trigger in that situation. To overcome this you can set a default value of ConnectivityWatcher to "false" or observe and pass this value to MutableLiveData in ViewModel and using coroutines set the value to false after a certain delay (say 1000ms) if it's null – Alex Shevchyshen Nov 30 '20 at 20:29
  • @AlexShevchyshen Thank you very much for your comment! – DmitryKanunnikoff Dec 02 '20 at 17:11
7

I ran into the same problem few days back and I decided to use this library Android-Job

This library uses JobSchedular, GcmNetworkManager and BroadcastReceiver depending on which Android version the app is running on.

Starting a job is fairly easy

new JobRequest.Builder(DemoSyncJob.TAG)
            .setRequiresCharging(true)
            .setRequiresDeviceIdle(false)
            .setRequiredNetworkType(JobRequest.NetworkType.CONNECTED) // this is what gets the job done
            .build()
            .schedule();
Noman Rafique
  • 3,735
  • 26
  • 29
  • 1
    i have tried same schedler and getting exception like this You're trying to build a job with no constraints, this is not allowed. can you please help us to solve this?? – Sanket Kachhela Jan 09 '17 at 15:46
  • Using Android-Job for this purpose is really not a very good solution. It is meant to run things at a specified time, either once or periodically. It is meant to bring retro-compatibility support for Alarms and such. This goes against the whole idea of why the API changed, and reading: https://developer.android.com/training/monitoring-device-state/connectivity-monitoring.html You can quickly get a sense to the why. – pedronveloso Jul 26 '17 at 16:46
  • only problem is that in Android N it can only be scheduled for a minimum of 15 mins in the future – Fire Crow Feb 12 '18 at 19:54
4

I wrote a Kotlin implementation which is based on Sayam's answer but without LiveData. I decided to invoke the (at this point in time) latest API method (ConnectivityManager#registerDefaultNetworkCallback) which targets Android Nougat.

/**
 * 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)
        }
    }

}

Usage:

val onConnectionAvailable = TODO()
val connectivityObserver = ConnectivityObserver(context, onConnectionAvailable)
connectivityObserver.start()
connectivityObserver.stop()

or:

val onConnectionAvailable = TODO()
val onConnectionLost = TODO()
ConnectivityObserver(context, 
    onConnectionAvailable, 
    onConnectionLost, 
    shouldStopAfterFirstResponse = true
).start()

Don't forget to add the ACCESS_NETWORK_STATE permission in your AndroidManifest.xml:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

I am looking forward to reading helpful comments and improvements from you.

JJD
  • 50,076
  • 60
  • 203
  • 339
  • 1
    I had to change something for callbacks to be able to "touch views" in an Activity (context) on main thread: `(context as AppCompatActivity).runOnUiThread(object: Runnable{ override fun run() { onConnectionAvailable.invoke() } })` instead of `onConnectionAvailable.invoke()`. The same for `onConnectionLost.invoke()`. – Андрей Воробьев Feb 17 '20 at 10:36
  • Yes, depending of your use case you might need to switch threads. I would not make it part of the class but instead let the consumer of the class take care of this. But thank you for the hint. – JJD Feb 17 '20 at 11:11
3

I've decided to check on the solution here, and made some improvements and sample. I also made it avoid multiple setting of the same value so it won't cause the observer to keep getting the same value, one after another :

ConnectionLiveData.kt

class ConnectionLiveData(private val context: Context) : LiveData<Boolean>() {
    private val handler = Handler(Looper.getMainLooper())
    private lateinit var connectivityManagerCallback: ConnectivityManager.NetworkCallback
    private lateinit var networkReceiver: BroadcastReceiver

    init {
        when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> {
                connectivityManagerCallback = object : ConnectivityManager.NetworkCallback() {
                    @AnyThread
                    override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
                            networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
                            setNewValue(true)
                        }
                    }

                    override fun onAvailable(network: Network) {
                        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1)
                            setNewValue(true)
                    }

                    override fun onLost(network: Network) {
                        setNewValue(false)
                    }
                }
            }
            else -> {
                networkReceiver = object : BroadcastReceiver() {
                    override fun onReceive(context: Context, intent: Intent) {
                        updateConnection()
                    }
                }
            }
        }
    }

    /**this prevents observers to get multiple times the same value*/
    @AnyThread
    private fun setNewValue(isConnected: Boolean) {
        handler.removeCallbacksAndMessages(null)
        if (isUiThread()) {
            if (value != isConnected)
                @SuppressLint("WrongThread")
                value = isConnected
            return
        }
        handler.post {
            if (value != isConnected)
                value = isConnected
        }
    }

    @UiThread
    override fun onActive() {
        super.onActive()
        val connectivityManager: ConnectivityManager = context.getSystemService()!!
        updateConnection()
        when {
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> connectivityManager.registerDefaultNetworkCallback(connectivityManagerCallback, handler)
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> connectivityManager.registerDefaultNetworkCallback(connectivityManagerCallback)
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
                val networkRequest =
                    NetworkRequest.Builder().addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build()
                connectivityManager.registerNetworkCallback(networkRequest, connectivityManagerCallback)
            }
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> {
                val networkRequest =
                    NetworkRequest.Builder().addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build()
                connectivityManager.registerNetworkCallback(networkRequest, connectivityManagerCallback)
            }
            else -> {
                @Suppress("DEPRECATION") context.registerReceiver(networkReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
            }
        }
    }

    override fun onInactive() {
        super.onInactive()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            val connectivityManager: ConnectivityManager = context.getSystemService()!!
            connectivityManager.unregisterNetworkCallback(connectivityManagerCallback)
        } else {
            context.unregisterReceiver(networkReceiver)
        }
    }


    @Suppress("DEPRECATION")
    private fun updateConnection() {
        setNewValue(isConnectedToInternet(context))
    }

    companion object {
        @JvmStatic
        fun isUiThread(): Boolean =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) Looper.getMainLooper().isCurrentThread else Thread.currentThread() === Looper.getMainLooper().thread

        private fun isConnectedToInternet(context: Context): Boolean {
            val connectivityManager: ConnectivityManager = context.getSystemService()!!
            val activeNetwork: NetworkInfo? = connectivityManager.activeNetworkInfo
            return activeNetwork?.isConnectedOrConnecting == true
        }
    }
}

MainActivity.kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val textView = findViewById<TextView>(R.id.textView)
        ConnectionLiveData(this).observe(this) {
            textView.text = if (it) "connected" else "disconnected"
            Log.d("AppLog", "connected?$it")
        }
        val internetSettings = findViewById<View>(R.id.internetSettings)
        internetSettings.isVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
        internetSettings.setOnClickListener {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
                startActivity(Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY))
        }
        findViewById<View>(R.id.wifiSettings).setOnClickListener {
            startActivity(Intent(Settings.ACTION_WIFI_SETTINGS))
        }
        findViewById<View>(R.id.mobileDataSettings).setOnClickListener {
            startActivity(Intent(Settings.ACTION_DATA_ROAMING_SETTINGS))
        }
    }
}

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center"
    android:orientation="vertical" tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" />

    <Button
        android:id="@+id/internetSettings" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="internet settings" />

    <Button
        android:id="@+id/wifiSettings" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="wifi settings" />

    <Button
        android:id="@+id/mobileDataSettings" android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="mobile-data settings" />
</LinearLayout>

manifest requires:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Note that it has a rare issue when airplane-mode gets turned on, that temporarily it thinks there is no connection, and then the opposite, but then back again that there is no connection. Reported about this here.

android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • Perfect Answer, So far i found on web. +1 for that – Shihab Uddin Feb 21 '22 at 06:21
  • I'm not sure why this answer doesn't;t have more upvotes because it works great. I tested it with WiFi on/off and Airplane Mode on/off and it worked perfectly in all situations. **Thanks for this!!!** I'd upvote twice if I could. – Lance Samaria Aug 02 '23 at 22:40
2

Apps targeting Android N (Nougat) do not receive CONNECTIVITY_ACTION broadcasts defined in the manifest (see Svelte).

Possible Solutions:

See also Android O - Detect connectivity change in background

rds
  • 26,253
  • 19
  • 107
  • 134
1

I agree with the answer suggested by @rds.

Do keep in mind that CONNECTIVITY_ACTION is deprecated in API level 28.

If you have the requirement that Wifi state (connect/disconnect) should be detected despite app being killed and you want to target the latest version then you don't have much choice.

You need to use connectivityManager.registerNetworkCallback(networkRequest, networkCallback)

Question is that you can't use BroadcastReceiver so how then?

You can either use JobScheduler or better if WorkManager (Periodic Request). Why Periodic because if it is a OneTimeRequest then it will only be able to run once and keep listening while your app is in foreground.

Documentation says:

The callbacks will continue to be called until either the application exits or link #unregisterNetworkCallback(NetworkCallback)} is called.

Once app is killed or removed from recent apps list, networkCallback won't be able to listen.

So, you need such periodic jobs to make the app continuously listen. How much should be the duration? That's up to you and depends on case to case.

I know it is a bit ugly way but this is how it is. One challenge could be that if the user's device is in Doze mode or app is in Standby State, your job might be delayed.

Wahib Ul Haq
  • 4,185
  • 3
  • 44
  • 41
  • Also keep in mind that on some heavily customized EMUI, MIUI Android OS workManager(periodic tasks) does not have to always work correctly. – Kebab Krabby Mar 13 '19 at 17:06
1

When we register a network callback using the registerNetworkCallback method sometimes it does not trigger and sometimes it does trigger false-positive:

  1. If we start an app with internet connection the onAvailable method triggers.
  2. But if there is no internet connection on device when we start an app nothing of the NetworkCallback is called (it's very strange because of p. 1)
  3. If we have wifi connection but without internet connection onAvailable method triggers. And I think it's false-positive behavior because we expect internet connection observing.

As you see in below code by default internet connection is available and it triggers only if it changes. No false-positive triggers.

Just summarize this and this answers (but only for API >= 21):

class ConnectionManager @Inject constructor(
    private val connectivityManager: ConnectivityManager,
    private val disposable: CompositeDisposable,
    private val singleTransformer: SingleTransformer<*, *>
) : LiveData<Boolean>() {

    private var isNetworkAvailable = true

    private val builder = NetworkRequest.Builder()
        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)

    private val callback = object : ConnectivityManager.NetworkCallback() {

        override fun onAvailable(network: Network) {
            ping()
        }

        override fun onLost(network: Network) {
            ping()
        }
    }

    private fun ping() {
        disposable.add(
            Single.fromCallable {
                try {
                    val timeoutMs = 1500
                    val socket = Socket()
                    val socketAddress = InetSocketAddress("8.8.8.8", 53)

                    socket.connect(socketAddress, timeoutMs)
                    socket.close()
                    true
                } catch (e: IOException) {
                    false
                }
            }
                .compose(singleTransformer as SingleTransformer<Boolean, Boolean>)
                .subscribeBy {
                    if (isNetworkAvailable != it){
                        value = it
                        isNetworkAvailable = it
                    }
                }
        )
    }

    override fun onActive() {
        ping()
        connectivityManager.registerNetworkCallback(builder.build(), callback)
    }

    override fun onInactive() {
        disposable.clear()
        connectivityManager.unregisterNetworkCallback(callback)
    }
}

How to provide the dependencies

@Provides
fun provideTransformer(): SingleTransformer<Boolean, Boolean> {
    return SingleTransformer<Boolean, Boolean> { upstream: Single<Boolean> ->
        upstream.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
    }
}

@Singleton
@Provides
fun provideConnectivityManager(context: Context): ConnectivityManager =
        context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

@Singleton
@Provides
fun provideConnectionManager(connectivityManager: ConnectivityManager, singleTransformer: SingleTransformer<Boolean, Boolean>): ConnectionManager =
        ConnectionManager(connectivityManager, singleTransformer)

And how to use:

@Inject
lateinit var connectionManager: ConnectionManager

//....

viewLifecycleOwner.observe(connectionManager) { isInternetAvailable ->
    // TODO 
}
Kalianey
  • 2,738
  • 6
  • 25
  • 43
bitvale
  • 1,959
  • 1
  • 20
  • 27
1

Here is my solution in Java! from android LOLLIPOP to android S and above

@RequiresApi (api = Build.VERSION_CODES.LOLLIPOP)
public final class NetworkWatcher extends LiveData<Boolean> {
//    Variables
private final Context context;

private final ConnectivityManager connectivityManager;
private ConnectivityManager.NetworkCallback networkCallback;

private NetworkRequest networkRequest;

private NetworkWatcher.NetworkStateWatcherReceiver networkStateWatcherReceiver;


//    Constructors
public NetworkWatcher(@NonNull Context context){
    this.context = context;

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
        this.connectivityManager = context.getSystemService(ConnectivityManager.class);

        this.networkCallback = new ConnectivityManager.NetworkCallback(){
            @Override
            public void onLost(@NonNull Network network) {
                NetworkWatcher.super.postValue(false);
            }

            @Override
            public void onCapabilitiesChanged(@NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
                NetworkWatcher.super.postValue(
                        networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
                                networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
                );
            }
        };
        this.networkRequest = new NetworkRequest.Builder()
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
                .build();
    } else {
        this.networkStateWatcherReceiver = new NetworkStateWatcherReceiver();
        this.networkStateWatcherReceiver.setOnNetworkChangedListener(NetworkWatcher.super::postValue);
    }
}


//    Override methods
@Override
protected void onActive() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
            this.connectivityManager.registerBestMatchingNetworkCallback(this.networkRequest, this.networkCallback, null);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
            this.connectivityManager.registerDefaultNetworkCallback(this.networkCallback);
        } else {
            this.connectivityManager.registerNetworkCallback(this.networkRequest, this.networkCallback);
        }
    } else {
        this.context.registerReceiver(
                this.networkStateWatcherReceiver,
                new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
        );
    }
}

@Override
protected void onInactive() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
        this.connectivityManager.unregisterNetworkCallback(this.networkCallback);
    } else {
        this.context.unregisterReceiver(this.networkStateWatcherReceiver);
    }
}



//    Inner method classes
@TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
private static final class NetworkStateWatcherReceiver extends BroadcastReceiver {
    //        Variables
    private NetworkStateWatcherReceiver.OnNetworkChangedListener onNetworkChangedListener;


    //        Constructors
    public NetworkStateWatcherReceiver() {
    }


    //        Override methods
@Override
    public void onReceive(@NonNull Context context, @NonNull Intent intent) {
        if (this.onNetworkChangedListener != null) {
            boolean isConnected = this.isConnected(context);
            this.onNetworkChangedListener.onNetworkChangedListener(isConnected);
        }
    }


    //        Methods
    private boolean isConnected(@NonNull Context context){
        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();

        return networkInfo != null && networkInfo.isConnected();
    }
    public void setOnNetworkChangedListener(@Nullable OnNetworkChangedListener onNetworkChangedListener) {
        this.onNetworkChangedListener = onNetworkChangedListener;
    }



    //        Inner interfaces
private interface OnNetworkChangedListener{
        void onNetworkChangedListener(boolean isConnected);
    }
}
}

Use it in your main activity inside onCreate method

new NetworkWatcher(this).observe(this, aBoolean -> {
        if (aBoolean){
            // Internet connection available
        } else {
           // No internet connection
        }
    });
0

You can easily use the library com.github.vladan29:internet_checker:1.0.3. With that library no need to know reactive programming and think about removing observers. Only a few lines of code will provide continuous and safe checking of internet connection.

You can find all necessary instructions at the: https://github.com/vladan29/internet_checker/blob/master/README.md#internet_checker

0

Nothing fancy in this I just converted the above answer to java as there are no answers with java.

  public class ConnectivityWatcher extends LiveData<Boolean> {
    private static final String TAG = "ConnectivityWatcher";
    private final ConnectivityManager connectivityManager;
    private ConnectivityManager.NetworkCallback connectivityManagerCallback;
    private final NetworkRequest.Builder networkRequestBuilder;
    @NotNull
    private final Context context;

    protected void onActive() {
        super.onActive();
        this.updateConnection();
        if (Build.VERSION.SDK_INT >= 24) {
            try {
                this.connectivityManager.registerDefaultNetworkCallback(this.getConnectivityMarshmallowManagerCallback());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        } else if (Build.VERSION.SDK_INT >= 23) {
            try {
                this.marshmallowNetworkAvailableRequest();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        } else if (Build.VERSION.SDK_INT >= 21) {
            try {
                this.lollipopNetworkAvailableRequest();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

    }

    protected void onInactive() {
        super.onInactive();
        Log.e(TAG, "onInactive: I am inActive ");
        if (Build.VERSION.SDK_INT >= 21) {
            connectivityManager.unregisterNetworkCallback(connectivityManagerCallback);
        }

    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void lollipopNetworkAvailableRequest() throws IllegalAccessException {
        this.connectivityManager.registerNetworkCallback(this.networkRequestBuilder.build(), this.getConnectivityLollipopManagerCallback());
    }

    @TargetApi(Build.VERSION_CODES.M)
    private void marshmallowNetworkAvailableRequest() throws IllegalAccessException {
        this.connectivityManager.registerNetworkCallback(this.networkRequestBuilder.build(), this.getConnectivityMarshmallowManagerCallback());
    }

    private ConnectivityManager.NetworkCallback getConnectivityLollipopManagerCallback() throws IllegalAccessException {
        if (Build.VERSION.SDK_INT >= 21) {
            this.connectivityManagerCallback = new ConnectivityManager.NetworkCallback() {
                public void onAvailable(@NotNull Network network) {
                    postValue(true);
                }

                public void onLost(@NotNull Network network) {
                    postValue(false);
                }
            };

            return this.connectivityManagerCallback;
        } else {
            throw new IllegalAccessException();
        }
    }

    private ConnectivityManager.NetworkCallback getConnectivityMarshmallowManagerCallback() throws IllegalAccessException {
        if (Build.VERSION.SDK_INT >= 23) {
            this.connectivityManagerCallback = new ConnectivityManager.NetworkCallback() {
                public void onCapabilitiesChanged(@NotNull Network network, @NotNull NetworkCapabilities networkCapabilities) {
                    if (connectivityManager != null) {
                        NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());
                        if (capabilities != null) {
                            if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
                                postValue(true);
                            }
                        }
                    }
                }

                public void onLost(@NotNull Network network) {
                    postValue(false);
                }
            };

            return this.connectivityManagerCallback;
        } else {
            throw new IllegalAccessException();
        }
    }

    private void updateConnection() {
        boolean isConnected;
        NetworkInfo activeNetwork = this.connectivityManager.getActiveNetworkInfo();
        if (activeNetwork != null) {
            isConnected = activeNetwork.isConnected();
        } else {
            isConnected = false;
        }
        this.postValue(isConnected);
    }

    @NotNull
    public final Context getContext() {
        return this.context;
    }

    public ConnectivityWatcher(@NotNull Context context) {
        this.context = context;
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (cm == null) {
            throw new NullPointerException("null cannot be cast to non-null type android.net.ConnectivityManager");
        } else {
            this.connectivityManager = cm;
            this.networkRequestBuilder = (new NetworkRequest.Builder()).addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR).addTransportType(NetworkCapabilities.TRANSPORT_WIFI).addTransportType(NetworkCapabilities.TRANSPORT_VPN);
        }
    }
}
developer
  • 189
  • 2
  • 6
0

While overriding onAvailable(Network network) does work sometimes, when onAvailable() is called, the new network might not be fully functional yet. I ended up using the following as per this doc https://developer.android.com/reference/android/net/ConnectivityManager.OnNetworkActiveListener

connectivityManager.addDefaultNetworkActiveListener()