RxSwift Chaining requests that depend from each other - swift

I'm still learning Rx and I'm trying to use RxSwift to make 3 requests that return depend on each other.
* DocumentsA
- CollectionA
* DocumentsB
- CollectionB
* DocumentsC
- CollectionC
The models are something like this:
struct DocumentA {
let documentsB: [DocumentB]
}
struct DocumentB {
let documentsC: [DocumentC]
}
struct DocumentsC {
let name: String
}
So using RxSwift, I'm trying to request each level of the document using separate methods for each document:
func fetchDocumentsA() -> Observable<DocumentA> {
return Observable.create { observer in
fetchDocumentsB().subscribe(onNext: { documentB in
if let documentA = DocumentA(documentB: documentB) {
observer.onNext(documentA)
}
}, onError: nil, onCompleted: {
observer.onCompleted()
}, onDisposed: nil).disposed(by: self.disposeBag)
return Disposables.create()
}
}
func fetchDocumentsB() -> Observable<DocumentB> {
return Observable.create { observer in
fetchDocumentsC().subscribe(onNext: { documentC in
if let documentB = DocumentB(documentC: documentC) {
observer.onNext(documentB)
}
}, onError: nil, onCompleted: {
observer.onCompleted()
}, onDisposed: nil).disposed(by: self.disposeBag)
return Disposables.create()
}
}
func fetchDocumentsC() -> Observable<DocumentC> {
return Observable.create { observer in
fetchName().subscribe(onNext: { name in
observer.onNext(DocumentC(name: name))
}, onError: nil, onCompleted: {
observer.onCompleted()
}, onDisposed: nil).disposed(by: self.disposeBag)
return Disposables.create()
}
}
Is there a better way to do this? It seems very convoluted.

In this case you can make this much nicer with map function, like in example bellow.
The Map operator applies a function of your choosing to each item emitted by the source Observable,
and returns an Observable that emits the results of these function
applications.
ReactiveX
func fetchDocumentsA() -> Observable<DocumentA> {
return fetchDocumentsB().map { (documentB) -> DocumentA in
DocumentA(documentB: documentB)
}
}
func fetchDocumentsB() -> Observable<DocumentB> {
return fetchDocumentsC().map { (documentC) -> DocumentB in
DocumentB(documentC: documentC)
}
}
func fetchDocumentsC() -> Observable<DocumentC> {
return fetchName().map { (name) -> DocumentC in
return DocumentC(name: name)
}
}

Related

Observable extension with generic type

