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 25, 2020

RxCocoa Basic – Merge Observables Input

RxSwift

Contents

  • Chuẩn bị
  • 1. Vấn đề
  • 2. Update Model API
  • 3. Create Location Search
    • 3.1. Current Location
    • 3.2. Location Input
  • 4. Merge Search Inputs
    • 4.1. Create Observables Input
    • 4.2. Merge Observables Input
    • 4.3. Update Loading View
  • Tạm kết

Chào bạn đến với Fx Studio. Bài viết lần này sẽ không có khái niệm mới được truyền tải. Chúng ta sẽ tập trung giải quyết các yêu cầu của project. Và với yêu cầu chính sẽ là Merge Observables Input trong một ViewController.

Vì đây là phần tiếp nối các phần trước trong series RxSwift với phần 3 – RxCocoa. Nên bạn cũng cần phải nắm được kiến thức trước đó. Hãy bắt đầu với RxCocoa Basic – Display Data.

Nếu mọi thứ đã ổn rồi, thì …

Bắt đầu thôi!

Chuẩn bị

    • Xcode 12
    • Swift 5.3
    • RxSwift 5.0

Trong bài này, chúng ta sẽ sử dụng tiếp nối Project từ bài trước (Delegate Proxy), nên cũng khá quan trọng. Nếu bạn đã tự tạo một project khác, thì hãy tiếp tục với logic của bạn. Và tất nhiên, bạn có thể checkout lại project để có thể theo dõi bài viết kĩ hơn.

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

1. Vấn đề

Trong các phần trước, chúng ta đã lần lượt đi qua từng vấn đề một trong UIKit, chúng đã được giải quyết bằng RxCocoa. Và cũng từ đó chúng ta thấy có nhiều vấn đề mới phát sinh thêm.

Trong số đó, nguy hiểm nhất vẫn là các sự kiện khác nhau xảy ra, nhưng lại kích hoạt cùng 1 xử lý/tương tác trong project.

Ví dụ, lấy phần gọi API để phân tích.

  • UITextField
    • Khi gõ chữ thì chúng ta tiến hành kiểm tra TextField.
    • Nếu kiểm tra xong cho text của TextField đã ổn rồi, thì sẽ gọi API
  • User Location
    • Lắng nghe sự update dữ liệu Current Location
    • Mỗi khi dữ liệu nhận được mà phù hợp các tiêu chí search, thì tiến hành gọi API
  • Data default
    • Thường khi khởi tạo 1 ViewController, ta sẽ cung cấp dữ liệu mặc định cho nó.
    • Trường hợp này sẽ được khởi tạo và gọi API
  • … (còn nhiều trường hợp khác mà tuỳ thuộc vào yêu cầu của project)

Không chỉ đơn giản trong ví dụ đó là chỉ có 1 xử lý/tương tác được thực hiện. Mà chúng ta còn có các Side Effect khác hoặc các xử lý phụ đi kèm. Ví dụ như: loading view … cũng gây ảnh hưởng tới nhiều thành phần trên giao diện.

Và đó là những vấn đề thường ngày khi bạn làm dự án. Nhất là với các dự án thường xuyên thay đổi yêu cầu. Giải quyết nó thì không khó. Khó là …

Cần xử lý một cách tập trung và thống nhất.

Tạm thời, mình sẽ mượn lại ví dụ đang dang dở ở trên, để tiến hành viết tiếp câu chuyện này ….

2. Update Model API

Về model cho API thì bạn hãy mở file WeatherAPI.swift để xem. Chúng ta đã có function tìm kiếm thời tiết của một thành phố, với tham số là tên thành phố. Bây giờ, chúng ta sẽ thêm một function nữa.

    func currentWeather(at coordinate: CLLocationCoordinate2D) -> Observable<Weather> {
      return request(pathComponent: "weather", params: [("lat", "\(coordinate.latitude)"),
                                                        ("lon", "\(coordinate.longitude)")])
        .map { data in
          let decoder = JSONDecoder()
          return try decoder.decode(Weather.self, from: data)
        }
    }

