Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Check Internet Connection in Android (API Level 29) Using Network Callback
/*
You need to call the below method once. It register the callback and fire it when there is a change in network state.
Here I used a Global Static Variable, So I can use it to access the network state in anyware of the application.
*/
// You need to pass the context when creating the class
public CheckNetwork(Context context) {
this.context = context;
}
// Network Check
public void registerNetworkCallback()
{
try {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkRequest.Builder builder = new NetworkRequest.Builder();
connectivityManager.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback(){
@Override
public void onAvailable(Network network) {
Variables.isNetworkConnected = true; // Global Static Variable
}
@Override
public void onLost(Network network) {
Variables.isNetworkConnected = false; // Global Static Variable
}
}
);
Variables.isNetworkConnected = false;
}catch (Exception e){
Variables.isNetworkConnected = false;
}
}
public class Variables {
// Global variable used to store network state
public static boolean isNetworkConnected = false;
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Register Callback - Call this in your app start!
CheckNetwork network = new CheckNetwork(getApplicationContext());
network.registerNetworkCallback();
// Check network connection
if (Variables.isNetworkConnected){
// Internet Connected
}else{
// Not Connected
}
}
}
@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Nov 21, 2019

I am trying this but the variables stays false. Only thing I modified was to add context as parameter in registerNetworkCallback() because or else context.getSystemService was showing error. And calling it via an object of checknetwork class.

I initially converted it to kotlin, but when It didn't work I created a fresh java project to try this, didn't work either. Any suggestions??

@PasanBhanu

This comment has been minimized.

Copy link
Owner Author

@PasanBhanu PasanBhanu commented Nov 21, 2019

This is for java and this needs higher api level - API Level 29. Could you please check the api level is correct in your project.

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Nov 22, 2019

Its API 29. I was searching for almost a month, a replacement for now deprecated NetworkInfo stuff. Something which I can use to replace my legacy isNetworkAvailable workflow without completely rewriting app from scratch. Your implementation seems to be the best but I can't get it work.

I might have first tried it as kotlin, But when it didn't work, I created a fresh Java project to try it out. But even then when global variable didn't set to true, I though I should ask for your help.

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Nov 22, 2019

On further playing with it, It seems that on wifi, it does not set itself back to true, unless I at least resume activity, ( like minimizing the app and then opening it again).

I played by switching between airplane mode <-> Wifi <-> Mobile data . I created a button to manually execute if-else block inside onCreate() and check network status by displaying it on textview. I also have a counter to make sure button is clicked.

Between Everything works as expected when switching to airplane mode or mobile data, But when I switch to Wifi mode ( from either airplane, or from mobile data ) It automatically sets itself to false ( I waited to make sure wifi is properly connected ). Only when I force execute onCreate, By minimizing app, or By closing it and relaunching it, I can get true on wifi.

Edit: I have posted my complete code as gist at https://gist.github.com/Abhinav1217/0ff6b39e70fa38379d61e85e09b49fe7

@PasanBhanu

This comment has been minimized.

Copy link
Owner Author

@PasanBhanu PasanBhanu commented Nov 22, 2019

Let me check my implementation again. My code is correct but as I remember I didn't create a new Network object as you did. Let me check and confirm. I think that is the issue.

And big thank for checking this. There is less resources for network check in API 29. If we can fix the issues, this will helps lots of developers.

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Nov 23, 2019

Let me check my implementation again. My code is correct but as I remember I didn't create a new Network object as you did. Let me check and confirm. I think that is the issue.

If you are talking about Line number 17 in MainActivity.java, That actually is My Network class which contains your registerNetworkCallback() code . I should have chosen less ambiguous name for my own files, sorry about that. Only thing I changed on your implementation is that my registerNetworkCallback function take context ( this ) as a parameter. Without that, context.getSystemService ( Line 11 and 13 on Network.java ) throws error. You have that function without any parameter.

And big thank for checking this. There is less resources for network check in API 29. If we can fix the issues, this will helps lots of developers.

