RxSwift convert Observable<String> into String - swift

I've having a problem in RxSwift in converting a Observable into a String.
I understand that an observable is a sequence, I just want the last change in the username.
I'm storing username and password as:
let username = BehaviorRelay<String>(value: "")
let password = BehaviorRelay<String>(value: "")
And previously have used combineLatest
func loginButtonValid(username: Observable<String>, password: Observable<String>) -> Observable<Bool> {
return Observable.combineLatest(username, password)
{ (username, password) in
return username.count > 0
&& password.count > 0
&& self.validateEmail(enteredEmail: username)
}
}
But how can I just take the latest from the username?
I've tried takelast and use combineLatest with just one argument, but neither seems to work.
I want to do this to validate whether an email is valid, and the validate email function I'm using is the following (for reference only):
func validateEmail(enteredEmail:String) -> Bool {
let emailFormat = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let emailPredicate = NSPredicate(format:"SELF MATCHES %#", emailFormat)
return emailPredicate.evaluate(with: enteredEmail)
}
My attempts are around the following ideas
func emailValid(username: Observable<String>) -> Observable<Bool> {
return username.takeLast()
{ (lastusername) in
return self.validateEmail(enteredEmail: (lastusername) )
}
}

If I understood correctly, you want to transform an Observable<String> to an Observable<Bool>, which is the job of the map method:
func emailValid(username: Observable<String>) -> Observable<Bool> {
return username.map(self.validateEmail)
}

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)
}

How to return Observable<String> based on the conditions of Observable<Bool> RxSwift

I'm quite new to RxSwift.
Is there any way I can create a function that will return Observable based on the conditions of two functions below
func isValidPassword(_ str: PublishSubject<String>) -> Observable<Bool> {
return str.asObservable().map {
validator.isValidPasswordRegex($0) && $0.count >= 8
}
}
func isNotEmpty(_ str: PublishSubject<String>) -> Observable<Bool> {
return str.asObservable().map {
$0.count != 0
}
}
This code below is just an example of what I'm trying to achieve, hoped you got the idea.
func someFunc(_ str:PublishSubject<String>) -> Observable<String> {
if !isValidPassword(str){
return "Not a valid password" //should return as an observable string
}
if isNotEmpty(str){
return "String is not empty" //should return as an observable string
}
}
I'm not sure about what you want to achieve here but I think you should use filter operator and not map. In this way you don't change the type of the starting observable but you remove elements that don't respect your conditions.
func isValidPassword(_ str: String) -> Bool {
validator.isValidPasswordRegex(str) && str.count >= 8
}
func someFunc(_ str: PublishSubject<String>) -> Observable<String> {
str
.filter(isValidPassword)
.filter { !$0.isEmpty }
}
Let me introduce you to zip... It allows you to combine multiple Observables.
func someFunc(_ str: Observable<String>) -> Observable<String> {
Observable.zip(isValidPassword(str), isNotEmpty(str))
.map { isValidPassword, isNotEmpty -> String in
if !isValidPassword {
return "Not a valid password"
}
if isNotEmpty {
return "String is not empty"
}
return "" // you didn't specify what it should return here...
}
}
Note that I updated the type signatures of your existing functions:
func isValidPassword(_ str: Observable<String>) -> Observable<Bool>
func isNotEmpty(_ str: Observable<String>) -> Observable<Bool>
Passing Subjects around like that is a recipe for disaster.
Subjects provide a convenient way to poke around Rx, however they are not recommended for day to day use.
-- Introduction to Rx
UPDATE
I think the code would be better if you implemented like this though:
func errorMessage(text: Observable<String>) -> Observable<String> {
text.map(errorMessage)
}
func errorMessage(_ str: String) -> String {
if !isValidPassword(str) {
return "Not a valid password"
}
if isNotEmpty(str) {
return "String is not empty"
}
return ""
}
func isValidPassword(_ str: String) -> Bool
func isNotEmpty(_ str: String) -> Bool
It's much easier to test this way.

Use of unresolved identifier while using extension in swift 3