Function này về mặt ý nghĩa thì cũng tương tự. Nhưng khác nhau tham số truyền vào. Tham số lần này là toạ độ của một vị trí trên bản đồ. Và ở bên trong function, chỉ là thay đổi lại params truyền vào thôi. Mọi thứ khác không cần thay đổi.

3. Create Location Search

Tại bài viết Delegate Proxy, ta dùng button.rx.tap để bắt sự kiện người dùng. Từ đó kích hoạt đối tượng CLLocationManager tiến hành tracking GPS.

        locationButton.rx.tap
            .subscribe(onNext: { [weak self] in
                guard let self = self else { return }
                
                self.locationManager.requestWhenInUseAuthorization()
                self.locationManager.startUpdatingLocation()
            })
            .disposed(by: bag)

Và khi nhấn Button, chương trình sẽ kiểm tra việc cấp phát quyền hay chưa. Nếu quyền truy cập tới GPS đã được cấp phát, thì sẽ bắt đầu việc tracking. Công việc tiếp theo là lắng nghe sự thay đổi dữ liệu Current Location, từ việc Custom Delegate Proxy của CLLocationManager.

        locationManager.rx.didUpdateLocation
            .subscribe(onNext: { locations in
                print(locations)
            })
            .disposed(by: bag)

Đã xong phần hồi tưởng lại quá khứ. Bây giờ, chúng ta tập trung vào công việc chính. Đó là:

Tạo Observables Input cho Current Location.

3.1. Current Location

Tạo đối tượng lắng nghe sự thay đổi dữ liệu của Current Location. Bạn mở file WeatherCityViewController, và tại function viewDidLoad thì thêm đoạn code sau vào.

        let currentLocation = locationManager.rx.didUpdateLocation
            .map { locations in locations[0] }
            .filter { location in
                return location.horizontalAccuracy < kCLLocationAccuracyHundredMeters
            }

Tại bước này, chỉ là lấy Observable từ Delegate Proxy mà thôi. Tất nhiên, thêm chút điều kiện để lọc bớt các location quá gần nhau.

3.2. Location Input

Tiếp tục, tạo đối tượng lấy sự kiện từ Button liên quan tới Location. Bạn thêm đoạn code này vào sau đoạn code trước.

        let locationInput = locationButton.rx.tap.asObservable()
            .do(onNext: {
                self.locationManager.requestWhenInUseAuthorization()
                self.locationManager.startUpdatingLocation()
                
            })

Cứ khi nào người dùng kích vào Button. Thì chúng ta sẽ kích hoạt việc update Location từ đối tượng CLLocationManager. Quan trọng hơn nữa là bạn sẽ dùng tới nó để kích hoạt Loading View.

Lúc có dữ liệu mới của GPS update, thì đối tượng currentLocation sẽ phát đi dữ liệu. Do đó, ta cần bắt được dữ liệu này.

let locationObs = locationInput
            .flatMap { return currentLocation.take(1) }

Và bạn chỉ cần lấy 1 phần tử mà thôi. Nhưng thay vì lấy dữ liệu, thì ta dùng flatMap để biến nó thành 1 Observable. Việc này để tiện sử dụng cho nhiều việc sau.

Tóm lại:

  • Nhấn button sẽ kích hoạt việc tracking Location
  • Tạo đối tượng lắng nghe sự thay đổi Current Location
  • Từ đối tượng của button.tap . Ta tiến hành flatMap để tạo ra các Observable, bằng cách lấy 1 dữ liệu từ đối tượng lắng nghe Current location.

Chúng ta sẽ sử dụng các Observable được tạo ra đó để request tới API.

4. Merge Search Inputs