I totally agree. I have been on this for over a month, there are multiple questions regarding this on stackoverflow even my own. Primary feedback is that google wants us to stop using if-else block for network based stuff, instead use pub-sub like network architecture (using some concept of Livedata, which I don't fully yet understand) for entire app. Unfortunately Legacy enterprise apps can not suddenly go through a complete rewrite. And a lot of tutorial ( even one with Android Q in title) still uses NetworkInfo with Suppressed annotation instead of something new. I tried using NetworkCapabilities but it was a bust. You my friend have best solution for graceful upgradation.

@PasanBhanu

This comment has been minimized.

Copy link
Owner Author

@PasanBhanu PasanBhanu commented Nov 23, 2019

I checked my actual implementation on the mobile app.

I have a global static variable to store the status.
And I have a separate class with the registerNetworkCallback() function. In the constructor of the class I pass the context.

public Functions(Context context) { this.context = context; }

And in MainActivity.java

Functions functions = new Functions(getApplicationContext()); functions.registerNetworkCallback();

I think now you can solve the issue. Sorry for my error.

@PasanBhanu

This comment has been minimized.

Copy link
Owner Author

@PasanBhanu PasanBhanu commented Nov 23, 2019

I updated the gist.

Check Network : Line 7
Main Activity : Line 8

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Nov 23, 2019

Still same results, Wifi is only treated as true, when it is available during app starting. Rest of the time it is treated as false. Mobile data doesn't have this issue, It must be how NetworkCallback treats wifi I guess.

I have added log.d in both onLost and onAvailable and every other remaining public methods of NetworkCallback. I will report to you once I understand the network lifecycle of these.

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Nov 23, 2019

ok.. I found the root cause But I need your help to workout a solution through this. This is basically how different events are triggered for wifi network when app is running and when app is started,

I did testing for two scenarios, and three network stages.

  1. When app is launched, what is the state of network. and how it is detected.

1.a When App is started with Air-plane mode, nothing is fired. at all.

1.b When App is started in a connected cellular data. 4 events are triggered in following sequence.

2019-11-23 16:23:50.373 7154-7197/? D/FLABS:: onAvailable
2019-11-23 16:23:50.373 7154-7197/? D/FLABS:: onCapabilitiesChanged
2019-11-23 16:23:50.373 7154-7197/? D/FLABS:: onLinkPropertiesChanged
2019-11-23 16:23:50.373 7154-7197/? D/FLABS:: onBlockedStatusChanged

1.c When App is started in a connected Wifi Mode, Same for events are triggered as above.

2019-11-23 16:26:50.035 7463-7506/com.example.simplenetwork D/FLABS:: onAvailable
2019-11-23 16:26:50.035 7463-7506/com.example.simplenetwork D/FLABS:: onCapabilitiesChanged
2019-11-23 16:26:50.035 7463-7506/com.example.simplenetwork D/FLABS:: onLinkPropertiesChanged
2019-11-23 16:26:50.035 7463-7506/com.example.simplenetwork D/FLABS:: onBlockedStatusChanged

I should point out that these events were triggered automatically without pressing any button.

Therefore as you can see, everything works as expected. States are correctly determined even for wifi when app is running.

  1. Now we switch between stages while app is running.

2.a When App is switched to airplane mode. A single onLost event is triggered.

2.b When App is switched to Cellular data.

2019-11-23 16:50:48.609 8416-8459/com.example.simplenetwork D/FLABS:: onLost

2019-11-23 16:50:52.085 8416-8459/com.example.simplenetwork D/FLABS:: onAvailable
2019-11-23 16:50:52.085 8416-8459/com.example.simplenetwork D/FLABS:: onCapabilitiesChanged
2019-11-23 16:50:52.085 8416-8459/com.example.simplenetwork D/FLABS:: onLinkPropertiesChanged
2019-11-23 16:50:52.085 8416-8459/com.example.simplenetwork D/FLABS:: onBlockedStatusChanged

2019-11-23 16:50:52.140 8416-8459/com.example.simplenetwork D/FLABS:: onLinkPropertiesChanged
2019-11-23 16:50:52.767 8416-8459/com.example.simplenetwork D/FLABS:: onCapabilitiesChanged

The first onLost is not triggered if previous state is airplane mode. Rest is same. The blank-lines I have added is to give you focus on timing.

2.c When App is switched to Wifi Mode.

2019-11-23 16:54:29.136 8416-8459/com.example.simplenetwork D/FLABS:: onAvailable
2019-11-23 16:54:29.136 8416-8459/com.example.simplenetwork D/FLABS:: onCapabilitiesChanged
2019-11-23 16:54:29.136 8416-8459/com.example.simplenetwork D/FLABS:: onLinkPropertiesChanged
2019-11-23 16:54:29.136 8416-8459/com.example.simplenetwork D/FLABS:: onBlockedStatusChanged
2019-11-23 16:54:29.307 8416-8459/com.example.simplenetwork D/FLABS:: onLosing

2019-11-23 16:54:29.325 8416-8459/com.example.simplenetwork D/FLABS:: onCapabilitiesChanged
2019-11-23 16:54:29.371 8416-8459/com.example.simplenetwork D/FLABS:: onLost

2019-11-23 16:54:29.959 8416-8459/com.example.simplenetwork D/FLABS:: onLinkPropertiesChanged
2019-11-23 16:54:29.975 8416-8459/com.example.simplenetwork D/FLABS:: onLinkPropertiesChanged
2019-11-23 16:54:30.972 8416-8459/com.example.simplenetwork D/FLABS:: onLinkPropertiesChanged
2019-11-23 16:54:31.693 8416-8459/com.example.simplenetwork D/FLABS:: onLinkPropertiesChanged
2019-11-23 16:54:32.053 8416-8459/com.example.simplenetwork D/FLABS:: onCapabilitiesChanged

Again I have added a blank line to give focus on time stamps.

As you can see, Wifi for some reason triggers onLost event therefore setting the global variable to false. I guess we can use onCapabilitiesChanged in combination with your technique to work things out. I had tried onCapibilitiesChanged (based on a tip from stackoverflow ) on its own which didn't work properly at that time. I wasn't using networkCallback at that time.

I hope this helps ..

@PasanBhanu

This comment has been minimized.

Copy link
Owner Author

@PasanBhanu PasanBhanu commented Nov 23, 2019

I think this will be a solution. I got this idea from the documentation. (Didn't Test)
How about check onCapabilitiesChanged() and check if the is networkCapabilities== NET_CAPABILITY_INTERNET

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Nov 23, 2019

NET_CAPABILITY_INTERNET didn't work reliably. Moreover because since onCapabilitiesChange is triggered multiple time, It is not only inefficient, but breaks reliability. I started getting false state on mobile data on some occasion. Mobile data was working properly before.

I can't seem to figure out proper place to put check, without breaking already working parts. I have posted it in stackoverflow and hope someone can point me to right direction.

https://stackoverflow.com/questions/59008731/stuck-on-android-api-29-compatible-network-connection

@PasanBhanu

This comment has been minimized.

Copy link
Owner Author

@PasanBhanu PasanBhanu commented Nov 24, 2019

That's good... Lets see we can find a solution

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Nov 26, 2019

After no answer on stackoverflow, and not getting any solutions by discussing with my seniors in office, I ended up asking the source.
https://issuetracker.google.com/issues/145163549

How about you, Did you find time to play around with it.
I am still playing with onCapabilitiesChanged() to come up with a decent and reliable solution. I am now trying to add something based on
https://issuetracker.google.com/issues/135044193

@PasanBhanu

This comment has been minimized.

Copy link
Owner Author

@PasanBhanu PasanBhanu commented Nov 26, 2019

That's is good. I'm trying to understand why this code is working for me.... :-)
My application sends api request when there is internet available. I think it works because of api call surrounded by try-catch.
Let's wait for their response.

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Nov 27, 2019

Maybe you haven't encountered this because it only happens when network is switched to wifi while app is running, If your app is launched with stable wifi, then it is not triggered.

It also depends on what is your condition on try-catch. I can confirm that if I ignore state of GlobalVariable and make API call, It will execute as internet is available, even though onLost was triggered.

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Nov 28, 2019

The engineer team has responded, They suggest that ConnectivityManager#registerDefaultNetworkCallback would be more appropriate for our scenario, instead of ConnectivityManager#registerNetworkCallback .

Here is entire response from team. Here is a link in case markdown converter changes something

Response from the Engineering team:

Modern Android devices can connect to multiple networks at the same time. The NetworkCallback interface will tell you about all the networks that match the NetworkRequest you passed, and will pass the relevant Network as an argument. Keep in mind that there is no limit to how many networks a device can be connected : phones often are connected to both cellular and mobile at the same time, and some devices can connect to two WiFi networks at the same time for example.

In your code, you create a default NetworkRequest which will match all networks (except the restricted ones and VPNs). So this callback will be called for almost all networks, including cellular and WiFi as well as Ethernet or Bluetooth networks if present.
When the system calls your onAvailable(Network), it will pass the network that is available. When the system calls your onLost(Network), it will pass the network that was lost. So onLost() doesn't necessarily mean complete loss of connectivity, it means one network was lost, and the argument tells you which.
However your code ignores the Network argument. If you print it in the log, you'll see that the onLost was called for a different network. In practice, that means your code assumes loss of connectivity when any network at all is lost, even if some other good network is still connected.

In this case, when the device connects to WiFi, the Mobile network may stay connected or may disconnect if it's not needed. I suspect the onLost you are seeing when you connect to WiFi is the mobile network disconnecting as a result of being now unneeded, because the WiFi network is better.

When a network connects, your callback will get a call to onAvailable. To figure out what this network is capable of, wait for onCapabilitiesChanged, which is guaranteed to immediately come after onAvailable (as you already noticed). You can tell whether this is a mobile or a WiFi network by querying the NetworkCapabilities with hasTransport() and the appropriate transport constant (TRANSPORT_CELLULAR or TRANSPORT_WIFI for example).

Finally, you may want to take a look at ConnectivityManager#registerDefaultNetworkCallback. While registerNetworkCallback will send you information about all networks that match the request you pass, registerDefaultNetworkCallback will only tell you about the current default Internet network of the device. I suspect this is closer to what you seem to be interested in. Read the public documentation for details on how this works.

I only have about 3 months as an android developer under my belt, I am still trying to figure out a lot of these stuff on the go. I will try to tweak with above suggestions, on weekend. If you can come up with some proper solution on your free time, I will gladly do testing for you.

Thanks.

@PasanBhanu

This comment has been minimized.

Copy link
Owner Author

@PasanBhanu PasanBhanu commented Nov 28, 2019

Sure. I'll try to implement their solution on my app.

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Dec 3, 2019

I have updated my gist with a new function CheckNetwork->registerDefultNetworkCallback(). There is no need for handler and it works very well for general use case. I haven't tested with other form of networks like nfc, bluetooth etc. Maybe next weekend. I will also create a version in kotlin.

Only downside of this function is that it is available only from API-24. While current recommendation is that we need to support atleast marshmallow(API-23).

In the end, Thanks for this journey, I have learned more about android networking, working on this gist, than any paid and pirated tutorial online.

@PasanBhanu

This comment has been minimized.

Copy link
Owner Author

@PasanBhanu PasanBhanu commented Dec 3, 2019

Great... I'll update my gist accordingly. And thanks a lot for solving this.... 💯

Can you please link your Kotlin Version here.

@gaucidaniel

This comment has been minimized.

Copy link

@gaucidaniel gaucidaniel commented Feb 21, 2020

Here's a solution that I implemented which should work well on all APIs. Since registerDefaultNetworkCallback() only works with API 24 and up, I attempted to replicate its behaviour using registerNetworkCallback().

val connectivityManagerCallback: ConnectivityManager.NetworkCallback = object : ConnectivityManager.NetworkCallback() {

            private val activeNetworks: MutableList<Network> = mutableListOf()

            @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)
            override fun onAvailable(network: Network) {
                super.onAvailable(network)

                // Add to list of active networks if not already in list
                if (activeNetworks.none { activeNetwork -> activeNetwork.networkHandle == network.networkHandle }) activeNetworks.add(network)
                val isNetworkConnected = activeNetworks.isNotEmpty()
            }

            @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)
            override fun onLost(network: Network) {
                super.onLost(network)

                // Remove network from active network list
                activeNetworks.removeAll { activeNetwork -> activeNetwork.networkHandle == network.networkHandle }
                val isNetworkConnected = activeNetworks.isNotEmpty()
            }
}