I have made a String extension for validating the form in Swift 3 language.
The code is below:
import UIKit
extension String {
// Validating Email ID
func isValidEmail(testStr:String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
let emailTest = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
return emailTest.evaluate(with: testStr)
}
// Validating the User name
func isValidUserName(testStr:String) -> Bool {
let RegEx = "\\A\\w{7,18}\\z"
let Test = NSPredicate(format:"SELF MATCHES %#", RegEx)
return Test.evaluate(with: testStr)
}
// Validating the phone number
var isPhoneNumber: Bool {
do {
let detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.phoneNumber.rawValue)
let matches = detector.matches(in: self, options: [], range: NSMakeRange(0, self.characters.count))
if let res = matches.first {
return res.resultType == .phoneNumber && res.range.location == 0 && res.range.length == self.characters.count
} else {
return false
}
} catch {
return false
}
}
// validating the password
/*
Use the function of Swift 3.0.
1. 8 characters length
2. alphabet
3. special character
regex Syntax Explanation :
(?=.[a-z]) for Character.
(?=.[$#$#!%?&]) for special character.
{8,} for length which you want to prefer.
*/
func isPasswordValid(_ password : String) -> Bool{
let passwordTest = NSPredicate(format: "SELF MATCHES %#", "^(?=.*[a-z])(?=.*[$#$#!%*?&])[A-Za-z\\d$#$#!%*?&]{8,}")
return passwordTest.evaluate(with: password)
}
// validating the password and confirm password are same........................
func isPasswordSame(password: String , confirmPassword : String) -> Bool {
if password == confirmPassword{
return true
} else {
return false
}
}
// validating Blank Text........................
var isBlank:Bool {
return self.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).isEmpty
}
}
But when i am trying to use this extension class in other view controller through the code :
if isValidEmail("kirit#gmail.com"){
print("Validate EmailID")
}
else{
print("invalide EmailID")
}
I am getting the error:
isValidEmail is not a loose function. You have defined it as an instance function on String. You would need to say
"someString".isValidEmail(testStr:"someOtherString")
That makes no sense, but that's how you've configured it. If you write it that way, your code will compile (though it will be very silly code).
Just change your definition to something like
extension String {
// Validating Email ID
func isValidEmail() -> Bool {
self.validate...
}
and then use it in your code as
#someString".isValidEmail()

Retrieve String value from function with closure in Swift

I am trying to retrieve a string value from Firebase in order to get each username with an unique UID that is passed to the function, which returns the username of the user. However - since the firebase ObserveEvent is in closures, I can't return any value back because the actions happens asynchronous(?). I was wondering if it was a way of accomplishing this?
The function looks like this:
func GetUsername(uid:String) -> String {
var username = String()
firebase.child("Users").child(uid).observeSingleEventOfType(.Value) { (snapshot:FIRDataSnapshot) in
username = snapshot.value!["Username"] as! String
}
return username
}
Obviously this doesn't work, but I want to be able to get the data by doing a GetUsername("whatevertheidmightbe"). Ideas?
You need to create completion handler like this
func GetUsername(uid:String , completion: (String) -> ()) {
firebase.child("Users").child(uid).observeSingleEventOfType(.Value) { (snapshot:FIRDataSnapshot) in
if let username = snapshot.value!["Username"] as? String
completion(username)
}
else {
completion("")
}
}
And call function like this way
self.GetUsername(str) { (name) -> () in
if name.characters.count > 0 {
print(name)
}
else {
print("Not found")
}
}

Trouble getting Swift 2 and Parse asynchronous query to return a boolean

Hi I'm working on customizing the Parse PFLogInViewController so that the logInButton will actually function as a sign up and login button by first checking to see if the username already exists. If it doesn't, then the user will be created. If it does, then the user will be logged in. I'm trying to run this query and check inside the shouldBeginLogInWithUsername function, but I think I'm having trouble because of the asynchronous query. I've been trying to figure it out for hours with no luck. Here's my code:
func logInViewController(logInController: PFLogInViewController, shouldBeginLogInWithUsername username: String, password: String) -> Bool {
var bool = false
var query = PFUser.query()
query!.whereKey("username", equalTo: username)
query?.findObjectsInBackgroundWithBlock({ (users, error) -> Void in
if let users = users {
if users.count == 0 {
var user = PFUser()
user.username = username
user.password = password
user.signUpInBackground()
} else {
return bool = true
}
} else {
return bool = true
}
})
return bool
}
As you can see, I need my Boolean variable bool to change from false to true if the username exists and the user needs to be logged in. I just can't figure out how to get around the asynch request.
Any help would be much appreciated!
Since the call is async you can't expect to be able to return true or false directly from your function. You need to pass a closure to the function, which will be called when the async operation completes. Sort of like this (this is totally untested, but hopefully you get the idea):
func logInViewController(logInController: PFLogInViewController, shouldBeginLogInWithUsername username: String, password: String, callback: (usernameExists : Bool) -> Void) -> Void {
var query = PFUser.query()
query!.whereKey("username", equalTo: username)
query?.findObjectsInBackgroundWithBlock({ (users, error) -> Void in
if let users = users {
if users.count == 0 {
var user = PFUser()
user.username = username
user.password = password
user.signUp()
callback(usernameExists: false)
} else {
callback(usernameExists: true)
}
} else {
callback(usernameExists: false)
}
})
}
Use it like this, sort of:
logInViewController(viewController, shouldBeginLogInWithUsername: "", password: "", callback: { (usernameExists) -> Void in
//Do your login logic here
if (usernameExists) {
} else {
}
})