Handling Internet inconsistencies in iOS App

Ashishpiseyshaadi/ August 10, 2019/ Engineering, Mobile

Let’s face it: if your iOS app is consuming web services, there is one common problem we developers are going to want to stand up to. And that is – not presenting a blank screen to users with unreliable internet

But the answer turns out to be more complex than you think at first.


We are not just talking about a simple binary choice between data and no data. We are also talking about the mushy frustrating middleground of users trying to access your app on a slow network connection.

Lets take a look at different possible scenarios

  1. User is connected to internet via Wifi.
  2. User is connected to Wifi without internet.
  3. User is not connected to Internet
  4. Connected to internet through cellular network.
  5. Connected to cellular without internet.

Now surprisingly, number 2 above (connected to Wifi without internet) happens to be the tricky one cause it’s difficult to detect. This is because traditional tools like Reachability provided by Apple are unable to accurately detect this. In iOS, Reachability is the standard way of detecting if a web service is reachable or not.

But Reachability does not guarantee if a web service can really connect to the internet; it only detects that an interface is available that might allow a connection. In other words it is not designed to detect if an actual data packet has been sent from a mobile device and received by a server. It only detects if the data packet has left the device.

This is why it fails where a user is actually connected to WIFI but that wifi has no internet access. For example, if a user is connected to RailWire Wifi at railway station and has not authenticated on its captive portal, all requests for data will fail until the user has agreed to the Wi-Fi hotspot’s terms of service or registered as a new user — depending on the requirements of the hotspot. This can result in attempts made by your app to retrieve data from the Internet to fail which means poor app experience for user.

Here’s a simple workaround:

We can simply try to connect with a reliable public URL like google and if we are not able to connect to that endpoint, we can conclude we are not reachable to internet.

This is what it would look like:

class func isConnectedToInternet() -> Bool {
  var Status:Bool = false
  let url = NSURL(string: "http://google.com/")
  let request = NSMutableURLRequest(url: url! as URL)
  request.httpMethod = "HEAD"
  request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData
  request.timeoutInterval = 1.0
  var response: URLResponse?
  _ = try? NSURLConnection.sendSynchronousRequest(request as URLRequest, returning: &response) as NSData?
  if let httpResponse = response as? HTTPURLResponse {
     if httpResponse.statusCode == 200 {
        Status = true
     }
  }
  return Status
}

The above code hits google.com to check if we have internet access. This code solves the issue when connected to a public network requiring authentication like RailWire Wifi at railway stations which would result in connection time out if not authenticated. But hitting an URL every now and then will not be ideal. Also it could risk in user getting stuck at some point if timeout interval is not set properly.

At shaadi.com, we decided to dig deeper and find a solution that would not only solve these issues but also provide mechanisms to detect internet status changes and handle those status changes across our app.

Common scenarios we wanted to address:

  1. Users connected and unconnected to wifi
  2. Users connected to Wifi or mobile data but internet was not reachable
  3. Users connected to Wifi but the connection is lost after request is sent to server.

How we propose to solve this:

Good news is that theiOS platform already has an answer to this problem but doesn’t have an API included in iOS SDK. iOS adopts a protocol called Wireless Internet Service Provider roaming (WISPr 2.0) published by the Wireless Broadband Alliance. This protocol provides an interface called Smart Client to Access Gateway which determines how to authenticate users accessing public IEEE 802.11 (Wi-Fi) networks using the Universal Access Method when a captive portal presents a login page to the user. The user must then register or provide login credentials via a web browser in order to be granted access to the network using RADIUS or another protocol providing centralized Authentication, Authorization, and Accounting (AAA).

In order to detect that it has connected to a Wi-Fi network with a captive portal, iOS contacts a number of endpoints hosted by Apple — here’s an example. Each endpoint hosts a small HTML page of the form:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
<html>
  <head>
    <title>Success</title>
  </head>
  <body>
    Success
  </body>
</html>