// When registering a network callback using registerNetworkCallback instead of registerDefaultNetworkCallback, the callback is called for
// every separate network instead of the default network. For example, if both WiFi and Cellular Data are connected, and only Cellular Data
// is turned off, onLost() will be called. This does not mean that the device network is lost, but rather that the specific network has been
// lost. The device could still be connected via another network. Because of this, a list of activeNetworks is maintained, and used to check
// if the device is connected to any active networks.
connectivityManager.registerNetworkCallback(networkRequest, connectivityManagerCallback)
@PasanBhanu

This comment has been minimized.

Copy link
Owner Author

@PasanBhanu PasanBhanu commented Feb 22, 2020

@gaucidaniel Great 💯
If you published this as Gist, like here please.

@NuPeX

This comment has been minimized.

Copy link

@NuPeX NuPeX commented Feb 28, 2020

Using Network callback helps you find out about network state changes... but what about if that specific network you're connected to has limited or no connectivity to Internet? Variables.isNetworkConnected will remain true. Is there way or workaround to fix that issue?
Thanks in advance.

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Feb 28, 2020

@NuPeX

https://issuetracker.google.com/issues/135044193

Basically A device has working network does not mean network is connected to internet. You would have to ping to some url like google.com to make sure internet is working or not. Think it like this. You can connect with ISP for internet, but you forgot to pay bill and ISP decides to block your access, Or you need a network login, or government decides to block it , or your speed is so slow that you get timeout.

