Changing IP address in Realm Object Server - ios

I have an issue when the Realm Object Server have a different IP. The application can login through by Credential but after that it will return empty data although my database sit right on that IP and can be accessed by Realm Browser. Actually, I only use one account in realm object server and I create a user table with username and password so that after it can connect through Credential to the server, I will read the username and password on screen and check it information in database.
Connect to Realm Object Server function:
class func login(username: String, password: String, action: AuthenticationActions, completionHandler: #escaping ()->()) {
let serverURL = NSURL(string: realmIP)!
let credential = Credential.usernamePassword(username: username, password: password, actions: [action])
SyncUser.authenticate(with: credential, server: serverURL as URL) { user, error in
if let user = user {
syncUser = user
let syncServerURL = URL(string: realmURL)!
let config = Realm.Configuration(syncConfiguration: (user, syncServerURL))
realm = try! Realm(configuration: config)
} else if error != nil {
}
completionHandler()
}
}
Query from table after login by SyncUser:
class func loginLocal(employee: String) -> Bool{
let predicate = NSPredicate(format: "employee = %#", employee)
if (realm != nil) {
let user = realm?.objects(MyUser.self).filter(predicate)
if ((user?.count)! > 0) {
return true
}
}
return false
}
The solution seems to be weird so that I have to call a function multiple times by pressing my login button and hope that it will go through to the server.
This is my first application using Realm and Realm Object Server so I don't have much experience in this situation.

I may need more information on how you're handling the logged in Realm after the login, but from the code you've shown there, it looks like you're accidentally accessing a local version of the Realm and not the synchronized one.
Once logged in, you need to make sure you use the same Configuration object whenever you create Realm instances after that. It's not recommended to create and then save the realm instance inside the login completion block, as this block occurs on a background thread, making it unavailable anywhere else.
If your app is always online, it's easier to simply set the sync configuration as the default Realm for your app:
SyncUser.authenticate(with: credential, server: serverURL as URL) { user, error in
if let user = user {
syncUser = user
let syncServerURL = URL(string: realmURL)!
let config = Realm.Configuration(syncConfiguration: (user, syncServerURL))
Realm.Configuration.defaultConfiguration = config
}
completionHandler()
}
Otherwise, you can either save the Configuration in some sort of global object, or recreate it each time you need to create a Realm instance. The important thing to remember is you need to make sure your Realm instance is using a Configuration object with the successfully logged in user, otherwise it will default back to using a normal, empty local Realm.

Related

Sync Realm Object Server connection completion with follow up realm object usage

I’m using Realm Object Server for a simple test project and I’m facing problems synchronizing ROS connection setup and follow up usage of the realm object to access the database.
In viewDidLoad I’m calling connectROS function to initialize realmRos object/connection:
var realmRos: Realm!
override func viewDidLoad() {
connectROS()
if(FBSDKAccessToken.current() != nil){
// logged in
getFBUserData()
}else{
// not logged in
print("didLoad, FB user not logged in")
}
}
func connectROS() {
let username = "realm-admin"
let password = "*********"
SyncUser.logIn(with: .usernamePassword(username: username, password: password, register: false), server: URL(string: "http://146.185.154.***:9080")!)
{ user, error in
print("ROS: checking user credentials")
if let user = user {
print("ROS: user credentials OK")
DispatchQueue.main.async {
// Opening a remote Realm
print("ROS: entering dispatch Q main async")
let realmURL = URL(string: "realm://146.185.154.***:9080/~/***book_test1")!
let config = Realm.Configuration(syncConfiguration: SyncConfiguration(user: user, realmURL: realmURL))
self.realmRos = try! Realm(configuration: config)
// Any changes made to this Realm will be synced across all devices!
}
} else if let error = error {
// handle error
print("ROS: user check FAIL")
fatalError(String(describing: error))
}
}
}
In viewDidLoad function next step is to get FB logged user (in this case I’m using FB authentication). After the logged FB user is fetched, the application perform check is that FB user is new user for my application and my proprietary ROS User’s table.
func checkForExistingProfile(user: User) -> Bool {
var userThatExist: User?
do {
try self.realmRos.write() {
userThatExist = self.realmRos.object(ofType: User.self, forPrimaryKey: user.userName)
}
} catch let error as NSError {
print("ROS is not connected")
print(error.localizedDescription)
}
if userThatExist != nil {
return true
} else {
return false
}
}
At this point checkForExistingProfile usually (not always) crashes at try self.realmRos.write() which happens to be nil.
I think the problem comes from the synchronization between connectROS execution (which is asynchrony) and checkForExistingProfile which execution comes before connectROS completion.
Since you didn't show how checkForExistingProfile() is called after viewDidLoad() this is conjecture, but based on everything else you described it's the likely cause.
What you need to do is not call checkForExistingProfile() until the sync user has logged in and your self.realmRos variable has been initialized. Cocoa Touch does nothing to automatically synchronize code written using an asynchronous pattern (like logIn(), which returns immediately but reports its actual completion state in a callback), so you need to manually ensure that whatever logIn() is supposed to do has been done before you call any additional code that depends on its completion.

