Call own function with a completion handler - swift

I have a method to create an account:
func createAccount (completion: #escaping (_ succes: Bool, _ message : String)->()) {
Auth.auth().createUser(withEmail: createMail(), password: createPassword()) { (result, error) in
if let _eror = error {
//something bad happning
print(_eror.localizedDescription )
if let errorCode = AuthErrorCode(rawValue: _eror._code) {
if(errorCode.rawValue == 17007) {
print("acount exist")
createAccount(completion: (Bool, String) -> ()
} else {
//call itself and try it again
}
}
} else {
//user registered successfully
print("user registered")
return completion(true, "");
}
}
}
I get an error when the software creates an account with an email that already exists, which is good (see the else statement - //call itself and try it again).
What needs to happen is that the function needs to call itself again to try it with a different email.
I tried to put createAccount(completion: (Bool, String) -> () in the else case, but that didn't work.
How can I call the createAccount() function again in the else case?

You need to pass the same paramter again
createAccount(completion:completion)

Related

Chaining two futures in Swift Vapor framework

I have this function that checks if an username already exists in the database during registration (REST API). If the username already exists, a nice error message is displayed. Now I want to add the same check for the email, with a nice error message and a check if both username and email already exists, again with it's own nice error message.
I don't have much experience with async coding and I don't understand how chain the two futures.
This is the main function:
fileprivate func create(req: Request) throws -> EventLoopFuture<NewSession> {
try UserSignup.validate(content: req)
let userSignup = try req.content.decode(UserSignup.self)
let user = try User.create(from: userSignup)
var token: Token!
return checkIfUserExists(userSignup.username, req: req).flatMap { exists in
guard !exists else {
return req.eventLoop.future(error: UserError.usernameTaken)
}
return user.save(on: req.db)
}.flatMap {
guard let newToken = try? user.createToken(source: .signup) else {
return req.eventLoop.future(error: Abort(.internalServerError))
}
token = newToken
return token.save(on: req.db)
}.flatMapThrowing {
NewSession(token: token.value, user: try user.asPublic())
}
}
This is the checkIfUserExists function:
private func checkIfUserExists(_ username: String, req: Request) -> EventLoopFuture<Bool> {
User.query(on: req.db)
.filter(\.$username == username)
.first()
.map { $0 != nil }
}
This is the checkIfEmailExists function:
private func checkIfEmailExists(_ email: String, req: Request) -> EventLoopFuture<Bool> {
User.query(on: req.db)
.filter(\.$email == email)
.first()
.map { $0 != nil }
}
I've tried if-else, tried .add() and other weird stuff but I can't get it to work. Also I need to keep this syntax and not using the async/await syntax.
Modify your query to include a group:
query.group(.or) { group in
group.filter(\User.$username == username).filter(\User.$email == email)
}

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

Rejecting a the returned promise inside a then block

Say I have two promises I want to combine with a when(resolved:). I want to reject the promise if there was a problem with the first promise, but resolve otherwise. Essentially, this is what I want to do:
func personAndPetPromise() -> Promise<(Person, Pet?)> {
let personPromise: Promise<Person> = ...
let petPromise: Promise<Pet> = ...
when(resolved: personPromise, petPromise).then { _ -> (Person, Pet?) in
if let error = personPromise.error {
return Promise(error: error) // syntax error
}
guard let person = personPromise.value else {
return Promise(error: myError) // syntax error
}
return (person, petPromise.value)
}
}
such that externally I can do something like this:
personAndPetPromise().then { person, pet in
doSomethingWith(person, pet)
}.catch { error in
showError(error)
}
The problem lies within the the then { _ in block in personAndPetPromise. There's no way that method can return both a Promise(error:) and a (Person, Pet?).
How else can I reject the block?
The problem is that there are two overloads of the then function:
public func then<U>(on q: DispatchQueue = .default, execute body: #escaping (T) throws -> U) -> Promise<U>
public func then<U>(on q: DispatchQueue = .default, execute body: #escaping (T) throws -> Promise<U>) -> Promise<U>
The first one's body returns a U and causes then to return Promise<U>.
The second one's body returns a Promise<U> and causes then to return Promise<U>.
Since in this case we want to return an error or a valid response, we're forced to use the second overload.
Here's a working version. The main difference is I changed it from -> (Person, Pet?) to -> Promise<(Person, Pet?)>:
func personAndPetPromise() -> Promise<(Person, Pet?)> {
let personPromise: Promise<Person> = ...
let petPromise: Promise<Pet> = ...
when(resolved: personPromise, petPromise).then { _ -> Promise<(Person, Pet?)> in
if let error = personPromise.error {
return Promise(error: error)
}
guard let person = personPromise.value else {
return Promise(error: myError)
}
return Promise(value: (person, petPromise.value))
}
}
Another way to do the same thing is by throwing the error rather than attempting to return it:
func personAndPetPromise() -> Promise<(Person, Pet?)> {
let personPromise: Promise<Person> = ...
let petPromise: Promise<Pet> = ...
when(resolved: personPromise, petPromise).then { _ -> (Person, Pet?) in
if let error = personPromise.error {
throw error
}
guard let person = personPromise.value else {
throw myError
}
return (person, petPromise.value)
}
}

Observable returned from function never sends onNext

I have and observable that never sends onNext if its returned by a function, but if i subscribe to it in the function that returns it, onNext is called.
class InfoViewModel {
func refreshPushToken() {
PushNotificationService.sharedInstance.pushToken!
.flatMapLatest { (pushToken: String) -> Observable<Result<User>> in
return UserService.registerPushToken(pushToken)
}
.subscribe { (event ) in
print(event)
}
.addDisposableTo(disposeBag)
}
}
struct UserService {
....
static func registerPushToken(_ pushToken: String) -> Observable<Result<User>> {
...
return self.postUser(user: user)
}
static fileprivate func postUser(user: User) -> Observable<Result<User>> {
let rxProvider: RxMoyaProvider<Backend> = RxMoyaProvider<Backend>(endpointClosure: Backend.endpointClosure)
return rxProvider.request(Backend.register(user: user))
.mapObject(type: User.self)
.map({ (user: User) -> Result<User> in
LogService.log(level: .debug, action: "postUser", message: "Posted user with success", parameters: ["user": user.deviceId])
return .success(user)
})
.catchError({ error -> Observable<Result<User>> in
LogService.log(level: .error, action: "postUser", message: "Error posting user", parameters: ["user": user.deviceId, "error": error.localizedDescription])
return Observable.just(.failure(error))
})
}
}
But if I do this
rxProvider.request(Backend.register(user: user))
...
.subscribe { (event ) in
print(event)
}
in the UserService, i will get a next event.
I have tried to use debug() on the observable in InfoViewModel, there is a subscription, i just never receive any events.
So i figured it out, I was creating the RxMoyaProvider inside the method, so as soon as i went out of the scope of the method, it was deallocated. Which means that when was subscribing to it, it could no longer create the request. The reason that this wouldn't fail is because of how the observable is created
open func request(_ token: Target) -> Observable<Response> {
// Creates an observable that starts a request each time it's subscribed to.
return Observable.create { [weak self] observer in
let cancellableToken = self?.request(token) { result in
switch result {
case let .success(response):
observer.onNext(response)
observer.onCompleted()
case let .failure(error):
observer.onError(error)
}
}
return Disposables.create {
cancellableToken?.cancel()
}
}
}
As you can see, the request is called upon subscription, but since self had been deallocated the request was never fired. And all i got back was an empty observable.

Best practice for Swift methods that can return or error [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
I’m practicing Swift and have a scenario (and a method) where the result could either be successful or a failure.
It’s a security service class. I have a method where I can authenticate with an email address and password, and want to either return a User instance if the credentials are correct, or throw some form of false value.
I’m a bit confused as my understanding of Swift methods is you need to specify a return type, so I have:
class SecurityService {
static func loginWith(email: String, password: String) -> User {
// Body
}
}
I’ve seen in Go and Node.js methods that return a “double” value where the first represents any errors, and the second is the “success” response. I also know that Swift doesn’t have things like errors or exceptions (but that may have changed since as I was learning an early version of Swift).
What would be the appropriate thing to do in this scenario?
If you want to handle errors that can happen during login process than use the power of Swift error handling:
struct User {
}
enum SecurityError: Error {
case emptyEmail
case emptyPassword
}
class SecurityService {
static func loginWith(email: String, password: String) throws -> User {
if email.isEmpty {
throw SecurityError.emptyEmail
}
if password.isEmpty {
throw SecurityError.emptyPassword
}
return User()
}
}
do {
let user = try SecurityService.loginWith1(email: "", password: "")
} catch SecurityError.emptyEmail {
// email is empty
} catch SecurityError.emptyPassword {
// password is empty
} catch {
print("\(error)")
}
Or convert to optional:
guard let user = try? SecurityService.loginWith(email: "", password: "") else {
// error during login, handle and return
return
}
// successful login, do something with `user`
If you just want to get User or nil:
class SecurityService {
static func loginWith(email: String, password: String) -> User? {
if !email.isEmpty && !password.isEmpty {
return User()
} else {
return nil
}
}
}
if let user = SecurityService.loginWith(email: "", password: "") {
// do something with user
} else {
// error
}
// or
guard let user = SecurityService.loginWith(email: "", password: "") else {
// error
return
}
// do something with user
Besides the standard way to throw errors you can use also an enum with associated types as return type
struct User {}
enum LoginResult {
case success(User)
case failure(String)
}
class SecurityService {
static func loginWith(email: String, password: String) -> LoginResult {
if email.isEmpty { return .failure("Email is empty") }
if password.isEmpty { return .failure("Password is empty") }
return .success(User())
}
}
And call it:
let result = SecurityService.loginWith("Foo", password: "Bar")
switch result {
case .Success(let user) :
print(user)
// do something with the user
case .Failure(let errormessage) :
print(errormessage)
// handle the error
}
Returning a result enum with associated values, throwing exception, and using a callback with optional error and optional user, although valid make an assumption of login failure being an error. However thats not necessarily always the case.
Returning an enum with cases for success and failure containing result and error respectively is almost identical as returning an optional User?. More like writing a custom optional enum, both end up cluttering the caller. In addition, it only works if the login process is synchronous.
Returning result through a callBack, looks better as it allows for the operation to be async. But there is still error handling right in front of callers face.
Throwing is generally preferred than returning an error as long as the scope of the caller is the right place to handle the error, or at least the caller has access to an object/method that can handle this error.
Here is an alternative:
func login(with login: Login, failure: ((LoginError) -> ())?, success: (User) -> ()?) {
if successful {
success?(user)
} else {
failure?(customError)
}
}
// Rename with exactly how this handles the error if you'd have more handlers,
// Document the existence of this handler, so caller can pass it along if they wish to.
func handleLoginError(_ error: LoginError) {
// Error handling
}
Now caller can; simply decide to ignore the error or pass a handler function/closure.
login(with: Login("email", "password"), failure: nil) { user in
// Ignores the error
}
login(with: Login("email", "password"), failure: handleLoginError) { user in
// Lets the error be handled by the "default" handler.
}
PS, Its a good idea to create a data structure for related fields; Login email and password, rather individually setting the properties.
struct Login {
typealias Email = String
typealias Password = String
let email: Email
let password: Password
}
To add an answer to this question (five years later), there’s a dedicated Result type for this exact scenario. It can return the type you want on success, or type an error on failure.
It does mean re-factoring some code to instead accept a completion handler, and then enumerating over the result in that callback:
class SecurityService {
static func loginWith(email: String, password: String, completionHandler: #escaping (Result<User, SecurityError>) -> Void) {
// Body
}
}
Then in a handler:
securityService.loginWith(email: email, password: password) { result in
switch result {
case .success(let user):
// Do something with user
print("Authenticated as \(user.name)")
case .failure(let error):
// Do something with error
print(error.localizedDescription)
}
}
I think that the result of calling loginWith could be derived from a network request, here is code that I could do in the scenario you presented:
Helper classes:
struct User {
var name: String
var email: String
}
class HTTP {
static func request(URL: String, method: String, params: [String: AnyObject], callback: (error: NSError?, result: [String:AnyObject]?) -> Void) -> Void {
// network request
}
}
class SecurityService {
static func loginWith(email: String, password: String, callback: (error: NSError?, user: User?) -> Void) -> Void {
let URL = ".."
let params = [
"email": email,
"password": password
]
HTTP.request(URL, method: "POST", params: params) { (error, result) in
if let error = error {
callback(error: error, user: nil)
} else {
guard let JSON = result else {
let someDomain = "some_domain"
let someCode = 100
let someInfo = [NSLocalizedDescriptionKey: "No results were sent by the server."]
let error = NSError(domain: someDomain, code: someCode, userInfo: someInfo)
callback(error: error, user: nil)
return
}
guard let name = JSON["name"] as? String, email = JSON["email"] as? String else {
let someDomain = "some_domain"
let someCode = 100
let someInfo = [NSLocalizedDescriptionKey: "No user properties were sent by the server."]
let error = NSError(domain: someDomain, code: someCode, userInfo: someInfo)
callback(error: error, user: nil)
return
}
let user = User(name: name, email: email)
callback(error: nil, user: user)
}
}
}
}
Using the SecurityService class:
SecurityService.loginWith("someone#email.com", password: "123456") { (error, user) in
if let error = error {
print(error)
} else {
guard let user = user else {
print("no user found.")
return
}
print(user)
}
}