Chapter 15: SwiftUI with Combine. Hacker News need another View Model

Usually subscription is not done in SceneDelegate, but in View Model. Also, if you move currentDate from ReaderView as @State to ReaderViewModel as @Published, you can watch the change of stories in Live View.
You don’t need import SwiftUI in View Model.
Screenshot 2020-02-28 at 12.52.03
You don’t need init in ReaderView.

Screenshot 2020-02-28 at 10.32.19

So can use Live View:
Screenshot 2020-02-28 at 10.39.21

You don’t need subscription in SceneDelegate.
Screenshot 2020-02-28 at 10.42.27

1 Like

One thing I’ve noticed about this solution is that once you use flatMap in ReaderViewModelNew to combine the api.stories() publisher and the $filter publisher, you lose the ability to receive the .success completion handler from the api.stories() publisher (i.e., you only ever receive errors in the sink).

Is there any way around this other than doing something like this?

let stories = api.stories().share()
$currentDate
    .setFailureType(to: API.Error.self)
    .flatMap { _ in
        Publishers.CombineLatest(stories, self.$filter.setFailureType(to: API.Error.self))
    }
...
stories
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: { print($0) }, receiveValue: { print($0) }).store(in: &subscriptions)

You are wrong.
Operator setFailureType (to: API.Error.self) is just a way to turn an infallible publisher into a fallible one. That’s it. (Chapter 16: Error Handling)
All @Published property has a failure type of Never and I take it from example.
If you want to control error of $filter you have to create another Publisher from simple $filter, probably just here in ReaderViewModelNew.
For example:

 private var validFilter:  AnyPublisher<[String], Error> {
        $filter
            .tryMap { (filter) ->[String] in
                guard check (filter) else {
                    throw API.Error.invalidResponse}
                return filter
        }
        .eraseToAnyPublisher()
    }

let stories = api.stories().share()
$currentDate
    .setFailureType(to: API.Error.self)
    .flatMap { _ in
        Publishers.CombineLatest(stories, self.validFilter)
    }
...
stories
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: { print($0) }, receiveValue: { print($0) }).store(in: &subscriptions)

To update List of stories on screen, when new story is coming.

Very need solution, I would go for it.

A maybe desired side effect when updating the filter keywords in ReaderView :

.sheet(isPresented: $presentingSettingsSheet,onDismiss:{
** self.model.filter = self.settings.keywords.map{**
** $0.value}**
}, content: {
SettingsView(dismissAction:{
self.presentingSettingsSheet = false
})
.environmentObject(self.settings)
})

is when you persist the keywords as in the sample code from the book, after restart the app the filter will not applied on startup.

@afrank Thank you for sharing your solution - much appreciated!