Realm Object Server creating DB and ROS but not syncing

I have set up a Realm Object Server on AWS (RHAT) and it appears to be working fine. I can look at the dashboard which tells me how many connections, and how many realms open on port 9080. My code is working fine on the device, updates are happening on the devices realm. But I dont see anything changing in the ROS realm. I may be getting something very basic wrong, but I can't tell what.
func setupRealm() {
// Authenticating the User
let username = "exampleuser"
let password = "examplepassword"
SyncUser.logInWithCredentials(SyncCredentials.usernamePassword(username, password: password), authServerURL: NSURL(string: "http://example.com:9080")!, onCompletion:
{ user, error in
if let user = user {
// Opening a remote Realm
let realmURL = NSURL(string: "realm://example.com:9080/~/VWF3")!
let config = Realm.Configuration(syncConfiguration: SyncConfiguration(user: user, realmURL: realmURL))
let realm = try! Realm(configuration: config)
// Any changes made to this Realm will be synced across all devices!
} else if let error = error {
// handle error
}
})
}
I call setupRealm() from ViewDidLoad.
My developers need to be able to see the changes to the ROS realm to ensure all is working correctly, but all that is there is the single users realm, structure of tables is accurate, zero data.
I checked the log files on the server and nothing stood out.
Matt
You need to make sure you're still referring to that particular Realm (and its sync configuration) when doing your writes. If you tried to do a write to let realm = try! Realm() even after this login, you'd still be referencing your default Realm, which isn't synchronized.
If you plan to make your entire app synchronized, you can set that particular Configuration as your default Realm's one:
if let user = user {
// Opening a remote Realm
let realmURL = NSURL(string: "realm://example.com:9080/~/VWF3")!
let config = Realm.Configuration(syncConfiguration: SyncConfiguration(user: user, realmURL: realmURL))
Realm.Configuration.defaultConfiguration = configuration
// All calls to `Realm()` will now use this configuration
}
Otherwise you can always just generate the configuration when you need it (Or have a global constants class that can manage it for you).
let realmURL = NSURL(string: "realm://example.com:9080/~/VWF3")!
let configuration = Realm.Configuration()
configuration.syncConfiguration = SyncConfiguration(user: SyncUser.current, realmURL: realmURL))
let realm = try! Realm(configuration: configuration)

Realm Swift Multi User Login

I am using Realm Swift in one of my iOS projects and one of the app requirements is to allow data of multiple users to co-exist. I am having an issue when the same users logs in as Realm is not able to identify the realm db file which is associated with that user.
Eg: So every time UserA re-logs in after logging out, a new Realm file is getting generated for UserA. This does not happen when UserA logs out and UserB logs in, then UserB logs out and UserA logs in.
UserA (logout) -> UserB (login) -> UseB (logout) -> UserA (login) [This works]
UserA (login) -> UserA (logout) -> UserA (login) [This does not work, a new Realm file is created and if there is a migration existing then try! Realm() also fails]
My AppDelegate code within application:didFinishLaunchingWithOptions looks like the following.
func setDefaultRealmForUser() {
var config = Realm.Configuration()
// Inside your application(application:didFinishLaunchingWithOptions:)
let currentLoggedInRegId = NSUserDefaults.standardUserDefaults().valueForKey(Constants.UserDefaults.CurrentLoggedInRegId)
if currentLoggedInRegId != nil {
let registrationId = currentLoggedInRegId as! String
// Use the default directory, but replace the filename with the username
config.fileURL = config.fileURL!.URLByDeletingLastPathComponent?
.URLByAppendingPathComponent("\(registrationId).realm")
}
// Set this as the configuration used for the default Realm
Realm.Configuration.defaultConfiguration = config
}
and my loginViewController code on success looks like the following
func setDefaultRealmForUser(onComplete: ()->()) {
var config = Realm.Configuration()
let currentLoggedInRegId = NSUserDefaults.standardUserDefaults().valueForKey(Constants.UserDefaults.CurrentLoggedInRegId)
if currentLoggedInRegId != nil {
let registrationId = currentLoggedInRegId as! String
// Use the default directory, but replace the filename with the username
config.fileURL = config.fileURL!.URLByDeletingLastPathComponent?
.URLByAppendingPathComponent("\(registrationId).realm")
}
// Set this as the configuration used for the default Realm
Realm.Configuration.defaultConfiguration = config
onComplete()
}
Update: I have made it work for the time being by loading the default Realm before loading the user realm config, which looks like the below code:
func reloadRealmWithDefault(onComplete: ()->()) -> (Void) {
var config = Realm.Configuration()
let defaultRealm = "default"
// Use the default directory, but replace the filename with the username
config.fileURL = config.fileURL!.URLByDeletingLastPathComponent?
.URLByAppendingPathComponent("\(defaultRealm).realm")
// Set this as the configuration used for the default Realm
Realm.Configuration.defaultConfiguration = config
onComplete()
}
But i am not completely happy with this approach as it's more a hack job.
What's the best way of doing a multi user login scenario?
It's probably not the greatest to continually change which Realm file the default configuration is pointing to. Realm itself caches references to files internally for performance reasons, so its not recommended to change the configuration once the file has actually been opened.
For a multiple user managed system, it would definitely make sense to have one Realm file per user.
On the code architecture level, I think it would be appropriate to have a singleton object that manages the state of the current user, and provides appropriately formatted Configuration objects whenever you need them.
class User {
static let currentUser = User()
private var userID: String? = nil
public var configuration: Realm.Configuration {
let configuration = Realm.Configuration()
configuration.fileURL = URL(filePath: "\(userID).realm")
return configuration
}
public func logIn(withUserID userID: String) {
self.userID = userID
}
public func logOut() {
self.userID = nil
}
}
let userRealm = try! Realm(configuration: User.currentUser.configuration)

