Skip to content
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Fx Studio
  • Home
  • Code
  • iOS & Swift
  • Combine
  • RxSwift
  • SwiftUI
  • Flutter & Dart
  • Tutorials
  • Art
  • Blog
Written by chuotfx on November 5, 2020

RxCocoa Basic – Display Data from API

RxSwift

Contents

  • Chuẩn bị
  • 1. Weather API
  • 2. JSON to Object
    • 2.1. Define
      • CodingKeys
      • Main
      • AdditionalInfo
      • Coordinate
    • 2.2. Decode
  • 3.  Connect API
    • 3.1. Request
      • Bước 1: URL & URLRequest
      • Bước 2: Add value
      • Bước 3: Connect
    • 3.2. Parse
    • 4. Update UI
  • Tạm kết

Chào bạn đến với Fx Studio. Bài viết lần này vẫn tiếp tục công việc Display Data trong thế giới RxCocoa. Nhưng lần này, dữ liệu chúng ta cần hiển thị sẽ là dữ liệu động và nó được lấy từ API.

Nếu bạn chưa đọc qua phần trước đó, thì tham khảo link sau:

    • RxCocoa Basic – Display Data

Còn mọi thứ đã ổn rồi, thì …

Bắt đầu thôi!

Chuẩn bị

    • Xcode 12
    • Swift 5.3
    • RxSwift 5.0

Chúng ta vẫn dùng lại project của bài trước. Với một màn hình là WeatherCityViewController, để hiện thị thông tin thời tiết của một thành phố nào đó. Ngoài ra, bạn có thể checkout source code demo ở đây.

    • Link: checkout
    • Thư mục: /Examples/BasicRxSwift

Bài lần này, chúng ta sẽ tập trung về mặt dữ kiệu và tương tác API nên phần giao diện sẽ vẫn giữ nguyên từ bài trước.

1. Weather API

Về API sử dụng cho bài viết này và các bài viết sau. Thì ta sử dụng API thời tiết của Open Weather Map và hiển nhiên là miễn phí rồi.

  • Weather API : https://openweathermap.org/

Công việc của bạn bây giờ là đăng ký một tài khoản và lấy một key API. Để sử dụng cho việc gọi các link API từ nó. Việc này khá EZ.

Vì tính bảo mật nên mình sẽ không để key API sẵn trong project demo.

Cũng có khá nhiều loại API được cung cấp trong hệ thống. Và cái mà ta sử dụng thì như sau:

  • Current Weather : https://openweathermap.org/current
  • Search by city name

Tất nhiên, các API trên là mình giới thiệu cho bạn. Bạn có thể dùng 1 API khác, thiết kế giao diện kiểu khác để phù hợp với dữ liệu của bạn nhận được.

2. JSON to Object

Công việc đầu tiên khi tương tác với API là bạn phải phân tích được dữ liệu bạn nhận được từ API. Theo như các API đề xuất ở trên, chúng ta có ví dụ cho dữ liệu trả về như sau:

{
    coord: {
        lon: -0.13,
        lat: 51.51
    },
    weather: [
        {
        id: 801,
        main: "Clouds",
        description: "few clouds",
        icon: "02n"
        }
    ],
    base: "stations",
    main: {
        temp: 10.74,
        feels_like: 8.36,
        temp_min: 10,
        temp_max: 12,
        pressure: 1020,
        humidity: 81
    },
    visibility: 10000,
    wind: {
        speed: 2.6,
        deg: 230
    },
    clouds: {
        all: 15
    },
    dt: 1599364473,
    sys: {
        type: 1,
        id: 1414,
        country: "GB",
        sunrise: 1599369706,
        sunset: 1599417370
    },
    timezone: 3600,
    id: 2643743,
    name: "London",
    cod: 200
}

Đó là dữ liệu đại diện cho thời tiết của một thành phố. Chúng ta hay so với class Weather thì thấy cấu trúc JSON phức tạp hơn. Khi dữ liệu cần lấy thì nó bị lồng vào trong vài cấp.

