Home > Software engineering >  How to define a fallback case if a remote GET request fails?
How to define a fallback case if a remote GET request fails?

Time:11-03

I recently started with iOS development, and I'm currently working on adding new functionality to an existing app. For this feature I need to obtain a JSON file from a web server. However, if the server is unreachable (no internet/server unavailable/etc), a local JSON needs to be used instead.

In my current implementation I tried using a do catch block, but if there's no internet connection, the app just hangs instead of going to the catch block. JSON parsing and local data reading seem to work fine, the problem is likely in the GET method, as I tried to define a callback to return the JSON data as a separate variable, but I'm not sure if that's the correct way.

What is the best way to handle this scenario?

 let url = URL(string: "https://jsontestlocation.com")  // test JSON

        do {
            // make a get request, get the result as a callback
            let _: () = getRemoteJson(requestUrl: url!, requestType: "GET") {
                remoteJson in
                performOnMainThread {
                    self.delegate.value?.didReceiveJson(.success(self.parseJson(jsonData: remoteJson!)!))
                }
            }

        }
        catch {
            let localFile = readLocalFile(forName: "local_json_file")
            let localJson = parseJson(jsonData: localFile!)
            if let localJson = localJson {
                self.delegate.value?.didReceiveJson(.success(localJson))
            }
        }

getRemoteJson() implementation:

private func getRemoteJson(requestUrl: URL, requestType: String, completion: @escaping (Data?) -> Void) {
        // Method which returns a JSON questionnaire from a remote API
        
        var request = URLRequest(url: requestUrl)  // create the request
        request.httpMethod = requestType
        
        // make the request
        let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
            
            // check if there is any error
            if let error = error {
                print("GET request error: \(error)")
            }
            
            // print the HTTP response
            if let response = response as? HTTPURLResponse {
                print("GET request status code: \(response.statusCode)")
            }
            
            guard let data = data else {return}  // return nil if no data
            
            completion(data)  // return
        }
        task.resume()  // resumes the task, if suspended
    }

parseJson() implementation:

private func parseJson(jsonData: Data) -> JsonType? {
        // Method definition
        do {
            let decodedData = try JSONDecoder().decode(JsonType.self, from: jsonData)
            return decodedData
            
        } catch {
            print(error)
        }
        
        return nil
    }

CodePudding user response:

func NetworkCheck() -> Bool {

var isReachable = false

let reachability = Reachability()
print(reachability.status)

if reachability.isOnline {
    
    isReachable = true
    
    // True, when on wifi or on cellular network.
}
else
{
    // "Sorry! Internet Connection appears to be offline
}

return isReachable

}

Call NetworkCheck() before your API request. If It returns false, read your local json file. if true do remote API call.

Incase after remote API call, any failure check with HTTP header response code.

if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {

}

CodePudding user response:

I think you need to stop the request from hanging when it’s waiting for a response. The app might be running on a poor connection and be able to get some but not all the data in which case you likely want to failover to the local JSON.

I think you can roughly use what you have but add a timeout configuration on the URLSession as described here: https://stackoverflow.com/a/23428960/312910

CodePudding user response:

If you don't have to use complex logic with reachability, error handling, request retry etc. just return nil in your completion in case of data task, HTTP and No data errors:

func getRemoteJson(requestUrl: URL, requestType: String, completion: @escaping (Data?) -> Void) {
    var request = URLRequest(url: requestUrl)
    request.httpMethod = requestType
    
    let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
        // Task error
        guard  error == nil else {
            print("GET request error: \(error!)")
            completion(nil)
            return
        }
        
        // HTTP error
        guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
            print("GET request failed: \(response!.description)")
            completion(nil)
            return
        }
        
        // No data
        guard let data = data else {
            completion(nil)
            return
        }
        
        completion(data)
    }
    task.resume()
}

let url = URL(string: "https://jsontestlocation.com")!
getRemoteJson(requestUrl: url, requestType: "GET") { remoteJson in
    if let json = remoteJson {
        print(json)
        ...
    }
    else {
        print("Request failed")
        ...
    }
}
  • Related