I have an iOS
app making a SignIn
using google Authentication and save the user inside Realm database for the first time the user object saved alright if I sign out and then sign in with the same user again the app crashed.
The Realm Model
import Foundation
import RealmSwift
import CoreLocation
class UserDB: Object {
@objc dynamic var id : String!
@objc dynamic var name: String?
@objc dynamic var email: String?
@objc dynamic var phoneNumber: String?
@objc dynamic var photo: String?
@objc dynamic var latitute: String?
@objc dynamic var longitute: String?
@objc dynamic var supporter: String?
override static func primaryKey()-> String? {
return "email"
}
}
Here SignIn function
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let error = error {
if (error as NSError).code == GIDSignInErrorCode.hasNoAuthInKeychain.rawValue {
print("The user has not signed in before or they have since signed out.")
} else {
print("\(error.localizedDescription)")
}
return
}
// Perform any operations on signed in user here.
let userId = user.userID
let idToken = user.authentication.idToken
let fullName = user.profile.name
let givenName = user.profile.givenName
let familyName = user.profile.familyName
let email = user.profile.email
let photo = user.profile.imageURL(withDimension: 200)
//Print all fields
if let email = email {
print("Your email is \(email)")
}
if let userId = userId {
print("Your Id is \(userId)")
}
if let fullName = fullName {
print("Your Full name is \(fullName)")
}
if let idToken = idToken {
print("Your token is \(idToken)")
}
if let familyName = familyName {
print("Your family Name is \(familyName)")
}
if let givenName = givenName {
print("Your givin name is \(givenName)")
}
if let photo = photo {
print("Your image is : \(photo)")
}
//Realm Object
account.id = userId
account.name = givenName
account.email = email
if let photo = photo?.absoluteString {
account.photo = photo
}
saveData(data: account)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "GoogleMaps") as! GoogleMaps
vc.accountMap = account
navigationController?.pushViewController(vc, animated: true)
} else {
let alert = UIAlertController(title: "UnSuccessful SignIn", message: "please try again", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Close" , style: .cancel) { (action) in})
self.present(alert,animated:true,completion:nil)
}
}
and then call saveData function to save the object into the Realm Database
func saveData(data: UserDB) {
do {
try realm.write {
let user = realm.object(ofType: UserDB.self, forPrimaryKey: data.email!)
if let user = user {
print("array count: ",user)
print("We found ",user.email!)
realm.add(user, update: .modified)
} else {
print("We didn't find ",data.email!)
//realm.add(data)
realm.create(UserDB.self, value: data, update: .all)
}
}
} catch {
print("Error saving account Data \(error)")
}
//here put the tableView reloadData()
//tableView.realoadData()
}
so for the first signIn it's alright and the user info get saved but if I sign out and then signIn again the app crashed.
The Error
2019-12-26 12:05:26.504060+0200 social2[1943:339412] *** Terminating app due to uncaught exception 'RLMException', reason: 'Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first.'
*** **First throw call stack:**
(
0 CoreFoundation 0x00000001088d68db __exceptionPreprocess + 331
1 libobjc.A.dylib 0x000000010aa65ac5 objc_exception_throw + 48
2 Realm 0x000000010928e349 _ZL27RLMVerifyInWriteTransactionP13RLMObjectBase + 105
3 Realm 0x000000010929399d _ZN12_GLOBAL__N_18setValueEP13RLMObjectBasemP8NSString + 29
4 Realm 0x0000000109293971 _ZZZN12_GLOBAL__N_110makeSetterIU8__strongP8NSStringS3_EEP11objc_objectP11RLMPropertyEUb0_ENKUlvE_clEv + 97
5 Realm 0x00000001092938d6 ___ZN12_GLOBAL__N_110makeSetterIU8__strongP8NSStringS3_EEP11objc_objectP11RLMProperty_block_invoke_2 + 310
6 social2 0x0000000103b8264b $s7social214ViewControllerC4sign_12didSignInFor9withErrorySo07GIDSignG0CSg_So13GIDGoogleUserCSgs0J0_pSgtF + 9851
7 social2 0x0000000103b836f3 $s7social214ViewControllerC4sign_12didSignInFor9withErrorySo07GIDSignG0CSg_So13GIDGoogleUserCSgs0J0_pSgtFTo + 147
8 social2 0x0000000104123924 __37-[GIDSignIn addCallDelegateCallback:]_block_invoke + 116
9 social2 0x000000010411ea46 -[GIDCallbackQueue fire] + 161
10 social2 0x000000010411e489 +[GIDAuthentication handleTokenFetchEMMError:completion:] + 364
11 social2 0x0000000104122b95 __38-[GIDSignIn maybeFetchToken:fallback:]_block_invoke + 311
12 AppAuth 0x0000000104f97060 __86+[OIDAuthorizationService performTokenRequest:originalAuthorizationResponse:callback:]_block_invoke_13 + 48
13 libdispatch.dylib 0x0000000108f9dd7f _dispatch_call_block_and_release + 12
14 libdispatch.dylib 0x0000000108f9edb5 _dispatch_client_callout + 8
15 libdispatch.dylib 0x0000000108fac080 _dispatch_main_queue_callback_4CF + 1540
16 CoreFoundation 0x000000010883da79 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
17 CoreFoundation 0x0000000108838126 __CFRunLoopRun + 2310
18 CoreFoundation 0x00000001088374d2 CFRunLoopRunSpecific + 626
19 GraphicsServices 0x000000010e1042fe GSEventRunModal + 65
20 UIKitCore 0x0000000117cbdfc2 UIApplicationMain + 140
21 social2 0x0000000103b89b4b main + 75
22 libdyld.dylib 0x000000010c7cd541 start + 1
23 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
I am a bit unclear on the question but I think what is being asked is
When signing in using Google Sign-In and the user object does not exist in Realm, how do I add it to Realm upon first sign in, and then be able to access it thereafter when the user signs in.
It appears by the code in the question we are simply duplicating all of the GIDGoogleUser properties in Realm so I will omit some of that to keep it brief.
Additionally the question is leveraging a users email address as a key and that can be problematic as email addresses change and that could invalidate all data. A safer bet is to leverage the unique user id that Google assigns to each user.
So, starting with a Realm user object with a primary key of user_id
class RealmUserClass: Object {
@objc dynamic var user_id = ""
@objc dynamic var full_name = ""
@objc dynamic var email = ""
override static func primaryKey() -> String? {
return "user_id"
}
}
Then the sign in delegate function, I would write it like this
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if error == nil {
self.handleSignIn(forGIDUser: user)
} else {
print(error.localizedDescription)
return
}
So when the user signs in, we call the handleSignIn function and pass it the google user object
func handleSignIn(forGIDUser: GIDGoogleUser) {
let gidUserId = forGIDUser.userID
let realm = try! Realm()
if let user = realm.object(ofType: RealmUserClass.self, forPrimaryKey: gidUserId) {
print(user) //the user exists in Realm
//proceed to the rest of the app
} else { //user did not exist in Realm, create it}
let userToAdd = RealmUserClass()
userToAdd.person_id = forGIDUser.userID
userToAdd.full_name = forGIDUser.profile.name
userToAdd.email = forGIDUser.profile.email
try! realm.write {
realm.add(userToAdd)
}
//proceed to the rest of the app
}
}
Note that I omitted a lot of error checking for brevity so be sure to add that in.