Swift Firebase Authentication - two questions about error handling (I'm not sure how to name these errors) - swift

It's really hard to find a proper title for this question. Please be easy on me.
The first part is a check to see if an account exists:
Auth.auth().fetchSignInMethods(forEmail: userEmail, completion: {
(providers, error) in
if error != nil {
self.displayAlertMessage(alertTitle: "Unhandled error", alertMessage: "Undefined error #SignUpViewController_0001");
return;
} else if providers != nil {
self.displayAlertMessage(alertTitle: "Error", alertMessage: "This account is not exist.");
return;
}
})
As you can see, I have something named Unhandled error with message Undefined error. I don't know how to name it properly. Can somebody explain that part to me?
The second one is about getting a localized string - any ideas to make it fancy?
Auth.auth().createUser(withEmail: userEmail, password: userPassword) { user, error in if error == nil && user != nil {
self.displayAlertMessage(alertTitle: "Success", alertMessage: "Your account created successfully. We send you a verification email.", dismiss: true);
} else {
self.displayAlertMessage(alertTitle: "Firebase error", alertMessage: "(error!.localizedDescription)");
}
}
Thanks for tips :)

You can handle the Errors this way:
Auth.auth().fetchSignInMethods(forEmail: email, completion: { (response, error) in
if let error = error, let errCode = AuthErrorCode(rawValue: error._code)
{
switch errCode {
case .emailAlreadyInUse:
GeneralHelper.sharedInstance.displayAlertMessage(titleStr: LocalizeConstant.CommonTitles.Alert.rawValue.localizedStr(), messageStr: LocalizeConstant.CommonTitles.Continue.rawValue.localizedStr())
case .accountExistsWithDifferentCredential:
GeneralHelper.sharedInstance.displayAlertMessage(titleStr: LocalizeConstant.CommonTitles.Alert.rawValue.localizedStr(), messageStr: LocalizeConstant.CommonTitles.Continue.rawValue.localizedStr())
default:
break
}
return
}
}
Here I am getting the errCode using AuthErrorCode provided by Firebase itself and then, I am passing in the received error code using error._code. So, now I can get the type of AuthErrorCode. Using this I am making cases like .emailAlreadyInUser, .accountExistsWithDifferentCredential etc. You can just type . and it will show you all the AuthErrorCodes. So, you can simply handle the error codes in this way.
Now, coming to the second part of the question, i.e. getting localized string. You can add localization to Firebase, for that you have to select the language code. Auth.auth().languageCode = "en" //For English. But, I do not think that it gives localized errors as there are many more languages than what Firebase supports. This mainly for sending localized emails.
To handle the localization, you have to create your own method as I did. You can see that I have called a function displayAlertMessage in which I am passing thetitleStr: LocalizeConstant.CommonTitles.Alert.rawValue.localizedStr(), which is a part of localization.
struct LocalizeConstant {
enum CommonTitles: String
{
case Alert = "common_alert"
}
}
This value designates to the key given by me in the localization file. If you do not know about localization, you have to do a Google search on it. Let's say I have two Localizable.strings one is in English and the other one is in French. In Localizable.strings(English), I've written Alert like this:
"common_alert" = "Alert";
And, In French:
"common_alert" = "Alerte!";
So, this is how I have manually added localization in my app. But, to achieve this you have to do two things. 1) You have to set up your appLanguage. 2) You have to call a method which will fetch the values from these keys defined in the Localizable.strings file.
To do this, I have created a method localizedStr(). It is an extension to String and you can use it as follows.
extension String{
func localizedStr() -> String
{
var finalRes = ""
if let path = Bundle.main.path(forResource: Constants.appLang, ofType: "lproj") //Constants.appLang is "en" here for "English", but you can set accordingly.
{
if let bundle = Bundle(path: path)
{
finalRes = NSLocalizedString(self, tableName: nil, bundle: bundle, value: " ", comment: " ")
}
}
return finalRes
}
}
Now, this method localizedStr() will give you a localized string according to your app language. Even, if Firebase provides localized error codes(which I think it does not), it is impossible to get the error description in each language. So this is the best way I came up with. It may not be the best method out there in the world, but it does the task.
P.S.: To optimize this throughout the app, either you can create an extension to AuthErrorCode or you can create a Helper function where you will just pass the error._code and it will return the localized string. I've added the long way so that you can understand everything in the best way.