Variable.isNetworkConnected only means that the network module of your phone is connected to some network, and you have ability to send and receive packets on that network. If you receive nothing ( in case of no Internet ) does not mean you can't receive.

You can do something like this with your code,

// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
when (res) {
  200 -> // Successful response 
  500 -> // Server error  (includes no internet condition )
  else -> // No Internet
}
@NuPeX

This comment has been minimized.

Copy link

@NuPeX NuPeX commented Feb 28, 2020

Thanks for your quick response... I understand what you mean. So in case i want to monitor my whole app for internet connectivity changes then i would probably have to implement a broadcast receiver listening for a "conn. change" response of a "ping google" class?

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Mar 2, 2020

@NuPeX
I don't see any reason why you would need to check for pings at all.

When making any network call, if you properly handle network errors in your code then you are good.

  1. Check is network is connected or not.
  2. If network is connect, send your request and wait for response.
  3. If response is success your code would be 200.
  4. If you get anything else, then you are connected to internet but server is the problem. If ISP is problem (like a ISP login gateway) Usually you will receive error code 3xx. For server it is mostly 4xx and 5xx . Timeout is 408.
  5. If you don't get any response code at all then you are offline.

Pinging google.com should be similar logic, and listening to it always means you are constantly making extra unnecessary calls to network for basically something that you would have to handle in your application code anyways.

@PasanBhanu

This comment has been minimized.

Copy link
Owner Author

@PasanBhanu PasanBhanu commented Mar 3, 2020

Totally agreed to @Abhinav1217

@NuPeX

This comment has been minimized.

Copy link

@NuPeX NuPeX commented Mar 3, 2020

Thanks for your reply @Abhinav1217
I'll try to implement it the way you suggest!👍

@dangby

This comment has been minimized.

Copy link

@dangby dangby commented Mar 10, 2020

If you find a working solution, will you update the code here? I've also been working on the same problem.

@PasanBhanu

This comment has been minimized.

Copy link
Owner Author

@PasanBhanu PasanBhanu commented Mar 11, 2020

We have a working solution. Check @Abhinav1217 s answer

@chydee

This comment has been minimized.

Copy link

@chydee chydee commented Mar 11, 2020

Hi, I love this and thank you for sharing this gist but I have a question,
private boolean isNetworkAvailable() { ConnectivityManager connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connManager != null ? connManager.getActiveNetworkInfo() : null; return networkInfo != null && networkInfo.isConnected(); }

Is this gist an advance or better approach to checking for network connectivity compared to the code snippet above?
Thank you

@GokhanArik

This comment has been minimized.

Copy link

@GokhanArik GokhanArik commented Mar 12, 2020

@gaucidaniel Network.networkHandle is added in API 23, how does it work on all API levels?

image

Update:
Network class overrides equals() method and compares by netId. Changing boolean expression in your code to this would probably make it work in all platforms
{ activeNetwork -> activeNetwork == network }

@GokhanArik

This comment has been minimized.

Copy link

@GokhanArik GokhanArik commented Mar 12, 2020

After the feedback from everyone, I merged recommendations from this gist into my NetworkStateManager API 21+. For my case, all I need to know is if the device is online or not. Please check it and let me know if you see any mistakes:

https://gist.github.com/GokhanArik/b5c9ad58ce39218d722069d6cde34702

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Mar 12, 2020

@chydee

Is this gist an advance or better approach to checking for network connectivity compared to the code snippet above?

NetworkInfo is deprecated . This gist uses one of the few recommended techniques which doesn't uses deprecated api's. This gist is a simple but better approach. If you want something more advance, try using onCapabilitiesChange() for a more refined control on device's network. This gist has a discussion on that too.

@GokhanArik
When I tried hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) approach, It didn't work reliably for me. It reported no internet when switching from wifi to mobile data, even though net was connected. I have discussed it in this gist. Maybe you managed to get the logic sequence right. I will have to test it to be sure.

My only itch is that your approach uses a 3rd party library. jakewharton is undoubtedly a reliable developer when it comes for using 3rd party library. But unless you are going to use that library in other parts of your project, including a 3rd party library just to check network status, which can be done via internal api's, is a bloat and overkill. But if someone is using his libraries for reactive programming, your approach looks nice.

@chydee

This comment has been minimized.

Copy link

@chydee chydee commented Mar 12, 2020

@Abhinav1217 thank you for explaining further. Now I understand.

@GokhanArik

This comment has been minimized.

Copy link

@GokhanArik GokhanArik commented Mar 12, 2020

