Realm objects intermittently not being present when app launches - ios

The title reflects what I think is happening, but I haven't been able to prove it beyond a shadow of a doubt. I've added a number of log calls to our sentry instance to try to narrow down what's happening
Background: Our app has a root VC called LoadingViewController which has some logic to determine whether we have a logged in user or not. If we do, it shows the homescreen, if we don't it shows the login/register screen. Intermittently, the login screen is shown when the homescreen should show. Our app uses JWT to authenticate with our backend, but I'm fairly confident token expiry is not the issue; the logging around an expired token forcing a logout is not getting called according to the logs
Research: I've also reviewed these questions/issues and while similar, don't solve what I'm seeing
Realm query sometimes return empty data I've checked for where I call delete on any User objects or deleting the entire Realm on logout. This code only fires when a user explicitly logs out or a token expires
Empty DB after application restart I am not doing any manual filesystem work (though I am doing keychain / encrypted Realm which I'll show the code for below)
Why is my Realm object not saving stored values?
Realm database object seems empty, but then isn't My user object has the appropriate dynamic properties
Clear complete Realm Database
What I see: The LoadingViewController starting up. Going into bootUser, not getting a User back from the UserDataService, returning false, and then resetting everything. This is why I think that the User object isn't there when it's being queried for.
This usually happens after I haven't used the app for a while, and not all the time. I haven't been able to force the issue by either force-quitting the app or loading up a bunch of games to try to force it out of memory and going back to it.
I'm at a little bit of a loss. Is there anything I haven't thought of or could check?
Thanks
Code
Subset of our LoadingViewController:
class LoadingViewController: UIViewController {
override func viewDidLoad() {
breadcrumbs.append("LoadingViewController.viewDidLoad \(UserDataService.getCurrentUser()?.id) \(UserDataService.getCurrentUser()?.token)")
super.viewDidLoad()
// determine if we have a user
if self.bootUser() {
return
}
// If we got hereUser is not logged in. wipe it
NSOperationQueue().addOperationWithBlock() {
let realm = KidRealm.realm()
CacheUtils.purgeRealm(realm)
}
}
func bootUser() -> Bool {
breadcrumbs.append("LoadingViewController.bootUser 0 \(UserDataService.getCurrentUser()?.id) \(UserDataService.getCurrentUser()?.token)")
if let user = UserDataService.getCurrentUser(),
let checkToken = user.token
where checkToken != "" {
breadcrumbs.append("LoadingViewController - 1 have user \(UserDataService.getCurrentUser()?.id) \(UserDataService.getCurrentUser()?.token)")
KA.initUser(currentUser)
// user is a full user, bring them to the homepage
if let vc = self.storyboard?.instantiateViewControllerWithIdentifier("betacodeview") as? BetaCodeViewController {
breadcrumbs.append("LoadingViewController - 3 betacodeview \(UserDataService.getCurrentUser()?.id) \(UserDataService.getCurrentUser()?.token)")
self.performSegueWithIdentifier("gotohomeview", sender: self)
return true
}
breadcrumbs.append("LoadingViewController - 4 skip? \(UserDataService.getCurrentUser()?.id) \(UserDataService.getCurrentUser()?.token)")
logLogoutIssues("bootUser with token. !full !vc \(UserDataService.count())")
}
breadcrumbs.append("LoadingViewController - 5 no token / user \(UserDataService.getCurrentUser()?.id) \(UserDataService.getCurrentUser()?.token)")
logLogoutIssues("bootUser with no token. token: (\(token)) \(UserDataService.count())")
return false
}
Subset of the User model
class User: Object {
dynamic var id: Int = -1
dynamic var UUID = ""
dynamic var email: String?
dynamic var firstName: String?
dynamic var lastName: String?
dynamic var facebookId: String?
dynamic var instagramId: String?
dynamic var photoUrl: String?
dynamic var token: String?
dynamic var fullUser = false
dynamic var donationPercent: Int = 0
// Extra info
dynamic var birthday: NSDate?
dynamic var phone: String?
dynamic var address1: String?
dynamic var address2: String?
dynamic var zip: String?
dynamic var city: String?
dynamic var state: String?
// User settings
dynamic var allowNotification: Bool = true
// temporary in memory, not saved
var profilePhoto = NSData()
override static func primaryKey() -> String? {
return "UUID"
}
func setUuid(UUID: String) {
self.UUID = UUID
}
func setUuid() {
self.UUID = SwiftyUUID.UUID().CanonicalString()
}
}
Subset of the UserDataService which the LoadingViewController uses to get the current user
struct UserDataService: Saveable, Deletable {
static func getCurrentUser(realm: Realm) -> User? {
let getUser = realm.objects(User)
if let user = getUser.first {
return user
}
return nil
}
static func getCurrentUser() -> User? {
let realm = KidRealm.realm()
return UserDataService.getCurrentUser(realm)
}
}
Subset of CacheUtils, used to clear the realm for the user to login or register (also called during logout)
struct CacheUtils {
static func purgeRealm(realm: Realm) {
try! realm.write {
realm.deleteAll()
}
}
}
Lastly, KidRealm, which sets up the encryption. This is used both in the main app, and a background process when push notifications come in
struct KidRealm {
static func realm() -> Realm {
let key = getKey()
let configuration = Realm.Configuration(encryptionKey: key)
let realm = try! Realm(configuration: configuration)
return realm
}
static func getKeyString(key: NSData) -> String {
return "\(key)".stringByTrimmingCharactersInSet(NSCharacterSet(charactersInString: "<>")).stringByReplacingOccurrencesOfString(" ", withString: "")
}
static func getKey() -> NSData {
// Identifier for our keychain entry - should be unique for your application
let keychainIdentifier = "com.kidfund.kidfund1.keychain"
let keychainIdentifierData = keychainIdentifier.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
// First check in the keychain for an existing key
var query: [NSString: AnyObject] = [
kSecClass: kSecClassKey,
kSecAttrApplicationTag: keychainIdentifierData,
kSecAttrKeySizeInBits: 512,
kSecReturnData: true,
kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlock
]
// To avoid Swift optimization bug, should use withUnsafeMutablePointer() function to retrieve the keychain item
// See also: https://stackoverflow.com/questions/24145838/querying-ios-keychain-using-swift/27721328#27721328
var dataTypeRef: AnyObject?
var status = withUnsafeMutablePointer(&dataTypeRef) { SecItemCopyMatching(query, UnsafeMutablePointer($0)) }
if status == errSecSuccess {
return dataTypeRef as! NSData
}
// No pre-existing key from this application, so generate a new one
let keyData = NSMutableData(length: 64)!
let result = SecRandomCopyBytes(kSecRandomDefault, 64, UnsafeMutablePointer<UInt8>(keyData.mutableBytes))
assert(result == 0, "Failed to get random bytes")
// Store the key in the keychain
query = [
kSecClass: kSecClassKey,
kSecAttrApplicationTag: keychainIdentifierData,
kSecAttrKeySizeInBits: 512,
kSecValueData: keyData,
kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlock
]
status = SecItemAdd(query, nil)
assert(status == errSecSuccess, "Failed to insert the new key in the keychain")
return keyData
}
}
Update 1:
I also have a global currentUser helper, which wraps calls to my UserDataService. I don't think this is an issue, but adding for completeness. This exists for legacy reasons and is on the list to be refactored out at some point.
var currentUser: User {
get {
if let user = UserDataService.getCurrentUser() {
return user
}
breadcrumbs.append("Getting empty currentUser \(UserDataService.count())")
logLogoutIssues("Getting empty currentUser \(UserDataService.count())")
return User()
}
}
Update 2:
Based on the thread that Peba pointed me to, I'm pushing some fixes now that include:
extra logging around the keychain activity
a sleep(1) retry if it doesn't get the key on the first try
same for the write
cache the key in memory so I don't hit the keychain as much (not thrilled about this but it is what it is)
adding the Keychain Sharing

Related

MongoDB Realm cannot update user embedded object in Swift

I'm trying to add to an embedded "Conversation" object within my user collection (for a chat app) in Mongo Realm. I would like to create a "default" conversation when the user account is created, such that every user should be a member of at least one conversation that they can then add others to.
The app currently updates the user collection via a trigger and function in Realm at the back end using the email / Password authentication process.
My classes are defined in Swift as follows:
#objcMembers class User: Object, ObjectKeyIdentifiable {
dynamic var _id = UUID().uuidString
dynamic var partition = "" // "user=_id"
dynamic var userName = ""
dynamic var userPreferences: UserPreferences?
dynamic var lastSeenAt: Date?
var teams = List<Team>()
dynamic var presence = "Off-Line"
var isProfileSet: Bool { !(userPreferences?.isEmpty ?? true) }
var presenceState: Presence {
get { return Presence(rawValue: presence) ?? .hidden }
set { presence = newValue.asString }
}
override static func primaryKey() -> String? {
return "_id"
}
#objcMembers class Conversation: EmbeddedObject, ObjectKeyIdentifiable {
dynamic var id = UUID().uuidString
dynamic var displayName = ""
dynamic var unreadCount = 0
var members = List<Member>()
}
So my current thinking is that I should code it in Swift as follows which I believe should update the logged in user, but sadly can't get this quite right:
// Open the default realm
let realm = try! Realm()
try! realm.write {
let conversation = Conversation()
conversation.displayName = "My Conversation"
conversation.unreadCount = 0
var user = app.currentUser
let userID = user.id
let thisUser = User(_id: userID)
realm.add(user)
}
Can anyone please spot where I'm going wrong in my code?
Hours spent on Google and I'm missing something really obvious! I have a fair bit of .NET experience and SQL but struggling to convert to the new world!
I'm a noob when it comes to NoSQL databases and SwiftUI and trying to find my way looking at a lot of Google examples. My example us based on the tutorial by Andrew Morgan https://developer.mongodb.com/how-to/building-a-mobile-chat-app-using-realm-new-way/
I am a bit unclear on the exact use case here but I think what's being asked is how to initialize an object with default values - in this case, a default conversation, which is an embedded object.
If that's not the question, let me know so I can update.
Starting with User object
class UserClass: Object {
#objc dynamic var _id = ObjectId.generate()
#objc dynamic var name = ""
let conversationList = List<ConversationClass>()
convenience init(name: String) {
self.init()
self.name = name
let convo = ConversationClass()
self.conversationList.append(convo)
}
override static func primaryKey() -> String? {
return "_id"
}
}
and the EmbeddedObject ConversationClass
class ConversationClass: EmbeddedObject {
dynamic var displayName = "My Conversation"
dynamic var unreadCount = 0
var members = List<MemberClass>()
}
The objective is that when a new user is created, they have a default conversation class added to the conversationList. That's done in the convenience init
So the entire process is like this:
let realm = Realm()
let aUser = UserClass(name: "Leroy")
try! realm.write {
realm.add(aUser)
}
Will initialize a new user, set their name to Leroy. It will also initialize a ConversationClass Embedded object and add it to the conversationList.
The ConversationClass object has default values set for displayName and unread count per the question.

Cache server data globally and refresh views

I'm building an app that uses firebase for authentication and database functionality. Once a user signs up, a database record is stored for that user, containing some basic information like first name, last name etc.
Once a user logs in with his credentials I want to set a global variable (perhaps userDefaults?) which contains the user data for that specific user. Otherwise I have to fetch user data for every time I want to fill a label with for instance, a user's first name.
I managed to set userdefaults upon login and use this info in UIlables. But when I let users make changes to their data, of which some is important for the functioning of the app, I can update the server AND the userdefaults but the app itself doesn't update with the correct data. It keeps the old data in (for example) UIlables.
I would love to get some more insight on what the best work-flow is to manage situations like these.
When opening the app, i have a tabBarController set as rootviewcontroller. In the load of tabbarcontroller I have the following code retrieving the user data from firebase and saving it to userdefaults:
guard let uid = Auth.auth().currentUser?.uid else { return }
Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot.value ?? "")
guard let dictionary = snapshot.value as? [String: Any] else { return }
let firstname = dictionary["First name"] as? String
let lastname = dictionary["Last name"] as? String
print("first name is: " + firstname!)
UserDefaults.standard.set(firstname, forKey: "userFirstName")
print(UserDefaults.standard.value(forKey: "userFirstName"))
self.setupViewControllers()
}
Then I continue on loading in all the viewcontrollers in the tabBarController:
self.setupViewControllers()
During that process the labels in those viewcontrollers get filled in with the userdefaults data.
This is an example of a label being filled in with userDefaults but not being updated upon changing of userdefaults:
let welcomeLabel: UILabel = {
let label = UILabel()
let attributedText = NSMutableAttributedString(string: "Welcome ")
attributedText.append(NSAttributedString(string: "\(UserDefaults.standard.string(forKey: "userFirstName")!)"))
label.attributedText = attributedText
label.font = UIFont.systemFont(ofSize: 30, weight: .bold)
return label
}()
this is a function i'm using to update the first name (via a textfield filled in by the user):
#objc func updateName() {
guard let uid = Auth.auth().currentUser?.uid else { return }
Database.database().reference().child("users").child(uid).updateChildValues(["First name" : updateNameField.text ?? ""])
UserDefaults.standard.set(updateNameField.text, forKey: "userFirstName")
print(UserDefaults.standard.value(forKey: "userFirstName"))
}
So you'll have to organize things first. In a new file define constants such as below. These constant will be accessible in global scope unless private
Constants.swift
private let storedusername = "usname"
private let storedName = "uname"
private let displaypic = "udp"
private let aboutme = "udesc"
var myusername : String {
get {
return (UserDefaults.standard.string(forKey: storedusername)!)
} set {
UserDefaults.standard.set(newValue, forKey: storedusername)
}
}
var myname : String {
get {
return (UserDefaults.standard.string(forKey: storedName)!)
} set {
UserDefaults.standard.set(newValue, forKey: storedName)
}
}
var myProfileImage : Data {
get {
return (UserDefaults.standard.data(forKey: displaypic)!)
} set {
UserDefaults.standard.set(newValue, forKey: displaypic)
}
}
var myAboutMe : String? {
get {
return (UserDefaults.standard.string(forKey: aboutme)!)
} set {
UserDefaults.standard.set(newValue, forKey: aboutme)
}
}
Now the next time you want to save anything in UserDefaults, you'll just do the following anywhere throughout your code base :
myusername = "#CVEIjk"
And to retrive it, just call it :
print(myusername)
IMPORTANT NOTE --
Always remember to initialize them. You can do this as the user signs up. As soon as they fill out their details and hit submit, just save them to these variables. That wouldn't cause unnecessary crash.
You'll have to save them at every location you perform updates regarding these nodes in the database.
Now, the refreshing views part. I am taking a scenario where your ProfileView.swift has the view and user goes to EditProfile.swift for updating the content.
You initialize all your observers the place where the update will have the immediate effect. Because the view immediately after the update matters. The rest will be called through the getter of the aboutme
ProfileView.swift
func openEditView() {
NotificationCenter.default.addObserver(self, selector: #selector(fetchUserDetails), name: Notification.Name("update"), object: nil)
//setting this right before the segue will create an observer specifically and exclusively for updates. Hence you don't have to worry about the extra observers.
perform(segue: With Identifier:)// Goes to the editProfile page
}
This function will be initially called in viewDidLoad(). At this time you need to make sure you have all the data, else it will produce no values. But if you are storing everything as the user signs up, you are safe.
#objc func fetchUserDetails() {
if uid != nil {
if myname.count > 0 { // This will check if the variable has anything in the memory or not. Dont confuse this with [Array].count
self.nameLabel = myname
}
}
}
This function also acts an ab observer method. So when the notifications are posted they can run again.
Now, EditProfile.swift
In the block where you are updating the server, save the values and then create a Notification.post and put this method right before you dismiss(toViewController:)
func updateUserCacheData(name: String, username: String, aboutme: String, ProfilePhoto: UIImage? = nil) {
DispatchQueue.global().async {
myname = name
myusername = username
myAboutMe = aboutme
if self.newImage != nil {
myProfileImage = self.newImage!.jpegData(compressionQuality: 1)!
}
DispatchQueue.main.async {
NotificationCenter.default.post(name: .refreshProfileViews, object: nil)
}
}
}
func updateToServerAndBackToProfileView() {
self.updateUserCacheData(name: iname!, username: iusername, aboutme: iaboutme!)
self.dismiss(animated: true, completion: nil)
}
}
As long as this goes back to ProfileView, your views will be instantly refreshed. You can keep an observer wherever you view will be first displayed after the dismiss. the rest will fetch updated content always. Also, don't forget to deinit your Observer in ProfileView
//This code is in ProfileView.swift
deinit {
NotificationCenter.default.removeObserver(self, name: Notification.Name("update"), object: nil)
}
Also, in cases where the content might be empty, simply initialize it with empty content. For example, if user doesn't choose to add aboutme while signing up, you can just put
`myaboutme = ""`
This will create a safe environment for you and you are well set.

