Showing posts with label intermediate. Show all posts
Showing posts with label intermediate. Show all posts

Saturday, June 11, 2016

Completion handlers for NSURLSessionDataTask. Returning downloaded data on iOS

This tutorial picks up exactly where I left my previous iOS tutorial of "Downloading and parsing JSON data in Swift 2.2", so you may want to at least read over that one to see where we're starting from, and how we got here.

Refactor code

The very first step is refactoring our code, primarily to make more sense of how a real application would use completion handlers to pass downloaded data around.

We'll begin by creating a new Swift class called "Utilities.swift" which will inherit from NSObject:
import Foundation
class Utilities : NSObject
{
    
}
New file added, and the project structure

We will then create a new static function (didn't we use to call them method in obj-c?) called getJsonData, which will take 1 parameter, of type String, and returns Void.
The Utilities class should look like this now:
import Foundation
class Utilities : NSObject
{
    static func getJsonData(jsonUrlAsString:String) -> Void
    {
        
    }
}
We then go back to our ViewController class we were using on the first tutorial, and cut everything from the network configuration, to the resume() call. We will paste this inside of our getJsonData inside of our Utilities class.

The Utilities class should be complaining about the UILabels we set in the previous tutorial.
We will need the code for this UILabels later, but since its easy to create we will just delete it from Utilities.

At this point, our Utilities class should look like this:
import Foundation
class Utilities : NSObject
{
    static func getJsonData(jsonUrlAsString:String) -> Void
    {
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
        let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Auth":"token"]
        configuration.HTTPAdditionalHeaders = headers
        let session = NSURLSession(configuration: configuration)
        
        let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
            if (error == nil)
            {
                print("dataReceived = \(dataReceived)")
                do
                {
                    let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
                    print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
                    
                    let nameRead = dataDownloadedAsJson["name"] as? String
                    let countryRead = dataDownloadedAsJson["country"] as? String
                    let genderRead = dataDownloadedAsJson["gender"] as? String
                    let ageRead = dataDownloadedAsJson["age"] as? Int
                    
                    print("nameRead = \(nameRead!)")
                    print("countryRead = \(countryRead!)")
                    print("genderRead = \(genderRead!)")
                    print("ageRead = \(ageRead!)")
                }
                catch
                {
                    
                }
            }
            else
            {
                print("Error downloading data. Error = \(error)")
            }
        }
        
        // actually execute the task
        downloadTask.resume()
    }
}

On our ViewController we want to call the function getJsonData from the Utilities class, so we do this passing the url as a String.
At this point, the ViewController class should look like this:
import UIKit
class ViewController: UIViewController
{

    @IBOutlet weak var labelName: UILabel!
    @IBOutlet weak var labelAge: UILabel!
    @IBOutlet weak var labelGender: UILabel!
    @IBOutlet weak var labelCountry: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let jsonUrlAsString = "https://api.myjson.com/bins/2c0aw"
        Utilities.getJsonData(jsonUrlAsString)        
    }
}
So now, if we were to run our application, we should not have any compiling errors, and we should see the output on the console displaying something like this:
dataReceived = Optional()
dataDownloadedAsJson = {
    age = 32;
    country = USA;
    gender = male;
    name = "Eduardo Flores";
}
nameRead = Eduardo Flores
countryRead = USA
genderRead = male
ageRead = 32
sdad

If the code transplant so far does the same thing as it does to me, then we're ready to move on.
Everything should work, except...well the app UI.
Before we fix that we need to create an object to return.

Create return Object
I could pass a 4 variables back, but that'll be horrible and not what you'll do on a regular basis. (you don't do that, right?)
So let's create a new swift class and let's call it "Person.swift"
In here, we'll create 4 variables: name, age, gender and country.
The whole object should look like this:
import Foundation
class Person: NSObject
{
    var name : String?
    var age : Int?
    var gender : String?
    var country : String?
}
Now that we have the object data, let's add the Person object to the getJsonData function from the Utilities class. Now the code inside getJsonData looks like this:
do
static func getJsonData(jsonUrlAsString:String) -> Void
{
    let person = Person()

    let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
    let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Auth":"token"]
    configuration.HTTPAdditionalHeaders = headers
    let session = NSURLSession(configuration: configuration)
    
    let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
        if (error == nil)
        {
            print("dataReceived = \(dataReceived)")
        do
        {
            let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
            print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
            
            let nameRead = dataDownloadedAsJson["name"] as? String
            let countryRead = dataDownloadedAsJson["country"] as? String
            let genderRead = dataDownloadedAsJson["gender"] as? String
            let ageRead = dataDownloadedAsJson["age"] as? Int
            
            print("nameRead = \(nameRead!)")
            print("countryRead = \(countryRead!)")
            print("genderRead = \(genderRead!)")
            print("ageRead = \(ageRead!)")
            
            person.name = nameRead!
            person.age = ageRead!
            person.gender = genderRead!
            person.country = countryRead!
        }
        catch
        {
            
        }
        }
        else
        {
            print("Error downloading data. Error = \(error)")
        }
    }
    
    // actually execute the task
    downloadTask.resume()
}
And now we need to return the Person object.
This is not as simple as you expect...

Return data as return type

The issue we have is that we have data in one class, but we want to use it on another class. This is a fairly common affair in programming, and the initial approach would be to return Person instead of Void from the getJsonData function.

So let's try it!

In Utilities, change getJsonData the function to this:
static func getJsonData(jsonUrlAsString:String) -> Person
{
    let person = Person()

    let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
    let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Auth":"token"]
    configuration.HTTPAdditionalHeaders = headers
    let session = NSURLSession(configuration: configuration)
    
    let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
        if (error == nil)
        {
            print("dataReceived = \(dataReceived)")
        do
        {
            let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
            print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
            
            let nameRead = dataDownloadedAsJson["name"] as? String
            let countryRead = dataDownloadedAsJson["country"] as? String
            let genderRead = dataDownloadedAsJson["gender"] as? String
            let ageRead = dataDownloadedAsJson["age"] as? Int
            
            print("nameRead = \(nameRead!)")
            print("countryRead = \(countryRead!)")
            print("genderRead = \(genderRead!)")
            print("ageRead = \(ageRead!)")
            
            person.name = nameRead!
            person.age = ageRead!
            person.gender = genderRead!
            person.country = countryRead!
        }
        catch
        {
            
        }
        }
        else
        {
            print("Error downloading data. Error = \(error)")
        }
    }
    
    // actually execute the task
    downloadTask.resume()
    return person
}
and in the ViewController, change the way you call the getJsonData function, to this (and applying the result to the UI):
override func viewDidLoad() {
    super.viewDidLoad()
    
    let jsonUrlAsString = "https://api.myjson.com/bins/2c0aw"
    let person = Utilities.getJsonData(jsonUrlAsString)
    
    print("person.name = \(person.name!)")
    labelName.text = person.name!
}
If you run this, you'll be getting a runtime error of something like this:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb) 

Why are we getting this?
Well, the issue is that the getJsonData is downloading data on a different thread, and while this is great, it also means that the return statement of the getJsonData function is getting called before the data finishes downloading.
So, because of this, you're returning an empty Person object, with just nil values. Therefore person.name is nil

In other words, we're returning nothing because we're reaching our return statement before we ever even make our network call.
And that's not good...

Returning data with completion handler

Since we cannot return the data as a good ol' return type, we need a different way to return the data, after we know the download has completed....whenever that is.

For this we use Completion Handlers, also known as Callbacks in other languages.

I won't get into details on this, so if you want to learn more about Completion Handlers there are tons of online resources for that.
In short, they are a parameter sent from whoever wants to be notified of something happening on a different class or function.
In this tutorial, I'm just teaching you how to use them with a specific example to achieve something.

1. The first thing we'll do is modify the getJsonData function to accept a completion handler parameter.
Change the getJsonData function declaration from this:
static func getJsonData(jsonUrlAsString:String) -> Person
to this:
static func getJsonData(jsonUrlAsString:String, completionHandlerPerson:(responsePerson:Person?, errorPerson:NSError?) -> ())

In here, we're passing a new parameter called completionHandlerPerson, which will return 2 elements: a Person object response (named responsePerson), and a NSError object (named errorPerson)
We're also just returning () or Void now, since the return will be handled by the completionHandlerPerson.
Since our completionHandlerPerson has a Person object and a NSError object, we must always use it retuning both elements. This is handy because if we do get data downloaded, the NSError element will be nil, which is easy to check.

2. With the function declaration modified, we now need to return our data with our completionHandlerPerson element.
Our entire getJsonData function will now look like this:
static func getJsonData(jsonUrlAsString:String, completionHandlerPerson:(responsePerson:Person?, errorPerson:NSError?) -> ())
{
    let person = Person()

    let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
    let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Auth":"token"]
    configuration.HTTPAdditionalHeaders = headers
    let session = NSURLSession(configuration: configuration)
    
    let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
        if (error == nil)
        {
            print("dataReceived = \(dataReceived)")
            do
            {
                let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
                print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
                
                let nameRead = dataDownloadedAsJson["name"] as? String
                let countryRead = dataDownloadedAsJson["country"] as? String
                let genderRead = dataDownloadedAsJson["gender"] as? String
                let ageRead = dataDownloadedAsJson["age"] as? Int
                
                print("nameRead = \(nameRead!)")
                print("countryRead = \(countryRead!)")
                print("genderRead = \(genderRead!)")
                print("ageRead = \(ageRead!)")
                
                person.name = nameRead!
                person.age = ageRead!
                person.gender = genderRead!
                person.country = countryRead!
                
                completionHandlerPerson(responsePerson: person, errorPerson: nil)
            }
            catch
            {
                
            }
        }
            else
            {
                print("Error downloading data. Error = \(error)")
                completionHandlerPerson(responsePerson: nil, errorPerson: error)

            }
        }
    
    // actually execute the task
    downloadTask.resume()
}
Notice how we call the completionHandlerPerson twice: once for when the response is valid, and once for when the error happens.
We should also return a third version for when the parser fails, but that'll be your homework.

And with that, the Utilities.getJsonData function is complete.
We need to modify the caller class of ViewController.

In ViewController, you need to change this:
let person = Utilities.getJsonData(jsonUrlAsString)
for this:
Utilities.getJsonData(jsonUrlAsString,
                              completionHandlerPerson: {(responsePerson, errorPerson) -> Void in
                          })
so now we're passing our custom completion handler as a parameter to Utilities, and whenever the download thread finishes, we will be notified to do whatever want to do.

With that, all we're missing is placing the UI elements inside the caller completion handler, and we'll be done.

Here's the final ViewController class:
import UIKit
class ViewController: UIViewController
{

    @IBOutlet weak var labelName: UILabel!
    @IBOutlet weak var labelAge: UILabel!
    @IBOutlet weak var labelGender: UILabel!
    @IBOutlet weak var labelCountry: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let jsonUrlAsString = "https://api.myjson.com/bins/2c0aw"
        Utilities.getJsonData(jsonUrlAsString,
                              completionHandlerPerson: {(responsePerson, errorPerson) -> Void in
                                if (errorPerson == nil)
                                {
                                    // don't forget to update the UI in the main thread!
                                    dispatch_async(dispatch_get_main_queue()) { () -> Void in
                                        self.labelName.text = responsePerson!.name!
                                        self.labelAge.text = String(responsePerson!.age!)
                                        self.labelGender.text = responsePerson!.gender!
                                        self.labelCountry.text = responsePerson!.country!
                                    }
                                }
                                
                            })
    }
}

and here's the final Utilities class:
import Foundation
class Utilities : NSObject
{
    static func getJsonData(jsonUrlAsString:String, completionHandlerPerson:(responsePerson:Person?, errorPerson:NSError?) -> ())
    {
        let person = Person()

        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
        let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Auth":"token"]
        configuration.HTTPAdditionalHeaders = headers
        let session = NSURLSession(configuration: configuration)
        
        let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
            if (error == nil)
            {
                print("dataReceived = \(dataReceived)")
                do
                {
                    let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
                    print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
                    
                    let nameRead = dataDownloadedAsJson["name"] as? String
                    let countryRead = dataDownloadedAsJson["country"] as? String
                    let genderRead = dataDownloadedAsJson["gender"] as? String
                    let ageRead = dataDownloadedAsJson["age"] as? Int
                    
                    print("nameRead = \(nameRead!)")
                    print("countryRead = \(countryRead!)")
                    print("genderRead = \(genderRead!)")
                    print("ageRead = \(ageRead!)")
                    
                    person.name = nameRead!
                    person.age = ageRead!
                    person.gender = genderRead!
                    person.country = countryRead!
                    
                    completionHandlerPerson(responsePerson: person, errorPerson: nil)
                }
                catch
                {
                    
                }
            }
                else
                {
                    print("Error downloading data. Error = \(error)")
                    completionHandlerPerson(responsePerson: nil, errorPerson: error)

                }
            }
        
        // actually execute the task
        downloadTask.resume()
    }
}

And that should be all!
Your app should now be ready to download data in some class, and consume it on another one.

Eduardo

(it's midnight so let me know if I missed something, or something doesn't make sense)

Downloading and parsing JSON data in Swift 2.2

I've primarily moved to the Android world, but I had a side gig that required me to get a jump start on Swift. Last time I touched Swift was with Swift 1.1, and things have changed a bit.

In this tutorial I'm going to show you how to download data from a JSON file stored somewhere on the web, then we're going to parse it, and finally we're going to show it in the UI of the app.

For this tutorial we're going to use NSURLSession and NSURLSessionDataTask to download the data, and we'll use the regular class NSJSONSerialization to parse the downloaded JSON data.
All 3 of these classes are part of the UIKit of iOS, and they are not third-party libraries.

Enough talk, let's get to work.
Note: this was done on June 2016, using Xcode 7.3.1 and Swift 2.2.

Sample JSON

We'll begin with a super simple JSON file that I have stored here, and in case the url is down by the time you read this, here's a copy of it's content:
{

    "name": "Eduardo Flores",
    "age": 32,
    "gender": "male",
    "country": "USA"

}
As you can see, this JSON is super simple. It only contains 1 JSON object (the root object), and 4 key/value pairs, where 3 of these values are Strings and 1 is an Int.

Download the data

For this project I'm going to assume that you know how to create an iOS project in Xcode, so I'll skip that part.
My project is a single view project with nothing on it.

We first need to define the url where our JSON file is, so we'll do that this way:
import UIKit
class ViewController: UIViewController
{

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let jsonUrlAsString = "https://api.myjson.com/bins/2c0aw"
    }
}
After this, we need to setup a configuration we're going to use.
While headers won't be really needed for this simple JSON, I will add them to this tutorial so you know where to place them. Headers go as a dictionary of key/value pairs.
Here's what my configuration, making a GET request, with headers looks like:
import UIKit
class ViewController: UIViewController
{

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let jsonUrlAsString = "https://api.myjson.com/bins/2c0aw"
        
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
        let headers: [NSObject : AnyObject] = ["Accept":"application/json"]
        configuration.HTTPAdditionalHeaders = headers
        let session = NSURLSession(configuration: configuration)
    }
}
If we had additional headers, like an authentication token or something else, that would look like this:
let headers: [NSObject : AnyObject] = ["Accept":"application/json","Auth":"token"]
So, with the configuration set, and the session variable created, we now need to create the NSURLSessionDataTask to actually make the network download.
For this we will use the NSURLSessionDataTask initializer that takes a NSURL, and a completion handler, like this:
session.dataTaskWithURL(NSURL:url>, completionHandler: <(NSData?, NSURLResponse?, NSError?) -> Void)
Since we want to use this, we need to assign this line to a variable.
The variable, with the completion handler using real variables, would then look like this:
let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
}
And in order to actually make the network call, we need to call the .resume() method of our downloadTask variable.
All together now, this looks like this:
import UIKit
class ViewController: UIViewController
{

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let jsonUrlAsString = "https://api.myjson.com/bins/2c0aw"
        
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
        let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Auth":"token"]
        configuration.HTTPAdditionalHeaders = headers
        let session = NSURLSession(configuration: configuration)

        let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
        }

        // actually execute the task
        downloadTask.resume()
    }
}
Yay, you've made a GET request. Woohoo!
But nothing visible has happened yet...

Check the downloaded data

In our completion handler of session.dataTaskWithURL we have 3 elements: a NSData object, a NSURLResponse object, and an NSError object.
If something went wrong during the download of the data, our NSError object will have an error, and therefore it won't be nil. If this happens, the NSData and NSURLResponse objects will be nil.
In other words, if the NSError object is nil then the NSData and NSURLResponse objects have data, and if the NSError object is not nil, then the NSData and NSURLResponse objects will be nil.
So, let's check for errors.
let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
    if (error == nil)
    {
        print("dataReceived = \(dataReceived)")
    }
    else
    {
        print("Error downloading data. Error = \(error)")
    }
}
This is now inside the downloadTask variable, and all I'm doing here is checking to see if the NSError object is nil. If the NSError object is nil, then I print the downloaded data to the console.
Otherwise it means that the NSError is not nil, and something went wrong somewhere.
Also, since the NSError object is nil, the dataReceived variable (coming from the completion handler of session.dataTaskWithURL) should contain data.

If you run the app now, you should have something like this in the console:
dataReceived = Optional(<os_dispatch_data: buf="0x7fc2d14453a0" data="" leaf="" size="66," x7fc2d1604cd0="">)
This is ugly and unusable, but at least it shows you we're getting data back!

Convert the downloaded NSData to JSON object

Assuming our data downloads correctly, and therefore there are no errors at this point, we need to convert the dataReceived into a readable JSONObject. For this, we'll use the NSJSONSerialization class, like this:
NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
However, this may throw an exception, as most serializers do, and we want to place the result of the Serialization inside of a variable. (note: there are several options inside the NSJSONReadingOptions class if you want to read into this)

So, adding the exception catcher, in true Java try/catch fashion, we do this now:
let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
    if (error == nil)
    {
        print("dataReceived = \(dataReceived)")
        do
        {
            let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
            print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
        }
        catch
        {
            
        }
    }
    else
    {
        print("Error downloading data. Error = \(error)")
    }
}

// actually execute the task
downloadTask.resume()
And now, the output should be something much friendlier, like this:
dataReceived = Optional()
dataDownloadedAsJson = {
    age = 32;
    country = USA;
    gender = male;
    name = "Eduardo Flores";
}
Yay, we have our JSON file, with readable data in the console!

Parsing the JSON data

Now that we have our JSON data available, we need to create Swift variables so we can pass the data around our application. We begin this process by parsing the entire JSON file into small variables for whatever elements we want.

The first thing we're going to do is get the name key of our JSON object. Since this JSON file is super simple, this is a 1 liner, like this:
let nameRead = dataDownloadedAsJson["name"] as? String
print("nameRead = \(nameRead!)")
And when you run it, you should have this output:
name = Eduardo Flores
So, what does this do?
This is actually fairly simple.
1. We have all of our serialized JSON data in a variable called dataDownloadedAsJson
2. Inside our JSON object, we're looking for the key of name
3. We believe, or expect, the value of our key name to be a String object. We could've used the as! keyword (with the exclamation point) instead of as? (with the question mark), but it is preferred to use the question mark version, which allows us to receive nil values. The as! keyword is expecting a String value, while the as? allows String AND nil values. (this is called Optionals, in case you want to read more about it)
4. String would be expected object type
5. We assign the result to this to a new variable called nameRead
6. And when we display it to the console we use the nameRead! with the exclamation point to unwrap the object into a String value

With that, we create variables for all of the key/values from our JSON. Note that you don't need to parse every single element in your JSON and you could just parse the elements you need.

And with that, our entire application looks like this now:
import UIKit
class ViewController: UIViewController
{

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let jsonUrlAsString = "https://api.myjson.com/bins/2c0aw"
        
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
        let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Auth":"token"]
        configuration.HTTPAdditionalHeaders = headers
        let session = NSURLSession(configuration: configuration)

        let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
            if (error == nil)
            {
                print("dataReceived = \(dataReceived)")
                do
                {
                    let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
                    print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
                    
                    let nameRead = dataDownloadedAsJson["name"] as? String
                    let countryRead = dataDownloadedAsJson["country"] as? String
                    let genderRead = dataDownloadedAsJson["gender"] as? String
                    let ageRead = dataDownloadedAsJson["age"] as? Int
                    
                    print("nameRead = \(nameRead!)")
                    print("countryRead = \(countryRead!)")
                    print("genderRead = \(genderRead!)")
                    print("ageRead = \(ageRead!)")
                }
                catch
                {
                    
                }
            }
            else
            {
                print("Error downloading data. Error = \(error)")
            }
        }
        
        // actually execute the task
        downloadTask.resume()
    }
}


Display data in UI

With the data downloaded and parsed, we now need to display our data in our UI.
For this I've created 4 UI elements in the storyboard, which I've wired up as IBOutlet in my ViewController file.

Control + Click to create connections

So now we could just try the regular self.something command we use to set elements, right?
Let's do it and see what happens. Here's the code of the downloadTask with the new code to set the UI elements:
let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
    if (error == nil)
    {
        print("dataReceived = \(dataReceived)")
        do
        {
            let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
            print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
            
            let nameRead = dataDownloadedAsJson["name"] as? String
            let countryRead = dataDownloadedAsJson["country"] as? String
            let genderRead = dataDownloadedAsJson["gender"] as? String
            let ageRead = dataDownloadedAsJson["age"] as? Int
            
            print("nameRead = \(nameRead!)")
            print("countryRead = \(countryRead!)")
            print("genderRead = \(genderRead!)")
            print("ageRead = \(ageRead!)")
            
            // set UI elements
            self.labelName.text = nameRead!
            self.labelAge.text = String(ageRead!)
            self.labelGender.text = genderRead!
            self.labelCountry.text = countryRead!
        }
        catch
        {
            
        }
    }
    else
    {
        print("Error downloading data. Error = \(error)")
    }
}
And run the app, and you'll get something like this:
Console output, and Simulator running
Your app runs, the console displays the correct output, but your app in the simulator never shows the correct values.
How is this possible, since we clearly have them in the console?

While we never explicitly requested this, the NSURLSessionDataTask class runs on a separate thread, which IS NOT THE UI THREAD.
This means that all of this code will run on the background, and someday, in the distant future, your UI will catch up and update with the code you run on the background thread.
This is a great feature of NSURLSessionDataTask because it allows us to make multiple network calls without locking up the UI for the user, but you need to be aware of it, and need to learn how to handle it properly (not just waiting forever for the UI to update).

So how do we solve this?
We call Apple's friendly (and C language looking) Grand Central Dispatch, and ask it to run our UI code in the UI thead, like this:
// set UI elements
// on the main thread
dispatch_async(dispatch_get_main_queue()) { () -> Void in
    self.labelName.text = nameRead!
    self.labelAge.text = String(ageRead!)
    self.labelGender.text = genderRead!
    self.labelCountry.text = countryRead!
}

So now, the entire code our of our entire application looks like this:
import UIKit
class ViewController: UIViewController
{

    @IBOutlet weak var labelName: UILabel!
    @IBOutlet weak var labelAge: UILabel!
    @IBOutlet weak var labelGender: UILabel!
    @IBOutlet weak var labelCountry: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let jsonUrlAsString = "https://api.myjson.com/bins/2c0aw"
        
        let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
        let headers: [NSObject : AnyObject] = ["Accept":"application/json", "Auth":"token"]
        configuration.HTTPAdditionalHeaders = headers
        let session = NSURLSession(configuration: configuration)