Related

How to make app automatically recognise current user Firestore document ID when signed in?

When a user is signed up through my form, a document gets created associated with that user. My main goal is to create a global function that can recognize the user that is signed in and get their document ID. I have a function setup for adding documents to a subcollection of the user document which is perfectly setup, the only downfall is that when I'm testing with multiple accounts, I have to manually switch the collection path. Here is what I mean.
#IBAction public func createEventButton(_ sender: UIButton) {
let error = validateFields()
if error != nil {
showError(error!)
} else {
db.collection("school_users/\(stThomas)/events").addDocument(data: ["event_name": nameTextField.text, "event_date": dateTextField.text, "event_cost": costTextField.text, "for_grades": gradesTextField.text]) { (error) in
if error != nil {
self.showError("There was an error trying to add user data to the system.")
} else {
self.dismiss(animated: true, completion: nil)
}
}
}
So as you can see here, I am using string interpolation with the "stThomas" constant I used to store a document ID. I basically want to create a function that will recognize the document ID of the user signed in so I can use my Constants instead of string interpolation and having to manually switch the user collection path each time, which would be eventually impossible during production.
Not to mention, I do have a function to grab the document ID, say for instance an event is clicked, but as a beginner in Swift, I can't seem to connect the dots. I will also show this function for clarification.
func getDocID() {
db.collection("school_users/\(notreDame)/events").getDocuments() { (querySnapshot, error) in
if let error = error {
print("There was an error getting the documents: \(error)")
} else {
self.documentsID = querySnapshot!.documents.map { document in
return DocID(docID: (document.documentID))
}
self.tableView.reloadData()
}
}
}
And in this function you can see my other constant "notreDame" with another stored document ID. If anybody knows a simple way to do this that would be great. And yes, I checked the Firebase documents, thank you for asking.
I've did some extra research and realized that I can use User IDs in collection paths. My problem is now solved. Many more problems to come though.

How To Fetch Error Code From Firebase?