How to reset and toggle API credentials

I have an app that can save multiple accounts to access my server API and retrieve data. Each account has its own credentials so the data pulled from server changes depending on which account is selected.
When my app runs first time, my API class checks my CoreData model to see which account is active and uses that account's credentials throughout the app. I can select a different account and set it as active and unset/inactivate all other accounts in my Accounts tableView and reset the Credentials object with the new selected account, but when I do my app still uses the account that the app first started with.
How do I structure my class to use the account's credentials that I select in my Accounts tableView? Below is my API class I use. This class is called when my app starts.
class API {
init(){
self.credentials = getActiveStore()
// I then use self.credentials to make API calls.
// Rest of the code is omitted for brevity
}
func getActiveStore() -> Credentials?{
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
let fetchRequest = NSFetchRequest(entityName: "User")
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.predicate = NSPredicate(format: "active = %#", 1 as NSNumber) //NSPredicate(format: "active == YES", argumentArray: nil)
var error:NSError?
let fetchResults = managedContext.executeFetchRequest(fetchRequest, error: &error) as! [WooStore]
if fetchResults.count == 1 {
let name = fetchResults[0].name
let url = fetchResults[0].url
let username = fetchResults[0].username
let password = fetchResults[0].password
return Credentials(name: name, url: url, username: username, password: password)
}
return nil
}
}
I would just use the NSManagedObject that is returned as your credentials object. There is no reason to create a new object that is just a copy of the NSManagedObject.
Subclass NSManagedObject, add any convenience methods that you want to the subclass and return it.
Update
My User is an NSManagedObject class which doesn't have an initializer
All objects have an initializer. Even NSManagedObject.
However, you don't need to initialize the object because it is already initialized and populated! It is sitting in your fetchResults array.
if let cred = fetchResults.lastObject {
return cred
} else {
return nil
}
Instead of referencing the first object in the array over and over again just to construct some other object.
Update
and I still don't see the effect I want to have. The data still belongs to previous user after I selected different account.
Based on your code, you determine the active user based on a boolean flag in the persistent store. While this is not a good idea, lets roll with it.
To change the user, you would need to set the current user's flag to 0. Then you need to fetch the soon to be active user and set their flag to 1. Then save the context so that the changes are persisted in the store. Then this code will do what you expect it to do.
You would do all of that wherever in your app you are switching users. NOT in the code you have provided here.

How to set/unset multiple accounts for API credentials

I have an app that saves multiple user accounts for retrieving data from my API. There is always one account set to be active or flagged TRUE. When I select a different account I deactivate previously active account and activate selected account or set it to be TRUE. I do these using CoreData in my Accounts table view.
When my app starts my API class checks my CoreData to see which account is active and assigns its credentials to my Credential object.
I then use these credentials to access and get data from my server. All works perfect until this point.
My problem is when I select a different account and reset my Credentials object in my didSelectRowAtIndexPath method, my API class still has the previously active account's credentials. I can see/print the Credentials object has the selected account's credentials.
How do I adjust my objects to have/use only selected account's credentials?
class API {
var credentials:Credentials!
init(){
self.credentials = getActiveStore()
// I use self.credentials.username to make API calls later in the code
}
func getActiveStore() -> Credentials?{
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext!
let fetchRequest = NSFetchRequest(entityName: "User")
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.predicate = NSPredicate(format: "active = %#", 1 as NSNumber)
var error:NSError?
let fetchResults = managedContext.executeFetchRequest(fetchRequest, error: &error) as! [User]
if fetchResults.count == 1 {
let name = fetchResults[0].name
let url = fetchResults[0].url
let username = fetchResults[0].username
let password = fetchResults[0].password
return Credentials(name: name, url: url, username: username, password: password)
}
return nil
}
}
Now this is the class that never changes. When I refresh my data after selecting a different account, the data I see belongs to previously active account. API class doesn't want to change.
Are you creating a new credentials every time you select a new cell? Because you credentials gets update just in the criation of the object
init(){
self.credentials = getActiveStore()
// I use self.credentials.username to make API calls later in the code
}
If you want credentials to be update a new instance of your API has to be created.
Another way is to create an update method and call it when a new cell is selected
func updateCredentials(){
self.credentials = getActiveStore()
// I use self.credentials.username to make API calls later in the code
}

Resources