10 min read

 In this article by Kerri Shotts, author of PhoneGap for Enterprise, we will see how an app reacts to the network changes and activities. In an increasingly connected world, mobile devices aren’t always connected to the network. As such, the app needs to be sensitive to changes in the device’s network connectivity. It also needs to be sensitive to the type of network (for example, cellular versus wired), not to mention being sensitive to the device the app itself is running on.

Given all this, we will cover the following topics:

  • Determining network connectivity
  • Getting the current network type
  • Detecting changes in connectivity
  • Handling connectivity issues

(For more resources related to this topic, see here.)

Determining network connectivity

In a perfect world, we’d never have to worry if the device was connected to the Internet or not, and if our backend was reachable. Of course, we don’t live in that world, so we need to respond appropriately when the device’s network connectivity changes.

What’s critical to remember is that having a network connection in no way determines the reachability of a host. That is to say, it’s entirely possible for a device to be connected to a Wi-Fi network or a mobile hotspot and yet is unable to contact your servers. This can happen for several reasons (any of which can prevent proper communication with your backend).

In short, determining the network status and being sensitive to changes in the status really tells you only one thing: whether or not it is futile to attempt communication. After all, if the device isn’t connected to any network, there’s no reason to attempt communication over a nonexistent network. On the other hand, if a network is available, the only way to determine if your hosts are reachable or not is to try and contact them.

The ability to determine the device’s network connectivity and respond to changes in the status is not available in Cordova/PhoneGap by default. You’ll need to add a plugin before you can use this particular feature.

You can install the plugin as follows:

cordova plugin add org.apache.cordova.network-information

The plugin’s complete documentation is available at: https://github.com/apache/cordova-plugin-network-information/blob/master/doc/index.md.

Getting the current network type

Anytime after the deviceready event fires, you can query the plugin for the status of the current network connection by querying navigator.connection.type:

var networkType = navigator.connection.type;
switch (networkType) {
case Connection.UNKNOWN:
console.log ("Unknown connection."); break;
case Connection.ETHERNET:
console.log ("Ethernet connection."); break;
case Connection.WIFI:
console.log ("Wi-Fi connection."); break;
case Connection.CELL_2G:
console.log ( "Cellular (2G) connection."); break;
case Connection.CELL_3G:
console.log ( "Cellular (3G) connection."); break;
case Connection.CELL_4G:
console.log ( "Cellular (4G) connection."); break;
case Connection.CELL:
console.log ( "Cellular connection."); break;
case Connection.NONE:
console.log ( "No network connection."); break;
}

If you executed the preceding code on a typical mobile device, you’d probably either see some variation of the Cellular connection or the Wi-Fi connection message. If your device was on Wi-Fi and you proceeded to disable it and rerun the app, the Wi-Fi notice will be replaced with the Cellular connection notice. Now, if you put the device into airplane mode and rerun the app, you should see No network connection.

Based on the available network type constants, it’s clear that we can use this information in various ways:

  • We can tell if it makes sense to attempt a network request: if the type is Connection.NONE, there’s no point in trying as there’s no network to service the request.
  • We can tell if we are on a wired network, a Wi-Fi network, or a cellular network. Consider a streaming video app; this app can not only permit full quality video on a wired/Wi-Fi network, but can also use a lower quality video stream if it was running on a cellular connection.

Although tempting, there’s one thing the earlier code does not tell us: the speed of the network. That is, we can’t use the type of the network as a proxy for the available bandwidth, even though it feels like we can. After all, aren’t Ethernet connections typically faster than Wi-Fi connections? Also, isn’t a 4G cellular connection faster than a 2G connection?

In ideal circumstances, you’d be right. Unfortunately, it’s possible for a fast 4G cellular network to be very congested, thus resulting in poor throughput. Likewise, it is possible for an Ethernet connection to communicate over a noisy wire and interact with a heavily congested network. This can also slow throughput.

Also, while it’s important to recognize that although you can learn something about the network the device is connected to, you can’t use this to learn anything about the network conditions beyond that network. The device might indicate that it is attached to a Wi-Fi network, but this Wi-Fi network might actually be a mobile hotspot. It could be connected to a satellite with high latency, or to a blazing fast fiber network.

As such, the only two things we can know for sure is whether or not it makes sense to attempt a request, and whether or not we need to limit the bandwidth if the device knows it is on a cellular connection. That’s it. Any other use of this information is an abuse of the plugin, and is likely to cause undesirable behavior.

