Unable to get uid from Firestore, unless user force closes the app and repopens - xcode

I am new to SwiftUI and I am currently practicing with Firebase Firestore. The problem that I am experiencing is when the user signs in, the code is unable to fetch the data (their first, last name, email and points) from the Firestore database, unless the user force closes the app and reopens it. Below is the code I have.
UserDataFetch.swift
import Foundation
import FirebaseAuth
import FirebaseFirestore
struct User {
let uid, firstName, lastName, email: String
let points: Int
}
class UserDataFetch: ObservableObject {
#Published var uname: User?
init() {
fetchCurrentUser()
}
private func fetchCurrentUser() {
guard let usID = Auth.auth().currentUser?.uid else {
return
}
Firestore.firestore().collection("users").document(usID).addSnapshotListener { snapshot, error in
if let error = error {
print("Failed to fetch", error)
return
}
guard let data = snapshot?.data() else {
return
}
let uid = data["uid"] as? String ?? ""
let firstName = data["firstName"] as? String ?? ""
let lastName = data["lastName"] as? String ?? ""
let points = data["points"] as? Int ?? 0
let email = data["email"] as? String ?? ""
self.uname = User(uid: uid, firstName: firstName, lastName: lastName, email: email, points: points)
}
}
}
Below is also the code from my login function. I am using #AppStorage("uid") var userID = "" to store and check if the user is logged in.
func loginUser() {
Auth.auth().signIn(withEmail: email, password: password) { authResult, error in
if let error = error {
print(error)
return
}
if let authResult = authResult {
print(authResult.user.uid)
userID = authResult.user.uid
}
}
}
As I am still new I tried looking into the documentation of Firebase, I watched videos on YouTube, but I am still unable to come to a resolution. Any help would be greatly appreciated. Thank you!

Related

Ktor session not being found

I've been banging my head against the wall trying to figure out what's going wrong here for a while. I created a simple Ktor server that allows you to create a user, which should return a token to the user and store the session. Then I want an authenticated endpoint to allow the user to be deleted. However, the authenticated call loads an empty session, and can't find the user, so the user can't be deleted. Any help would be appreciated! Code here:
Application.kt
...
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
#Suppress("unused")
#kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
install(Locations) {
}
install(Sessions) {
cookie<MySession>("MY_SESSION") {
cookie.extensions["SameSite"] = "lax"
}
}
DatabaseFactory.init()
val db = MyRepository()
val jwtService = JwtService()
val hashFunction = { s: String -> hash(s) }
install(Authentication) {
jwt("jwt") { //1
verifier(jwtService.verifier) // 2
realm = "My Server"
validate { // 3
val payload = it.payload
val claim = payload.getClaim("id")
val claimString = claim.asInt()
val user = db.findUser(claimString) // 4
user
}
}
}
install(ContentNegotiation) {
gson {
}
}
routing {
users(db, jwtService, hashFunction)
}
}
UserRoute.kt
...
const val USERS = "$API_VERSION/users"
const val USER_CREATE = "$USERS/create"
const val USER_DELETE = "$USERS/delete"
#KtorExperimentalLocationsAPI
#Location(USER_CREATE)
class UserCreateRoute
#KtorExperimentalLocationsAPI
#Location(USER_DELETE)
class UserDeleteRoute
#KtorExperimentalLocationsAPI
fun Route.users(
db: Repository,
jwtService: JwtService,
hashFunction: (String) -> String
) {
post<UserCreateRoute> {
val request = call.receive<CreateUserRequest>()
val password = request.password
?: return#post call.respond(
HttpStatusCode.Unauthorized, "Missing Fields")
val email = request.email
?: return#post call.respond(
HttpStatusCode.Unauthorized, "Missing Fields")
val hash = hashFunction(password)
try {
val newUser = db.addUser(email, hash)
newUser?.userId?.let {
call.sessions.set(MySession(it))
call.respondText(
jwtService.generateToken(newUser),
status = HttpStatusCode.Created
)
}
} catch (e: Throwable) {
call.respond(HttpStatusCode.BadRequest, "Problems creating User")
}
}
authenticate("jwt") {
delete<UserDeleteRoute> {
try {
val userId = call.sessions.get<MySession>()?.userId
if (userId == null) {
call.respond(
HttpStatusCode.BadRequest, "Problem retrieving User")
return#delete
}
if (db.deleteUser(userId)) {
call.respond(HttpStatusCode.NoContent, "User deleted")
} else {
call.respond(HttpStatusCode.BadRequest, "Failed to delete user")
}
} catch (e: Exception) {
application.log.error("Failed to delete user")
call.respond(HttpStatusCode.BadRequest, "Failed to delete user")
}
}
}
}
Is there something I'm missing? The token is returned successfully, and then my delete request is routed to the right place, but the line val userId = call.sessions.get<MySession>()?.userId returns null every time.
You don't show the client code but it is just as important. Likely the problem is on the client not on server. When the clients does the delete does it send the token?
jwt would be more complicated for for basic auth after you get a session each request must include the session header:
curl -H "MY_SESSION: f152dad6e955ba53" -D - localhost:8080/api/admin/principle

