Retrieve String value from function with closure in Swift - 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")
}
}

Related

SwiftUI: wait for Alamofire request to finish BEFORE executing the code below [duplicate]

This question already has answers here:
How to return value from Alamofire
(5 answers)
Closed 3 years ago.
I'm using an Alamofire request in a function to retrieve an user from a database. Thing is, the function the request is used in is dependant on the request since I'm returning the result as an object of a User class.
Currently, it's always returning the default nil value declared at the top of my function since the code below the request doesn't bother to wait for the request to finish. Basic print statements in the .success or .failure parts prove that (because it doesn't print anything).
I just don't know how to solve it. I've been looking online for a while, and:
either I encounter suggestions on forums on what classes to use without even bothering to explain how to use it
or I get a video that explains an entirely different thing, like handling multiple requests
Can I get any help from you guys? I'm just going to post the code here down below:
The User class:
public class User
{
private var _name : String
public var Naam : String
{
get {return self._name}
set
{
/*
Just a function from a static class which trims a string. It's not really relevant to my problem …
*/
let name : String? = StringOps.trimString(string: newValue)
if name == nil
{
fatalError()
}
self._name = newValue
}
}
/*
* And some fields and properties for:
* - firstname
* - username
* - password
* - email
* They are basically equivalent to the one above
*/
// And a constructor
init(id : Int)
{
self._id = id
self._name = ""
self._firstname = ""
self._username = ""
self._email = ""
self._password = ""
}
}
The request inside my login func in my LoginViewController:
public func login(username: String, password: String)
-> User?
var user : User? = nil
/* Some code that is trimming and checking the input.
The username parameter gets put into a let parameter userName and the same goes for the password parameter. It's Ommitted here.
*/
let parameters : Parameters = [
"userName" : userName!,
"passWord" : passWord!
]
// _loginUrl is a private let String parameter
Alamofire.request(_loginUrl, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: nil)
.responseJSON
{
(response : DataResponse<Any>) in
switch (response.result)
{
case .success(_):
let data: NSDictionary = (response.value as! NSArray)[0] as! NSDictionary
/* The part commented out isn't working anyways,
and before I started changing the return type of my
function (was a Bool at first),
it worked well enough without it */
/*
if response.result.value != nil
{
/* Convert the JSON to a User object */
let data = (response.value as! NSArray)[0] as! NSDictionary
user = User(id: data["id"] as! Int)
user!.Email = data["e_mail"] as! String
user!.Username = data["username"] as! String
user!.Name = data["name"] as! String
user!.Firstname = data["firstname"] as! String
user!.Password = data["password"] as! String
}
*/
user = User(id: data["id"] as! Int)
user!.Email = data["e_mail"] as! String
user!.Username = data["username"] as! String
user!.Name = data["name"] as! String
user!.Firstname = data["firstname"] as! String
user!.Password = data["password"] as! String
break
case .failure(_):
print(response.result.error as Any)
// _error is also a private var member of LoginViewController
self._error = "Something's wrong with your login credentials!"
user = nil
break
}
}
return user
}
This function gets used in a private function in my LoginView:
struct LoginView
: View
{
// Some unimportant bits
private func logIn()
{
// username and password are #State private vars which are bound to their corresponding Text()
let user : User? = self.controller.login(username: self.username, wachtwoord: self.password)
// other code
}
}
You need to create a completionHandler
By the time your code gets the reference to the User object, is still nil since the response from the server did not come yet.
public func login(username: String, password: String) -> User?
change that to:
public func login(username: String, password: String, completion: #escaping (User?) -> Void)
Then you can do this:
private func logIn() {
self.controller.login(username: self.username, wachtwoord: self.password) { user in
guard let user = user else {
print("Error while retrieving user")
return
}
/// Access your user here
}
}
}

RxSwift convert Observable<String> into String

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

How can I return an object that I create in my data service class through Firebase?

I am trying to adhere to MVC practices and keep all the network code inside the data service class that I am using in my app. On one screen I have the user's name and username that needs to be displayed. When updating this, I am calling this function:
func grabUserData() -> User {
REF_USERS.child(getCurrentUID()).observeSingleEvent(of: .value) {
(snapshot) in
if let userDict = snapshot.value as? Dictionary<String, String> {
let user = User(
first: userDict["firstName"]!,
last: userDict["lastName"]!,
username: userDict["username"]!
)
return user
}
}
}
But I am getting an error trying to return the user! It says:
Unexpected non-void return value in void function.
But the function clearly isn't void. So what do I do?
You are confusing your grabUserData function return value with the Firebase closure return value — the former is User but the latter is Void ;)
You are actually returning from this closure
— I'm now using a explicit return type to be clear:
{
(snapshot) -> Void in
if let userDict = snapshot.value as? Dictionary<String,String> {
let user = User(
first: userDict["firstName"]!,
last: userDict["lastName"]!,
username: userDict["username"]!
)
return user
}
}
which is passed as the last argument to the observeSingleEvent Firebase function. This is a very common mistake ;)
Completion handler. A standard pattern here is to return the desired User via a completion handler instead. This solution nicely models the asynchronous nature of network requests such as Firebase database calls. For instance:
func grabUserData(completion: #escaping (User?) -> Void) {
REF_USERS.child(getCurrentUID()).observeSingleEvent(of: .value) {
(snapshot) in
if let userDict = snapshot.value as? Dictionary<String, String> {
let user = User(
first: userDict["firstName"]!,
last: userDict["lastName"]!,
username: userDict["username"]!
)
completion(user) // Returns user!
} else {
completion(nil) // User not found!
}
}
}
Finally, in your data service client code, call it like this:
grabUserData() {
(user) in
if let user = user {
print("Grabbed user: \(user)")
} else {
print("User 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 {
}
})

Can I return typed objects from a Dictionary<String,AnyObject>?

Here's what I have right now:
enum TestKeys: String {
case login = "login"
case username = "username"
}
var testData: Dictionary<String,AnyObject> = // a dictionary of known key/values
// I want the return value from this function to be typed
func getTestObject(key: TestKeys) -> AnyObject! {
if let obj: AnyObject = testData[key.rawValue] {
return obj
} else {
assertionFailure("We cannot find \(key.rawValue) in our testData")
return nil
}
}
I know in advance the keys and specific value types in my dictionary. What I'd like to be able to do is associate a type with each key, so that I can have typed return values when I access the content of the dictionary.