I have created below function with chaining of multiple observables however whatever I do it does not seem to call completed
? it only return the following:
(facebookSignInAndFetchData()) -> subscribed
(facebookSignInAndFetchData()) -> Event next(())
even though when I debug
the observables individually they all return completed
here is my chaining function
func facebookSignInAndFetchData() {
observerFacebook.flatMap { (provider: FacebookProvider) in
return provider.login()
}.flatMap { token in
return self.loginViewModel.rx_authenticate(token: token)
}.flatMap {
return self.loginViewModel.fetchProfileData()
}.debug().subscribe(onError: { error in
//Guard unknown ErrorType
guard let err = error as? AuthError else {
//Unknown error message
self.alertHelper.presentAlert(L10n.unknown)
return
}
//error message handling
switch err {
case .notLoggedIn:
print("not logged in")
break
default:
self.alertHelper.presentAlert(err.description)
}
}, onCompleted: {
self.goToInitialController()
}).addDisposableTo(self.disposeBag)
}
rx_authenticate
func rx_authenticate(token: String) -> Observable<Void> {
return Observable.create({ observer in
let credentials = SyncCredentials.facebook(token: token)
SyncUser.logIn(with: credentials, server: URL(string: Globals.serverURL)!, onCompletion: { user, error in
//Error while authenticating
guard error == nil else {
print("error while authenticating: \(error!)")
observer.onError(AuthError.unknown)
return
}
//Error while parsing user
guard let responseUser = user else {
print("error while authenticating: \(error!)")
observer.onError(AuthError.unknown)
return
}
//Authenticated
setDefaultRealmConfiguration(with: responseUser)
//next
observer.onNext()
//completed
observer.onCompleted()
})
return Disposables.create()
})
}
fetchProfileData
func fetchProfileData() -> Observable<Void> {
return Observable.create({ observer in
//Fetch facebookData
let params = ["fields" : "name, picture.width(480)"]
let graphRequest = GraphRequest(graphPath: "me", parameters: params)
graphRequest.start {
(urlResponse, requestResult) in
switch requestResult {
case .failed(_):
//Network error
observer.onError(AuthError.noConnection)
break
case .success(let graphResponse):
if let responseDictionary = graphResponse.dictionaryValue {
guard let identity = SyncUser.current?.identity else {
//User not logged in
observer.onError(AuthError.noUserIdentity)
return
}
//Name
let name = responseDictionary["name"] as! String
//Image dictionary
let pictureDic = responseDictionary["picture"] as! [String: Any]
let dataDic = pictureDic["data"] as! [String: Any]
let imageHeight = dataDic["height"] as! Int
let imageWidth = dataDic["width"] as! Int
let url = dataDic["url"] as! String
//Create Person object
let loggedUser = Person()
loggedUser.id = identity
loggedUser.name = name
//Create photo object
let photo = Photo()
photo.height = imageHeight
photo.width = imageWidth
photo.url = url
//Append photo object to person object
loggedUser.profileImage = photo
//Save userData
let realm = try! Realm()
try! realm.write {
realm.add(loggedUser, update: true)
}
//next
observer.onNext()
//completed
observer.onCompleted()
} else {
//Could not retrieve responseData
observer.onError(AuthError.noResponse)
}
}
}
return Disposables.create()
})
}
observerFacebook
//FacebookProvider
private lazy var observerFacebook: Observable<FacebookProvider>! = {
self.facebookButton.rx.tap.map {
return FacebookProvider(parentController: self)
}
}()
The chain starts with calling observerFacebook
, which returns an observable that will emit values everytime facebookButton
is tapped.
This observable will only complete when facebookButton
gets released, most probably when the view controller holding it is removed from screen.
The rest of the chain will map
or flatMap
, but never force completion as another tap will trigger the whole chain again.
The easy way to solve this would be to add a call to take(1)
on facebookButton.rx.tap
, so that the function would be defined like so:
private lazy var observerFacebook: Observable<FacebookProvider>! = {
self.facebookButton.rx.tap
.take(1)
.map {
return FacebookProvider(parentController: self)
}
}()
Now, observerFacebook
will complete after the first tap and you should see a call to onCompleted
.
Note that you'll need to resubscribe to the chain on errors if you want to perform it again when another tap comes in.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With