Context
I want to wrap the Alamofire.upload into an observable and having info regarding the upload progress.
For that I have created a custom UploadElement that is an enum representing either the progress and the value or the result. So far I have:
enum UploadElement<Result> where Result: Codable {
case progress(Double)
case response(Result)
}
private func buildUploadRequest(url: URL, parts: [Data]) -> Observable<UploadRequest> {
let uploadRequest = manager.upload(
multipartFormData: { multipartFormData in /* build multipart */ },
to: url
)
return Observable.just(uploadRequest)
}
func upload<Result: Codable>(url: URL, parts: [Data]) -> Observable<UploadElement<Result>> {
buildUploadRequest(url: url, parts: parts)
.flatMap { request in
Observable<UploadElement<Result>>.create { observer in
request.response { response in
do {
observer.on(.next(.response(/* decode here */)))
observer.on(.completed)
} catch let error {
observer.on(.error(error))
}
}.uploadProgress { progress in
observer.on(.next(.progress(progress.fractionCompleted)))
}
.resume()
return Disposable.create { request.cancel() }
}
}
}
Now I would like to have an extension on an Observable<UploadEment<Result>> to have a nicer way to be notified.
Basically it would be:
service.upload(url: ..., parts: ...)
.progress { progress in /* */ }
.result { result in /* */ }
.subscribe()
.dispose(by: disposeBag)
To do that I tried:
extension ObservableType where Element == UploadElement<Resource> {
func progress(progressCompletion: #escaping (Double) -> Void) -> Self {
return self.do(onNext: { element in
switch element {
case .progress(let progress): progressCompletion(progress)
case .response: return
}
})
}
func result(resultCompletion: #escaping (Result) -> Void) -> Self {
return self.do(onNext: { element in
switch element {
case .response(let result): resultCompletion(result)
case .progress: return
}
})
}
}
I tried multiple variation of that but the errors that I get are:
Cannot find 'Result in scope'
Reference to generic type ... required argument
Is it possible to achieve something like that?
You just need to move the where clause from class scope down to function scope (shown below).
That said, I don't think breaking out of the monad like this in the middle of a stream is "a nicer way to be notified".
Better would be to break your Observable into two streams and subscribe to each of them:
extension ObservableType {
func progress<Resource>() -> Observable<Double> where Element == UploadElement<Resource> {
self.compactMap { element in
switch element {
case let .progress(progress):
return progress
case .response:
return nil
}
}
}
func result<Resource>() -> Observable<Resource> where Element == UploadElement<Resource> {
self.compactMap { element in
switch element {
case .progress:
return nil
case let .response(resource):
return resource
}
}
}
}
With the above you can now do something like this:
let response = service.upload(url: ..., parts: ...)
.share()
response
.progress()
.subscribe(onNext: { progress in /*...*/ })
.disposed(by: disposeBag)
response
.result()
.subscribe(onNext: { result in /*...*/ })
.dispose(by: disposeBag)
Now you don't have any empty subscribes.
I found something that is working:
extension ObservableType {
func progress<O: Codable>(progressCompletion: #escaping (Double) -> Void) -> Observable<UploadElement<O>> where Element == UploadElement<O> {
return self.do(onNext: { element in
if case .progress(let progress) = element {
progressCompletion(progress)
}
})
}
func response<O: Codable>(responseCompletion: #escaping (O) -> Void) -> Observable<UploadElement<O>> where Element == UploadElement<O> {
return self.do(onNext: { element in
if case .response(let response) = element {
responseCompletion(response)
}
})
}
}
Now I can use the "planned" api:
service.update(data: /* ... */)
.progress { progress in /* */ }
.response { result in /* */ }
.subscribe(
onError: { error in /* */ }
)
.dispose(by: disposeBag)
However as Daniel mentioned this might not be the "nicer way of being notified".

Run multiple request at a time and continue as soon there is success

Given an array of urls, is there a way to run those at once simultaneously? But in such a way so as soon as there is one success move to the next request using that successful url.
So far I tried chaining concatMap, and failed with zip.
func updateAccountInfo() -> Single<Bool> {
var disposable: Disposable? = nil
return Single<Bool>.create { observer in
do {
var urls = try self.settings.domains.value()
disposable = Observable.from(urls)
.enumerated()
.concatMap { index, url -> Single<URL> in
return self.verifyUrl(url)
}
.concatMap { url -> Single<Account> in
return self.apiManager.loadAccountInfo(from: url)
}
.observeOn(MainScheduler.instance)
.do(onNext: { (account: AccountInfo) in
// use account unfo here
disposable!.dispose()
})
.subscribe()
} catch {
observer(.error(error))
}
return Disposables.create()
}
}
Tried like so too:
disposable = Observable.from(urls)
.enumerated()
.concatMap { index, url -> Single<(Bool, URL)> in
return self.verifyUrl(url)
}
.subscribe(onNext: { reachable, url in
if reachable {
self.apiManager.loadAccountInfo(from: url)
.subscribe(onSuccess: { accountInfo in
// use account info here
}, onError: { error in
})
.disposed(by: self.bag)
disposable!.dispose()
} else {
}
}, onError: { error in
}, onCompleted: {
})
Maybe I use zip but how would I create an array of verifyUrl(url) calls? Does zip accept arrays of Observable at all?
let obs = Observable.from(urls)
.enumerated()
.concatMap { index, url -> Single<URL> in
return self.verifyUrl(url)
}
let test = Observable
.zip(obs).map { [urls] in
return [urls]
}
If I understand the question correctly, you are looking for something like this:
func example() throws {
let urls = try self.settings.domains.value()
Observable.merge(urls.map { verifyUrl($0).asObservable() })
.flatMap { [apiManager] url in
apiManager!.loadAccountInfo(from: url)
}
.observe(on: MainScheduler.instance)
.subscribe(onNext: { account in
// use account unfo here
})
.disposed(by: self.disposeBag)
}
But it's hard to tell. Your code samples are a bit jumbled. Putting all your code in a Single.create is odd. Returning a Disposables.create() from the closure when you have a disposable to return is odd. Calling dispose() on a disposable inside the do block is odd. So much weirdness... I suggest you post some code on https://codereview.stackexchange.com or look at sample code.

call func in Asynchronous requests chaining RxSwift and Alamofire

I'm a begginer with RxSwift and this is my issue, my app must do a 3 requests, the 3 are gets, my work team suggests me use a flatmap to do this a three request in row, but I don't know how I should use flatmap.
these are my requests
public func login(param: [String:String]) -> Observable<messageModel>{
return Observable.create { observer -> Disposable in
self.alamoFireManager!.request(self.urlServer!+endPoints.login.login, method: .post, parameters: param, encoding: URLEncoding.default, headers: nil, interceptor: nil).responseDecodable { (res: DataResponse<messageModel,AFError>) in
if let error = res.error {
observer.onError(error)
} else if let valueEntitie = res.value {
observer.onNext(valueEntitie)
}
observer.onCompleted()
}
return Disposables.create()
}
}
public func me() -> Observable<meModel>{
return Observable.create { observer -> Disposable in
self.alamoFireManager!.request(self.urlServer!+endPoints.login.me, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil, interceptor: nil).responseDecodable { (res: DataResponse<meModel,AFError>) in
if let error = res.error {
observer.onError(error)
} else if let valueEntitie = res.value {
observer.onNext(valueEntitie)
}
observer.onCompleted()
}
return Disposables.create()
}
}
public func entitie(entityId: String) -> Observable<entitieModel>{
return Observable.create { observer -> Disposable in
self.alamoFireManager!.request(self.urlServer!+endPoints.login.entities+"/"+entityId, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil, interceptor: nil).responseDecodable { (res: DataResponse<entitieModel,AFError>) in
if let error = res.error {
observer.onError(error)
} else if let valueEntitie = res.value {
observer.onNext(valueEntitie)
}
observer.onCompleted()
}
return Disposables.create()
}
}
the first endpoint is login with its parameters, then me, function me response a id, this id its necessary for the third request, that is entitie.
I'm doing the flatmap as this way.
networkManagerShareCore.share.login(param: param)
.flatMap { resMessageModel in
//saveData(resMessageModel)
networkManagerShareCore.share.me()
.flatMap { resMeModel in
//saveData(resMessageModel)
networkManagerShareCore.share.entitie(entityId: "\(resModelMe.data.personId!)")
}
}.subscribe(onNext: { (model) in
print(model)
}, onError: { (error) in
self.errorMsg.accept(error.localizedDescription)
self.isSuccess.accept(false)
}, onCompleted: nil) {
print("Disposed")
}
the code works, but in the line //saveData(resMessageModel) I can't call it the function, I want to save the model, but if I try a call a function, Xcode show me this error: Unable to infer complex closure return type; add explicit type to disambiguate
so, how can I resolve this?
RxSwift flatMap expects a return value of some Observable.
The FlatMap operator transforms an Observable by applying a function
that you specify to each item emitted by the source Observable, where
that function returns an Observable that itself emits items.
You need to add a return statement within each flatMap call. In addition, you need to explicitly define the return type for the closures.
So the inner most flatMap call should look something like this:
networkManagerShareCore.share.me()
.flatMap { resMeModel -> Observable<entitieModel> in
//saveData(resMessageModel)
return networkManagerShareCore.share.entitie(entityId: "\(resModelMe.data.personId!)")
You wouldn't normally embed flatMap within flatMap, but just compose them sequentially to make the code clearer. And don't forget disposed(by:). The final result would look something like this:
networkManagerShareCore.share.login(param: param)
.flatMap { resMessageModel -> Observable<meModel> in
//saveData(resMessageModel)
return networkManagerShareCore.share.me()
}
.flatMap { resMeModel -> Observable<entitieModel> in
//saveData(resMessageModel)
return networkManagerShareCore.share.entitie(entityId: "\(resModelMe.data.personId!)")
}
.subscribe(
onNext: { (model) in
print(model)
},
onError: { (error) in
self.errorMsg.accept(error.localizedDescription)
self.isSuccess.accept(false)
},
onCompleted: nil) {
print("Disposed")
}
.disposed(by: disposeBag)
Dale's answer works but I would be inclined to move the saves into do operators instead. Like this:
networkManagerShareCore.share.login(param: param)
.do(onNext: { [weak self] resMessageModel in
self?.saveData(resMessageModel)
})
.flatMap { _ in
networkManagerShareCore.share.me()
}
.do(onNext: { [weak self] resMeModel in
self?.saveData(resMeModel)
})
.flatMap { resMeModel in
networkManagerShareCore.share.entitie(entityId: "\(resMeModel.data.personId!)")
}
.subscribe(
onNext: { (model) in
print(model)
},
onError: { (error) in
self.errorMsg.accept(error.localizedDescription)
self.isSuccess.accept(false)
},
onCompleted: nil,
onDisposed: {
print("Disposed")
}
)
.disposed(by: disposeBag)

Refresh Observable in response to another

I have an observable that emits a list of CNContacts, and I want to reload the list when there is a change to the Contacts database (.CNContactStoreDidChange).
So the observable should emit a value on subscription, and whenever the other observable (the notification) emits a value. That sounds like combining them with withLatestFrom, but it doesn't emit anything.
let myContactKeys = [
CNContactIdentifierKey as CNKeyDescriptor,
CNContactFormatter.descriptorForRequiredKeys(for: .fullName)
]
func fetchContacts(by identifiers: [String],
contactKeys: [CNKeyDescriptor]) -> Observable<Event<[CNContact]>> {
return Observable<[String]>.just(identifiers)
.withLatestFrom(NotificationCenter.default.rx.notification(Notification.Name.CNContactStoreDidChange)) { ids, _ in ids}
.flatMap { ids in
Observable<[CNContact]>.create { observer in
let predicate = CNContact.predicateForContacts(withIdentifiers: ids)
do {
let contacts = try CNContactStore().unifiedContacts(matching: predicate, keysToFetch: contactKeys)
observer.onNext(contacts)
} catch {
observer.onError(error)
}
return Disposables.create()
}
.materialize()
}
.observeOn(MainScheduler.instance)
.share(replay: 1)
.debug()
}
fetchContacts(by: ["123"], contactKeys: myContactKeys)
.subscribe(
onNext: { contacts in
contacts.forEach { print($0.fullName) }
},
onError: { error in
print(error.localizedDescription)
})
.dispose(by: disposeBag)
The problem with your code is that you are starting with Observable<[String]>.just(identifiers) which will emit your identifiers and immediately complete. You don't want it to complete, you want it to continue to emit values whenever the notification comes in.
From your description, it sounds like you want something like the below. It emits whenever the notification fires, and starts with the contacts.
let myContactKeys = [
CNContactIdentifierKey as CNKeyDescriptor,
CNContactFormatter.descriptorForRequiredKeys(for: .fullName)
]
func fetchContacts(by identifiers: [String], contactKeys: [CNKeyDescriptor]) -> Observable<Event<[CNContact]>> {
func update() throws -> [CNContact] {
let predicate = CNContact.predicateForContacts(withIdentifiers: identifiers)
return try CNContactStore().unifiedContacts(matching: predicate, keysToFetch: contactKeys)
}
return Observable.deferred {
NotificationCenter.default.rx.notification(Notification.Name.CNContactStoreDidChange)
.map { _ in }
.map(update)
.materialize()
}
.startWith({ () -> Event<[CNContact]> in
do {
return Event.next(try update())
}
catch {
return Event.error(error)
}
}())
.share(replay: 1)
.debug()
}

RxSwift Chaining two signals in right order

So basically I have two actions I need to execute:
first is login
second is get user profile
They have to be done in right order because getting user profile cannot be done without logging in first.
So I had bunch of code that looked like this:
func signIn(signinParameters: SignInParameters) -> Observable<SignInResult> {
return Observable<SignInResult>.create { [unowned self] observer in
self.signinParameters = signinParameters
self.apiConnector
.signIn(with: signinParameters)
.do(onNext: { [weak self] signinResult in
self!.apiConnector
.get()
.do(onNext: { user in
let realm = RealmManager.shared.newRealm()!
let realmUser = RealmUser()
realmUser.configure(with: user, in: realm)
try? realm.write {
realm.add(realmUser, update: true)
}
self!.setState(.authenticated)
observer.onNext(signinResult)
}, onError: { (error) in
observer.onError(error)
}, onCompleted: {
observer.onCompleted()
}).subscribe()
}, onError: { error in
observer.onError(error)
}, onCompleted: {
print("completed")
observer.onCompleted()
}).subscribe()
return Disposables.create()
}
I know this is not right because I cannot send onNext signal with signin result when both actions are finished. I've been reading and I figured out i need to flatmap both actions, combine them into one signal and then manipulate signinresult but I dont have a clue how to do that. So any help would be nice.
Thank you
EDIT 1:
so I've refactored code to look something like this, but there is still problem that I can't send signal when BOTH actions are finished, or am I wrong?
func signIn(signinParameters: SignInParameters) -> Observable<SignInResult> {
return Observable<SignInResult>.create { [unowned self] observer in
self.signinParameters = signinParameters
self.apiConnector
.signIn(with: signinParameters)
.do(onNext: { (result) in
}, onError: { (error) in
}, onCompleted: {
})
.flatMap({ (result) -> Observable<User> in
self.apiConnector.get().asObservable()
})
.do(onNext: { (user) in
}, onError: { (error) in
}, onCompleted: {
}).subscribe()
return Disposables.create()
}
}
Your code is not very clean and it is hard to understand what is going on (my opinion).
If you need two actions to be executed you can create two functions:
struct Parameters{}
struct Profile{}
struct User{}
func login(parameters: Parameters) -> Observable<User> {
// get user
}
func profile(user: User) -> Observable<Profile> {
// get profile
}
func serial(parameters: Parameters) -> Observable<Profile> {
return login(parameters: parameters).flatMap({ profile(user: $0) })
}
login function or profile function can be also split into smaller functions if required:
func profileStored(user: User) -> Observable<Profile?> {
// get stored profile
}
func profileRequested(user: User) -> Observable<Profile> {
// get profile from network
}
func profile(user: User) -> Observable<Profile> {
let observable = profileStored(user: user)
.shareReplayLatestWhileConnected()
let observableStored = observable
.filter({ $0 != nil })
.map({ $0! })
.shareReplayLatestWhileConnected()
let observableRequested = observable
.filter({ $0 == nil })
.flatMap({ _ in profileRequested(user: user) })
.shareReplayLatestWhileConnected()
return Observable
.of(observableStored, observableRequested)
.merge()
.shareReplayLatestWhileConnected()
}
As a result you can mix smaller functions with flatMap or any other operator.
That is how I do it. Hope it'll be helpful