To observe the connection changes in real time you can use LiveData. Before this we used BroadcastReceiver, but as you can see Declaring a broadcastreceiver for android.net.conn.CONNECTIVITY_CHANGE is deprecated for apps targeting N and higher.
I'll post the full code and explain every part of it.
public class ConnectionLiveData extends LiveData<Boolean> {
private ConnectivityManager.NetworkCallback networkCallback;
private final ConnectivityManager cm;
private final HashSet<Network> validNetworks = new HashSet<>();
private final ExecutorService executor = Executors.newSingleThreadExecutor();
public static final int NETWORK_STATUS_POST_DELAY = 1000;
public ConnectionLiveData(Context context) {
cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
}
private void checkValidNetworks() {
new Handler(Looper.getMainLooper()).postDelayed(() ->
postValue(validNetworks.size() > 0),
NETWORK_STATUS_POST_DELAY);
}
@Override
protected void onActive() {
networkCallback = createNetworkCallback();
NetworkRequest networkRequest = new NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build();
cm.registerNetworkCallback(networkRequest, networkCallback);
}
@Override
protected void onInactive() {
cm.unregisterNetworkCallback(networkCallback);
}
private ConnectivityManager.NetworkCallback createNetworkCallback() {
return new ConnectivityManager.NetworkCallback() {
/*
Called when a network is detected. If that network has internet, save it in the Set.
Source: https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onAvailable(android.net.Network)
*/
@Override
public void onAvailable(@NonNull Network network) {
NetworkCapabilities capabilities = cm.getNetworkCapabilities(network);
boolean hasInternetCapability = capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
if (hasInternetCapability) {
// check if this network actually has internet
executor.execute(() -> {
boolean hasInternet = DoesNetworkHaveInternet.execute(network.getSocketFactory());
if (hasInternet) {
validNetworks.add(network);
checkValidNetworks();
}
});
}
}
/*
If the callback was registered with registerNetworkCallback() it will be called for each network which no longer satisfies the criteria of the callback.
Source: https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback#onLost(android.net.Network)
*/
@Override
public void onLost(@NonNull Network network) {
validNetworks.remove(network);
checkValidNetworks();
}
};
}
}
We extend LiveData here and implement 2 functions: onActive() and onInactive(). As you know LiveData is lifecycle-aware, when there is one observer, the onActive will be called. I use them to register/unregister the ConnectivityManager.
When we register our ConnectivityManager we pass a networkCallback as a parameter. It has several methods, but as you can see in the createNetworkCallback() function we override only onAvailable() and onLost() functions. The onAvailable() and onLost() will be called if you will switch your network on/off.
There may be a case when the connection is on, but there's no connection with the server. In this case, I found a simple class that we can use to ping and validate our connection.
Using ExecutorService I validate the internet connection by pinging.
boolean hasInternet = DoesNetworkHaveInternet.execute(network.getSocketFactory());
The DoesNetworkHaveInternet code:
public class DoesNetworkHaveInternet {
private static final String HOST_NAME = "8.8.8.8";
private static final int PORT = 53;
private static final int CONNECT_TIMEOUT = 1500;
@WorkerThread
static Boolean execute(SocketFactory socketFactory) {
try {
Socket socket = socketFactory.createSocket();
socket.connect(new InetSocketAddress(HOST_NAME, PORT), CONNECT_TIMEOUT);
socket.close();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
}
It's pinging and validates that we have a stable connection.
In the end, after verifying that we have a connection, we call the checkValidNetworks() functions. There is a delay because in some cases your connection may be off and on for a moment and for that I found there's no need to show network error. The checkValidNetworks() checks if validNetworks is not empty. It is a list as there are some types of connections, like wifi and mobile networks. If there's at least one stable connection, the validNetworks will not be empty.
That's it. You can observe it in your fragment or in an activity like this:
ConnectionLiveDataJava connectionLiveData = new ConnectionLiveDataJava(this);
connectionLiveData.observe(this, isConnected -> {
});