iOS downloads HTML content of this page and checks if it contains “Success”. If the word “Success” is found then internet is reachable. Or else if a login page is presented by a captive portal then it will not contain the expected template. Apple hosts several of these pages so that if one of these pages go down, a number of fallbacks can be checked to know if internet is reachable or whether our connection is blocked by a captive portal.

Unfortunately iOS exposes no framework to developers which allows us to make use of the operating system’s awareness of captive portals.

So we realised we need a good iOS library around the reachability API which will combine reachability to check if connected via Wifi and then detect a captive portal. It also needs to provide a mechanism to notify status change throughout the app. We found a library named ‘Connectivity’ which provides just this.

Connectivity to the rescue

Connectivity is an open source library provided under MIT licence. It enables us to solve the problem of detecting connectivity to captive portals and it includes Reachability provided by Apple too. It tries to connect with an array of connectivity URLs provided by us whenever there is any change in Wifi or WWAN connectivity.

Its API syntax is similar to Reachability, with startNotifier() and stopNotifier(). We can observe connection status asynchronously by listening to observer with notification name kNetworkConnectivityChangedNotification. This is accessed via Notification.Name.ConnectivityDidChange in Swift. We can also choose to receive connectivity status change synchronously by using status property.

Setup

  1. Create an instance of connectivity:
let connectivity: Connectivity = Connectivity()
  1. Create a function to handle UI when connectivity status changes
func updateConnectionStatus(_ status: Connectivity.ConnectivityStatus) {
  switch status {
    case .connectedViaWiFi:
    case .connectedViaWiFiWithoutInternet:
    case .connectedViaWWAN:
    case .connectedViaWWANWithoutInternet:
    case .notConnected:
   }
}

Now this function will be invoked via a closure shown below.

  1. Create a closure and assign it too connectivity.whenConnected. This closure will be invoked when there is any change in ConnectivityStatus
let connectivityChanged: (Connectivity) -> Void = { [weak self] connectivity in
    self?.updateConnectionStatus(connectivity.status)
}
connectivity.whenConnected = connectivityChanged
connectivity.whenDisconnected = connectivityChanged
  1. Start a Notifier to listen to connectivity changes asynchronously
connectivity.startNotifier()

One time checks

In case we don’t need an observer to notify connectivity status, we can simply check connectivity status. e.g. Checking network status before calling an api. This one time check can be done as follows:

let connectivity = Connectivity()
switch connectivity.status {
    case .connectedViaWiFi:
    case .connectedViaWiFiWithoutInternet:
    case .connectedViaWWAN:
    case .connectedViaWWANWithoutInternet:
    case .notConnected:
 }

Polling

Sometimes you need a constant update of connectivity status, for example in following cases:

  1. Real time chat
  2. Downloading a file or streaming a video
  3. Tracking your ride in a ride booking app

In this case, you can simply enable polling to receive status change update as follows:

connectivity.isPollingEnabled = true
connectivity.startNotifier()

Connectivity will now immediately post status change update via whenConnected / whenDisconnected closures. It will not wait on changes in Reachability state but it will poll provided connectivity URLs every 10 secs.

Success Threshold

Connectivity also provides ability to decide success threshold as percentage of total connections required in order to be considered as successful. If we are okay with 4 out 5 connectivity URLs getting successfully connected then we can set success threshold as 80%.

connectivity.successThreshold = Connectivity.Percentage(80.0)

SSL

If we want to allow HTTP URLs for ConnectivityURLs then we can set

connectivity.isHTTPSOnly = false.

This property will not work if you have not set the NSAllowsArbitraryLoads flag in your app’s Info.plist.

How we used this library in Shaadi

Before Connectivity handling we were managing no internet connection the same way as other apps by checking connectivity while making web service call and displaying annoying popups like this

Login screen without connectivityLogin screen without connectivity 😢

It is irritating for the user to see this message after he enters his username and password. Why can’t we let him know his internet connectivity status upfront ? With Connectivity now integrated, our user experience now looks like this:

Login screen without connectivity
Login screen without connectivity 😃