Showing posts with label network. Show all posts
Showing posts with label network. Show all posts

Saturday, June 11, 2016

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.

Tuesday, February 2, 2016

Deserialize JSON data returned from Retrofit, using GSON.

This tutorial takes off right where we ended the previous Retrofit tutorial.
At this point we have downloaded data and we have a single WeatherData object coming back from the service.

What we want now is to get the additional data from the JSON.
Before we continue, let's look at the sample JSON again.

Sample JSON

{
 "cnt": 3,
 "list": [{
  "coord": {
   "lon": 37.62,
   "lat": 55.75
  },
  "sys": {
   "message": 0.0045,
   "country": "RU",
   "sunrise": 1454390479,
   "sunset": 1454421906
  },
  "weather": [{
   "id": 803,
   "main": "Clouds",
   "description": "broken clouds",
   "icon": "04n"
  }],
  "main": {
   "temp": -1.4,
   "temp_min": -1.401,
   "temp_max": -1.401,
   "pressure": 1001.24,
   "sea_level": 1021.82,
   "grnd_level": 1001.24,
   "humidity": 93
  },
  "wind": {
   "speed": 3.55,
   "deg": 219.507
  },
  "clouds": {
   "all": 80
  },
  "dt": 1454380676,
  "id": 524901,
  "name": "Moscow"
 }, {
  "coord": {
   "lon": 30.52,
   "lat": 50.43
  },
  "sys": {
   "type": 1,
   "id": 7358,
   "message": 0.0282,
   "country": "UA",
   "sunrise": 1454391071,
   "sunset": 1454424722
  },
  "weather": [{
   "id": 800,
   "main": "Clear",
   "description": "Sky is Clear",
   "icon": "01n"
  }],
  "main": {
   "temp": -0.66,
   "pressure": 1016,
   "humidity": 100,
   "temp_min": -1,
   "temp_max": 0
  },
  "visibility": 10000,
  "wind": {
   "speed": 4,
   "deg": 220
  },
  "clouds": {
   "all": 0
  },
  "dt": 1454378400,
  "id": 703448,
  "name": "Kiev"
 }, {
  "coord": {
   "lon": -0.13,
   "lat": 51.51
  },
  "sys": {
   "message": 0.0141,
   "country": "GB",
   "sunrise": 1454398618,
   "sunset": 1454431885
  },
  "weather": [{
   "id": 804,
   "main": "Clouds",
   "description": "overcast clouds",
   "icon": "04n"
  }],
  "main": {
   "temp": 9.22,
   "temp_min": 9.224,
   "temp_max": 9.224,
   "pressure": 1017.04,
   "sea_level": 1027.01,
   "grnd_level": 1017.04,
   "humidity": 74
  },
  "wind": {
   "speed": 10.5,
   "deg": 255.507
  },
  "clouds": {
   "all": 92
  },
  "dt": 1454380483,
  "id": 2643743,
  "name": "London"
 }]
}
Remember, I'm getting the sample JSON from Open Weather Map

 

Understand JSON

Note: if you already understand what JSONs are, you may want to skip this section.
JSON strings are super easy. They are based on two concepts: JSON Objects and JSON Arrays, and key value pairs.

Key value pairs

These are 2 words that are separated with a colon (:), and each key value pair is separated with a comma (,). They are presented as this:

"key1":"value1", "key2":"value2","myKey":"my value","monster":"Cookie Monster", "myAge":32

Keys can have spaces, but they usually have issues on some programming languages, so that makes most keys to always be without spaces.
Values can be whatever you want. If they're Strings then they have quotes ("") around them, while integers and doubles don't have them. However, sometimes the output from the log from an IDE might place quotes around everything.

JSON Objects and JSON Arrays

By default a single JSON string contains a single JSON Object, but the most important part to remember about JSON Objects and JSON Arrays is this:

JSON Object are represented as {} (curly braces)
JSON Arrays are (usually) represented as [] (square brackets)

Note: XCode usually outputs JSON Arrays as () (parenthesis)

So with that, we can create a JSON Object like this:
{
 "key1": "value1",
 "monster": "Cookie Monster"
}
This is a JSON Object with 2 key values (we call that fields now)
Before we talk about JSON Arrays, you need to see what we can do now: we can do a key value pair with a key a JSON Object, like this:
{
 "key1": "value1",
 "monster": "Cookie Monster",
 "carObject": {
  "brand": "Ford",
  "model": "Model A"
 },
 "computerObject": {
  "brand": "Apple",
  "model": "Macbook pro"
 }
}
In here, we not have a key of carObject with a value of a new JSON Object. This new JSON Object has it's own set of key values. Same thing with computerObject.

JSON Arrays are basically what you would expect them to be: an array of JSON Objects. The only thing to remember is that JSON Arrays wrap JSON Objects, so they are represented before a curly brace.
Here's an example of a JSON Object with a JSON Array:
{
 "key1": "value1",
 "monster": "Cookie Monster",
 "carObject": [{
  "brand": "Ford",
  "model": "Model A"
 }, {
  "brand": "Chevy",
  "model": "Camaro"
 }]
}
So now inside the key of carObject we now have a JSON Array of JSON Objects. In this case we have 2 JSON Objects, each of them with their own key value pairs.

And with that under our belt, we go back to our Android application to parse our JSON.

Create Java objects

The point of our deserialization is to end up with POJOs (Plain Old Java Objects) out of this whole deal. In order to do that, we need to deserialize our JSON string with POJOs in the same order of our JSON. Let's look back at the JSON, in the simplest form (just 1 group instead of 3):
{
 "cnt": 3,
 "list": [{
  "coord": {
   "lon": 37.62,
   "lat": 55.75
  },
  "sys": {
   "message": 0.0045,
   "country": "RU",
   "sunrise": 1454390479,
   "sunset": 1454421906
  },
  "weather": [{
   "id": 803,
   "main": "Clouds",
   "description": "broken clouds",
   "icon": "04n"
  }],
  "main": {
   "temp": -1.4,
   "temp_min": -1.401,
   "temp_max": -1.401,
   "pressure": 1001.24,
   "sea_level": 1021.82,
   "grnd_level": 1001.24,
   "humidity": 93
  },
  "wind": {
   "speed": 3.55,
   "deg": 219.507
  },
  "clouds": {
   "all": 80
  },
  "dt": 1454380676,
  "id": 524901,
  "name": "Moscow"
 }]
}
In the Java side, we already have a class named WeatherData, and inside here we have this:
package eduardoflores.com.test_networkconnection;

import com.google.gson.annotations.SerializedName;

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

    @SerializedName("cnt")
    public int count;
}
We know this works, but now we need to move to the JSON key of list. When you look at the list key in the JSON you can kind of see that while the key itself says list, this is more like a location. So we will create a new Location.java class.
With the new Location class, we will update our WeatherData to get the list key as a Location object, and we will call this variable location.
package eduardoflores.com.test_networkconnection;

import com.google.gson.annotations.SerializedName;

import java.util.List;

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

    @SerializedName("cnt")
    public int count;

    @SerializedName("list")
    public List<Location> location;
}
Notice how the location variable is actually a List of Location object. Why? Because the JSON string returns a JSON Array for the key of list.

With that, we move to the Location object.
The Location object contains multiple other objects: coords, sys and weather.
We will create a new java class named Coordinates for the coords key, and a Weather class for the weather key. I decided to skip the sys object to demonstrate that not every key needs to be deserialized.

With the new Coordinates and Weather classes created, the Location object now looks like this:
package eduardoflores.com.test_networkconnection;

import com.google.gson.annotations.SerializedName;

import java.util.List;

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

    @SerializedName("coord")
    public Coordinates coordinates;

    @SerializedName("weather")
    public List<Weather> weather;

}
This should be simple now for you, but we will continue into the Weather class to show one last thing.
In the Weather java class, add fields for id, main, description and icon:
package eduardoflores.com.test_networkconnection;

import com.google.gson.annotations.SerializedName;

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

    @SerializedName("id")
    public Integer weatherId;

    public String main;

    public String description;

    public String icon;

}
This should show one last thing: while you can use the @SerializedName annotation for all your fields, you don't really need to add it if you want to create a variable using the same name of the field. For example, the JSON Object returns the key of description, and that is fine with me so I created the variable as description as well, and since they're both the same I don't have to use the @SerializedName annotation.

Testing it!

So before we run the app, let's go back to the MainClass.java (created in the previous tutorial) and into the success method of the callback.
In here, we just want to verify things work, so let's create some log statements, like this:
public Callback<WeatherData> weatherCallback = new Callback<WeatherData>() {
    @Override
    public void success(WeatherData weatherQuery, Response response) {
        Log.i("MY_APP", "count = " + weatherQuery.count);
        Log.i("MY_APP", "latitude = " + weatherQuery.location.get(0).coordinates.latitude);
        Log.i("MY_APP", "weather description = " + weatherQuery.location.get(0).weather.get(0).description);
    }

    @Override
    public void failure(RetrofitError error) {
        Log.e("MY_APP", error.getLocalizedMessage());
    }
};
(Yes, create the coordinates object by yourself. It's easy!)

With that, the output should be something like this:
02-01 20:00:09.930 10210-10210/eduardoflores.com.test_networkconnection I/MY_APP: count = 3
02-01 20:00:09.930 10210-10210/eduardoflores.com.test_networkconnection I/MY_APP: latitude = 55.75
02-01 20:00:09.930 10210-10210/eduardoflores.com.test_networkconnection I/MY_APP: weather description = broken clouds

And there you have it! You should now be able to deserialize any JSON string that gets thrown at you using GSON and Retrofit.

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.