Auth.auth().signIn(withEmail: emailTextField.text!, password: passwordTextField.text!)
{ (user, error) in
if error != nil {
print(error!)
self.warningLabel.isHidden = false;
self.passwordTextField.text = "";
} else {
print("Log in succesful")
self.performSegue(withIdentifier: "welcomeSeg", sender: self)
}
}
Whenever I sign in or sign up a user I just print a generic warning label instead of the actual issue. I print the error I receive and it's too verbose to show to the user.
Error Domain=FIRAuthErrorDomain Code=17009 "The password is invalid or the user does not have a password." UserInfo={NSLocalizedDescription=The password is invalid or the user does not have a password., error_name=ERROR_WRONG_PASSWORD}
Error Domain=FIRAuthErrorDomain Code=17008 "The email address is badly formatted." UserInfo={NSLocalizedDescription=The email address is badly formatted., error_name=ERROR_INVALID_EMAIL}
Is there any way to fetch the error code so I can be more specific with my error messages? I've looked through the documentation but have been unsuccessful in coming up with anything.
I would recommend creating an AuthErrorCode object (provided by the Firebase SDK) from the error you receive and using that as you see fit. If I remember correctly, AuthErrorCode is an enum with cases like .wrongPassword, .invalidEmail, etc.
A simple pseudocode example:
if error != nil {
if let error = AuthErrorCode(rawValue: error?.code) {
switch error {
case .wrongPassword:
// do some stuff with the error code
}
}
Also, I feel your pain. I've found that the Swift SDK documentation lags quite a bit when changes come along.

Workaround for EKParcipant URL accessing crash?

Some of my users have been sent me logs identifying a EXC_BREAKPOINT (SIGTRAP) Error on this line of code. I've been been trying to make it safe but all of the properties of EKParticipant are non optional so comparing to nil just gives me a warning saying it will always be true. If something is nil here how should I handle it?
Error Line
let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "")
Apple Error Description
Trace Trap [EXC_BREAKPOINT // SIGTRAP]
Similar to an Abnormal Exit, this exception is intended to give an
attached debugger the chance to interrupt the process at a specific
point in its execution. You can trigger this exception from your own
code using the __builtin_trap() function. If no debugger is attached,
the process is terminated and a crash report is generated. Lower-level
libraries (e.g, libdispatch) will trap the process upon encountering a
fatal error. Additional information about the error can be found in
the Additional Diagnostic Information section of the crash report, or
in the device's console. Swift code will terminate with this exception
type if an unexpected condition is encountered at runtime such as:
a non-optional type with a nil value
a failed forced type conversion Look at the Backtraces to determine where the unexpected condition was encountered. Additional
information may have also been logged to the device's console. You
should modify the code at the crashing location to gracefully handle
the runtime failure. For example, use Optional Binding instead of
force unwrapping an optional."
Full Method
/**
Parses participants for a given event.
Goes through the EKEvents attendees array to build Attendee objects used to model a participant.
- parameter event: The calendar event we'll be finding the participants for.
- returns: An array of Attendee objects with the participants name, email, required/optional status and whether they've accepted their invitation to the event.
*/
private static func parseParticipantsIn(event: EKEvent) -> [Attendee] {
var participants = [Attendee]()
if let attendees = event.attendees, event.attendees?.isEmpty == false {
for participant in attendees {
let participantName : String? = parse(EKParticipantName: participant)
let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "")
let isRequiredParticipant : Bool = participant.participantRole == EKParticipantRole.required
let hasAccepted : Bool = participant.participantStatus == EKParticipantStatus.accepted
guard (participantName != nil && participantEmail != nil)
else
{
log.error("Participant could not be parsed")
continue
}
let attendee = Attendee(name: participantName!, email: participantEmail!, required: isRequiredParticipant, hasAccepted: hasAccepted)
participants.append(attendee)
}
}
return participants
}
This appears to be a problem with the EKParticipant.url property. Any attempted access of EKParticipant.url causes a crash if you have a participant with the following email field within it.
"Bill Gates" <billgates#google.com>
I'd guess the quotation marks end the String prematurely. It is fine when accessed from EKParticipant.description so I intend to parse it from there.
This is a ridiculous issue, and Deco pinpointed it exactly. I used a different approach to get around it though: Since I'm already working in a mixed code base (obj-c and Swift), I created a class method on one of my obj-c classes that takes an EKParticipant and returns its URL as a string. Then, in Swift, I call that class method to get the URL instead of directly accessing the property (and crashing). It's hacky, but better than crashing and saved me from parsing the description.
This is rather old question but yet I hit this issue myself. My solution is to fallback to ObjC in order to workaround it.
Just add this ObjC functions to swift bridging header file and you are good to use them in swift.
static inline BOOL
participantHasNonNilURL (EKParticipant* _Nonnull participant) {
return participant.URL != nil;
}
static inline NSURL* _Nullable
participantURL(EKParticipant* _Nonnull participant) {
if (participant.URL != nil) {
return participant.URL;
}else {
return nil;
}
}
Example of usage:
extension EKParticipant {
var optionalURL: URL? {
return participantURL(self)
}
var hasURL: Bool {
return participantHasNonNilURL(self)
}
}
This is still an issue on macOS 11.2... I have reported it to Apple. I encourage anyone else hitting this issue to do the same.
The only Swift-only workaround that worked for me is:
extension EKParticipant {
public var safeURL: URL? {
perform(#selector(getter: EKParticipant.url))?.takeUnretainedValue() as? NSURL? as? URL
}
}
Validations are added incorrectly, please check the below response about how the guard could be used.
if let attendees = event.attendees, event.attendees?.isEmpty == false {
for participant in attendees {
guard let participantName : String? = parse(EKParticipantName: participant) else{
log.error("error in participant name")
return
}
guard let participantEmail : String? = participant.url.absoluteString.lowercased().replacingOccurrences(of: "mailto:", with: "") else{
log.error("error in participant email")
return
}
let isRequiredParticipant : Bool = participant.participantRole == EKParticipantRole.required
let hasAccepted : Bool = participant.participantStatus == EKParticipantStatus.accepted
/* guard validation is not required here */
if (participantName != nil && participantEmail != nil){
let attendee = Attendee(name: participantName!, email: participantEmail!, required: isRequiredParticipant, hasAccepted: hasAccepted)
participants.append(attendee)
}
}
}
return participants

How can I get the description error from stripe?

I am integrating stripe in my app. The code works but I would like to get the description error and show it to user with a label in case something goes wrong (CC number not correct etc):
// other stuff here
STPAPIClient.sharedClient().createTokenWithCard(stripeCard) { (token, error) -> Void in
if let error = error {
print(error.userInfo)
}
else if let token = token {
self.createBackendChargeWithToken(token) { status in
if status == PKPaymentAuthorizationStatus.Success{
dispatch_async(dispatch_get_main_queue()){
self.paymentActivity.hidden = true
self.paymentActivity.stopAnimating()
self.paymentActivityLabel.text = "Transaction approved!"
}
}
}
}
}
}
when I print error.userInfo I get the following:
[com.stripe.lib:ErrorMessageKey: Your card number is incorrect.,
com.stripe.lib:CardErrorCodeKey: com.stripe.lib:IncorrectNumber,
com.stripe.lib:ErrorParameterKey: number,
NSLocalizedDescription: Your card's number is invalid]
How can I extrapolate NSLocalizedDescription?
An error.userInfo is usually an NSDictionary.
When you print this dictionary, there's no types like with a Swift dictionary, so the print function does not always recognize what are strings, and does not show double quotes. But your userInfo dictionary keys are still probably strings.
I would first try accessing the error value like this:
print(error.userInfo["NSLocalizedDescription"])
to verify that indeed NSLocalizedDescription is a string key.
A tip: hold ALT and CLICK on a variable, it will show you its type, it helps for quickly debugging in Xcode.

Any way to get the name of iPhone user?

Outside of asking the user to input their name, is there any way to get it off the device?
I tried this library, which attempts to extract the name from [UIDevice currentDevice] name], but that doesn't work in a lot of situations:
https://github.com/tiboll/TLLNameFromDevice
Is the user's name present in the phonebook or anywhere else that we have access to in iOS 6?
Well you could go through all the contacts in the AddressBook and see if any of them are marked with the owner flag.
Just be aware that doing this will popup the "this app wants access to the address book" message. Also Apple isn't very keen on these kind of things. In the app review guide it is specified that an app can not use personal information without the user's permission.
You could use Square's solution:
Get the device's name (e.g. "John Smith's iPhone").
Go through the contacts on the phone and look for a contact named "John Smith".
JBDeviceOwner and ABGetMe will both do this for you.
You could use CloudKit. Following a snippet in Swift (ignoring errors):
let container = CKContainer.defaultContainer()
container.fetchUserRecordIDWithCompletionHandler(
{
(recordID, error) in
container.requestApplicationPermission(
.PermissionUserDiscoverability,
{
(status, error2) in
if (status == CKApplicationPermissionStatus.Granted)
{
container.discoverUserInfoWithUserRecordID(
recordID,
completionHandler:
{
(info, error3) in
println("\(info.firstName) \(info.lastName)")
}
)
}
}
)
}
)
The above code was based on the code at http://www.snip2code.com/Snippet/109633/CloudKit-User-Info
to save folks time. in swift4:
let container = CKContainer.default()
container.fetchUserRecordID(
completionHandler: {
(recordID, error) in
guard let recordID = recordID else {
return
}
container.requestApplicationPermission(
.userDiscoverability,
completionHandler: {
(status, error2) in
if (status == CKContainer_Application_PermissionStatus.granted)
{
if #available(iOS 10.0, *) {
container.discoverUserIdentity(withUserRecordID:
recordID,
completionHandler:
{
(info, error3) in
guard let info = info else {
return
}
print("\(info.firstName) \(info.lastName)")
}
)
}
}
}
)
}
)
however: CKUserIdentity no longer exposes either first or last name
So this answer no longer works.
You can use:
NSLog(#"user == %#",[[[NSHost currentHost] names] objectAtIndex:0]);
I did receive compiler warnings that the methods +currentHost and -names were not found. Given the warning, I’m not sure of Apple’s intention to make this available (or not) as a publicly accessible API, however, everything seemed to work as expected without the need to include any additional header files or linking in additional libraries/frameworks.
Edit 1:
You may also take a look at this Link
Edit 2:
If you have integrated your app with Facebook you can easily retrieve the user info, see Facebook Fetch User Data
For SWIFT you can use
NSUserName() returns the logon name of the current user.
func NSUserName() -> String