CONNECTIVITY_ACTION intent received twice when Wifi connected

Solution 1:

NOTE: For a recent, up-to-date answer, see this one below!

After a lot of googling and debugging, I believe this is the correct way to determine if Wifi has connected or disconnected.

The onReceive() method in the BroadcastReceiver:

public void onReceive(final Context context, final Intent intent) {

if(intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
    NetworkInfo networkInfo =
        intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
    if(networkInfo.isConnected()) {
        // Wifi is connected
        Log.d("Inetify", "Wifi is connected: " + String.valueOf(networkInfo));
    }
} else if(intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
    NetworkInfo networkInfo =
        intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
    if(networkInfo.getType() == ConnectivityManager.TYPE_WIFI &&
        ! networkInfo.isConnected()) {
        // Wifi is disconnected
        Log.d("Inetify", "Wifi is disconnected: " + String.valueOf(networkInfo));
    }
}
}

Together with the following receiver element in AndroidManifest.xml

<receiver android:name="ConnectivityActionReceiver"
    android:enabled="true" android:label="ConnectivityActionReceiver">
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
        <action android:name="android.net.wifi.STATE_CHANGE"/>
    </intent-filter>
</receiver>

Some explanation:

  • When only considering ConnectivityManager.CONNECTIVITY_ACTION, I always get two intents containing identical NetworkInfo instances (both getType() == TYPE_WIFI and isConnected() == true) when Wifi connects - the issue described in this question.

  • When only using WifiManager.NETWORK_STATE_CHANGED_ACTION, there is no intent broadcasted when Wifi disconnects, but two intents containing different NetworkInfo instances, allowing to determine one event when Wifi is connected.

NOTE: I've received one single crash report (NPE) where the intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO) returned null. So, even if it seems to be extremely rare to happen, it might be a good idea to add a null check.

Cheers, Torsten

Solution 2:

If you're listening on WifiManager.NETWORK_STATE_CHANGED_ACTION you'll receive this twice because there are 2 methods in the NetworkInfo

  • isConnectedOrConnecting()
  • isConnected()

First time isConnectedOrConnecting() returns true and isConnected() false
Second time isConnectedOrConnecting() and isConnected() return true

Cheers

Solution 3:

This is the proper way to register for connectivity changes on API 21 and higher. The following code can be placed in a base activity and that way you can expect every screen in your app (that inherits from this activity) to get these callbacks.

First, create a network callback which will monitor connectivity changes.

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private val networkCallback: ConnectivityManager.NetworkCallback = object : ConnectivityManager.NetworkCallback() {

    // Implement the callback methods that are relevant to the actions you want to take.
    // I have implemented onAvailable for connecting and onLost for disconnecting.

    override fun onAvailable(network: Network?) {
        super.onAvailable(network)
    }

    override fun onLost(network: Network?) {
        super.onLost(network)
    }
}

Then, register and unregister this callback in the relevant spots.

override fun onResume() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
        cm?.registerNetworkCallback(NetworkRequest.Builder().build(), networkCallback)
    }
}

And unregister when appropriate.

override fun onPause() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
        cm?.unregisterNetworkCallback(networkCallback)
    }
}

Notice that there is a check for Build.VERSION_CODES.LOLLIPOP. This functionality is only available in Lollipop and above. Be sure to have a plan for how to handle network status changes in Pre-Lollipop devices if you support less than API 21 in your app.

Solution 4:

Updated the code of Torsten, such that when WIFI gets disconnected, only the single appropriate broadcast is acted upon.

Used NetworkInfo.getDetailedState() == DetailedState.DISCONNECTED for the checking.

public void onReceive(final Context context, final Intent intent) {
    if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
        NetworkInfo networkInfo = intent
            .getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
        if (networkInfo.isConnected()) {
            // Wifi is connected
            Log.d("Inetify","Wifi is connected: " + String.valueOf(networkInfo));
        }
    } else if (intent.getAction().equals(
        ConnectivityManager.CONNECTIVITY_ACTION)) {
        NetworkInfo networkInfo = intent
            .getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
        if (networkInfo.getDetailedState() == DetailedState.DISCONNECTED) {
            // Wifi is disconnected
            Log.d("Inetify","Wifi is disconnected: "+String.valueOf(networkInfo));
        }
    }
}

Solution 5:

If you registered the activity as an intent listener then you will recieve the same message twice. Specifically, you need to choose whether you want to listen on the Package level (XML) or the programatic level.

If you set up a class for a broadcast reciever and attach the listen to it AND you attach an intent filter to the activity, then the message will be replicated twice.

I hope this solves your issue.