Add a Document's Document ID to Its Own Firestore Document - Swift 4

How do I go about adding the document ID of a document I just added to my firestore database, to said document?
I want to do this so that when a user retrieves a "ride" object and chooses to book it, I can know which specific ride they've booked.
The problem that i'm facing is that you can't get the document ID until after it's created, so the only way to add it to said document would be to create a document, read its ID, then edit the document to add in the ID. At scale this would create twice as many server calls as desired.
Is there a standard way to do this? Or a simple solution to know which "ride" the user booked and edit it accordingly in the database?
struct Ride {
var availableSeats: Int
var carType: String
var dateCreated: Timestamp
var ID: String // How do I implement this?
}
func createRide(ride: Ride, completion: #escaping(_ rideID: String?, _ error: Error?) -> Void) {
// Firebase setup
settings.areTimestampsInSnapshotsEnabled = true
db.settings = settings
// Add a new document with a generated ID
var ref: DocumentReference? = nil
ref = db.collection("rides").addDocument(data: [
"availableSeats": ride.availableSeats,
"carType": ride.carType,
"dateCreated": ride.dateCreated,
"ID": ride.ID,
]) { err in
if let err = err {
print("Error adding ride: \(err)")
completion(nil, err)
} else {
print("Ride added with ID: \(ref!.documentID)")
completion(ref?.documentID, nil)
// I'd currently have to use this `ref?.documentID` and edit this document immediately after creating. 2 calls to the database.
}
}
}
While there is a perfectly fine answer, FireStore has the functionality you need built in, and it doesn't require two calls to the database. In fact, it doesn't require any calls to the database.
Here's an example
let testRef = self.db.collection("test_node")
let someData = [
"child_key": "child_value"
]
let aDoc = testRef.document() //this creates a document with a documentID
print(aDoc.documentID) //prints the documentID, no database interaction
//you could add the documentID to an object etc at this point
aDoc.setData(someData) //stores the data at that documentID
See the documentation Add a Document for more info.
In some cases, it can be useful to create a document reference with an
auto-generated ID, then use the reference later. For this use case,
you can call doc():
You may want to consider a slightly different approach. You can obtain the document ID in the closure following the write as well. So let's give you a cool Ride (class)
class RideClass {
var availableSeats: Int
var carType: String
var dateCreated: String
var ID: String
init(seats: Int, car: String, createdDate: String) {
self.availableSeats = seats
self.carType = car
self.dateCreated = createdDate
self.ID = ""
}
func getRideDict() -> [String: Any] {
let dict:[String: Any] = [
"availableSeats": self.availableSeats,
"carType": self.carType,
"dateCreated": self.dateCreated
]
return dict
}
}
and then some code to create a ride, write it out and leverage it's auto-created documentID
var aRide = RideClass(seats: 3, car: "Lincoln", createdDate: "20190122")
var ref: DocumentReference? = nil
ref = db.collection("rides").addDocument(data: aRide.getRideDict() ) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
aRide.ID = ref!.documentID
print(aRide.ID) //now you can work with the ride and know it's ID
}
}
I believe that if you use Swift's inbuilt ID generator, called UUID, provided by the Foundation Framework, this will let you do what you want to do. Please see the below code for my recommended changes. Also by doing it this way, when you first initialise your "Ride" struct, you can generate its ID variable then, instead of doing it inside the function. This is the way I generate unique ID's throughout my applications and it works perfectly! Hope this helps!
struct Ride {
var availableSeats: Int
var carType: String
var dateCreated: Timestamp
var ID: String
}
func createRide(ride: Ride, completion: #escaping(_ rideID: String, _ error: Error?) -> Void) {
// Firebase setup
settings.areTimestampsInSnapshotsEnabled = true
db.settings = settings
// Add a new document with a generated ID
var ref: DocumentReference? = nil
let newDocumentID = UUID().uuidString
ref = db.collection("rides").document(newDocumentID).setData([
"availableSeats": ride.availableSeats,
"carType": ride.carType,
"dateCreated": ride.dateCreated,
"ID": newDocumentID,
], merge: true) { err in
if let err = err {
print("Error adding ride: \(err)")
completion(nil, err)
} else {
print("Ride added with ID: \(newDocumentID)")
completion(newDocumentID, nil)
}
}
}
This is my solution which works like a charm
let opportunityCollection = db.collection("opportunities")
let opportunityDocument = opportunityCollection.document()
let id = opportunityDocument.documentID
let data: [String: Any] = ["id": id,
"name": "Kelvin"]
opportunityDocument.setData(data) { (error) in
if let error = error {
completion(.failure(error))
} else {
completion(.success(()))
}
}