@Abhinav1217 I didn't notice you had a separate gist. Your gist is still missing an important piece that was pointed out on the response from the Google engineering team. That's where the fix from @gaucidaniel's code comes in.

My implementation is based on if the internet is available or not, regardless of it is Wifi or Mobile Data. If you want to know which network is active you need to modify the code.

There are 2 main issues with the connectivity check:

  1. onLost doesn't get called when you first open the app. onLost gets called if previously active Network loses the connection. However onAvailable gets called when the app connects to a Network. In addition to registering a callback, you also need to check the initial status connection status. To test this, turn on aeroplane mode and log onLost

  2. As they mentioned in the response, your device might be connected to 2 Networks at the same time. Thus onLost(Network)/onAvailable(Network)/onCapabilitiesChanged(Network) will be called for the given network. In @gaucidaniel's code, he keeps track of all the networks the app is connected to and checks if the list is empty before setting isNetworkConnected to false.

  • NET_CAPABILITY_INTERNET probably didn't work for the same reason. onCapabilitiesChanged(Network, NetworkCapabilities) gets called for a single network, you need to check if any of your networks have the capability. Setting a global variable to true/false will fail it.
  • The code in my gist is from my own project, I use RxRelay in my project, you don't need to use it.

To simplify further, here is a simple scenario. Assume you are connected to both Wifi and Mobile data.

Launch the app:

// onAvailable gets called twice for each network
onAvailable(Network) // Wifi
onAvailable(Network) // Cellular

Turn off the Wifi:

onLost(Network) // Called for Wifi only, but Cellular is still available. If you set your boolean to false, it will be inaccurate
@PasanBhanu

This comment has been minimized.

Copy link
Owner Author

@PasanBhanu PasanBhanu commented Mar 13, 2020

@GokhanArik got your point. Thanks for the explanation. I would like to request what are the changes I need to do in my code to check whether the phone is connected to the internet or not. Since lots of people checking this, it's better if we can show the correct code at first sight.

Cheers for your dedication @GokhanArik @chydee @Abhinav1217 @NuPeX @gaucidaniel

@GokhanArik

This comment has been minimized.

Copy link

@GokhanArik GokhanArik commented Mar 13, 2020

@PasanBhanu No problem. I shared my gist, check that and convert from it. Mine is in Kotlin

https://gist.github.com/GokhanArik/b5c9ad58ce39218d722069d6cde34702

  1. When you register to check the initial status of the connection
  2. Update onLost and onAvailable methods accordingly. In my code wherever I have sendEvent() replace it with your global variable.
@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Mar 13, 2020

@GokhanArik Thanks for explanation. And thanks for sharing your gist. I ended up using registerDefaultNetworkCallback instead of registerNetworkCallback as per explanation from google engineering team. registerDefaultNetworkCallback works as expected without any configuration with only caveat that it is available from API 24 only. And because the company I am working needs to support upto API 17, I just kept a check for API-24 for modern approach using registerDefaultNetworkCallback, and older networkInfo.getActiveNetworkInfo() for anything below it. I got lazy figuring out configuration and ignored registerNetworkCallbackaltogather.

I was thinking of taking another attempt at configuring registerNetworkCallback using @gaucidaniel 's and your's approach. Wish me luck this weekend.

@M7mdZain

This comment has been minimized.

Copy link

@M7mdZain M7mdZain commented Mar 17, 2020

I got through this gist, and I got through several stages to get this work, more details in this answer, This supports API-15 through API-29; divided them into two patches:
first is Pre-API-21 which I used BroadcastReceiver and NetworkInfo, and the second is Post-API-21 which I used ConnectivityManager.NetworkCallback and mConnectivityMgr.registerNetworkCallback() ; and tackled the following problems:

  1. When both WiFi and cellular networks are on, then onLost() won't interrupt the connectivity when the WiFi is disconnected while transitioning to the cellular network.
  2. When the cellular network is on, then both onLost() & onAvailable() won't interrupt the connectivity when the WiFi is connected and being the active network (as this is the preferred network).

tackled them with a boolean field mIsConnected that indicates whether device is connected to the internet or not. and by tracking the no. of connected networks with getAvailableNetworksCount()

wish that can help you out, and feel free to raise any issues.

@iRYO400

This comment has been minimized.

Copy link

@iRYO400 iRYO400 commented Apr 30, 2020

Hey everyone, let me leave my two cents :D
Here is my implementation of network connectivity that is used where context and lifecycle are presented.
API21+. Nothing is deprecated on 29

https://gist.github.com/iRYO400/5da269f4d5edbc365c952bdcbdfe6b68

@PasanBhanu

This comment has been minimized.

Copy link
Owner Author

@PasanBhanu PasanBhanu commented Apr 30, 2020

@iRYO400 Thanks for the contribution!

@chydee

This comment has been minimized.

Copy link

@chydee chydee commented May 19, 2020

Hi @iRYO400 @Abhinav1217 and @everyone ! This might be a bit out of the scope but I've gotta ask, after setting up a class or an activity that performs connectivity checks, whats the best ways to implement? For example here's how I normally do it (Alert! Boilerplates)
after setting up a public scope function or method in my Application class I then call the AppClass.isConnected() and do whatever I have to do or display a message. So my point is lets say I now do it the right way like @iRYO400 set up a ConnectivityMonitor.kt class, how do I set empty screens or show users messages on every part of the app to let them know they are offline (Keep in mind what I want do is reuse a single resource for this function across the project). I'd appreciate if you share any resource that shows how to do this the right way. Thank you.
maxresdefault

@iRYO400

This comment has been minimized.

Copy link

@iRYO400 iRYO400 commented May 19, 2020

