【SwiftUI】FirestoreのラッパーをCombineで書いてみた[備忘録]
メモ程度なので雑に書いてます。
Firestore+Combine.swift
import Foundation
import Combine
import FirebaseFirestore
extension Firestore {
func fetchAll<T: EntityProtocol>(collectionKey: String) -> AnyPublisher<[T], Error> {
return Future<[T], Error> { [weak self] promise in
self?.collection(collectionKey).getDocuments() { snapshot, error in
if let err = error {
promise(.failure(err))
}
if let snapshot = snapshot {
let entities: [T] = snapshot.documents.compactMap { T.decode(document: $0.data()) }
promise(.success(entities))
}
}
}.eraseToAnyPublisher()
}
}
Repository.swift
import Foundation
import Combine
import FirebaseFirestore
protocol RepositoryProtocol {
func fetchAll() -> AnyPublisher<[Entity], Error>
}
final class Repository: RepositoryProtocol {
func fetchAll() -> AnyPublisher<[Entity], Error> {
return Firestore.firestore().fetchAll(collectionKey: "sample")
}
}
※EntityProtocolにはdecodeとdocument変換([String: Any])を定義しています。
ViewModel.swift
import Foundation
import SwiftUI
import Combine
final class ViewModel: ObservableObject {
@Published private(set) var entities: [Entity] = []
var cancellables: Set<AnyCancellable> = []
init(with repository: RepositoryProtocol = Repository()) {
repository.fetchAll()
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { _ in },
receiveValue: { [weak self] entities in
self?.entities = entities
}).store(in: &cancellables)
}
deinit {
cancellables.forEach { $0.cancel() }
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
@ObservedObject var viewModel: ViewModel
var body: some View {
NavigationView {
ScrollView {
ForEach(viewModel.subscriptions, id: \.id) { entity in
HStack {
VStack {
Text(entity.title)
.font(.system(size: 18, weight: .medium, design: .default))
Text(entity.description)
.font(.system(size: 14, weight: .medium, design: .default))
}
}
}
}
.navigationTitle("Content View")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(viewModel: .init())
}
}