Cấu trúc này khá là ói ăm khi name chỉ có thể lấy trực tiếp. Còn 3 properties còn lại nó nằm trong cấu trúc main. Mà chúng lại không giống tên của properties Weather.

Ông trời không tuyệt đường sống của ai bao giờ.

2.1. Define

Với class Weather thì đã kế thừa lại protocol Decodable, do đó bạn sẽ vẫn xử lý nó một cách gọn & đẹp được. Công việc mình sẽ tiến hành chỉnh sửa class Weather như sau:

CodingKeys

    enum CodingKeys: String, CodingKey {
        case cityName = "name"
        case main
        case weather
        case coordinate = "coord"
    }

Đây là enum chính trong việc decode dữ liệu lên các thuộc tính của class Weather. Bạn sẽ thấy với cityName và coordinate thì trong cấu trúc JSON nó có tên khác. Đối với những trường hợp này, bạn có thể thay đổi giá trị của case cho phù hợp.

Với main  & weather thì sẽ phức tạp hơn một chút.

Main

    enum MainKeys: String, CodingKey {
        case temp
        case humidity
    }

Trong cấu trúc JSON thì key main sẽ chứa trong nó nhiều thứ. Khi bạn chỉ muốn lấy temp & humidity , thì bạn hãy khai báo thêm 2 case đó vào trong enum MainKeys. Áp dụng tương tự cho khi bạn muốn lấy thêm các giá trị khác. Lưu ý, chúng nó phải trùng tên với nhau.

AdditionalInfo

    private struct AdditionalInfo: Decodable {
        let id: Int
        let main: String
        let description: String
        let icon: String
    }

Lần này, chúng ta sẽ đổi cách khác. Thay vì enum thì mình dùng struct. Bắt buộc phải kế thừa Decodable để thừa hường siêu năng lực biển đổi nhanh này. Tên của các thuộc tính trùng với key trong cấu trúc JSON.

Coordinate

    private struct Coordinate: Decodable {
      let lat: CLLocationDegrees
      let lon: CLLocationDegrees
    }

Enum này sẽ phục vụ việc lấy dữ liệu cho thuộc tính coordinate. Vì chúng ta cần phải biến đổi một tí.

2.2. Decode

Sau khi đã hoàn thành việc define các enum & struct cho việc phân tích dữ liệu. Chúng ta sang phần Decode dữ liệu thành đối tượng.  Với cấu trúc JSON trả về toàn chứa các thành phần bất hảo. Dó đó, chúng ta không thể nào áp dụng một cách tự động để có được Object ngay.

Bạn cần phải thêm hàm khởi tạo như sau:

    init(from decoder: Decoder) throws {
        // Coding Keys
        let values = try decoder.container(keyedBy: CodingKeys.self)
        
        // CityName
        cityName = try values.decode(String.self, forKey: .cityName)
        
        let info = try values.decode([AdditionalInfo].self, forKey: .weather)
        // icon
        icon = iconNameToChar(icon: info.first?.icon ?? "")
        
        let mainInfo = try values.nestedContainer(keyedBy: MainKeys.self, forKey: .main)
        // temp
        temperature = Int(try mainInfo.decode(Double.self, forKey: .temp))
        // humidity
        humidity = try mainInfo.decode(Int.self, forKey: .humidity)
        
        
        let coordinate = try values.decode(Coordinate.self, forKey: .coordinate)
        // coord
        self.coordinate = CLLocationCoordinate2D(latitude: coordinate.lat, longitude: coordinate.lon)
    }

Giải thích:

  • Đầu tiên bạn cần phải decode bằng CodingKeys, nó là key chính.
  • cityName sẽ lấy được một cách đơn giản. Do nó đang là cấp đầu tiên trong cấu trúc JSON
  • Với thuộc tính icon thì bạn sẽ tạo ra một AdditionalInfo trước. Cũng bằng decode với key weather.
  • Với temperature & humidity thì dùng kiểu CodingKeys lồng CodingKeys. Với key lồng là MainKeys.
  • Thuộc tính coordinate sẽ có được bằng việc khởi tạo 1 đối tượng CLLocationCoordinate2D. Nó không phải là dữ liệu có thể decode trực tiếp từ JSON được. Mà sẽ khởi tạo với 1 giá trị lat & long lấy từ việc decode.

Chúng ta đã thực hiện xong việc cài đặt biến đổi dữ liệu là JSON thành một Object với việc áp dụng Decodable. Cũng là khá EZ!

3.  Connect API

Bây giờ, chúng ta sẽ hoàn thiện model WeatherAPI để phục vụ cho việc Display Data from API. Bạn ở file WeatherAPI.swift và tiến hành thêm các đoạn code như sau:

    /// API key
    private let apiKey = "<your key api>"
    /// API base URL
    let baseURL = URL(string: "https://api.openweathermap.org/data/2.5")!

Đây chính là 2 thuộc tính cần sử dụng.

  • apiKey chính là key mà bạn lấy được khi sử dụng các dịch vụ API của Open Weather Map.
  • baseURL là link cơ bản của một API. Các API khác nhau thì chúng ta sẽ tuỳ chỉnh các param thêm.

3.1. Request

Tiếp tục bằng việc thêm function sau cho WeatherAPI.

private func request(method: String = "GET", pathComponent: String, params: [(String, String)]) -> Observable<Data> {


}

Mặc dù, mình đã viết một bài về tương tác API xịn sò với RxSwift rồi, nhưng mình vẫn muốn trình bày lại để cho bạn nhớ lâu hơn. Hoặc nếu bạn lười thì có thể sử dụng chúng.

    • RxSwift vs. UIKit – Networking

Bước 1: URL & URLRequest

Chúng ta bắt đầu thêm code cho function request. Tạo các đối tượng URL & URLRequest cho việc tương tác API.

        let url = baseURL.appendingPathComponent(pathComponent)
        var request = URLRequest(url: url)

        let keyQueryItem = URLQueryItem(name: "appid", value: apiKey)
        let unitsQueryItem = URLQueryItem(name: "units", value: "metric")
        let urlComponents = NSURLComponents(url: url, resolvingAgainstBaseURL: true)!

Trong đó:

  • url & request là 2 đối tượng chính chúng ta sử dụng. Cũng khá cơ bản và không liên quan tới Rx
  • Với NSURLComponents giúp bạn thêm các query item cho link một cách nhanh nhất.

Bước 2: Add value

Với một API thì sẽ có nhiều Method, như: GET, POST, PUT, DELETE … Việc tiếp theo là xác định Method và truyền giá trị cần gởi lên API theo request vừa được tạo. Bạn tiếp tục thêm đoạn code sau.

        if method == "GET" {
            var queryItems = params.map { URLQueryItem(name: $0.0, value: $0.1) }
            queryItems.append(keyQueryItem)
            queryItems.append(unitsQueryItem)
            urlComponents.queryItems = queryItems
        } else {
            urlComponents.queryItems = [keyQueryItem, unitsQueryItem]
            
            let jsonData = try! JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
            request.httpBody = jsonData
        }

Với method là:

  • GET
    • Dữ liệu sẽ được thêm vào query string (hay link URL).
    • Với dạng key1=value1&key2=value2 ....
  • POST
    • Dữ liệu sẽ đưa về dạng Dictionary (hay kiểu JSON)
    • Thêm vào httpBody của request

Bổ sung thêm việc kiểm tra lần cuối url của chúng ta có hoàn hảo hay không, thì bạn có thể sử dụng dòng code sau:

print("🔴 URL: \(urlComponents.url!.absoluteString)")

Tiếp tục là bạn sẽ hoàn thiện đối tượng request với đoạn code sau.

        request.url = urlComponents.url!
        request.httpMethod = method
        
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

Bước 3: Connect

Với 2 bước trên, chúng ta vẫn chưa dùng tới RxSwift hay RxCocoa. Giờ tới bước cuối, chúng ta sẽ sử dụng Rx trong việc gọi kết nối.

let session = URLSession.shared

Bạn sử dụng đối tượng singleton của URLSession và tiến hành gọi request thông qua lệnh sau:

        return session.rx.data(request: request)

Kết quả của function này sẽ trả về cho ta một Observable với kiểu dữ liệu cho phần tử phát đi là Data. Và cũng kết thúc function request này. Nó sẽ được gọi từ các function khác. Ví dụ như currentWeather ở bài trước.

3.2. Parse

Phát cuối cùng, bạn về lại function currentWeather và sửa lại như sau.

    func currentWeather(city: String) -> Observable<Weather> {
        return request(pathComponent: "weather", params: [("q", city)])
            .map { data in
                let decoder = JSONDecoder()
                return try decoder.decode(Weather.self, from: data)
            }
    }

Vì function request trả về một Observable, thì chúng ta có thể subscribe tới nó. Khi đó, kết nối sẽ được thực hiện và việc gọi API mới hoạt động. Còn về function currentWeather chịu trách nhiệm gọi API và biến đổi data thành đối tượng Weather.

Mọi thứ diễn ra một cách tự động theo những gì chúng ta đã cài đặt ở trên cho việc Decode JSON.

4. Update UI

Công việc cuối cùng chính là Display Data. Trước hết thì mình nhắc lại bản chất của Rx.

Một khi subscriber đăng ký tới một Observable, thì subscription sẽ được tạo. Và khi đó, Observable mới phát (emit) dữ liệu đi.

Áp dụng cho chương trình của chúng ta. Thì khi có subscribe tới function currentWeather, lúc đó kết nối API mới được thực thi. Sau đó, bạn sẽ nhận được dữ liệu và tiến hành cập nhật lên giao diện.

Với cách code truyền thông trước đây. Chúng ta sẽ làm tới 3 công việc:

  • Kết nối API
  • Xử lý dữ liệu nhận được
  • Cập nhật dữ liệu lên giao diện

Còn bây giờ với code Rx, công việc của chúng ta khá là đơn giản hơn nhiều.

  • Subscribe tới Observable phụ trách kết nối
  • Cập nhật lại giao diện

Tuy là 2 việc nhưng chúng sẽ liên kết với nhau, tạo thành một mạch (sequence) liên tục. Đó cũng chính là tư tưởng của Rx trong việc xử lý.

Nói dài như vậy thôi, còn về phần cập nhật giao diện này, bạn không cần phải làm gì hết. Chúng ta đã làm tất cả ở bài trước rồi. Công việc bây giờ chỉ còn là bấm nút build project và cảm nhận kết quả.

Bạn hãy gõ tên thành phố nào đó vào TextField, sao đó sẽ thấy kết quả hiển thị thông tin thời tiết của thành phố đó. Cuối cùng, để cho đẹp mắt. Bạn hãy xoá đi đoạn code này trong function viewDidLoad của file WeatherCityViewController.swift. Vì nó không còn ý nghĩa nữa rồi.

        // First subscribe
        WeatherAPI.shared.currentWeather(city: "")
                    .observeOn(MainScheduler.instance)
                    .subscribe(onNext: { weather in
                        self.cityNameLabel.text = weather.cityName
                        self.tempLabel.text = "\(weather.temperature) °C"
                        self.humidityLabel.text = "\(weather.humidity) %"
                        self.iconLabel.text = weather.icon
                    })
                    .disposed(by: bag)

Tạm kết

  • Define class/struct với Decodable, để biến đổi JSON thành Object một cách nhanh chóng
  • Handle request API

Okay! Tới đây thì mình xin kết thúc bài viết này. Nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gởi email theo trang Contact.

Cảm ơn bạn đã đọc bài viết này!

FacebookTweetPinYummlyLinkedInPrintEmailShares21