Sync data between two viewcontrollers to avoid creating same observer again

In my main VC I look for changes in my FB database like this:
ref.child("posts").observe(.childChanged, with: { (snapshot) in
.......
})
From this VC I can enter VC2 which is set to be "present modally" in my segue.
Now I wonder if I can pass live FB data from VC1 to VC2? I know that I can use a segue.identifier and pass data when I segue to the next VC but this is one time send only. Or should I setup a delegate to fetch data from vc1 to vc2?
So is there any way I can send data from VC1 to VC2 once a node has been updated or must I setup a new .observe() function in VC2?
First I would like to remind you about the singleton design patter :
In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.
So the first thing you need to do is to create a call that contains as a parameters the data you get from firebase, I have did the following to get the following in order to get the user data when he logged in into my app and then use these data in every part of my application (I don't have any intention to pass the data between VC this is absolutely the wrong approach )
my user class is like this :
import Foundation
class User {
static let sharedInstance = User()
var uid: String!
var username: String!
var firstname: String!
var lastname: String!
var profilePictureData: Data!
var email: String!
}
after that I have created another class FirebaseUserManager (you can do this in your view controller but it's always an appreciated idea to separate your view your controller and your model in order to make any future update easy for you or for other developer )
So my firebaseUserManager class contains something like this
import Foundation
import Firebase
import FirebaseStorage
protocol FirebaseSignInUserManagerDelegate: class {
func signInSuccessForUser(_ user: FIRUser)
func signInUserFailedWithError(_ description: String)
}
class FirebaseUserManager {
weak var firebaseSignInUserManagerDelegate: FirebaseSignInUserManagerDelegate!
func signInWith(_ mail: String, password: String) {
FIRAuth.auth()?.signIn(withEmail: mail, password: password) { (user, error) in
if let error = error {
self.firebaseSignInUserManagerDelegate.signInUserFailedWithError(error.localizedDescription)
return
}
self.fechProfileInformation(user!)
}
}
func fechProfileInformation(_ user: FIRUser) {
var ref: FIRDatabaseReference!
ref = FIRDatabase.database().reference()
let currentUid = user.uid
ref.child("users").queryOrderedByKey().queryEqual(toValue: currentUid).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() {
let dict = snapshot.value! as! NSDictionary
let currentUserData = dict[currentUid] as! NSDictionary
let singletonUser = User.sharedInstance
singletonUser.uid = currentUid
singletonUser.email = currentUserData["email"] as! String
singletonUser.firstname = currentUserData["firstname"] as! String
singletonUser.lastname = currentUserData["lastname"] as! String
singletonUser.username = currentUserData["username"] as! String
let storage = FIRStorage.storage()
let storageref = storage.reference(forURL: "gs://versus-a107c.appspot.com")
let imageref = storageref.child("images")
let userid : String = (user.uid)
let spaceref = imageref.child("\(userid).jpg")
spaceref.data(withMaxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
// Uh-oh, an error occurred!
print(error.localizedDescription)
} else {
singletonUser.profilePictureData = data!
print(user)
self.firebaseSignInUserManagerDelegate.signInSuccessForUser(user)
}
}
}
})
}
}
so basically this class contains some protocols that we would implements and two functions that manager the firebase signIn and fechProfileInformation , that will get the user information
than in my login View controller I did the following :
1 implement the protocol
class LoginViewController: UIViewController, FirebaseSignInUserManagerDelegate
2 in the login button I did the following
#IBAction func loginAction(_ sender: UIButton) {
guard let email = emailTextField.text, let password = passwordTextField.text else { return }
let firebaseUserManager = FirebaseUserManager()
firebaseUserManager.firebaseSignInUserManagerDelegate = self
firebaseUserManager.signInWith(email, password: password)
}
3 implement the protocol method :
func signInSuccessForUser(_ user: FIRUser) {
// Do something example navigate to the Main Menu
}
func signInUserFailedWithError(_ description: String) {
// Do something : example alert the user
}
So right now when the user click on the sign in button there is an object created which contains the user data save on firebase database
now comes the funny part (the answer of your question : how to get the user data in every where in the app)
in every part of my app I could make
print(User.sharedInstance.uid) or print(User.sharedInstance. username)
and I get the value that I want to.
PS : In order to use the singleton appropriately you need to make sure that you call an object when it's instantiated.