Hi @chydee !
I would recommend you check this example https://github.com/android10/Android-CleanArchitecture-Kotlin/blob/master/app/src/main/kotlin/com/fernandocejas/sample/core/platform/NetworkHandler.kt
Yes, it's a bit complicated for study, but it covers your case. Single place for network check and custom UI handling to show whether network is connected.

Also you'll need to refactor NetworkHandler, because it uses deprecated api. Check my example above

@chydee

This comment has been minimized.

Copy link

@chydee chydee commented May 20, 2020

Checking it out already. Thank you @iRYO400. Thank you so much for sharing

@GokhanArik

This comment has been minimized.

Copy link

@GokhanArik GokhanArik commented Jul 13, 2020

TIL, if your phone is connected to a Network but the Network doesn't have a validated/working internet connection, it will give false result if you are only using NET_CAPABILITY_INTERNET. In addition, you also need to check for NET_CAPABILITY_VALIDATED. The fun part: It is only available after API 23.

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Jul 13, 2020

TIL, if your phone is connected to a Network but the Network doesn't have a validated/working internet connection, it will give false result if you are only using NET_CAPABILITY_INTERNET. In addition, you also need to check for NET_CAPABILITY_VALIDATED. The fun part: It is only available after API 23.

This was the reason why NET_CAPABILITY_INTERNET was working unreliably as I mentioned before. It also fails when sometimes it takes long time for handshake between router and device. Thats when it will tell you you dont have connection when in truth, you just got a little longer to get validated.

registerDefaultNetworkCallback might be simple and rudimentary, but it works without hassle. And from that as a starting point, you can move on to more complex approach as needed by your app. The app I was working on, I have now implemented a timeout/retry logic for use cases where network can be patchy. So Its not just the register which is changing the value of global isNetworkConnected variable, But a retry logic which also changes that variable if more than 5 retries fails in a coroutine.

@waqqas7

This comment has been minimized.

Copy link

@waqqas7 waqqas7 commented Sep 10, 2020

Thanks for the code. Works like a charm
Cheers mate !

@ExNDY

This comment has been minimized.

Copy link

@ExNDY ExNDY commented Oct 24, 2020

Hi,
I want to add my own version of this solution using LiveData. As I understand my solution targetting on API 24+ (delete parts deplecated solutions and not universal code other finding solutions). If u find mistakes or u know how to improve, please write about it. I was looking for a solution for a long time to replace Deplecated methods BroadcastReceiver. Thank You for best start!
https://gist.github.com/ExNDY/2c27b9c29b9642accbb0cd4f33ca9421

@TheCroid

This comment has been minimized.

Copy link

@TheCroid TheCroid commented Nov 16, 2020

Hi,
I want to add my own version of this solution using LiveData. As I understand my solution targetting on API 24+ (delete parts deplecated solutions and not universal code other finding solutions). If u find mistakes or u know how to improve, please write about it. I was looking for a solution for a long time to replace Deplecated methods BroadcastReceiver. Thank You for best start!
https://gist.github.com/EndyMellman/2c27b9c29b9642accbb0cd4f33ca9421

@EndyMellman Thanks +1

@rohit-32

This comment has been minimized.

Copy link

@rohit-32 rohit-32 commented Dec 18, 2020

                activeNetworks.removeAll { activeNetwork -> activeNetwork.networkHandle == network.networkHandle }
                val isNetworkConnected = activeNetworks.isNotEmpty()

You are a savior

@ayoubrem

This comment has been minimized.

Copy link

@ayoubrem ayoubrem commented Dec 23, 2020

Hi
My own version for checking if the user already doesn't have a connection, also handle the connection change status
https://gist.github.com/ayoubrem/81689ca066f77ecd1ebb5e14de245e6d

@jisung-choi

This comment has been minimized.

Copy link

@jisung-choi jisung-choi commented Jan 26, 2021

line 7 in CheckNetwork.java is throwing out error. I wanted to test if this code still works by directly copying/downloading, but failed to make it work.

@ayoubrem

This comment has been minimized.

Copy link

@ayoubrem ayoubrem commented Jan 26, 2021

what error ?
the code work fine for me

@jisung-choi

This comment has been minimized.

Copy link

@jisung-choi jisung-choi commented Jan 26, 2021

"class" or "interface" expected pops up for line7... It won't build. (I'm new to android so guide me please...)

@ayoubrem

This comment has been minimized.

Copy link

@ayoubrem ayoubrem commented Jan 26, 2021

you have syntax error in the code
check semicolons & brackets

@jisung-choi

This comment has been minimized.

Copy link

@jisung-choi jisung-choi commented Jan 26, 2021

I didn't change a thing from the original code. I downloaded Zip file, moved all the java files into Android Studio file
image

I just added the line with "package" on the first line like so
image

Had to change GlobalVariable file name to Variable b/c Android Studio made me so.

@jisung-choi

This comment has been minimized.

Copy link

@jisung-choi jisung-choi commented Jan 26, 2021

Hi
My own version for checking if the user already doesn't have a connection, also handle the connection change status
https://gist.github.com/ayoubrem/81689ca066f77ecd1ebb5e14de245e6d

I tried your version && it works for me. So I think I will use your version for my app. :D

@huyhb

This comment has been minimized.

Copy link

@huyhb huyhb commented Mar 4, 2021

NetworkRequest.Builder builder = new NetworkRequest.Builder(); 

what to do?

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Mar 4, 2021

what to do?

You might find some examples here
You should also try official docs

@nishantkp

This comment has been minimized.

Copy link

@nishantkp nishantkp commented Mar 8, 2021

