RxSwift Firebase RemoteConfig データ取得メモ


Firebase RemoteConfig のデータを取得するためのメモ 

メモなので今後も追記予定
Jsonを保存しておいてパースして使う


protocol ViewModelInputs {
    var fetchTrigger: PublishSubject<Void> { get }
}

protocol ViewModelOutputs {
    var items: Observable<[Item]> { get }
    var isLoading: Observable<Bool> { get }
    var error: Observable<ActionError> { get }
}

protocol ViewModelType {
    var inputs: ViewModelInputs { get }
    var outputs: ViewModelOutputs { get }
}


class ViewModel: ViewModelInputs, ViewModelOutputs, ViewModelType {

    var inputs: ViewModelInputs { return self }
    var outputs: ViewModelOutputs { return self }

    // MARK: - Inputs
    let fetchTrigger = PublishSubject<Void>()

    // MARK: - Outputs
    let items: Observable<[Item]>
    let isLoading: Observable<Bool>
    let error: Observable<ActionError>

    // 内部変数
    private let status = BehaviorRelay<RemoteConfigFetchStatus>(value: .noFetchYet)
    private let itemJson = RemoteConfig.remoteConfig().configValue(forKey: "xxx_json")
    private let configAction: Action<(), RemoteConfigFetchStatus>
    private let disposeBag = DisposeBag()

    init() {

        // Actionを定義 
        self.configAction = Action { _ in
            return RemoteConfig.remoteConfig().rx
            .fetch(withExpirationDuration: TimeInterval(180), activateFetched: true)
        }

        // 実行結果をbind
        self.configAction.elements
            .bind(to: status)
            .disposed(by: disposeBag)

        // 一時的な変数
        let tmpItems    = BehaviorRelay<[Item]>(value: [])
        // Observableをoutputs変数へ
        self.items      = tmpItems.asObservable()

        self.status
            .filter { $0 == RemoteConfigFetchStatus.success } // 接続成功時のみ
            .withLatestFrom(Observable.of(itemJson.stringValue)) // データ取得
            .filterNil()
            .compactMap { $0.data(using: .utf8) }
            .compactMap { data in
                // Jsonを解析して配列を生成
                do {
                    return try (JSONSerialization.jsonObject(with: data, options: []) as? [[String : Any]])?.compactMap { Item(json: JSON($0)) }
                } catch {
                    return []
                }
            }
        .bind(to: tmpItems)
        .disposed(by: disposeBag)

        // ローディング 初期値はfalse
        self.isLoading = self.configAction.executing.startWith(false)

        // エラー
        self.error = self.configAction.errors

        // 開始
        self.fetchTrigger
            .bind(to: self.configAction.inputs)
            .disposed(by: disposeBag)
    }
}

こんなふうにして使う

        self.viewModel = ViewModel()

        // Firebase Remote Config へ接続
        self.viewModel.inputs.fetchTrigger.onNext(())

        // FireBase から データを取得してテーブルに表示
        self.viewModel.outputs.items.asObservable()
            .subscribe(onNext:{ [unowned self] items in
                self.items = items
                self.tableView.reloadData()  // DataSourceもRxにするとさらに楽
            }).disposed(by: rx.disposeBag)

        // ローディング 
        self.viewModel.isLoading.asDriver(onErrorJustReturn: false)
            .drive(SVProgressHUD.rx.isAnimating())
            .disposed(by: rx.disposeBag)

        // エラー表示
        self.viewModel.outputs.error
            .subscribe(onNext: { [unowned self] error in
                // アラートとかで表示する
                print(error.localizedDescription)
            }).disposed(by: rx.disposeBag)

ついでにこんなのを作ってローディング中に表示する
trueで表示、falseで非表示
Actionのexecutingが実行中にtrueを流すのでそのまま表示

import UIKit
import SVProgressHUD
import RxSwift
import RxCocoa

extension Reactive where Base: SVProgressHUD {
    static func isAnimating() -> AnyObserver<Bool> {
        return AnyObserver { event in
            MainScheduler.ensureExecutingOnScheduler()
            switch event {
            case .next(let value):
                if value {
                    SVProgressHUD.show()
                } else {
                    SVProgressHUD.dismiss()
                }
            default:
                break
            }
        }
    }
}