Related Posts:

  • Display a basic TableView - RxSwift
    Display a basic TableView - RxSwift
  • RxCocoa Basic – Forward Delegate
    RxCocoa Basic – Forward Delegate
  • RxSwift vs. UIKit - Fetching Data from API
    RxSwift vs. UIKit - Fetching Data from API
  • Fetching Data to List from API - SwiftUI Notes #44
    Fetching Data to List from API - SwiftUI Notes #44
Tags: rxswift
Written by chuotfx

Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Donate – Buy me a coffee!

Fan page

Fx Studio

Tags

Actor Advanced Swift api AppDistribution Asynchronous autolayout basic ios tutorial blog callback ci/cd closure collectionview combine concurrency CoreData Core Location crashlytics darkmode dart dart basic dart tour Declarative decoding delegate deploy fabric fastlane firebase flavor flutter GCD iOS mapview MVVM optional protocol rxswift Swift Swift 5.5 SwiftUI SwiftUI Notes tableview testing TravisCI unittest

Recent Posts

  • Raw String trong 10 phút
  • Dispatch Semaphore trong 10 phút
  • Tổng kết năm 2022
  • KeyPath trong 10 phút – Swift
  • Make color App Flutter
  • Ứng dụng Flutter đầu tiên
  • Cài đặt Flutter SDK & Hello world
  • Coding Conventions – người hùng hay kẻ tội đồ?
  • Giới thiệu về Flutter
  • Tìm hiểu về ngôn ngữ lập trình Dart

You may also like:

  • RxCocoa Basic – Merge Observables Input
    RxCocoa Basic – Merge Observables Input
  • RxCocoa Basic – Delegate Proxy
    RxCocoa Basic – Delegate Proxy
  • RxSwift vs. UIKit - Fetching Data from API
    RxSwift vs. UIKit - Fetching Data from API
  • Fetching Data to List from API - SwiftUI Notes #44
    Fetching Data to List from API - SwiftUI Notes #44
  • RxCocoa Basic - Working with multi UI Control
    RxCocoa Basic - Working with multi UI Control

Archives

  • February 2023 (1)
  • January 2023 (2)
  • November 2022 (2)
  • October 2022 (1)
  • September 2022 (5)
  • August 2022 (6)
  • July 2022 (7)
  • June 2022 (8)
  • May 2022 (5)
  • April 2022 (1)
  • March 2022 (3)
  • February 2022 (5)
  • January 2022 (4)
  • December 2021 (6)
  • November 2021 (8)
  • October 2021 (8)
  • September 2021 (8)
  • August 2021 (8)
  • July 2021 (9)
  • June 2021 (8)
  • May 2021 (7)
  • April 2021 (11)
  • March 2021 (12)
  • February 2021 (3)
  • January 2021 (3)
  • December 2020 (3)
  • November 2020 (9)
  • October 2020 (7)
  • September 2020 (17)
  • August 2020 (1)
  • July 2020 (3)
  • June 2020 (1)
  • May 2020 (2)
  • April 2020 (3)
  • March 2020 (20)
  • February 2020 (5)
  • January 2020 (2)
  • December 2019 (12)
  • November 2019 (12)
  • October 2019 (19)
  • September 2019 (17)
  • August 2019 (10)

About me

Education, Mini Game, Digital Art & Life of coders
Contacts:
contacts@fxstudio.dev

Fx Studio

  • Home
  • About me
  • Contact us
  • Mail
  • Privacy Policy
  • Donate
  • Sitemap

Categories

  • Art (1)
  • Blog (22)
  • Code (4)
  • Combine (22)
  • Flutter & Dart (24)
  • iOS & Swift (86)
  • RxSwift (37)
  • SwiftUI (76)
  • Tutorials (70)

Newsletter

Stay up to date with our latest news and posts.
Loading

    Copyright © 2023 Fx Studio - All rights reserved.

    Share this ArticleLike this article? Email it to a friend!

    Email sent!