mono0926
10/14/2017 - 6:48 AM

FirestoreのRxSwiftとの組み合わせ。Codableも活用。

FirestoreのRxSwiftとの組み合わせ。Codableも活用。

func update(user: User, parameters: UsersRequests.ProfileUpdateParameters) -> Single<()> {
    return self.apiClient.response(UsersRequests.Update(userId: user.id,
                                                        parameters: parameters))
        .flatMap { response in
            return Firestore.firestore().rx.setData(User.self,
                                                    documentRef: user.makeDocumentRef(),
                                                    fields: response.asDictionary(containsId: false))
    }
}
Firestore.firestore().rx
    .observe(User.self,
             documentRef: user.makeDocumentRef())
    .subscribe { [unowned self] (event: Event<User>) in
        switch event {
        case .completed: break
        case .error(let error): logger.error(error)
        case .next(let user):
            self.user = user
            guard let profile = user.fields?.profile else { return }
            self.userNameLabel.text = profile.name
            self.userImageView.set(imageUrl: profile.imageUrl, rounded: true)
        }
    }
    .disposed(by: rx.disposeBag)
public struct User: DatabaseCollection {
    public static let collectionName = "users"
    public let id: String
    public let fields: Fields?
    public struct Fields: Codable {
        public let profile: Profile
    }
    public struct Profile: Codable {
        public let name: String
        public let imageUrl: URL
        public let birthday: Date
        public let gender: Gender
    }
    public init(id: String, fields: Fields?) {
        self.id = id
        self.fields = fields
    }
}
import Foundation
import Lib
import FirebaseFirestore

public protocol DatabaseCollection {
    associatedtype FieldType: Codable
    static var collectionName: String { get }
    var id: String { get }
    var fields: FieldType? { get }
    init(id: String, fields: FieldType?)
    init(id: String, json: [String: Any]) throws
    static func makeCollectionRef() -> CollectionReference
    static func makeDocumentRef(id: String) -> DocumentReference
    func makeDocumentRef() -> DocumentReference
}

extension DatabaseCollection {
    public init(id: String) {
        self.init(id: id, fields: nil)
    }
    public init(id: String, json: [String: Any]) {
        // TODO: If performance is prioritized, map by hand
        do {
            let data = try JSONSerialization.data(withJSONObject: json)
            let decoded = try JSONDecoder.ghost.decode(FieldType.self, from: data)
            self.init(id: id, fields: decoded)
        } catch {
            logger.error(error)
            self.init(id: id)
        }
    }
    public static func makeCollectionRef() -> CollectionReference { return Firestore.firestore().collection(collectionName) }
    public static func makeDocumentRef(id: String) -> DocumentReference { return Self.makeCollectionRef().document(id) }
    public func makeDocumentRef() -> DocumentReference { return Self.makeDocumentRef(id: id) }

}
//
//  Database.swift
//  Model
//
//  Created by mono on 2017/10/14.
//  Copyright © 2017 Masayuki Ono All rights reserved.
//

import Foundation
import FirebaseCore
import FirebaseFirestore
import RxSwift
import Lib
import Api

extension Reactive where Base: Firestore {
    public func setData<T: DatabaseCollection>(_ type: T.Type,
                                               documentRef: DocumentReference,
                                               fields: [String: Any]) -> Single<()> {
        return Single.create { observer in
            documentRef
                .setData(fields) { error in
                    if let error = error {
                        logger.error(error)
                        observer(.error(error))
                    } else {
                        observer(.success(()))
                    }
            }
            return Disposables.create()
        }
    }

    public func updateData<T: DatabaseCollection>(_ type: T.Type,
                                                  documentRef: DocumentReference,
                                                  fields: [String: Any]) -> Single<()> {
        return Single.create { observer in
            documentRef
                .updateData(fields) { error in
                    if let error = error {
                        logger.error(error)
                        observer(.error(error))
                    } else {
                        observer(.success(()))
                    }
            }
            return Disposables.create()
        }
    }

    public func get<T: DatabaseCollection>(_ type: T.Type,
                                           documentRef: DocumentReference) -> Single<T> {
        return Single.create { observer in
            documentRef
                .getDocument { snapshot, error in
                    if let error = error {
                        observer(.error(error))
                        return
                    }
                    guard let snapshot = snapshot else {
                        observer(.error(ApplicationError.unknown))
                        return
                    }
                    do {
                        observer(.success(try snapshot.makeResult(id: snapshot.documentID)))
                    } catch {
                        logger.error(error)
                        observer(.error(error))
                    }
            }
            return Disposables.create()
        }
    }

    func get<T: DatabaseCollection>(_ type: T.Type,
                                    collectionRef: CollectionReference) -> Single<[T]> {
        return Single.create { observer in
            collectionRef
                .getDocuments { snapshot, error in
                    if let error = error {
                        observer(.error(error))
                        return
                    }
                    guard let snapshot = snapshot else {
                        observer(.error(ApplicationError.unknown))
                        return
                    }
                    let results = snapshot.documents.flatMap { snapshot -> T? in
                        do {
                            return try snapshot.makeResult(id: snapshot.documentID)
                        } catch {
                            // TODO: error handling
                            logger.error(error)
                            return nil
                        }
                    }
                    observer(.success(results))
            }
            return Disposables.create()
        }
    }

    public func observe<T: DatabaseCollection>(_ type: T.Type,
                                               documentRef: DocumentReference) -> Observable<T> {
        return Observable.create { observer in
            documentRef
                .addSnapshotListener { snapshot, error in
                    if let error = error {
                        observer.on(.error(error))
                        return
                    }
                    guard let snapshot = snapshot else {
                        observer.on(.error(ApplicationError.unknown))
                        return
                    }
                    do {
                        observer.on(.next(try snapshot.makeResult(id: snapshot.documentID)))
                    } catch {
                        logger.error(error)
                        observer.on(.error(error))
                    }
            }
            return Disposables.create()
        }
    }

    public func observe<T: DatabaseCollection>(_ type: T.Type,
                                               collectionRef: CollectionReference) -> Observable<[T]> {
        return Observable.create { observer in
            collectionRef
                .addSnapshotListener { snapshot, error in
                    if let error = error {
                        observer.on(.error(error))
                        return
                    }
                    guard let snapshot = snapshot else {
                        observer.on(.error(ApplicationError.unknown))
                        return
                    }
                    // TODO: ここでは全件返しているが、RxRealmのようにchangesetメソッドも欲しい https://github.com/RxSwiftCommunity/RxRealm
                    let results = snapshot.documents.flatMap { snapshot -> T? in
                        do {
                            return try snapshot.makeResult(id: snapshot.documentID)
                        } catch {
                            // TODO: error handling
                            logger.error(error)
                            return nil
                        }
                    }
                    observer.on(.next(results))
            }
            return Disposables.create()
        }
    }
}

extension DocumentSnapshot {
    func makeResult<T: DatabaseCollection>(id: String) throws -> T {
        guard exists else {
            throw ApplicationError.notFoundEntity(documentId: documentID)
        }
        let json = data()
        logger.debug(json)
        return try T(id: id, json: json)
    }
}