If you just wanna check device is connected to WIFI or Cellular, you can try this...
https://gist.github.com/nishantkp/2ee7b8c34ea3e21cab1d64accd52078f

@baha2046a

This comment has been minimized.

Copy link

@baha2046a baha2046a commented Mar 26, 2021

@Abhinav1217 I didn't notice you had a separate gist. Your gist is still missing an important piece that was pointed out on the response from the Google engineering team. That's where the fix from @gaucidaniel's code comes in.

My implementation is based on if the internet is available or not, regardless of it is Wifi or Mobile Data. If you want to know which network is active you need to modify the code.

There are 2 main issues with the connectivity check:

  1. onLost doesn't get called when you first open the app. onLost gets called if previously active Network loses the connection. However onAvailable gets called when the app connects to a Network. In addition to registering a callback, you also need to check the initial status connection status. To test this, turn on aeroplane mode and log onLost
  2. As they mentioned in the response, your device might be connected to 2 Networks at the same time. Thus onLost(Network)/onAvailable(Network)/onCapabilitiesChanged(Network) will be called for the given network. In @gaucidaniel's code, he keeps track of all the networks the app is connected to and checks if the list is empty before setting isNetworkConnected to false.
  • NET_CAPABILITY_INTERNET probably didn't work for the same reason. onCapabilitiesChanged(Network, NetworkCapabilities) gets called for a single network, you need to check if any of your networks have the capability. Setting a global variable to true/false will fail it.
  • The code in my gist is from my own project, I use RxRelay in my project, you don't need to use it.

To simplify further, here is a simple scenario. Assume you are connected to both Wifi and Mobile data.

Launch the app:

// onAvailable gets called twice for each network
onAvailable(Network) // Wifi
onAvailable(Network) // Cellular

Turn off the Wifi:

onLost(Network) // Called for Wifi only, but Cellular is still available. If you set your boolean to false, it will be inaccurate

According to this post.
Well, I think we don't need to keep check all different networks value.
In my guess, just change isNetworkConnected to an Int,
onAvailable -> +1
onLost -> -1
I have no prove but if the onAvailable and onLost is called properly it should work.

@baha2046a

This comment has been minimized.

Copy link

@baha2046a baha2046a commented Mar 26, 2021

object CheckNetwork {
    var available:Int = 0
        private set

    fun registerNetworkCallback(context:Context) {
        kotlin.runCatching {
            val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?

            connectivityManager?.registerDefaultNetworkCallback(object : NetworkCallback() {
                override fun onAvailable(network: Network) {
                    available += 1
                    Log.d("CheckNetwork", "A $network")
                }

                override fun onLost(network: Network) {
                    available -= 1
                    Log.d("CheckNetwork", "L $network")
                }
            })
        }
    }
}

I just try it and seems it work
2021-03-26 18:14:52.901 2434-2816/com.example.tmdb D/CheckNetwork: A 140
2021-03-26 18:18:14.533 2434-2816/com.example.tmdb D/CheckNetwork: L 140
2021-03-26 18:18:21.915 2434-2816/com.example.tmdb D/CheckNetwork: A 141
2021-03-26 18:18:25.513 2434-2816/com.example.tmdb D/CheckNetwork: L 141
2021-03-26 18:18:30.613 2434-2816/com.example.tmdb D/CheckNetwork: A 142

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Mar 26, 2021

@baha2046a

In my guess, just change isNetworkConnected to an Int,
onAvailable -> +1
onLost -> -1

This is a really bad strategy, These registers are triggered multiple times throughout app cycle. How will you determine the state, if there it was available 2 times but lost on third time, $available would be 1. And nonetheless, _if conditions_ needs a boolean. It will be either available or not available. Why waste cpu cycles comparing numeric equations to determine if condition is true or false, when you can just pass boolean directly. Android OS Apis provided those registers for a reason.

If you are wondering about the double calling during wifi->cellular switching, In real world scenario, there would be several edge cases handled when implementing it, so it should be covered. If I remember correctly, When network was switched, onLost was called by wifi, then onAvailable was triggered by cellular when it connected.

I have no prove but if the onAvailable and onLost is called properly it should work.

Without proof, nothing can be assured. Looking on your code, It won't work because you are assuming onAvailable and onLost to be called linearly which is never the case. I can't see any reason why you logged $network, and you haven't demonstrated how are you using available in your app. CheckNetwork object is just updating the value of available variable but how are you actually going to use it to determine state of your network. Publish your code on a separate gist and tag me there, I would be happy to take a look there. Lets keep this gist free.

Also, don't name your functions same as system functions, I got confused seeing at registerDefaultNetworkCallback being called inside your registerNetworkCallback function.

@baha2046a

This comment has been minimized.

Copy link

@baha2046a baha2046a commented Mar 26, 2021

@baha2046a

In my guess, just change isNetworkConnected to an Int,
onAvailable -> +1
onLost -> -1

This is a really bad strategy, These registers are triggered multiple times throughout app cycle. How will you determine the state, if there it was available 2 times but lost on third time, $available would be 1. And nonetheless, _if conditions_ needs a boolean. It will be either available or not available. Why waste cpu cycles comparing numeric equations to determine if condition is true or false, when you can just pass boolean directly. Android OS Apis provided those registers for a reason.

If you are wondering about the double calling during wifi->cellular switching, In real world scenario, there would be several edge cases handled when implementing it, so it should be covered. If I remember correctly, When network was switched, onLost was called by wifi, then onAvailable was triggered by cellular when it connected.

I have no prove but if the onAvailable and onLost is called properly it should work.

