Skip to content

Instantly share code, notes, and snippets.

@mono0926
Last active June 2, 2022 08:15
Show Gist options
  • Star 33 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mono0926/ae6c491862370348ad4b4fcc7b4a5556 to your computer and use it in GitHub Desktop.
Save mono0926/ae6c491862370348ad4b4fcc7b4a5556 to your computer and use it in GitHub Desktop.
FirestoreのRxSwiftとの組み合わせ。Codableも活用。 [追記] FirestoreのデータはCodable非対応のプロパティを持ててしまいそれが含まれると対応難しいのでCodableは捨てた方が良いかもしれない
//
// 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)
}
}
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) }
}
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
}
}
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)
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))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment