Implementing a call to a RESTful API : iOS vs Android (Part I)

Week 5 was off at Hackership, to help us learners not burn out and take some time to visit Germany. So I took the opportunity of some free time to  go on holiday  work on finishing my iOS app, and sent it to Apple for review and publication! I used an amazing web app, LaunchKit, to do my screenshots, and here is the final result:

Capture d’écran 2015-08-16 à 18.22.09

As it is my first app I’m expecting Apple to complain about this and that, so I prefered to ship it way ahead of schedule in order to have time for an eventual debug session before the end of  Hackership.

I then moved on to reproduce the same app in Android, knowing a bit java and having played with Android dev in the past makes the process easier and faster than learning iOS, so I’m hopeful that I will have something decent to show by the end of the month.

By switching to Android I realized that, assuming you are not using Parse, one of the crucial first step is creating a nice and concise class to connect to your API, so I thought I’ll share the 2 approches I have used for iOS and Android.

Both are using a class called `queryApi` to wrap the main call to the api, making the other functions lighter and less prones to bugs.

Connecting to a RESTful API in Swift

class ApiResponse {
    var success: Bool = false
    var message: String = ""
    var data: AnyObject!
}


class queryAPI {
    
    var hostname = APP_DELEGATE.hostname
    
    func requestAPI(url:String, params:String, completion: (ApiResponse) -> ()) -> NSURLSessionDataTask {
        
        // we connect to the API with NSURLSession
        let session = NSURLSession.sharedSession()
        let urlStr = hostname+url
        
        var request = NSMutableURLRequest(URL: NSURL(string:urlStr )!)
        request.HTTPMethod = "POST"
        
        request.HTTPBody = params.dataUsingEncoding(NSUTF8StringEncoding)
        request.addValue("XMLHTTPRequest", forHTTPHeaderField: "X-Requested-With")
        request.addValue("KaliMessenger", forHTTPHeaderField: "User-Agent")
        
        var task = session.dataTaskWithRequest(request){
            (data, response, error) -> Void in

            var res = ApiResponse();
            
            if error != nil {
                res.success = false
                res.message = error.localizedDescription
                res.data = nil;
                
                println(error.localizedDescription)
                
            }
            else {
                
                var error: NSError?
                println("Performing request: "+urlStr)
                if let  jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as? NSDictionary //return a dictionnary with a list of indexes (array)
                {
                    res.success = jsonResult["success"] as! Bool;
                    if(res.success)
                    {
                        res.message = "";
                        res.data = jsonResult["data"]
                    }
                    else
                    {
                        res.message = jsonResult["msg"] as! String;
                        res.data = nil
                    }
                } else {
                    //when we do not manage to get the result, we display the url called, what we have received, and set the response to false
                    var dataContent = NSString(data: data, encoding: NSASCIIStringEncoding);
                    println("Error decoding JSON: ")
                    println(dataContent)
                    res.message = "error decoding json response"
                    res.data = nil
                    res.success = false
                }
                
            }
            
            //we return our items to the main thread
            let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
            dispatch_async(dispatch_get_global_queue(priority, 0)){
                
                dispatch_async(dispatch_get_main_queue()){
                    
                    completion(res)
                    
                }
                
            }
            
        }
        
        task.resume()
        return task
    }

}

So we basically are using the class `ApiReponse` to always return the same elements, making the following functions easier to use and debug. We also return NSURLSessionDataTask in order to be able to refer to the task if necessary, for example to kill the async task when the view will disappear, thus preventing memory leaks.

Still in `queryApi`, we can then use the wrapper `requestApi` as follow:

func user(userId:String, completion: (ModelUser?) -> ()) -> NSURLSessionDataTask {
        
        let url = "owapi/user/profile/"+userId
        var task = self.requestAPI(url, params: "") { (res: ApiResponse) -> () in
            var item: ModelUser? 
            if(res.success)
            {
                var data = res.data as! NSDictionary
                item = ModelUser(data: data)
            }
            completion(item)
            
        }
        return task
        
    }

And anywhere in the code, we can call this function easily like this:

let query = queryApi() 
query.user(userId, completion: { (user) -> () in
     self.currentUser = user
})

In case we need to kill the task, we can just return the task and store it in a class property :

let query = queryApi() 
self.taskQueryUser = query.user(userId, completion: { (user) -> () in
     self.currentUser = user
})

And then cancel it:

 override func viewWillDisappear(animated: Bool) {
        super.viewWillDisappear(animated)
        
        if (taskQueryUser != nil)
        {
            taskQueryUser?.cancel()
        }
    }

 

Next: Implementing a RESTful API in Android (Part II)