Combine bind Publisher to PassthroughSubject - ios

This question has effectively been asked before (Combine assign publisher to PassthroughSubject) but the answer assumed the architecture of the question's example was totally wrong.
I'm faced with the same issue, in what I feel is a different example case, and I'd dearly love clarity on the issue.
I have a viewmodel class which provides a public errorMessage publisher for the client layer to do what it will with. Just a string here. Privately this publisher is backed by a PassthroughSubject - I'm "entering the monad" from different places internally and so I want to send error messages imperatively at that stage.
One of my entry points here happens to be another Combine publisher. I'd like to just map and bind, as I would in RxSwift:
private let _errorMessageSubject = PublishSubject<String>()
public let errorMessageRail = _errorMessageSubject.asObservable()
private func setup() {
...
myEngine.errorMessages
.map { msg in "Internal error: \(msg)" }
.bind(to: _errorMessageSubject)
...
}
private func someOtherMethod() {
_errorMessageSubject.onNext("Surprise")
}
In Combine I'm not sure how to do it other than:
private let _errorMessageSubject = PassthroughSubject<String,Never>()
public let errorMessageRail = _errorMessageSubject.eraseToAnyPublisher()
private func setup() {
...
myEngine.errorMessages
.map { msg in "Internal error: \(msg)" }
.sink { [weak self] msg in
self?._errorMessageSubject.send(msg)
}
...
}
private func someOtherMethod() {
_errorMessageSubject.send("Surprise")
}
Before we get into chatting concurrency issues, let's say I'm always carefully pushing to _errorMessageSubject on a specific dispatch queue. I omit that from the code above for clarity.
So given this example, unless I'm missing something staggeringly obvious a flatMap won't help me here.
Am I stuck with this sink -> send dance?
Or is there some eye-watering code-smell about my public/private publisher/subject pattern (that I use a lot for bridging imperative with reactive architectures) and can some kind soul point me in the direction of self-improvement?

It sounds like what you want your rail to be the merge of _errorMessageSubject and another publisher. So I'll make errorMessageRail a variable so it can be changed (by this class only) after it's initialized:
public private(set) var errorMessageRail = _errorMessageSubject.eraseToAnyPublisher()
Then, in setup, you change the rail so it includes the additional stream:
func setup() {
...
errorMessageRail = _errorMessageSubject.merge( with:
myEngine.errorMessages
.map { msg in "Internal error: \(msg)" }
).eraseToAnyPublisher()
}

Related

Update jetpcak composable when a static class member changes

I have a Paho MQTT connection, with a callback updating a object and i need to update a coposable..
I can see the in Logcat that i receive information, but the composable is not updated.
I am suspecting that the issue is that i am using a static object and it is not mutable. What is the practice on this scenario? I did not implement a ViewModel. It could be done with a timer, but i think it is not an elegant solution.
snippet:
object MyCallBack : MqttCallback {
public var message = ""
override fun messageArrived(topic: String?, message: MqttMessage?) {
this.message = message.toString()
Log.e(ContentValues.TAG,"mqtt Arrived: $message")
}
......
}
and a composable function used to display the information:
#Composable
fun ShowMessage() {
var myCallBack = MyCallBack //here i can access the updated information
var message by remember {
mutableStateOf(myCallBack.message)
Text("Changed ${myCallBack.message}", color = Color.White)
}
}
Thank you!
i have tried to use mutableStateOf() but it did not called for composition, i think it is not observable.

Resolve combine future outside of future's closure

I'm using Future to create a publisher that returns a single value from outside of the future creation closure. For this, I store the promise property locally:
return Future<Something, Never> { promise in
self.somePromise = promise
}.eraseToAnyPublisher()
Then after some work is finished I resolve the promise:
self.somePromise(.success(data))
I've searched the documentation and haven't found any references, is this a correct usage or is there a better way to do it?
To put it more into context, my idea is to have some kind of "broker" class, that returns promises and that at some time in the future will publish a result:
class FruitBroker {
// MARK: - Private Properties
private var applePromise: Future<[Apple], Never>.Promise!
private var orangePromise: Future<[Orange], Never>.Promise!
// MARK: - Methods
func getApples() -> AnyPublisher<[Apple], Never> {
return Future<[Apple], Never> { promise in
self.applePromise = promise
}.eraseToAnyPublisher()
}
func getBananas() -> AnyPublisher<[Orange], Never> {
return Future<[Orange], Never> { promise in
self.orangePromise = promise
}.eraseToAnyPublisher()
}
// MARK: - Private Methods
private func onHarvestFinished() {
applePromise(.success(apples))
orangePromise(.success(oranges))
}
}
This looks like a situation where you should just use PassthroughSubject or CurrentValueSubject:
class FruitBroker {
var applePublisher = PassthroughSubject<[Apple],Never>()
var orangePublisher = PassthroughSubject<[Orange],Never>()
private func onHarvestFinished(apples: [Apple], oranges: [Orange]) {
applePublisher.send(apples)
orangePublisher.send(oranges)
}
}
is this a correct usage
No. What you're doing is not Combine. Combine is publisher -> operators -> subscriber, where this path constitutes a pipeline. You have no pipeline. You're just storing a method and later calling it; you don't need a Combine Future to do that.

Understanding how to initialize a Vapor 4 repository

I am trying to migrate some code using a Repository pattern from Vapor 3 to Vapor 4. I have gone through the documentation of this specific pattern from the Vapor 4 documentation, and I think I understand it for the most part.
The one thing I am not getting, however, is the way that the repository factory gets set within the Application extension. The example from the documentation shows this:
extension Application {
private struct UserRepositoryKey: StorageKey {
typealias Value = UserRepositoryFactory
}
var users: UserRepositoryFactory {
get {
self.storage[UserRepositoryKey.self] ?? .init()
}
set {
self.storage[UserRepositoryKey.self] = newValue
}
}
}
If I am reading the getter method correctly (and I might not be - I'm far from a Swift expert), a new instance of the UserRepositoryFactory structure will be created and returned when app.users is referenced. At that time, however, it does not appear that the contents of self.storage[UserRepositoryKey.self] is changed in any way. So if I happened to access app.users two times in a row, I would get 2 different instances returned to me and self.storage[UserRepositoryKey.self] would remain set to nil.
Following through the rest of the sample code in the document, it appears to define the make function that will be used by the factory when configuring the app as so:
app.users.use { req in
DatabaseUserRepository(database: req.db)
}
Here it seems like app.users.use would get a new factory instance and call its use function to set the appropriate make method for that instance.
Later, when I go to handle a request, I use the request.users method that was defined by this Request extension:
extension Request {
var users: UserRepository {
self.application.users.make!(self)
}
}
Here it seems like self.application.users.make would be invoked on a different repository factory instance that is referenced by self.application.users. It would therefore not apply the factory's make method that was set earlier when configuring the application.
So what am I missing here?
It looks like the docs are slightly out of date for that. You can have a look at how views or client is done, but somewhere you need to call initialize() to set the repository. Here's what my working repository looks like:
import Vapor
extension Application {
struct Repositories {
struct Provider {
let run: (Application) -> ()
public init(_ run: #escaping (Application) -> ()) {
self.run = run
}
}
final class Storage {
var makeRepository: ((Application) -> APIRepository)?
init() { }
}
struct Key: StorageKey {
typealias Value = Storage
}
let application: Application
var repository: APIRepository {
guard let makeRepository = self.storage.makeRepository else {
fatalError("No repository configured. Configure with app.repositories.use(...)")
}
return makeRepository(self.application)
}
func use(_ provider: Provider) {
provider.run(self.application)
}
func use(_ makeRepository: #escaping (Application) -> APIRepository) {
self.storage.makeRepository = makeRepository
}
func initialize() {
self.application.storage[Key.self] = .init()
}
private var storage: Storage {
if self.application.storage[Key.self] == nil {
self.initialize()
}
return self.application.storage[Key.self]!
}
}
var repositories: Repositories {
.init(application: self)
}
}
That autoinitializes itself the first time it's used. Note that APIRepository is the protocol used for my repostiory. FluentRepository is the Fluent implementation of that protocol. Then like you I have an extension on Request to use it in request handlers:
extension Request {
var repository: APIRepository {
self.application.repositories.repository.for(self)
}
}
Finally, you need to configure it to use the right repository. So in my configure.swift I have:
app.repositories.use { application in
FluentRepository(database: application.db)
}
and in tests I can switch it for the in-memory repository that doesn't touch the DB:
application.repositories.use { _ in
return inMemoryRepository
}
I have managed to get the example from the docs working as-is.
Tracing through the execution with the debugger, there is the predictable call to get, as you say, and this returns the instance from .init() as the failover from not having a previously stored value. Included in the example you refer to is:
struct UserRepositoryFactory {
var make: ((Request) -> UserRepository)?
mutating func use(_ make: #escaping ((Request) -> UserRepository)) {
self.make = make
}
}
This use function is executed next, which is mutating and updates the variable make. I believe it is this change to make that then triggers a call to set. It certainly happens immediately after use and before execution moves on in configure.swift. So, by the time the server formally starts and you actually use the Repository in a route, there is a stored instance that is reused as required.

Why this produces compiler error in Combine?

just trying to implement SwiftUI and Combine in my new project.
But stuck in this:
func task() -> AnyPublisher<Int, Error> {
return AnyPublisher { subscriber in
subscriber.receive(Int(arc4random()))
subscriber.receive(completion: .finished)
}
}
This produces the following compiler error:
Type '(_) -> ()' does not conform to protocol 'Publisher'
Why?
Update
Actually Random here is just as an example. The real code will look like this:
func task() -> AnyPublisher<SomeCodableModel, Error> {
return AnyPublisher { subscriber in
BackendCall.MakeApiCallWithCompletionHandler { response, error in
if let error == error {
subscriber.receive(.failure(error))
} else {
subscriber.receive(.success(response.data.filter))
subscriber.receive(.finished)
}
}
}
}
Unfortunately, I don't have access to BackendCall API since it is private.
It's kind of pseudocode but, it pretty close to the real one.
You cannot initialise an AnyPublisher with a closure accepting a Subscriber. You can only initialise an AnyPublisher from a Publisher. If you want to create a custom Publisher that emits a single random Int as soon as it receives a subscriber and then completes, you can create a custom type conforming to Publisher and in the required method, receive(subscriber:), do exactly what you were doing in your closure.
struct RandomNumberPublisher: Publisher {
typealias Output = Int
typealias Failure = Never
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
subscriber.receive(Int.random(in: 0...Int.max))
subscriber.receive(completion: .finished)
}
}
Then in your task method, you simply need to create a RandomNumberPublisher and then type erase it.
func task() -> AnyPublisher<Int, Never> {
return RandomNumberPublisher().eraseToAnyPublisher()
}
If all you want is a single random value, use Just
fun task() -> AnyPublisher<Int, Never> {
return Just(Int.random(in: 0...Int.max)).eraseToAnyPublisher()
}
Sidenote: don't use Int(arc4random()) anymore.
You're likely better off wrapping this in a Future publisher, possibly also wrapped with Deferred if you want it to response when subscriptions come in. Future is an excellent way to wrap external async API calls, especially ones that you can't fully control or otherwise easily adapt.
There's an example in Using Combine for "wrapping an async call with a Future to create a one-shot publisher" that looks like it might map quite closely to what you're trying to do.
If you want it to return more than a single value, then you may want to compose something out of PassthoughSubject or CurrentValueSubject that gives you an interface of -> AnyPublisher<YourType, Error> (or whatever you're looking for).

How to use swift-4 Promises then, done, catch and other blocks

I would like to learn about promises in swift-4. How to use multiple then statements and done, catch blocks.
Here I am trying to get the value from the promise. But I'm getting errors. Could someone help me to understand promises?
Here is my code.
import UIKit
import PromiseKit
struct User {
var firstname : String?
var lastname : String?
}
struct APIError {
var message : String?
}
class ViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let userPromise : Promise = self.getUserDetails()
userPromise.then { user -> Void in
//print(user.f)
}
}
func getUserDetails()->Promise<User> {
return Promise<User> { resolve in
let user = User(firstname: "Scot", lastname: "dem")
if ((user.firstname?.count) != nil) {
resolve.fulfill(user)
} else {
let error = APIError(message: "User not valid")
resolve.reject(error as! Error)
}
}
}
}
Once I get the user details I want to make a full name, uppercase promises which are dependent on userPromise.
I would like to use multiple then, done, finally blocks. Just want to understand usage.
Why I'm getting an error here when we use userPromise.then { user -> Void in
what should I give inside the block
In PromiseKit 6, then can no longer return Void. This is mainly due to the tuplegate issue in Swift 4.
Quote from PromieKit 6 Release News
With PromiseKit our then did multiple things, and we relied on Swift
to infer the correct then from context. However with multiple line
thens it would fail to do this, and instead of telling you that the
situation was ambiguous it would invent some other error. Often the
dreaded cannot convert T to AnyPromise. We have a troubleshooting
guide to combat this but I believe in tools that just work, and when
you spend 4 years waiting for Swift to fix the issue and Swift doesn’t
fix the issue, what do you do? We chose to find a solution at the
higher level.
So we split then into then, done and map.
then is fed the previous promise value and requires you return a promise.
done is fed the previous promise value and returns a Void promise (which is 80% of chain usage)
map is fed the previous promise value and requires you return a non-promise, ie. a value.
Hence .then { (user) -> Void in is no longer valid and that's why you're getting an error.
It's now designed to return a Thenable like so:
userPromise.then { user -> Promise<User> in
//return yet another promise
}
The .then that used to return Void is now it's own .done.
i.e:
userPromise.done { (user) in
print(user)
}
So when you mix 'em up:
We get:
userPromise
.then { (user) -> Promise<User> in
//return another Promise
return self.uppercasePromise(on: user)
}
.done { (user) in
/*
Depending on your sequence, no more promises are left
and you should have a matured user object by now
*/
print(user)
}
.catch { (error) in
print(error)
}
.finally {
print("finally")
}
func uppercasePromise(on user: User) -> Promise<User> {
/*
Didn't understand your statement but do whatever you meant when you said:
"Once I get the user details I want to make a full name, "
uppercase promises which are dependent on userPromise."
*/
return Promise { seal in
seal.fulfill(user)
}
}

Resources