Chúng ta đã có nhiều nguồn hay nhiều xuất phát điểm để tiến hành gọi API để lấy dữ liệu. Mọi thứ đối với bạn và bạn vẫn có thể handle trong tầm tay. Nhưng bạn có dám chắc một điều rằng: “mình bỏ sót 1 hay vài trường hợp nào đó không?”

Bạn biết rằng, cứ mỗi sự kiện gọi API, bạn sẽ nhận được dữ liệu. Cấu trúc dữ liệu bạn nhận được sẽ không đổi. Nên tại sao, chúng ta không hợp nhất tất cả sự kiện gọi API về 1 điểm duy nhất. Hay một nguồn phát duy nhất. Khi đó, công việc còn lại khá là đơn giản. Lắng nghe những gì nó phát ra.

Okay! tiến hành thôi.

4.1. Create Observables Input

Chúng ta sẽ làm gọn lại các Observables liên quan tới TextField và chúng cũng áp dụng tương tự cho Location Input. Bạn tiếp tục với file WeatherCityViewController, tại viewDidLoad thì bạn xoá đi dòng lệnh sau:

let search = searchInput
            .flatMapLatest { text  in
                return WeatherAPI.shared.currentWeather(city: text)
                    .catchErrorJustReturn(Weather.empty)
            }
            .asDriver(onErrorJustReturn: Weather.empty)

Sau đó, chúng ta tiến hành tạo Observable cho search với TextField. Bạn thêm đoạn code sau vào:

        let textSearch = searchInput.flatMap { text in
             return WeatherAPI.shared.currentWeather(city: text)
                 .catchErrorJustReturn(.dummy)
         }

textSearch phụ trách việc gọi lại function currentWeather(city:) với dữ liệu text từ TextField. Chúng sẽ trả về một Observable.

Áp dụng tương tự cho Location Input. Bạn lại thêm đoạn code sau vào:

        let locationSearch = locationObs.flatMap { location  in
            return WeatherAPI.shared.currentWeather(at: location.coordinate)
                    .catchErrorJustReturn(.dummy)
        }

locationSearch sẽ gọi hàm currentWeather(at:) với dữ liệu là location. Nó sẽ trả về cho chúng ta một Observable.

4.2. Merge Observables Input

let search = Observable
            .merge(locationSearch, textSearch)
            .asDriver(onErrorJustReturn: .dummy)

Bạn nhẹ nhàng thêm đoạn code trên vào tiếp. Trong đó,

  • Sử dụng toán tử merge với tham số là 2 Observable tạo ở trên. Sau đó biến đổi chúng nó thành 1 Drive bằng toán tử asDriver , để có thể đưa dữ liệu trực tiếp lên các UI Control của UIKit.
  • Với toán tử merge thì sẽ phát đi dữ liệu khi các Observable con phát đi dữ liệu. Bạn sẽ an tâm, từ bất cứ sự kiện nào gọi API đi nữa. Chúng ta chỉ cần subscribe tới Observable merge kia thì sẽ có được dữ liệu.

Các công việc còn lại chỉ là subscribe hoặc drive mà thôi. Chúng không có gì thay đổi hết.

        search.map { "\($0.temperature) °C" }
            .drive(tempLabel.rx.text)
            .disposed(by: bag)
        
        search.map { $0.cityName }
            .drive(cityNameLabel.rx.text)
            .disposed(by: bag)
        
        search.map { "\($0.humidity) %" }
            .drive(humidityLabel.rx.text)
            .disposed(by: bag)
        
        search.map { $0.icon }
            .drive(iconLabel.rx.text)
            .disposed(by: bag)
        
        search.map { $0.cityName }
            .drive(self.rx.title)
            .disposed(by: bag)
        
        search.map { $0.cityName }
            .drive(self.rx.title)
            .disposed(by: bag)

4.3. Update Loading View