How query works in Vapor?

How to return user if based on facebook user ID it already exist, and create a new user if not exist in Vapor? You can see how I tried fetch data, but get error.
final class User: Content {
var id: Int?
var fbId: String
init(id: Int? = nil, fbId: String) {
self.id = id
self.fbId = fbId
}
}
router.get("user") { (request) -> Future<User> in
return Future.map(on: request) { () -> User in
let fbId = try request.query.get(String.self, at: "fbId")
return User.query(on: request).filter(\.fbId == fbId).first().map { (user) -> (U) in
if user == nil {
user = User(fbId: fbId)
}
return user
}
}
}
You have a few things going on here. To start with you don't need the first Future.map - not sure what that's doing.
Then you have the issue of the compiler - you have to return the same type in each closure and the function, which is awkward because if you already have a user you can return that, if you don't you need to create and save one, which returns Future<User>, which is not the same to User.
So to answer your question, U there should be User, but really you want to change first().map to first().flatMap in which case U becomes Future<User>. Then you can do something like:
router.get("user") { req -> Future<User> in
let fbID = try req.query.get(String.self, at: "fbId")
return User.query(on: req).filter(\.fbId == fbID).first().flatMap { user in
let returnedUser: Future<User>
if let foundUser = user {
returnedUser = req.future(foundUser)
} else {
let newUser = User(fbId: fbID)
returnedUser = newUser.save(on: req)
}
return returnedUser
}
}
To solve your problems. Hope that helps!
.map { (user) -> (U) in
This defines that you get a user into the closure and have to return a U. In your example you want to return a User (so change U to User).
If you want to create the user (in case it is nil) you probably also want to store it in the database? If that's the case, you'll have to change map to flatMap and update like this:
.flatMap { (user) -> EventLoopFuture<User> in
if user == nil {
return User(fbId: fbId).save(on: req)
}
return req.future(user)
}

Is it possible to catch error "fatal error: unexpectedly found nil while unwrapping an Optional value" using do/catch block with swift 2?

Sample code:
if let result = Locksmith.loadDataForUserAccount("UserAccount") {
do {
account = Account(
id: result["id"] as! String,
uid: result["uid"] as! String,
username: result["username"] as! String,
name: result["name"] as! String,
image: result["image"] as! String,
token: result["token"] as! String,
client: result["client"] as! String
)
} catch _ {
// Xcode show this warning:
// 'catch' block is unreachable because no errors are thrown in 'do' block
}
}
Any of the result's keys can be nil.
Is there a way to just catch the error in case any of them is nil?
You can't do that because the force unwrap of nil value does generate a fatal_error (which is not like throwing an ErrorType).
Failable Initializer
But you can solve your problem simply adding a failable initializer to Account (it's easier if it's a struct).
struct Account {
let id: String
let uid: String
let username: String
let name: String
let image: String
let token: String
let client: String
init?(dict:[String:Any]) {
guard let
id = dict["id"] as? String,
uid = dict["uid"] as? String,
username = dict["username"] as? String,
name = dict["name"] as? String,
image = dict["image"] as? String,
token = dict["token"] as? String,
client = dict["client"] as? String else { return nil }
self.id = id
self.uid = uid
self.username = username
self.name = name
self.image = image
self.token = token
self.client = client
}
}
Now
if let
result = Locksmith.loadDataForUserAccount("UserAccount"),
account = Account(dict:result) {
// use account here
}
Throwing Initializer
If knowing that some field is missing is not enough for you, and you need to know which is the first missing field that stopped the initialisation of Account you can define a throwing initializer.
First of all you need a way to represent the error
enum AppError: ErrorType {
case MissingField(String)
}
Next
struct Account {
let id: String
let uid: String
let username: String
let name: String
let image: String
let token: String
let client: String
init(dict:[String:Any]) throws {
guard let id = dict["id"] as? String else { throw AppError.MissingField("id") }
guard let uid = dict["uid"] as? String else { throw AppError.MissingField("uid") }
guard let username = dict["username"] as? String else { throw AppError.MissingField("username") }
guard let name = dict["name"] as? String else { throw AppError.MissingField("name") }
guard let image = dict["image"] as? String else { throw AppError.MissingField("image") }
guard let token = dict["token"] as? String else { throw AppError.MissingField("token") }
guard let client = dict["client"] as? String else { throw AppError.MissingField("client") }
self.id = id
self.uid = uid
self.username = username
self.name = name
self.image = image
self.token = token
self.client = client
}
}
Let's see how it does work
do {
let dict:[String:Any] = ["id": "1", "uid": "1234"]
let account = try Account(dict: dict)
} catch let appError {
print(appError)
}
Output
MissingField("username")

Swift 2.1 Searching Contacts by E-mail in iOS 9

In the new Contacts framework there appears to be a way to search by name:
let predicate = CNContact.predicateForContactsMatchingName("john")
let toFetch = [CNContactGivenNameKey, CNContactFamilyNameKey]
do {
let contacts = try store.unifiedContactsMatchingPredicate(
predicate, keysToFetch: toFetch)
for contact in contacts{
print(contact.givenName)
print(contact.familyName)
print(contact.identifier)
}
}
catch let err {
print(err)
}
But no apparent way to search by e-mail that I can find in the documentation or searches.
How do I search contacts by e-mail address?
These articles here and here have been helpful in learning the new framework but neither of these have revealed how to search by e-mail.
Having the same problem, I managed to find a solution. I have solved this by fetching all the contacts and then iterating over them to find matching contacts.
The code can surely be refactored.
import UIKit
import Contacts
public class ContactFinder: NSObject {
private lazy var store = CNContactStore()
private var allContacts: [CNContact] = []
private let keysToFetch: [CNKeyDescriptor] = [CNContactEmailAddressesKey, CNContactPhoneNumbersKey, CNContactFamilyNameKey, CNContactGivenNameKey, CNContactPostalAddressesKey, CNContactBirthdayKey, CNContactImageDataKey, CNContactImageDataAvailableKey]
public func requestAccess(completion:((success: Bool, error: NSError?)->())) {
let status = CNContactStore.authorizationStatusForEntityType(.Contacts)
if status == .NotDetermined {
store.requestAccessForEntityType(.Contacts, completionHandler: { [weak self](success, error) -> Void in
if success {
self?.getAllContacts(completion)
} else {
completion(success: false, error: error)
}
})
} else if status == .Authorized {
getAllContacts(completion)
} else {
completion(success: false, error: nil)
}
}
public func searchForContactUsingEmail(email: String, completion:((contacts: [CNContact])->())) {
var contacts: [CNContact] = []
for contact in allContacts {
let em = contact.emailAddresses
if em.count > 0 {
let results = em.filter({ val in
let ce = (val.value as! String).trimmedString.lowercaseString
return ce == email.trimmedString.lowercaseString
})
if results.count > 0 {
contacts.append(contact)
}
}
}
completion(contacts: contacts)
}
private func getAllContacts(completion:((success: Bool, error: NSError?)->())) {
let request = CNContactFetchRequest(keysToFetch: keysToFetch)
request.predicate = nil
request.unifyResults = true
do {
try store.enumerateContactsWithFetchRequest(request, usingBlock: { [weak self](contact, _) -> Void in
self?.allContacts.append(contact)
})
} catch let e as NSError {
print(e.localizedDescription)
completion(success: false, error: e)
return
}
completion(success: true, error: nil)
}
}

swift: Update parse object without knowing his objectId

i have a userSignUp and login System where a user is stored with his email address and password.
Later i want to add more information to this user, so i created a new data class on parse.com with the name "UserProfile" where i store the userObjectId from the user that has been signed up.
So now i want to update this user in the data class UserProfile but i just now the objectId from the user in the UserClass.
So i want to update like "update user where userObjectId = PFUser.currentUser().objectId"
i have this snippet of code already:
var query = PFQuery(className:"UserProfiles")
var userId = PFUser.currentUser()?.objectId?
query.whereKey("userObjectId", equalTo: userId)
//i don't think, that the following code is correct
query.getObjectInBackgroundWithId("xWMyZEGZ") {
(gameScore: PFObject?, error: NSError?) -> Void in
if error != nil {
println(error)
} else if let gameScore = gameScore {
gameScore["cheatMode"] = true
gameScore["score"] = 1338
gameScore.saveInBackground()
}
}
Assuming your "userObjectId" field is a string field and not a pointer field, then you can do this.
var query = PFQuery(className: "UserProfiles")
var userId = PFUser.currentUser()!.objectId
query.whereKey("userObjectId", equalTo: userId)
//here you would just find the results
query.findObjectsInBackgroundWithBlock {(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
//you have the user, assuming one but their could be more
//this will only return one user since you're using the objectID as a field and those are unique, so...
for user in objects! {
user["aColumnYouWishToUpdate"] = whatYouWishToUpdateTo
user["anotherColumnToUpdate"] = anotherUpdatedVariable
//do this for any columns you want updated
user.saveInBackground() //to save the newly updated user
}
} else {
//you have an error
}
}

Resources