        let downloadTask = session.dataTaskWithURL(NSURL(string: jsonUrlAsString)!) { (dataReceived, response, error) in
            if (error == nil)
            {
                print("dataReceived = \(dataReceived)")
                do
                {
                    let dataDownloadedAsJson = try NSJSONSerialization.JSONObjectWithData(dataReceived!, options: .AllowFragments)
                    print("dataDownloadedAsJson = \(dataDownloadedAsJson)")
                    
                    let nameRead = dataDownloadedAsJson["name"] as? String
                    let countryRead = dataDownloadedAsJson["country"] as? String
                    let genderRead = dataDownloadedAsJson["gender"] as? String
                    let ageRead = dataDownloadedAsJson["age"] as? Int
                    
                    print("nameRead = \(nameRead!)")
                    print("countryRead = \(countryRead!)")
                    print("genderRead = \(genderRead!)")
                    print("ageRead = \(ageRead!)")
                    
                    // set UI elements
                    // on the main thread
                    dispatch_async(dispatch_get_main_queue()) { () -> Void in
                        self.labelName.text = nameRead!
                        self.labelAge.text = String(ageRead!)
                        self.labelGender.text = genderRead!
                        self.labelCountry.text = countryRead!
                    }
                }
                catch
                {
                    
                }
            }
            else
            {
                print("Error downloading data. Error = \(error)")
            }
        }
        
        // actually execute the task
        downloadTask.resume()
    }
}

And there you have it folks!
Now you can run your app, and the UI will be updating as soon as the data gets downloaded.

On my next tutorial I will be showing you how to return the downloaded data to another class, using your own completion handler.
This is more the likely the pattern you'll be using to download data in a larger app.

Eduardo.

Friday, March 25, 2016

Network calls using Retrofit 2.0

Hey look!
And with that, I'll make a new entry showing how to use Retrofit 2.0!

Note 1: this tutorial will use GSON as our deserializer. I've already written a GSON tutorial, so if you need help understanding GSON, check out what I did here.

Note 2: I have already written a tutorial on retrofit 1 in case you need to work with that instead. I refer the tutorial for Retrofit 1 a few times.

Create a new project

I'll assume that by now you know how to create a project. Alternatively you can apply this to an existing project, but for clarity I'll do this tutorial on a new blank project.

Add dependencies

Go to the build.gradle, and add the following dependencies:
compile 'com.squareup.retrofit2:retrofit:2.0.0'
compile 'com.squareup.retrofit2:converter-gson:2.0.0'
compile 'com.google.code.gson:gson:2.6.2'
We will be using the release version of retrofit 2.0, along with gson and the gson converter for retrofit 2.0.

So with those dependencies in place, this is what my entire gradle.build file looks like:
apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "eduardoflores.com.test_retrofit2"
        minSdkVersion 16
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.1'

    compile 'com.squareup.retrofit2:retrofit:2.0.0'
    compile 'com.squareup.retrofit2:converter-gson:2.0.0'
    compile 'com.google.code.gson:gson:2.6.2'
}
Done with the gradle.build file.

Our JSON url

We will be parsing the JSON that comes from this url:
http://api.nestoria.co.uk/api?country=uk&pretty=1&encoding=json&listing_type=buy&action=search_listings&page=1&place_name=london

This will give a long and complex JSON. In case the site goes down in the future, here's a sample of what this looks like.

Create Object models for deserialization

In the JSON we can see the root objects of request and response, but both of these objects are inside a larger JSON object. I will call this wrapping JSON object the ServiceResponse (this key name does not show up in the JSON and I just made it up, but I will reference it in Java)

As convention, you may want to create a new folder/package in your Android studio project to hold only your model objects. In my case, I named this folder/package as 'model'

In Android studio, inside of model, create a new Java class and name it ServiceResponse.java.
In here, we're going to have only 2 objects: a Request object, and a Response object.

My ServiceResponse.java java file now looks like this:
package eduardoflores.com.test_retrofit2.model;

import com.google.gson.annotations.SerializedName;

/**
 * @author Eduardo Flores
 */
public class ServiceResponse {

    public Request request;

    public Response response;
}
And we're done with ServiceResponse.java.

Now let's create a Request.java file. This Request.java class is going to handle this portion of the code:
   "request" : {
      "country" : "uk",
      "language" : "en",
      "listing_type" : "buy",
      "location" : "london",
      "num_res" : "20",
      "offset" : 0,
      "output" : "json_xs",
      "page" : "1",
      "pretty" : "1",
      "product_type" : "realestate",
      "property_type" : "property",
      "size_type" : "gross",
      "size_unit" : "m2",
      "sort" : "nestoria_rank"
   }
Because of that, in our Request.java class we should create a property for country, language, listing_type, location, num_res...

My Request.java class now looks like this:

package eduardoflores.com.test_retrofit2.model;

import com.google.gson.annotations.SerializedName;

/**
 * @author Eduardo Flores
 */
public class Request
{
    public String country;

    public String language;

    @SerializedName("listing_type")
    public String listingType;

    public String location;

    @SerializedName("num_res")
    public String numRes;

    public Integer offset;

    @SerializedName("json_xs")
    public String jsonXs;

    public String page;

    public String pretty;

    @SerializedName("product_type")
    public String productType;

    @SerializedName("property_type")
    public String propertyType;

    @SerializedName("size_type")
    public String sizeType;

    @SerializedName("size_unit")
    public String sizeUnit;

    public String sort;
}
Again, if you're lost on the GSON conversion, make sure you review my GSON tutorial.
Also, I have no idea what some of these things are, like "json_xs", "num_res" or "pretty" and I probably will never use them.

Now let's create the Response.java file. This file will target this section of the code:
"response" : {
      "application_response_code" : "110",
      "application_response_text" : "listings returned, location very large",
      "attribution" : {
         "img_height" : 22,
         "img_url" : "http://s.uk.nestoria.nestimg.com/i/realestate/all/all/pbr.png",
         "img_width" : 183,
         "link_to_img" : "http://www.nestoria.com"
      },
      "created_http" : "Fri, 25 Mar 2016 16:47:00 GMT",
      "created_unix" : 1458924420,
      "link_to_url" : "http://www.nestoria.co.uk/london/property/buy/results-20",
      "listings" : [
         {
             // a listing object
         }
       ]
       }
   }
The import part here is to see that we will have an Attribution object, and a list of Listings objects. We will need to create these objects as well.

Here's now my Response.java class:
package eduardoflores.com.test_retrofit2.model;

import com.google.gson.annotations.SerializedName;

import java.util.List;

/**
 * @author Eduardo Flores
 */
public class Response
{
    @SerializedName("application_response_code")
    public String applicationResponseCode;

    @SerializedName("application_response_text")
    public String applicationResponseText;

    public Attribution attribution;

    public List<Listing> listings;
}
Now that you know the drill, here are also my Attribution.java and Listing.java objects.
Attribution.java class:
package eduardoflores.com.test_retrofit2.model;

import com.google.gson.annotations.SerializedName;

/**
 * @author Eduardo Flores
 */
public class Attribution
{
    @SerializedName("img_url")
    public String imageUrl;

    // additional properties...
}

And Listing.java class:
package eduardoflores.com.test_retrofit2.model;

import com.google.gson.annotations.SerializedName;

/**
 * @author Eduardo Flores
 */
public class Listing {

    @SerializedName("datasource_name")
    public String datasourceName;

    public String guid;

    public String title;

    // additional properties...
}
And now we're done with the model objects! that took a while...

Now let's go back to focus on Retrofit 2, which is really what we care about.

Retrofit workflow

Let's remember the Retrofit workflow from v1 we want to continue for v2:


We will break our url apart, and then start from right to left, with the Service Interface.

Break URL into parts

You should notice by now that we're making a GET call.
Our entire url is this:
http://api.nestoria.co.uk/api?country=uk&pretty=1&encoding=json&listing_type=buy&action=search_listings&page=1&place_name=london

Our base url is: http://api.nestoria.co.uk

Our interface url is:  api

Our url parameters are: country=uk&pretty=1&encoding=json&listing_type=buy&action=search_listings&page=1&place_name=london

In retrofit 2 the interface URL no longer needs to start with "/" but the code should work with it too. There are discussions on whether it is better to have the URL with and without the "/". For now I will keep it.

Create Retrofit Service Interface

Create a new interface file named Services.java and add the interface portion of the URL:
package eduardoflores.com.test_retrofit2;

import java.util.Map;

import eduardoflores.com.test_retrofit2.model.ServiceResponse;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.QueryMap;

/**
 * @author Eduardo Flores
 */
public interface Services
{
    @GET("/api")
    Call getListings(@QueryMap Map<String,String> parameters);
}
And now you should be asking "What is that Call return type??" and maybe if you were using Retrofit 1 you should be asking "Where's the Callback??"