Trong bài Working with multi UI Control, bạn đã thêm một Loading View. Đây là 1 UI Control chịu tác động từ nhiều Observables. Do đó, chúng ta cũng phải thay đổi nó một chút nữa. Mục đích chính là đồng bộ với nhiều nguồn Inputs vừa được tạo ra.

Bạn mở file WeatherCityViewController, bạn tới đoạn code của loading và sửa lại như sau:

let loading = Observable.merge(
                searchInput.map { _ in true },
                locationInput.map { _ in true }, // update with search at Location
                search.map { _ in false }.asObservable()
            )
            .startWith(true)
            .asDriver(onErrorJustReturn: false)

Trong toán tử merge , bạn thêm một Observable nữa. Đó là locationInput. Vì nó cũng là một trong các điều kiệu để kích hoạt Loading View.

Bạn vẫn giữ nguyên các phần còn lại và tiến hành build project và cảm nhận kết quả.

Tạm kết

  • Tách các sự kiện và biến đổi thành các Observables
  • Tạo các Observables tưng ứng với các Inputs theo các sự kiện
  • Merge các Observables Input
  • Cập nhật giao diện khi có dữ kiệu nhận được từ Observable Merge

 

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!

FacebookTweetPinYummlyLinkedInPrintEmailShares14

Related Posts:

  • Basic TextField - SwiftUI Notes #27
    Basic TextField - SwiftUI Notes #27
  • RxCocoa Basic – Forward Delegate
    RxCocoa Basic – Forward Delegate
  • RxCocoa Basic – Extend UIKit
    RxCocoa Basic – Extend UIKit
  • RxCocoa Basic – Display Data from API
    RxCocoa Basic – Display Data from API
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!

5 comments

  • Toan Hoang Huy has written: June 4, 2021 at 9:39 am Reply

    let locationObs = locationInput
    .flatMap { return currentLocation.take(1) }

    bản thân thằng currentLocation là Observable rồi, nó luôn được cập nhật mới nhất, bác đâu cần take(1) làm gì, bác tưởng nó là Observable hả

    • chuotfx has written: June 6, 2021 at 8:18 am Reply

      Tuỳ thuộc vào ý đồ của bạn muốn làm gì thôi nhoé.

      – locationInput –> đã là 1 Observable rồi nhưng vẫn lấy `take(1)` vì chỉ muốn lấy 1 giá trị thôi.
      – Do mình kết hợp nhiều thứ lại ở phía dưới. Nếu cứ để tự động cập nhật thì nó sẽ kéo theo nhiều thứ chạy theo. ==> phiền phức lắm.

      Bài viết phục vụ cho mục đích tò mò là chính. Bạn cứ thoả sức sáng tạo thêm.

    • chuotfx has written: June 6, 2021 at 8:21 am Reply

      À mà đôi lúc mình viết xong mà chừ lại quên mất ý đồ viết bài là gì nữa. Nên ngẫm ra thì nó cũng bất hợp lý lắm. 😀

  • Toan Hoang Huy has written: June 4, 2021 at 9:42 am Reply

    chết comment của mình bị xóa kiểu của thằng Observable đi kèm, ý mình là currentLocation nó là Observable của cái CLLocation rồi chứ ko phải của 1 mảng CLLocation

    • chuotfx has written: June 6, 2021 at 8:13 am Reply

      Comment không xoá nhoé, do mình chống spam thôi. Nên phải có phê duyệt đã mới hiện lên.

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:

  • Display a basic TableView - RxSwift
    Display a basic TableView - RxSwift
  • Basic Grid Layout - SwiftUI Notes #52
    Basic Grid Layout - SwiftUI Notes #52
  • SwiftUI - Phần 6 : Basic UI Controls
    SwiftUI - Phần 6 : Basic UI Controls
  • RxCocoa Basic – Forward Delegate
    RxCocoa Basic – Forward Delegate
  • RxCocoa Basic – Extend UIKit
    RxCocoa Basic – Extend UIKit

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!