my current android application has a requirement to inform the user when internet access has been lost during a user session
i employ the following callback listener:-
#OptIn(DelicateCoroutinesApi::class)
class ConnectivityListener #Inject constructor() : CoroutineScope by GlobalScope, ConnectivityManager.NetworkCallback() {
private var backgroundStateJob: Job? = null
private val internalSharedWorkState = MutableSharedFlow<BackgroundState>(replay = 1, extraBufferCapacity = 7, onBufferOverflow = BufferOverflow.DROP_OLDEST)
private val sharedWorkScheduleState: SharedFlow<BackgroundState> = internalSharedWorkState
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
Timber.e( "onBlockedStatusChanged() called with: network = $network, blocked = $blocked")
}
override fun onLosing(network: Network, maxMsToLive: Int) {
Timber.e( "onLosing() called with: network = $network, maxMsToLive = $maxMsToLive")
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
Timber.e( "onCapabilitiesChanged() called with: network = $network, networkCapabilities = $networkCapabilities")
}
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
Timber.e( "onLinkPropertiesChanged() called with: network = $network, linkProperties = $linkProperties")
}
override fun onUnavailable() {
Timber.e( "onUnavailable() called")
}
override fun onAvailable(network: Network) {
Timber.e("onAvailable() called with: network = $network")
backgroundStateJob?.cancel()
backgroundStateJob = launch(Dispatchers.IO) {
internalSharedWorkState.emit(BackgroundState.Connected())
}
}
override fun onLost(network: Network) {
Timber.e("onLost() called with: network = $network")
backgroundStateJob?.cancel()
backgroundStateJob = launch(Dispatchers.IO) {
internalSharedWorkState.emit(BackgroundState.Disconnected())
}
}
fun stateFlow(): SharedFlow<BackgroundState> {
return sharedWorkScheduleState
}
}
this listener is employed within my base activity as follow:-
private val networkRequest = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build()
...
override fun onResume() {
Timber.e("onResume() called")
super.onResume()
val connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.registerNetworkCallback(networkRequest, connectivityListener)
}
override fun onPause() {
Timber.e("onPause() called")
super.onPause()
val connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.unregisterNetworkCallback(connectivityListener)
}
the listener works as expected while the application is in the foreground and reliable informs the user on the current network status whenever internet access is lost.
the listener also works fine when i exit the application, it goes into the background and is subsequently resumed. the listener successfully reports i have internet access.
however the listener does not work as required when i put the application into the background, enable airplane mode then resume the application.
it does not detect that there is no internet access, even though it does detect when internet access is available when the app is resumed from the background and airplane mode is not enabled.
what am i doing wrong?
how can i detect the current network status when resuming my application from the background?
i managed to achieve the desired result by adding a second
network callback as follows with a timeout by employing requestNetwork()
override fun onResume() {
super.onResume()
val connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.registerNetworkCallback(networkRequest, connectivityListenerMonitor)
connectivityManager.requestNetwork(networkRequest, connectivityListener, 1000)
}
override fun onPause() {
super.onPause()
val connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.unregisterNetworkCallback(connectivityListener)
connectivityManager.unregisterNetworkCallback(connectivityListenerMonitor)
}
now when my application is in the background and i enable airplane mode
then bring my application to the foreground my users are notified that there is no internet connection.
Related
I'm trying to connect to wifi from my android application, using the wifi Network request API.
Here is the code:
val builder =
WifiNetworkSpecifier.Builder()
.setBssid(MacAddress.fromString(item.bssid))
.setWpa2Passphrase("somePassword")
if (item.ssid.isNotEmpty()) {
builder.setSsid(item.ssid)
}
val networkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setNetworkSpecifier(builder.build())
.build()
Next I do that
cm.requestNetwork(
networkRequest,
WifiConnectionCallBack(ConnectivityManager)
)
WifiConnectionCallBack is my class, what extends NetworkCallback
class WifiConnectionCallBack(
private val connectionWatcher : ConnectionWatcher,
private val bssid : String,
private val connManager: ConnectivityManager,
private val onConnection : (Boolean, Network) -> Unit
) : NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
val result = connManager.bindProcessToNetwork(network)
onConnection.invoke(result, network)
connectionWatcher.onChangeState(ConnectionState.WifiConnected(bssid, network))
}
override fun onUnavailable() {
super.onUnavailable()
connectionWatcher.onChangeState(ConnectionState.UnAvailable)
}
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
super.onLinkPropertiesChanged(network, linkProperties)
connectionWatcher.onChangeState(
ConnectionState.LinkPropertiesChanged(
bssid,
network,
linkProperties
)
)
}
override fun onLost(network: Network) {
super.onLost(network)
connectionWatcher.onChangeState(ConnectionState.Lost(network))
}
It works fine... But, when I HAVE NO access to the internet. Any suggestions, what can be wrong here?
Thank you for reading this!
I tried methods from this topic Connect to Wifi in Android Q programmatically
But it doesnt work
I have a sample in Github (https://github.com/alirezaeiii/Movies) where I have a utility class in order to check internet network connection :
class NetworkUtils(context: Context) : ConnectivityManager.NetworkCallback() {
private val networkLiveData: MutableLiveData<Boolean> = MutableLiveData()
private val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
fun getNetworkLiveData(): LiveData<Boolean> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
connectivityManager.registerDefaultNetworkCallback(this)
} else {
val builder = NetworkRequest.Builder()
connectivityManager.registerNetworkCallback(builder.build(), this)
}
var isConnected = false
connectivityManager.allNetworks.forEach { network ->
val networkCapability = connectivityManager.getNetworkCapabilities(network)
networkCapability?.let {
if (it.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
isConnected = true
return#forEach
}
}
}
networkLiveData.postValue(isConnected)
return networkLiveData
}
override fun onAvailable(network: Network) {
networkLiveData.postValue(true)
}
override fun onLost(network: Network) {
networkLiveData.postValue(false)
}
fun unRegister() {
connectivityManager.unregisterNetworkCallback(this)
}
}
In an Activity I observe in onCreate() and unRegisterNetworkCallback in onDestroy() :
override fun onCreate() {
super.onCreate(savedInstanceState)
handleNetwork()
}
override fun onDestroy() {
super.onDestroy()
networkUtils.unRegister()
}
private fun handleNetwork() {
networkUtils.getNetworkLiveData().observe(this) { isConnected: Boolean ->
if (!isConnected) {
...
} else {
Log.d("Test", "Connected")
...
}
}
}
The 1st time that I launch the app "Connected" tag will be called once, but when I rotate the device it will be called twice. Why is that?
I have another sample where I did not use Navigation architecture component in it and it get called once when I rotate :https://github.com/alirezaeiii/TMDb-Paging/blob/master/app/src/main/java/com/sample/android/tmdb/ui/BaseActivity.kt
Provider using hilt :
#Module
#InstallIn(SingletonComponent::class)
class AppUtilsModule {
#Singleton
#Provides
fun provideNetworkUtils(context: Context): NetworkUtils {
return NetworkUtils(context)
}
}
So as this is singleton. After the First creation of the activity and invoking getNetworkLiveData, internal networkLiveData is filled with value.
After rotation, you are using the same NetworkUtils object, that already has value stored in networkLiveData, during registration existing LiveData is returned, but jus before returning it there is code that posts new value to it.
networkLiveData.postValue(isConnected)
return networkLiveData
As postValue is invoked asynchronously in most cases changing of the internal value will be invoked after the return statement.
So at the end we are registering to livaData which has old value, and just a few ms later we receive new value.
There are a few options to avoid that kind of situation:
Post new value only if it's different from the previous one (Still not perfect)
add Transformations.distinctUntilChanged(networkLiveData) when returning live data (Still not perfect)
Rework a bit of utils, and separate logic related to observing live data and log related to receiving updates from ConnectivityManager
Edit:
There is also another problem after registerNetworkCallback, the callback is invoked with the current status. So there again there is an update of the networkLiveData
I'm trying to get my app to connect to a WiFi AP to provide Internet with WifiNetworkSpecifier using code like this. But it's always calling onUnavailable in the NetworkCallback.
private val callback: ConnectivityManager.NetworkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
}
override fun onLost(network: Network) {
super.onLost(network)
}
override fun onLosing(network: Network, maxMsToLive: Int) {
super.onLosing(network, maxMsToLive)
}
override fun onUnavailable() {
super.onUnavailable()
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
super.onCapabilitiesChanged(network, networkCapabilities)
}
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
super.onLinkPropertiesChanged(network, linkProperties)
}
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
super.onBlockedStatusChanged(network, blocked)
}
}
val networkSpecifier: NetworkSpecifier = WifiNetworkSpecifier.Builder()
.setSsid("SsidName")
.setWpa2Passphrase("wifipassword")
.build()
val networkRequest: NetworkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.setNetworkSpecifier(networkSpecifier)
.build()
connectivityManager.requestNetwork(networkRequest, callback, 100000)
The WifiNetworkSpecifier is only meant to connect to local-only Wifi networks, for example, to set up IoT devices, as confirmed here: https://developer.android.com/guide/topics/connectivity/wifi-bootstrap
So if you .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET), Android will always just do nothing and call onUnavailable.
You can see why in the Android platform code here: https://cs.android.com/android/platform/superproject/+/master:packages/modules/Wifi/service/java/com/android/server/wifi/WifiNetworkFactory.java;drc=08124f52b883c61f3e17bc57dc28eca4c7f7bb72;l=487
The message in your LogCat will be E/WifiNetworkFactory: Request with wifi network specifier cannot contain NET_CAPABILITY_INTERNET. Rejecting
If you want Internet, you need to remove the .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) line (which makes a lot of sense, wha!) and add connectivityManager.bindProcessToNetwork(network) in onAvailable as described over in this answer. Note this is a bit of a hack and will enable Internet via Wifi for your app only.
I'm working on an Android application which monitors the device's network usage in the background. As far as I know, a couple years ago the go-to approach would have been to register a BroadcastReceiver which catches every CONNECTIVITY_CHANGE event, but it seems like it was deprecated in Android 7. I can see a class named JobScheduler has been introduced since as a sort of replacement, but I'm not sure that might suit this specific use case.
Use ConnectivityManager class to listen about connectivity change.
From official doc
Class that answers queries about the state of network connectivity. It
also notifies applications when network connectivity changes.
Use registerNetworkCallback to receive notifications about all networks which satisfy the given NetworkRequest
Here is sample code in Kotlin:
enum class NetworkResult {
CONNECTED,
DISCONNECTED,
DISCONNECTING
}
class NetworkCallback : ConnectivityManager.NetworkCallback(){
val result = MutableLiveData<NetworkResult>()
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
super.onCapabilitiesChanged(network, networkCapabilities)
}
override fun onLost(network: Network) {
Timber.i("onLost networkL $network")
result.postValue(NetworkResult.DISCONNECTED)
}
override fun onLosing(network: Network, maxMsToLive: Int) {
Timber.i("onLosing: network: $network maxMsToLive: $maxMsToLive")
result.postValue(NetworkResult.DISCONNECTING)
}
override fun onAvailable(network: Network) {
Timber.i("onAvailable: network: $network")
result.postValue(NetworkResult.CONNECTED)
}
}
class ConnectivityFactory {
fun internetRequest() : NetworkRequest{
return NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.build()
}
}
class NetworkConnectionManager(val context: Context){
private val factory : ConnectivityFactory = ConnectivityFactory()
private val callback : NetworkCallback = NetworkCallback()
private val connectivityManager = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
val result : LiveData<NetworkResult> get() = callback.result
fun registerCallback(){
val request = factory.internetRequest()
connectivityManager.registerNetworkCallback(request, callback)
}
fun unregisterCallback(){
connectivityManager.unregisterNetworkCallback(callback)
}
}
Usage:
val networkManager : NetworkConnectionManager by lazy { NetworkConnectionManager(this) }
// observer state
networkManager.result.observe(this, Observer {
Timber.i(it.name)
})
// register
networkManager.registerCallback()()
// unregister
networkManager.unregisterCallback()
As getActiveNetworkInfo is deprecated now in Android according to official document, I am using below implementation to get callback about Internet connectivity.
private val connectivityManager: ConnectivityManager by lazy {
getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
}
private val builder: NetworkRequest.Builder by lazy {
NetworkRequest.Builder()
}
private val networkCallback: NetworkCallback by lazy {
object : NetworkCallback() {
override fun onAvailable(network: Network?) {
println("Connection is online")
}
override fun onLost(network: Network?) {
println("Connection is offline")
}
}
}
override fun onResume() {
super.onResume()
connectivityManager.registerNetworkCallback(builder.build(), networkCallback)
}
override fun onPause() {
super.onPause()
connectivityManager.unregisterNetworkCallback(networkCallback)
}
It works well when this callback register when connection is online, but it does not work properly when internet is off and then we register callback. To test such scenarion, I kept my app closed. Then keep internet connection off and then open app.
Do we have any way to know even app is opening? If so, please help to share it. Thanks.
According to your question, I would suggest reading this part of the Android documentation. You are using onPause() and onResume(), you should try out onStop().
But I think you have to call the super.onPause() after your logic connectivityManager.unregisterNetworkCallback(networkCallback)
because it will pause the app before your method is even called.