Detecting changes in connectivity

Determining the type of network connection once does little good as the device can lose the connection or join a new network at any time. This means that we need to properly respond to these events in order to provide a good user experience.

Do not rely on the following events being fired when your app starts up for the first time. On some devices, it might take several seconds for the first event to fire; however, in some cases, the events might never fire (specifically, if testing in a simulator).

There are two events our app needs to listen to: the online event and the offline event. Their names are indicative of their function, so chances are good you already know what they do.

The online event is fired when the device connects to a network, assuming it wasn’t connected to a network before. The offline event does the opposite: it is fired when the device loses a connection to a network, but only if the device was previously connected to a network. This means that you can’t depend on these events to detect changes in the type of the network: a move from a Wi-Fi network to a cellular network might not elicit any events at all.

In order to listen to these events, you can use the following code:

document.addEventListener ("online", handleOnlineEvent, false);
document.addEventListener ("offline", handleOfflineEvent, false);

The event listener doesn’t receive any information, so you’ll almost certainly want to check the network type when handling an online event. The offline event will always correspond to a Connection.NONE network type.

Having the ability to detect changes in the connectivity status means that our app can be more intelligent about how it handles network requests, but it doesn’t tell us if a request is guaranteed to succeed.

Handling connectivity issues

As the only way to know if a network request might succeed is to actually attempt the request; we need to know how to properly handle the errors that might rise out of such an attempt.

Between the Mobile and the Middle tier, the following are the possible errors that you might encounter while connecting to a network:

  • TimeoutError: This error is thrown when the XHR times out. (Default is 30 seconds for our wrapper, but if the XHR’s timeout isn’t otherwise set, it will attempt to wait forever.)
  • HTTPError: This error is thrown when the XHR completes and receives a response other than 200 OK. This can indicate any number of problems, but it does not indicate a network connectivity issue.
  • JSONError: This error is thrown when the XHR completes, but the JSON response from the server cannot be parsed. Something is clearly wrong on the server, of course, but this does not indicate a connectivity issue.
  • XHRError: This error is thrown when an error occurs when executing the XHR. This is definitely indicative of something going very wrong (not necessarily a connectivity issue, but there’s a good chance).
  • MaxRetryAttemptsReached: This error is thrown when the XHR wrapper has given up retrying the request. The wrapper automatically retries in the case of TimeoutError and XHRError.

In all the earlier cases, the catch method in the promise chain is called. At this point, you can attempt to determine the type of error in order to determine what to do next:

function sendFailRequest() {
XHR.send( "GET", "http://www.really-bad-host-name.com /this/will/fail" ) .then(function( response ) {
   console.log( response );
})
.catch( function( err ) {
   if ( err instanceof XHR.XHRError ||     err instanceof XHR.TimeoutError ||     err instanceof XHR.MaxRetryAttemptsReached ) {
     if ( navigator.connection.type === Connection.NONE ) {
       // we could try again once we have a network connection
       var retryRequest = function() {
         sendFailRequest();
         APP.removeGlobalEventListener( "networkOnline",         retryRequest );
       };
       // wait for the network to come online – we'll cover       this method in a moment
       APP.addGlobalEventListener( "networkOnline",       retryRequest );
     } else {
       // we have a connection, but can't get through       something's going on that we can't fix.
       alert( "Notice: can't connect to the server." );
     }
   }
   if ( err instanceof XHR.HTTPError ) {
     switch ( err.HTTPStatus ) {
     case 401: // unauthorized, log the user back in
       break;
       case 403: // forbidden, user doesn't have access
       break;
       case 404: // not found
       break;
       case 500: // internal server error
       break;
       default:       console.log( "unhandled error: ", err.HTTPStatus );
     }
   }
   if ( err instanceof XHR.JSONParseError ) {
     console.log( "Issue parsing XHR response from server." );
   }
}).done();
}
sendFailRequest();

Once a connection error is encountered, it’s largely up to you and the type of app you are building to determine what to do next, but there are several options to consider as your next course of action:

  • Fail loudly and let the user know that their last action failed. It might not be terribly great for user experience, but it might be the only sensible thing to do.
  • Check whether there is a network connection present, and if not, hold on to the request until an online event is received and then send the request again. This makes sense only if the request you are sending is a request for data, not a request for changing data, as the data might have changed in the interim.

Summary

In this article you learnt how an app built using PhoneGap/Cordova reacts to the changing network conditions, also how to handle the connectivity issues that you might encounter.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here