Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect Connectivity change using JobScheduler [duplicate]

Tags:

android

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?

like image 928
Raghuram db Avatar asked Apr 05 '16 09:04

Raghuram db


8 Answers

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 component is instantiated (not destroyed) and you have registered your receiver programmatically with its context, instead of doing it in the manifest.

Or you can use NetworkCallback instead. 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
   }
}
like image 127
Amokrane Chentir Avatar answered Oct 14 '22 07:10

Amokrane Chentir


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.

like image 41
Kebab Krabby Avatar answered Oct 14 '22 05:10

Kebab Krabby


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.

like image 40
David Wasser Avatar answered Oct 14 '22 06:10

David Wasser


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 
})
like image 29
Sayem Avatar answered Oct 14 '22 07:10

Sayem


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"}")
})
like image 21
DmitryKanunnikoff Avatar answered Oct 14 '22 06:10

DmitryKanunnikoff


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();
like image 26
Noman Rafique Avatar answered Oct 14 '22 06:10

Noman Rafique


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.

like image 4
JJD Avatar answered Oct 14 '22 06:10

JJD


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

Possible Solutions:

  • Explicitly register a network callback with ConnectivityManager.registernetworkCallback() once the application is running.
  • Use a JobScheduler, and specify an unmetered network via setRequiredNetworkType().

See also Android O - Detect connectivity change in background

like image 2
rds Avatar answered Oct 14 '22 05:10

rds