In Retrofit 2, the return type of the interface method is Call, which allows for your network call to be either synchronous or asynchronous! The specific call type is now determined when we call the method, instead of in the method itself.
And about the missing Callback...well, you no longer need it.

The parameters that I'm passing will be the key-value pairs for the GET call.
If you need more information on what QueryMap is, I've added a more detailed explanation of Retrofit annotations on my Retrofit 1 tutorial.

So that's all you need in the interface class.

Create the Retrofit Service class

Now we need to setup the heart of Retrofit 2.
Create a new java class named Service.java.
We will only use one method in here, so this method will be static, but you could setup the reusable portions of this method into a constructor (like I did on my Retrofit 1 tutorial).

Here's my Service.java class. I'll explain what everything does below the code.
package eduardoflores.com.test_retrofit2;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import eduardoflores.com.test_retrofit2.model.ServiceResponse;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * @author Eduardo Flores
 */
public class Service {

    public static Call getListings(String listingType, String city)
    {
        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(new Interceptor() {
                    @Override
                    public okhttp3.Response intercept(Chain chain) throws IOException {
                        okhttp3.Response response = chain.proceed(chain.request());
                        System.out.println("request = " + chain.request().url().toString());
                        System.out.println("response = " + response);
                        return response;
                    }
                }).build();

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://api.nestoria.co.uk")
                .addConverterFactory(GsonConverterFactory.create())
                .client(client)
                .build();

        Services services = retrofit.create(Services.class);

        Map parameters = new HashMap<>();
        parameters.put("country", "uk");
        parameters.put("pretty", "1");
        parameters.put("encoding", "json");
        parameters.put("listing_type", listingType);
        parameters.put("action", "search_listings");
        parameters.put("page", "1");
        parameters.put("place_name", city);

        return services.getListings(parameters);
    }
}

OkHttpClient: this is was the interceptor was on Retrofit 1. This can serve 2 purposes
1. Here's where you would be adding headers, if you need to add them to network call. These are added the same way as it was on Retrofit 1.
2. This can be used as a debugging tool. Right now I'm outputting the request and the response. This shows me if I'm getting a code 200 from the server, or something else, along with that's the entire call I'm making.
It is worth mentioning that the OkHttpClient is not required.

Retrofit: this used to be the RestAdapter. In here we add the baseUrl, the deserializer adapter, and the client (interceptor)

GsonConverterFactory: this is required in order to use GSON to parse our JSON data, and use the models we created earlier. There are options for XML as well, using Simple-XML.

We then create the Services object (from our Services.java interface) using the retrofit object we just created.

We create the parameters for the GET call as key-value pairs, and then make the call to the getListings() from the interface file.

All of this will return the Call type of object we are expecting from the interface.

Create the Consuming Activity

The time has come to finally consume (use) the data coming from the JSON, and from all of our hard work.
This part is actually pretty simple.

Here's the code for my standard default blank activity MainActivity.java:
package eduardoflores.com.test_retrofit2;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import eduardoflores.com.test_retrofit2.model.Listing;
import eduardoflores.com.test_retrofit2.model.ServiceResponse;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Call<ServiceResponse> call = Service.getListings("buy", "london");
        call.enqueue(new Callback<ServiceResponse>() {
            @Override
            public void onResponse(Call<ServiceResponse> call, Response<ServiceResponse> response) {
                ServiceResponse serviceResponse = response.body();

                System.out.println("imageUrl = " + serviceResponse.response.attribution.imageUrl);

                for (Listing listing : serviceResponse.response.listings)
                {
                    System.out.println("listing title = " + listing.title);
                }
            }

            @Override
            public void onFailure(Call<ServiceResponse> call, Throwable t) {
                System.out.println("failure. Throwable = " + t);
            }
        });
    }
}
BUT WHAT DOES IT DOOOOO???!!
Call<ServiceResponse> call = Service.getListings("buy", "london");
This calls our static method getListings(), which returns a Call type, right? That's true, except we are getting a Call object with a deserialized object type ServiceResponse (our root wrapper for the JSON call, remember?)

Then we have 2 choices: synchronous vs asynchronous.
call.enqueue(...)
The enqueue version of the Call object provides us with an asynchronous network call in Retrofit 2. Inside the call.enqueue we can now add a Callback object, and handle the output in whatever way we want.

Alternatively, there's this:
call.execute()
This would execute your network call synchronously.

Additionally you can now also call things like:
call.cancel()
to stop a network call, like when a user returns to the previous activity after a network call has started, but not finished.

So there you have it!
Don't forget about the Internet permission in your manifest, and you should be all set to go with Retrofit 2.0.

Sunday, February 28, 2016

Why bother with Unit Testing, JUnit?

One of "my" students came asking me a very important question that I think it is very hard to explain at a classroom level.

His question was "what is this JUnit thing, and why should I even bother with it?"

See, this makes perfect sense for this student to question this concept to himself. In a classroom environment, specially on low level classes, all of your information, classes and data models are created in a very small scale, and they are all created by you. So if the app compiles and runs, you don't have an error. Because of this Unit Testing, or JUnit in Java, makes almost no sense.

Let's talk about this concept to hopefully clear out for you what Unit Testing is, and why you actually want it.

What is JUnit?

JUnit is a Unit Testing framework for Java. For the rest of this blog post I will stop calling this concept JUnit, and we'll talk about unit testing in general so it can be applied to other languages.

What is Unit Testing?

"Unit testing is a software development process in which the smallest testable parts of an application, called units, are individually and independently scrutinized for proper operation. Unit testing is often automated but it can also be done manually."

What does this mean, and why should I care about it?

This brings up back to the original question.
See, Unit Testing is often (read: 99.9% of the time) done automatically, so you should think of Unit Testing as a code you create to validate how you expect your code to work.
This makes more sense in a real-world type of example.

Let's say we are developing an app that downloads the data of all of the students at your school from some school server. This server gives us the first name, last name and age of each student.
For Java, we would create a student object like this:
public class Student {

    private String firstName;
    private String lastName;
    private int age;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

And the server would return data like this:
{
    "students": [{
 "firstName": "Eduardo",
 "lastName": "Flores",
 "age": 20
 }, {
 "firstName": "Mary",
 "lastName": "Johnson",
 "age": 21
 }, {
 "firstName": "Mike",
 "lastName": "Pascal",
 "age": 22
 }, {
 "firstName": "John",
 "lastName": "Smith",
 "age": 25
 }
        // continue for 50000 students
        ]
}
This is a JSON string, but don't worry about that means right now. What matters is that you can see that every student has a firstName and lastName as String, and an age as a int.
We would assume your school also has something like 50,000 students...not just 4. You get the idea.

You create your app, it downloads and parses the data, everything works and you release your app. Life is good!

But then, one day, you hear your app crashed that morning. Then it crashed again when you tried it!

You debug your app, and you find out that the crash is cause by the app parsing the data that comes from the server. In other words, something the server is sending you is making the app crash.

Fixing this scenario without Unit Testing

Let's say you didn't do Unit Testing, and now you need to make sure the data coming from the server is valid. Well, unfortunately for you, the data is valid in general terms (there are no invalid characters) so since you don't have unit testing setup in your app, nothing created by a third party website or service can help you find the issue.

All you get is a crash report from the stack trace saying something about "not of type Integer (int)"

So...you'll have to loop through every single one of the students...all 50,000 of them...one...by...one...until you find the problem.

What's worse is that you created this app 6 months ago, and by now you have completely forgotten how the app works.

Fixing this scenario with Unit Testing

If you would've done unit testing on your application (and done it thoroughly), you can now run the unit test, and within 2 minutes you will know that a particular student has an age of 20.9 (no idea why, but I've seen it happen)

How would you know this?
Your unit test would fail when validating the age field of every student object downloaded, but it will tell you (again, if setup properly) that a student object coming from the server has an age of a double instead of an int.
This one student object is what is causing the crash in your app.

So now instead of having to go one-by-one on the data coming from the server, you can see that student "Jimmy Smith" of age 20.9 is causing the problem. And you did all of this within 2 minutes.

When you should write your Unit Test

Since your unit test is an individual test over a specific piece of data, you should write your unit tests when your app is running and it is stable. You should have all of your tests pass (unless you're intentionally creating failed tests) when your application and data is working properly.

Writing a unit test when the app is having a problem is very risky because it can give you false positives, but it can be done if you know exactly what you should expect out of your app.

What Unit Testing doesn't do for you


This seems to be the area where students struggle the most with Unit Testing, so here are the main 2 things Unit Testing does not do for you:

1. Unit testing can help you find a problem, and it can do it very fast and easy. However, it will not tell you how to fix the problem.
But think about it, in the example above, what would the solution be?
Should we modify our app?
Should we complain to the server to provide us with valid data?
Should the server remove that problem object?

In every situation the solution would be different, so Unit Testing can't tell you how to fix this.

2. Unit testing will not create any new end-user functionality to your app. Unit testing is basically a tool within an app created by developers for developers. This means that you can write unit testing cases for your app for a whole month, and your users won't see anything new (except possibly less issues). This gives new developers a feeling of writing code for nothing.

So there you have it. I hope a real-world example helps you.

Yes there could also be a better debugging environment, but the exaggerated scenario described here is very possible and it should highlight the benefits of Unit Testing.

Please let me know if you still have questions about Unit Testing, or provide any comments you may have about the concept of Unit Testing.

Monday, February 1, 2016

Network calls using Retrofit on Android

The purpose of this tutorial is to teach you how to setup Retrofit to make a network call on a clean, brand new Android application.
Retrofit works great with JSON and XML data, but the setup for JSON and XML is different in the deserializer, so in this tutorial I will stop once you get the data from the callback. In a later tutorial I will show how to deserialize (parse) the JSON or XML data received.

Note: this tutorial uses Android Studio 1.5.9 and Retrofit 1.9.0. For this tutorial we will get sample weather data in JSON format from open weather map. 

Sample URL and Sample JSON

We will use weather data from open weather map for this sample, so you might want to create a free account there to get a token. Their site is http://openweathermap.org/
We will be getting their Call for several cities ID network call, which returns a JSON like this:
{
  "cnt": 1,
  "list": [{
    "coord": {
      "lon": -0.13,
      "lat": 51.51
    },
    "sys": {
      "type": 1,
      "id": 5091,
      "message": 0.0048,
      "country": "GB",
      "sunrise": 1454226003,
      "sunset": 1454258868
    },
    "weather": [{
      "id": 803,
      "main": "Clouds",
      "description": "broken clouds",
      "icon": "04n"
    }],
    "main": {
      "temp": 12.47,
      "pressure": 1011,
      "humidity": 82,
      "temp_min": 11.8,
      "temp_max": 13
    },
    "wind": {
      "speed": 9.3,
      "deg": 250
    },
    "clouds": {
      "all": 75
    },
    "dt": 1454277230,
    "id": 2643743,
    "name": "London"
  }]
}
When it's all said and done, this should be the JSON we want to download and parse.

Workflow

Because using Retrofit requires a setup class and an interface, I created a super awesome looking workflow to hopefully explain this better:

So in the most basic scenario, you'll need at least 2 classes. Ideally you would set this up in at least 3 classes:
  1. An activity or class that triggers the call. This is usually an Android activity that starts the process after a button is pressed, or some other user interaction occurs
  2. A service class. While this could be in the same class as the activity, the idea of Retrofit is to reuse some elements to make multiple calls. This class sets up the RequestInterceptor, RestAdapter and deserializer. After that this calls an Interface to make the actual HTTP call
  3. The Service interface. This is an interface with just the url, parameters and type of call to make (GET, PUT, POST...). In here we receive the callback and we will update it so the activity that triggered this process (element #1 in this list) gets the data in an asynchronous way.
All of this happens in a asynchronous way on a separate thread, so you can (and will) call this process from the main UI thread without having to worry about creating or managing threads.

Now that we know the workflow we will use, let's get working from the Service Interface!

Setup

Before we start using Retrofit, we need to get the Retrofit SDK into our application.
Open your build.gradle file, and go to the dependencies section.
In here, we will add retrofit, okhttp and the gson libraries, like this:
    compile 'com.squareup.retrofit:retrofit:1.9.0'
    compile 'com.squareup.okhttp:okhttp:2.5.0'
    compile 'com.google.code.gson:gson:2.4'
So now, in a simple brand new android application, the whole gradle file would look like this:
apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "eduardoflores.com.test_networkconnection"
        minSdkVersion 16
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.android.support:design:23.1.1'
    compile 'com.squareup.retrofit:retrofit:1.9.0'
    compile 'com.squareup.okhttp:okhttp:2.5.0'
    compile 'com.google.code.gson:gson:2.4'
}
As usual, you may have additional information here, but we what we really care about for this tutorial are the lines for retrofit 1.9.0, okhttp 2.5.0 and gson 2.4.

Now, sync your gradle file to get the new SDK.

Update your manifest file

Since we're making a network call, and somehow Android still requires a permission for Internet in 2016, we need to add the permission for internet to the manifest file.
So, open the manifest file and add the Internet permission:
<uses-permission android:name="android.permission.INTERNET"/>

Create a basic return method

I know I said we will leave JSON and XML parsing for another time, and I will get much more specific on a different tutorial, but retrofit requires at least 1 object type to return in the callback.
Sure, we could use "Object" but let's do things right.

Create a new Java class named WeatherData. This WeatherData.java file will, for now, only contain 1 field for count.
Since the JSON key we will be getting for count is actually ctn, and I don't want to use that non-obvious name, we need to use an annotation to convert ctn to count. In the end, our entire WeatherData.java class looks like this:
package eduardoflores.com.test_networkconnection;

import com.google.gson.annotations.SerializedName;

/**
 * @author Eduardo Flores
 */
public class WeatherData {

    @SerializedName("cnt")
    public int count;

}


Understanding your HTTP request

If you know what GET calls are, you might want to skip this part.
 
Like mentioned before, we are going to get sample weather data in JSON form from Open Weather Map. This is their full url (although you need your own unique token):

http://api.openweathermap.org/data/2.5/group?id=524901,703448,2643743&units=metric&appid=44db6a862fba0b067b1930da0d769e98

Before moving forward, let's understand what this url is.
  • This is a GET call with multiple parameters
  • The host url is just http://api.openweathermap.org (you can create a free account here to get a similar JSON)
  • The path is "/data/2.5/group"
  • The first GET parameter is "id" with a value of "524901,703448,2643743"
  • The second GET parameter is "units" with a value of "metric"
  • The third, and last, GET parameter is "appid" with a value of "44db6a862fba0b067b1930da0d769e98"
This appid parameter is your token. You need to get a new one for this to work.

Setup the Service Interface

So we're going to break our URL into at least 2 parts, the host, and whatever else is in the url, starting with the slash "/".

We will create a new call (interface) named ServicesDownloader, and in here we will create a method call named getWeatherData with multiple parameters for the id, units, appid, and the callback of WeatherData type.
The entire ServicesDownloader.java interface looks like this:
package eduardoflores.com.test_networkconnection;

import retrofit.Callback;
import retrofit.http.GET;
import retrofit.http.Query;

/**
 * @author Eduardo Flores
 */
public interface ServicesDownloader
{
    @GET("/data/2.5/group")
    void getWeatherData(@Query("id")String id,
            @Query("units")String units,
            @Query("appid")String appid,
            Callback callback);
}


Retrofit Annotations

WHAT THE @&#* ARE THOSE @GET AND @QUERY THINGS??!!!!

Let me explain this. Retrofit uses these things called annotations, and these annotations do a lot of the heavy lifting for us in a simple word or line.
Here are some of the most commonly used annotations with Retrofit:

@GET("/someURL")
@POST("/someURL")
@PUT("/someURL")

These 3 make a GET, POST or PUT HTTP calls. The URL begin with the / after the domain.

@Query("queryName") String param
@Path("pathName") String param
@QueryMap("keyValuePair") Map <String, String> param
@Body("requestBody") String bodyOfRequest

The @Query annotation is used for adding elements to the URL GET call. For example, if the GET request uses a url like this:

http://www.example.com/someGETrequest?param1=data1&param2=data2

Then our Retrofit call would be:

@GET("/someGETrequest")
void someJavaMethodName(@Query("param1") String myData1, @Query("param2") String myData2;

Notice how we don't need to enter ?, & or = symbols. Retrofit does this for us.

@QueryMap and @Body are used the same way.

The @Path annotation is used for when we need to place something in the url, like a variable.
For example, if our URL GET call is:

http://www.example.com/en_US/someGETrequest

The en_US will be a locale, and this will vary depending on the locale we want. So for this, we would format our url like this:

@GET("/{locale}/someGETrequest")
void someJavaMethod(@Path("locale") String someLocale);

You can now mix and match them. For additional information, you may want to refer to the retrofit documentation.

Create the Service class

With the Services interface all finished, we now need to work on the next piece of the workflow, which is the Service java class.
Create a new Java class, and name it ServiceDownloader.java

In the new ServiceDownloader class, create a new private variable that refers to our previously created Interface:
private final ServicesDownloader servicesDownloader;

Create a constructor

Next we will create a constructor for the ServiceDownloader class, with a parameter of heades (even though we're not really using them in this tutorial)
public ServiceDownloader(final Map headers)
{
    RequestInterceptor requestInterceptor = new RequestInterceptor() {
        @Override
        public void intercept(RequestFacade request) {
            // handle the headers
            if ( !headers.isEmpty())
            {
                for (Map.Entry entry: headers.entrySet())
                {
                    request.addHeader(entry.getKey(), entry.getValue());
                }
            }
        }
    };

    // Get GSON
    Gson gson = new GsonBuilder().create();

    OkHttpClient client = new OkHttpClient();
    client.setReadTimeout(2, TimeUnit.MINUTES);

    // setting up the log level
    RestAdapter.LogLevel logLevel = RestAdapter.LogLevel.FULL;

    // create the rest adapter
    RestAdapter restAdapter = new RestAdapter.Builder()
            .setLogLevel(logLevel)
            .setEndpoint("http://api.openweathermap.org")
            .setRequestInterceptor(requestInterceptor)
            .setClient(new OkClient(client))
            .setConverter(new GsonConverter(gson))
            .build();

    servicesDownloader = restAdapter.create(ServicesDownloader.class);
}
In here we are doing the following:
  • Create a RequestInterceptor, and add the headers (if there are any)
  • Create a new GSON serializer. You can go to town with this, but a basic GsonBuilder will work for 99% of your requests
  • Setup a new OkHttpClient, and set a timeout. I set mine at 2 mins
  • You can set a log level. For debugging purposes full debug is my preference
  • Create the RestAdapter. In here add the log level you created, the RequestInterceptor, the okHttpClient, the Gson converter (could've been xml), and the "end point"
  • Add the newly created RestAdapter to your servicesDownloader variable.
As you can see here, the setEndPoint variable has a url of ("http://api.openweathermap.org"). This is the domain of our url. You can even set this in the build gradle file if you want, but the important part of this is to understand that retrofit uses the concept of "domain + something else". In here we set the domain part.

Create a method

With the constructor done, we need to create a method to actually call the ServicesDownloader interface, but with the setup you added in the constructor (that's why it's a variable)
I created this method:
public void getWeatherData(Callback callback)
{
    String id = "524901,703448,2643743";
    String units = "metric";
    String appid = "44db6a862fba0b067b1930da0d769e98";
    servicesDownloader.getWeatherData(id, units, appid, callback);
}
This is a super straight forward Java method. The id, units and maybe even appid variables could come from the calling activity, but for the purpose of easy reading I decided to place them here.

That's all. We're done with the gradle file, the manifest, the interface and the service class
Now all we have to do is finish the calling activity.

Modify the Activity

In the activity (MainActivity.java for me) we have to do 2 things for sure, and one optional:
  1. Call the getWeatherData method in the ServiceDownloader java class with a callback
  2. Create a callback, and handle the success or failure scenarios
  3. Create the http headers (optional)

Create the http headers 

This is not required for this tutorial, but odds are you're gonna have to add the headers to your real call, so might as well add them. These headers are basic and won't make or break anything, but the concept and structure would be demostrated.
public static Map getRequestHeaders() {
    Map headers = new HashMap<>();
    headers.put("Accept", "application/json");
    headers.put("Content-Type", "application/json");
    return headers;
}

Calling the getWeatherData method

In the onCreate method of the activity we then add this:
ServiceDownloader serviceDownloader = new ServiceDownloader(getRequestHeaders());
serviceDownloader.getWeatherData(weatherCallback);
And as you can see, I'm missing the variable weatherCallback. Let's make it!

Make the callback


In the activity (in the class as a variable, outside of any method), create a new callback variable, like this:
public Callback<Weatherdata> weatherCallback = new Callback<Weatherdata>() {
    @Override
    public void success(WeatherData weatherQuery, Response response) {
        Log.i("MY_APP", "count = " + weatherQuery.count);
    }

    @Override
    public void failure(RetrofitError error) {
        Log.e("MY_APP", error.getLocalizedMessage());
    }
};

This is a variable of type Callback which takes a type of what we're expecting back from the network. In our case we will use theWeatherData type we created at the begining of the tutorial (the one with just 1 field of count) because this is the type we're expecting.
In the end, the whole MainActivity.java looks like this:
package eduardoflores.com.test_networkconnection;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

import java.util.HashMap;
import java.util.Map;

import retrofit.Callback;
import retrofit.RetrofitError;
import retrofit.client.Response;

public class MainActivity extends AppCompatActivity {

    public Callback<WeatherData> weatherCallback = new Callback<WeatherData>() {
        @Override
        public void success(WeatherData weatherQuery, Response response) {
            Log.i("MY_APP", "count = " + weatherQuery.count);
        }

        @Override
        public void failure(RetrofitError error) {
            Log.e("MY_APP", error.getLocalizedMessage());
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ServiceDownloader serviceDownloader = new ServiceDownloader(getRequestHeaders());
        serviceDownloader.getWeatherData(weatherCallback);
    }

    public static Map<String string> getRequestHeaders() {
        Map<string string=""> headers = new HashMap<>();
        headers.put("Accept", "application/json");
        headers.put("Content-Type", "application/json");
        return headers;
    }
}

Finally, run the app and you should see an output in the console of count = 3. (Why 3? because we passed 3 groups of cities as the 'id' parameter to the service)

You can also see in the console a lot of output with the tag Retrofit. This means the downloader is working, and we can so far parse the element ctn in the root.

Yay we did it!

Now's time to learn how to deserialize the JSON data received.

Wednesday, December 2, 2015

Notificaciones en Android usando Parse.com

El proposito de este tutorial
El producto final va a ser una simple notificacion enviada desde la pagina web de Parse.com, y usaremos la plataforma de Parse para recibirla en Android. Cuando el usuario toque la notificacion en Android, la applicacion de Android se abrira automaticamente.
Todo esto se hara al configurar la plataforma de Parse.com y su SDK de Android.


Si prefieres ver este tutorial en video, subi un video mostrando esto pasa a paso en este link.


Que se necesita para este tutorial


Una cuenta en Parse.com. Una cuenta gratis sera suficiente para este ejercicio.
Un proyecto en Parse. Cualquier proyecto en Parse.com servira. (Si esto es muy complicado mandame un email y agrego los pasos para hacer el proyecto)
Android Studio
Un telefono Android, o el emulador

Comencemos!

Este tutorial esta hecho usando la guia de Parse.com para un proyecto ya existente de Android en vez de un proyecto nuevo. De esta forma este tutorial servira para ambos casos.

1. Obten la informacion de tu proyecto en Parse
Una vez que creas el proyecto en Parse, deberias ver algo similar a esto (Nov. 2015):


Primero que nada vamos a usar Gradle para instalar las librerias, por lo que no no va a ser necesario descargar el archivo .zip. No te preocupes de esto e ignora el primer paso mencionado en Parse.
Sin embargo, ya que vamos a usar Gradle para crear el proyector, es necesario agregar las dependencias que se muestran en el segundo paso. Haremos esto mas adelante.

La siguiente seccion es tambien muy importante ya que tiene nuestro ID y llave del proyecto de parse (Parse Application Id y Client Key).
Tu deberias tener tu propio par de ID y llave para usar especificamente con tu proyecto (Yo escondi los mios aca):

Por ahora deja abierta esta pagina web con la informacion de tu proyecto de Parse. Vamos a usar esta information en el proyecto de Android.

2. Crea un proyecto en Androi, o abre un proyecto existente
Si vas a crear un proyecto nuevo, cualquier proyecto va a servir para esta prueba. Es mejor si que tu proyecto soporte Android OS 4.1, o mas nuevo.

Para este tutorial yo cree un nuevo proyecto llamado "Test_ParsePush", y el nombre de el paquete de este proyecto es "com.eduardoflores.test_parsepush".
No importa cual es el nombre de tu paquete, pero si es necesario acordarse cual es ya que lo usaremos en el archivo de manifest.

3. Modifica tu archivo build.gradle (Module:app)
En este momento la estructura de tu proyecto en Android Studio deberia ser similar a esta:

Abre el archivo build.gradle que esta en tu modulo (si solo tienes 1 modulo, el modulo se llamara "app").
Al final de este archivo, en la seccion de "dependencies" vamos a agregar las dependencias de Parse que te dio Parse en su website al comienzo. Esto agregara el SDK de Parse a tu proyecto:
    compile 'com.parse.bolts:bolts-android:1.+'
    compile 'com.parse:parse-android:1.+'
Tus dependencias deberian ser ahora similares a estas (tu talvez tengas un poco mas o menos dependencias que venian con tu proyecto):
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.android.support:design:23.0.1'
    compile 'com.parse.bolts:bolts-android:1.+'
    compile 'com.parse:parse-android:1.+'
}

Ahora agrega el repositorio maven de donde vas a sacar las herramientas de Parse. (nota: esto es algo que no se menciona en los tutoriales de Parse):
buildscript {
    repositories {
        mavenCentral()
        maven { url 'https://maven.parse.com/repo' }
    }
    dependencies {
        classpath 'com.parse.tools:gradle:1.+'
    }
}
Y ahora finalmente agrega el plugin de Parse al comienzo de tu archivo build.gradle, asi:
apply plugin: 'com.parse'
Ahora tu archivo build.gradle completo deberia se deberia ver asi:
apply plugin: 'com.android.application'
apply plugin: 'com.parse'

buildscript {
    repositories {
        mavenCentral()
        maven { url 'https://maven.parse.com/repo' }
    }
    dependencies {
        classpath 'com.parse.tools:gradle:1.+'
    }
}

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "eduardoflores.com.test_parsepush"
        minSdkVersion 16
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.android.support:design:23.0.1'
    compile 'com.parse.bolts:bolts-android:1.+'
    compile 'com.parse:parse-android:1.+'
}
 
Estamos listos con el archivo build.gradle y lo puedes cerrar.

4. Modifica tu archivo Manifest
Abre el archivo de manifest en tu aplicacion, en Android.
- En el archivo manifest, antes de <application, necesitas agregar 2 cosas.
La primera cosa es esta:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
La segunda cosa require cambiar el nombre del paquete a el nombre del paquete de tu aplicacion de Android:
<permission android:protectionLevel="signature"
                android:name="TU_PAQUETE.permission.C2D_MESSAGE" />
<uses-permission android:name="TU_PAQUETE.permission.C2D_MESSAGE" />
Aca, es importante de que cambies el texto de "TU_PAQUETE" con el nombre del paquete de tu aplicacion de Android. En mi caso, como lo mencione en el paso numero 2, el nombre del paquete de mi aplicacion es "com.eduardoflores.test_parsepush"

- Ahora en el mismo archivo de manifest, entre las ultimas etiquetas </activity> y </application>, necesitas poner la informacion de GCM para recibir tu mensaje:
<service android:name="com.parse.PushService" />
<receiver android:name="com.parse.ParsePushBroadcastReceiver"
          android:exported="false">
    <intent-filter>
        <action android:name="com.parse.push.intent.RECEIVE" />
        <action android:name="com.parse.push.intent.DELETE" />
        <action android:name="com.parse.push.intent.OPEN" />
    </intent-filter>
</receiver>
<receiver android:name="com.parse.GcmBroadcastReceiver"
          android:permission="com.google.android.c2dm.permission.SEND">
    <intent-filter>
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
        <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
        <category android:name="TU_PAQUETE" />
    </intent-filter>
</receiver>
Date cuenta de que tambien debes poner el nombre del paquete de tu aplicacion en la ultima categoria de nombre.
Y asi, con el nombre del paquete de la aplicacion reemplazado, todo tu archivo manifest deberia ser similar a esto:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="eduardoflores.com.test_parsepush" >

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <permission android:protectionLevel="signature"
                android:name="eduardoflores.com.test_parsepush.permission.C2D_MESSAGE" />
    <uses-permission android:name="eduardoflores.com.test_parsepush.permission.C2D_MESSAGE" />

    <application
        android:name=".StarterClass"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name="com.parse.PushService" />
        <receiver android:name="com.parse.ParsePushBroadcastReceiver"
                  android:exported="false">
            <intent-filter>
                <action android:name="com.parse.push.intent.RECEIVE" />
                <action android:name="com.parse.push.intent.DELETE" />
                <action android:name="com.parse.push.intent.OPEN" />
            </intent-filter>
        </receiver>
        <receiver android:name="com.parse.GcmBroadcastReceiver"
                  android:permission="com.google.android.c2dm.permission.SEND">
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                <category android:name="eduardoflores.com.test_parsepush" />
            </intent-filter>
        </receiver>
    </application>
</manifest>
Y ahora estas tambien listo con el archivo de manifest (lo de name=.StarterClass lo voy a explicar despues).

5. Escribe el codigo de Java en tu aplicacion Android
En tu actividad inicial debes inicializar el SDK de Parse con las llaves de Parse y el ID, y luego le dices a Parse que guarde eso en otro proceso.

Lo siguiente es muy importante: TU APLICACION NO VA A FUNCIONAR SI INICIALIZAS PARSE MAS DE UNA VEZ DENTRO DE TU APLICACION.

Que significa esto? Si tu inicializas Parse en el metodo de tu MainActivity, la aplicacion va a funcionar y vas a poder recibir notificaciones, pero solo si la aplicacion esta corriendo.
Una vez que la notificacion llegue tu aplicacion va a comenzar, el metodo onCreate() va a ser llamado nuevamente y Parse va a ser inicializado de nuevo. Esto hara que la aplicacion se termine con un mensaje de "Unable to create service com.parse.PushService: java.lang.NullPointerException" (no es posible crear un servicio de com.parse.PushService)
Para evitar esto vamos a crear una actividad simple que lo unico que va a ser es comenzar la aplicacion, junto con inicializar la libreria de Parse.


Crea una clase simple

Crea un nuevo archivo de java llamado "StarterClass.java" y haz que esta clase extienda Activity.

En la clase StarterClass, crea un metodo onCreate(), y adentro de este metodo, despues del super() metodo, agrega esto:
Parse.initialize(this, APPLICATION_ID, CLIENT_KEY);
ParseInstallation.getCurrentInstallation().saveInBackground();

Asi con eso, tu clase basica completa va a ser asi:
package eduardoflores.com.test_parsepush;

import com.parse.Parse;
import com.parse.ParseInstallation;

import android.app.Application;

/**
 * @author Eduardo Flores
 */
public class StarterClass extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        // Inicializa Parse
        Parse.initialize(this, "application_id_from_parse", "client_key_from_parse");
        ParseInstallation.getCurrentInstallation().saveInBackground();
    }
}
 
Esta clase StarterClass que creamos es lo que definimos en el archivo manifest bajo el nombre de name=".StarterClass" para la aplicacion. Asi esta clase es invocada solo una vez, haciendo que Parse tambien se inicialize una sola vez.

Y con eso, todo lo que necesitas ahora es mandar notificaciones desde Parse.com!
Corre tu aplicacion de Android y manda un mensaje desde Parse.com, y todo deberia funcionar como esto:


 

Y finalmente, puedes bajar todo el codigo de este tutorial desde aca.


Eduardo Flores.