Without proof, nothing can be assured. Looking on your code, It won't work because you are assuming onAvailable and onLost to be called linearly which is never the case. I can't see any reason why you logged $network, and you haven't demonstrated how are you using available in your app. CheckNetwork object is just updating the value of available variable but how are you actually going to use it to determine state of your network. Publish your code on a separate gist and tag me there, I would be happy to take a look there. Lets keep this gist free.

Also, don't name your functions same as system functions, I got confused seeing at registerDefaultNetworkCallback being called inside your registerNetworkCallback function.

Naming is because I just modify the code from this thread.
For how to use the value, that is simple, when the value > 0 there has somehow a network connection exist.
As I know its a bitwise compare and I don't think there has a measurable performance different for true false.
The order of calling available and lost does not matter.
And I said this depend on they engineer the callback correctly (for example, when device switch on once only one onAvailable will call instead on dup calling, that is the part I cannot 100% prove).

Also, I notice a problem for the base design of the program from this thread.
Register the callback on onCreate may cause it add more then one times.
The callback work well at background but if the program get kill at background to free memory,
when it become active it will run onCreate again. I wonder that is the problem someone encountered.
So a better practice is
register at onStart, unregister at onStop
I found nothing wrong after I make this modification.

@Abhinav1217

This comment has been minimized.

Copy link

@Abhinav1217 Abhinav1217 commented Mar 26, 2021

@baha2046a

Register the callback on onCreate may cause it add more then one times.

Tha'ts not whats happening in actual, Registration is done only done once, than a global static is set. As for the update of value, It is done by android system once registered ( something similar to pub-sub way, can't explain fully here ). So there is essentially no overhead. If you accidentally try to register it again, no effect takes place.
On other hand, the old way to initiate NetworkInfo, and then find out if network is available, That was something that was being done everytime check was asked for. Since Android 10, Google is Promoting pub-sub/callback/relay style of apis instead of old functional apis because of performance reason. Anything that needs to be queried from system should be subscribed for, thats the direction they seems to going for.

@CuongPTIT

This comment has been minimized.

Copy link

@CuongPTIT CuongPTIT commented Apr 8, 2021

Hi
My own version for checking if the user already doesn't have a connection, also handle the connection change status
https://gist.github.com/ayoubrem/81689ca066f77ecd1ebb5e14de245e6d

hi bro, your link is page not found, Do you share again? Thank you

@sparsh820

This comment has been minimized.

Copy link

@sparsh820 sparsh820 commented May 28, 2021

@SuppressLint("NewApi")
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void doWork() {

   ConnectivityManager cm=(ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);


    cm.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback(){

        public void onAvailable(Network network) {
            Log.e("hola", "The default network is now: " + network);
             network=cm.getActiveNetwork();
              if(network!=null){
                  SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
                  String minMagnitude = sharedPrefs.getString(
                          getString(R.string.settings_min_magnitude_key),
                          getString(R.string.settings_min_magnitude_default));
                  String orderBy = sharedPrefs.getString(
                          getString(R.string.settings_order_by_key),
                          getString(R.string.settings_order_by_default)
                                                  );

                  if (orderBy.equals(getString(R.string.settings_order_by_default))){
                      extractdata(minMagnitude, "magnitude-asc");
                  }else{
                      extractdata(minMagnitude,orderBy);

                  }

              }
        }

        public void onCapabilitiesChanged(@NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
            super.onCapabilitiesChanged(network, networkCapabilities);
            Log.i(TAG,network+" "+networkCapabilities.toString()+" "+networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET));
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    View loadingIndicator = findViewById(R.id.progressBar);
                    loadingIndicator.setVisibility(View.VISIBLE);
                    rvCourses=findViewById(R.id.rvCourses);
                    mEmptyTextview=findViewById(R.id.empty_view);
                    mEmptyTextview.setVisibility(View.GONE);
                    rvCourses.setVisibility(View.VISIBLE);
                }
            });
            if(networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)){

                SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
                String minMagnitude = sharedPrefs.getString(
                        getString(R.string.settings_min_magnitude_key),
                        getString(R.string.settings_min_magnitude_default));
                String orderBy = sharedPrefs.getString(
                        getString(R.string.settings_order_by_key),
                        getString(R.string.settings_order_by_default)
                );

                if (orderBy.equals(getString(R.string.settings_order_by_default))){
                    extractdata(minMagnitude, "magnitude-asc");
                }else{
                    extractdata(minMagnitude,orderBy);

                }


            }
        }

           public void onLost(Network network) {
            /*View loadingIndicator = findViewById(R.id.progressBar);
            loadingIndicator.setVisibility(View.GONE);
            Log.e(TAG, "The application no longer has a default network. The last default network was " + network);
            rvCourses=findViewById(R.id.rvCourses);
            mEmptyTextview=findViewById(R.id.empty_view);
            mEmptyTextview.setText("NO INTERNET CONNECTION");
            rvCourses.setVisibility(View.GONE);
            mEmptyTextview.setVisibility(View.VISIBLE);*/

            runOnUiThread(new Runnable() {
                @Override
                public void run() {

                    View loadingIndicator = findViewById(R.id.progressBar);
                    loadingIndicator.setVisibility(View.GONE);
                    Log.e(TAG, "The application no longer has a default network. The last default network was " + network);
                    rvCourses=findViewById(R.id.rvCourses);
                    mEmptyTextview=findViewById(R.id.empty_view);
                    mEmptyTextview.setText("NO INTERNET CONNECTION");
                    rvCourses.setVisibility(View.GONE);
                    mEmptyTextview.setVisibility(View.VISIBLE);

                }
            });


        }





    }

    );


}
@sparsh820

This comment has been minimized.

Copy link

@sparsh820 sparsh820 commented May 28, 2021

Hope this helps ;P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment