the making of the weather app (Pt. 2)

Click to download project open source on my Github repo :)

Pt. 2 featuring: API, closure, parsing JSON.

About API

An API (Application Programming Interface) is a set of rules and protocols that allows different software applications to communicate with each other. It defines the methods and data formats that applications can use to request and exchange information. APIs are used to enable the integration of different systems, allowing them to work together seamlessly.

Read more in-depth about API on this AWS page.

For our weather app, we are using OpenWeather’s API.

First, register a new account to get a free API KEY.

Click on the Current weather data, then you will see the code sample.

Go to your user profile on the top right of the site and find your API KEY.

Let’s go back to the API sample page and find the API call by city name.

Scroll down to find the sample, copy and paste this URL into your browser, replace {API key} (including the curly braces!!!) with your API KEY.

If your code display like this:

It will look pretty once you install the extension Jason Viewer Pro (for Chrome)

Notice the temperature is not in our standard unit format, you can change this by modifying the URL. One the main page, you will find the code for Units of measurement here.

Add this code to your URL, the order of city name, API KEY or units doesn’t matter. After adding it, your temperature will appear in standard unit either metric or imperial depending your area preference.

About CLOSURE

Closure is a self-contained block of functionality that can be passed around and used in your code. Closures can capture and store references to variables and constants from the context in which they are defined. This is known as closing over those variables, hence the name "closure."

Key Points:

  • Anonymous Functions: Closures are often referred to as anonymous functions because they do not require a name.

  • Capture Values: Closures can capture and store references to variables and constants from the surrounding context.

  • First-Class Citizens: Closures can be assigned to variables, passed as arguments to functions, and returned from functions.

Basic Closure Syntax:

{ (parameters) -> returnType in
    // code
}

Simple Closure:

let greet = {
    print("Hello, World!")
}

greet() // Output: Hello, World!

Closure with Parameters and Return Type:

let add: (Int, Int) -> Int = { (a, b) in
    return a + b
}

let result = add(3, 5)
print(result) // Output: 8

Trailing Closure Syntax:

When a closure is the last argument to a function, you can use trailing closure syntax for cleaner code.

func performOperation(a: Int, b: Int, operation: (Int, Int) -> Int) {
    let result = operation(a, b)
    print(result)
}

performOperation(a: 3, b: 5) { (a, b) in
    return a + b
} // Output: 8

Capturing Values:

Closures can capture values from their surrounding context.

func makeIncrementer(incrementAmount: Int) -> () -> Int {
    var total = 0
    let incrementer: () -> Int = {
        total += incrementAmount
        return total
    }
    return incrementer
}

let incrementByTwo = makeIncrementer(incrementAmount: 2)
print(incrementByTwo()) // Output: 2
print(incrementByTwo()) // Output: 4

How it applies to Our Code:

In the provided code snippet, closures are used in the dataTask method of URLSession to handle the response asynchronously.

func performRequest(with urlString: String) {
        if let url = URL(string: urlString) {
            let session = URLSession(configuration: .default)
            let task = session.dataTask(with: url) { (data, response, error) in
                if error != nil {
                    delegate?.didFailWithError(error: error!)
                    return
                }

                if let safeData = data {
                    if let weather = parseJSON(safeData) {
                        delegate?.didUpdateWeather(self, weather: weather)
                    }
                }
            }
            task.resume()
        }
    }

The highlighted part inside of the curly braces is how trailing closure is used in our case.

Here’s a bonus I want to share with you, since the closure syntax is a lot…this link has all together.

About JSON

JSON (JavaScript Object Notation) is a lightweight data interchange format that is easy for humans to read and write and easy for machines to parse and generate. It is commonly used for transmitting data in web applications between a server and a client.

Key Points:

  • Lightweight: JSON is a lightweight format, making it efficient for data exchange.

  • Human-Readable: JSON is easy to read and write for humans.

  • Language-Independent: JSON is language-independent but uses conventions familiar to programmers of the C-family of languages, including C, C++, C#, Java, JavaScript, Perl, Python, and many others.

  • Structured Data: JSON is used to represent structured data as key-value pairs.

JSON Syntax:

  • Objects: Represented as key-value pairs enclosed in curly braces {}.

  • Arrays: Ordered lists of values enclosed in square brackets [].

  • Values: Can be strings, numbers, objects, arrays, true, false, or null.

Example JSON from our weather app:

{
    "weather": [
        {
            "id": 800,
            "main": "Clear",
            "description": "clear sky",
            "icon": "01d"
        }
    ],
    "main": {
        "temp": 293.55,
        "feels_like": 293.13,
        "temp_min": 292.04,
        "temp_max": 295.37,
        "pressure": 1013,
        "humidity": 53
    },
    "name": "Mountain View"
}

Explanation:

  • Object: The entire JSON data is an object.

  • Array: The value of the "weather" key is an array containing one object.

  • Key-Value Pairs: Each key is a string, and each value can be a string, number, object, array, boolean, or null.

Parsing JSON in Swift:

In Swift, you can use the JSONDecoder class to parse JSON data into Swift structs or classes.

Example Code:

Here is how our weather app code snippet parses JSON data:

performRequest(with:) Method:

  • Creates a Data Task: It creates a data task with the URL object and a completion handler.

  • Handles Completion: The completion handler checks for errors and, if there are no errors, parses the JSON data using the parseJSON(_:) method.

  • Updates Delegate: If the JSON data is successfully parsed, it creates a WeatherModel object and calls the didUpdateWeather(_:weather:) method of the delegate to pass the weather data to the WeatherViewController.

  • Handles Errors: If there is an error while fetching the weather data, it calls the didFailWithError(error:) method of the delegate to handle the error.

func performRequest(with urlString: String) {
    if let url = URL(string: urlString) {
        let session = URLSession(configuration: .default)
        let task = session.dataTask(with: url) { (data, response, error) in
            if error != nil {
                delegate?.didFailWithError(error: error!)
                return
            }

            if let safeData = data {
                if let weather = parseJSON(safeData) {
                    delegate?.didUpdateWeather(self, weather: weather)
                }
            }
        }
        // Start the data task, which will make the network request, and call the completion handler when the data task is completed
        task.resume()
    }
}

parseJSON(_:) Method:

  • Decodes JSON Data: It takes the JSON data as a parameter and uses JSONDecoder to decode the JSON data into a WeatherData object.

  • Extracts Data: It extracts the weather condition ID, city name, and temperature from the WeatherData object.

  • Creates WeatherModel: It creates a WeatherModel object with the extracted data and returns it.

  • Handles Errors: If there is an error while parsing the JSON data, it calls the didFailWithError(error:) method of the delegate to handle the error.

func parseJSON(_ weatherData: Data) -> WeatherModel? {
    let decoder = JSONDecoder()
    do {
        let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
        let id = decodedData.weather[0].id
        let name = decodedData.name
        let temp = decodedData.main.temp

        let weather = WeatherModel(conditionId: id, cityName: name, temperature: temp)
        return weather

    } catch {
        delegate?.didFailWithError(error: error)
        return nil
    }
}

The code snippet above shows how to parse JSON, we are getting ahead of ourselves a little because we don’t have WeatherData yet, but once we complete that part of it, the code should look like the snippet above. In PART 3 of the blog, we will continue to talk about creating WeatherData.

—> Continue to read PART 3

Previous
Previous

the making of the weather app (Pt. 3)

Next
Next

the making of the weather app (Pt. 1)