I have a CurrentValueSubject
to hold data received from Firebase
fetch request.
final class CardRepository: ObservableObject {
private let store = Firestore.firestore()
var resultSubject = CurrentValueSubject<[Card], Error>([])
init() {
}
func get() {
store.collection(StorageCollection.EnglishCard.getPath)
.addSnapshotListener { [unowned self] snapshot, err in
if let err = err {
resultSubject.send(completion: .failure(err))
}
if let snapshot = snapshot {
let cards = snapshot.documents.compactMap {
try? $0.data(as: Card.self)
}
resultSubject.send(cards)
}
}
}
}
In my ViewModel, I want whenever resultSubject
sends or emits
a value
. It will change the state
and has that value
attached to the succes
state.
class CardViewModel: CardViewModelProtocol, ObservableObject {
@Published var repository: CardRepository
@Published private(set) var state: CardViewModelState = .loading
private var cancellables: Set<AnyCancellable> = []
required init (_ repository: CardRepository) {
self.repository = repository
bindingCards()
}
private func bindingCards() {
let _ = repository.resultSubject
.sink { [unowned self] comp in
switch comp {
case .failure(let err):
self.state = .failed(err: err)
case .finished:
print("finised")
}
} receiveValue: { [unowned self] res in
self.state = .success(cards: res)
}
}
func add(_ card: Card) {
repository.add(card)
}
func get() {
repository.get()
}
}
On my ContentView, it will display a button that print
the result
.
struct ContentView: View {
@StateObject var viewModel = CardViewModel(CardRepository())
var body: some View {
Group {
switch viewModel.state {
case .loading:
ProgressView()
Text("Loading")
case .success(cards: let cards):
let data = cards
Button {
print(data)
} label: {
Text("Tap to show cards")
}
case .failed(err: let err):
Button {
print(err)
} label: {
Text("Retry")
}
}
Button {
viewModel.get()
} label: {
Text("Retry")
}
}.onAppear {viewModel.get() }
}
}
My problem is the block below only trigger once when I first bind it to the resultSubject
.
} receiveValue: { [unowned self] res in
self.state = .success(cards: res)
}
I did add a debug and resultSubject.send(cards)
works every time.
You need to store the Cancellable
returned from the .sink
in the class so it doesn't get deallocated:
Either in a set var cancellables = Set<AnyCancellable>()
if you want to use multiple Publishers
, or in var cancellable: AnyCancellable?
.
Add .store(in &cancellables)
like so:
} receiveValue: { [unowned self] res in
self.state = .success(cards: res)
}.store(in: &cancellables)
Edit:
In ObservableObject
classes we don't use sink
, we assign
to an @Published
:
let _ = repository.resultSubject
.assign(to: &$self.state)