iOS Swift - SharkORM won't commit

I'm using SharkORM on iOS Swift project and I'm having problem with a specific object. I have other objects in the project that works, but this one.
My class is like this:
import Foundation
import SharkORM
class Exam: SRKObject {
dynamic var serverId: NSNumber?
dynamic var type: String?
dynamic var when: String?
dynamic var file: String?
dynamic var filename: String?
dynamic var url: String?
func toJson() -> [String:Any?] {
return [
"name" : type,
"date" : when,
"serverId" : serverId,
"file" : file,
"filename" : filename,
"url" : url,
"id" : id
]
}
static func fromJson(_ json: [String:Any?]) -> Exam {
let exam = Exam()
exam.id = json["id"] as? NSNumber ?? NSNumber(value: 0)
exam.type = json["name"] as? String ?? ""
exam.file = json["file"] as? String ?? ""
exam.filename = json["filename"] as? String ?? ""
exam.url = json["url"] as? String ?? ""
exam.serverId = json["serverId"] as? NSNumber ?? NSNumber(value: 0)
exam.when = json["date"] as? String ?? ""
return exam
}
}
I add to an array objects that needs to be saved and after user press save button, the app starts committing it.
// save exams
for exam in self.examsToSave {
if !exam.commit() {
print("Error commiting exam.")
}
}
if let rs = Exam.query().fetch() {
print("exams: \(rs.count)")
}
The commit method returns true and I added a print right after it finishes committing and result is zero.
Any idea?
I found out the problem right after post it. In my text here, my variable "when" was colored like a keyword. I just changed the name to "whenDate" and it started committing. Weird it didn't show up any error or a crash. Anyway, a variable named "when" is not allowed inside a SRKObject.
Given same Commit problem, figured best to keep to topic here. And I've spent number of hours trying to debug this so thought I'd try this:
I have a simple class (and overly simplified but tested as provided here):
class user: SRKObject {
#objc dynamic var name: String = ""
}
(No, no odd syntax coloring on the object property names.)
And I do the following (simplified test case), first defining
public var currUser = user()
Then in a function:
let users = user.query().fetch() as! [user]
if users.count > 0 {
currUser = users[0]
NSLog("Num users \(users.count) name \(currUser.name)")
} else {
self.currUser.name = "T1 User"
if !self.currUser.commit() {
print ("Failed to commit")
}
else {
let u = user.query().fetch()
print("Num users \(u.count)")
}
}
The commit() call succeeds -- at least I don't get the "Failed to commit" message. However, I do get zero count in the last fetch().
Viewing the DB file (in Simulator) from a "DB Browser for SQLite" shows the DB is created fine but the "user" record is not in there, and neither is the "committed" data.
BTW when I had this code in SRKTransaction.transaction, it DID fall into the failure (rollback) block, so yes, did get a transaction error, but tracking that down will be next.
In the meantime, appreciate in advance any help given this case as presented should work.
#retd111, I copied and pasted your code and got the same error.
Then, I moved the currUser to a local var, like this:
override func viewDidLoad() {
super.viewDidLoad()
var currUser: user? = nil
let users = user.query().fetch() as! [user]
if users.count > 0 {
currUser = users[0]
NSLog("Num users \(users.count) name \(currUser!.name)")
} else {
currUser = user()
currUser!.name = "T1 User"
if !currUser!.commit() {
print ("Failed to commit")
}
else {
let u = user.query().fetch()
print("Num users \(u?.count ?? 0)")
}
}
}
It works without problems.
For some reason, if you instantiate the currUser as a class member variable, as your example:
public var currUser = user()
it won't work.

Resources