iOS Swift Realm Sync Issue - ios

I used this configuration code for local storage.
var configuration = Realm.Configuration.defaultConfiguration
configuration.encryptionKey = getKey() as Data
I used this configuration code for sync with server.
let syncServerURL = URL(string: serverUrl + objectName!)!
var configuration = Realm.Configuration.defaultConfiguration
configuration.encryptionKey = getKey() as Data configuration.syncConfiguration = SyncConfiguration(user: SyncUser.current!, realmURL: syncServerURL)
I create some data without sync, it is saved locally. However, if I turn on sync(different configuration), the previously created data(locally) is not synced to the server? How to sync already saved data?

Realms are uniquely referenced by one of three mutually exclusive Realm.Configuration properties:
fileURL
inMemoryIdentifier
syncConfiguration
Realm configurations with any of these properties with different values will refer to separate Realms.
So your initial Realm has a fileURL value (with nil for the other two properties), whereas your second Realm has a syncConfiguration value (with nil for the other two properties) so they refer to separate Realms.
If you wish to copy data from the first (local) Realm to the second (synced) Realm, you may do so using Realm's APIs for reading objects & creating objects just like you would for any other data.

Related

Disable Realm Sync only for non-premium users

I'm creating an iOS application, where I intend to provide data sync across device feature, only to the premium users. I find Realm Sync as a good solution to keep the local on-device database and cloud MongoDB Atlas in sync. However, I don't want to sync the data of the non-premium users to the cloud database.
I'm enlisting a couple of ways that I can think of to prevent Realm Sync from triggering for non-premium users, but I'm not sure on what is the best way for this problem.
Prevent syncing by leveraging Sync permissions - I can store list of premium user ids and only give sync permissions to those users.
{
"%%user.id": [
"5f4863e4d49bd2191ff1e623",
"5f48640dd49bd2191ff1e624",
"5f486417d49bd2191ff1e625"
]
}
Configure Realm objects on client side i.e. only allow all Realm objects / models if the user is premium.
// Get a configuration to open the synced realm.
var configuration = user.configuration(partitionValue: "user=\(user.id)")
// For non-premium user it would be [User.self]
configuration.objectTypes = [User.self, Project.self]
Realm.asyncOpen(configuration: configuration) { [weak self](result) in /*...*/ }
I'm looking for insights / recommended approach to this problem.
Edit
I've a few additional questions about handling two use cases differently - non-premium one by opening a local only Realm() and the premium one with Realm.asyncOpen().
How to handle a use case when an existing user switches to a premium subscription? Should calling Realm.asyncOpen() suffice or do I need to do any special handling?
I plan to sync all my User (custom document in a collection) records for all users (premium + non-premium). My guess is I should open a normal Realm for all my conent and synced Realm with just [User.self] object in the configuration.
This is super easy to do!
When you only want to work with a local realm, connect to it with no config - like this
let realm = try! Realm()
let someObject = realm.results(SomeObject.self)
or a config that maybe contains a local file name. All of the app data will only be read and written locally with no sync'ing.
When you want to use MongoDB Realm Sync, connect to it like this
let app = App(id: YOUR_REALM_APP_ID)
// Log in...
let user = app.currentUser
let partitionValue = "some partition value"
var configuration = user!.configuration(partitionValue: partitionValue)
Realm.asyncOpen(configuration: configuration) { result in
switch result {
case .failure(let error):
print("Failed to open realm: \(error.localizedDescription)")
// handle error
case .success(let realm):
print("Successfully opened realm: \(realm)")
// Use realm
}
}
and then later with a config
let config = user?.configuration(partitionValue: "some partition")
let realm = try! Realm(configuration: config)
EDIT
Answering the two followup question:
How to handle a use case when an existing user switches to a premium
subscription? Should calling Realm.asyncOpen() suffice or do I need to
do any special handling?
Connecting to MongoDB Realm with the Sync'ding solution will add additional files and start syncing. If this is a new user that's 'premium', theres nothing else to do, other than (initially) ensure your objects are correctly structured with _id and partitionKey properties.
If this user is upgrading from a non-premium local only to a premium that's sync'd you will need to copy your realm objects from the local only realm to a sync'd realm.
There are several ways to to that; probably the simplist is to include code in your app then when upgrading, connects to a sync realm (using .async), then connects to your existing local realm and finally iterate over the objects to copy to the sync'd realm.
Another option is to export the the realm objects as JSON and then write them to the server directly. The next time your app connects with .async, it will force a client reset and download and create the locally sync'd files. There are some tidbits of information that may help with this particular process in the Realm Legacy Migration Guide
I plan to sync all my User (custom document in a collection) records
for all users (premium + non-premium). My guess is I should open a
normal Realm for all my conent and synced Realm with just [User.self]
object in the configuration.
Non-premium users don't sync so they are not really 'users' as such. You wouldn't need to store them or sync them so you really don't need any authentication or store any data on the server - it's just a locally run and used app so there isn't even a 'user' object to worry about. You will need to do that once they upgrade.

Core data testing: force external storage on iOS

I am trying to test core data's External Storage. Is there a way to force core data to write the data to a file? Adding a large data/image does not seem to work.
https://stackoverflow.com/a/7926505/429763
func setupOnDiskTestStore() {
let mom = NSManagedObjectModel.mergedModel(from: [Bundle.main, Bundle(for: type(of: self))])
psc = NSPersistentStoreCoordinator(managedObjectModel: mom!)
let store = try! psc.addPersistentStore(ofType: NSSQLiteStoreType,
configurationName: nil,
at: storeURL(),
options: nil)
expect(store).notTo(beNil())
moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
moc.persistentStoreCoordinator = psc
}
There's no way to force it to use external storage. The checkbox says it's allowed, but there's no way to make it required. As noted in the page you link to, it's related to the size of the data, so larger data blobs will be stored externally.
For testing only-- you can inspect the contents of the external storage directory to see what's there. This is completely undocumented, so you can't rely on it in an app, but it might be useful for testing. The data goes in a hidden directory in the same directory as your persistent store file. For example if your persistent store is named MyData.sqlite and it's located in the application support directory (which is where NSPersistentContainer puts it, unless you tell it to use a different location), then the external storage (if any) will be in Application Support/.MyData_SUPPORT/_EXTERNAL_DATA/. There will be one file per externally stored data object.
You can't match the files to managed objects by name, because the file names are UUIDs and the UUIDs aren't available in code. But if you were to create a single new managed object with external storage enabled, and you then found that there was a single new file in that directory, you'd know that the new file corresponds to the new object.

Switching between realms (iOS / Swift 3)

I was trying to log in or register a user and connect them to an existing realm. Then, depending on the info stored in that realm, I may need them to instead connect to a different realm.
Is it not possible to try! Realm with a different configuration after it is initially configured? Is it discouraged? Does it need to be done outside of the initial DispatchQueue?
Here is the code:
SyncUser.logIn(with: usernameCredentials, server: URL(string: "http://11.22.333.0:9080")!) {
user, error in
guard let user = user else {
fatalError(String(describing: error))
}
DispatchQueue.main.async {
let configuration = Realm.Configuration(
syncConfiguration: SyncConfiguration(user: user, realmURL: URL(string: "realm://11.22.333.0:9080/ab56realmURL/NameOfRealm1")!)
)
self.realm = try! Realm(configuration: configuration)
if (someCheckOfData in realm) {
let configuration2 = Realm.Configuration(
syncConfiguration: SyncConfiguration(user: user, realmURL: URL(string: "realm://11.22.333.0:9080/ab56realmURL/NameOfRealm2")!)
)
self.realm = try! Realm(configuration: configuration2)
}
}
}
Thanks so much for any help!
No, it's not discouraged. All you're doing here is creating 2 discrete copies of Configuration, which will then subsequently be creating 2 separate Realm instances on your server.
The two will be completely separate, so there's no chance of causing an exception by incorrectly changing the configuration after it was used to create an initial Realm instance.
One thing we do recommend though is not holding onto specific Realm references like that though. They are not thread safe, and GCD isn't guaranteed to execute the same queues on the same threads, so you may be setting yourself up for a future exception.
If this is going to be your primary Realm, it's usually recommended to set that Configuration as the default Realm one. Otherwise, since Configuration is thread-safe (Assuming you don't modify it later), you can hold onto that, and use it to try! Realm(configuruation:) whenever you actually need to use Realm.

I cannot get "default.realm" file in my iOS Simulator

I want to access the Realm data file which is created on iOS Simulator. With the instruction of this question, I got to the following directory:
~/Library/Developer/CoreSimulator/Devices/0AB2D954-A3D5-4ABF-A5C5-CC470FB4ABDE/data/Containers/Data/Application/7192723E-7582-48EB-8B66-14E1073D7CE6/Documents
However, the directory has only the following Realm files:
default.realm.note
default.realm.log_a
default.realm.log_b
default.realm.log
default.realm.lock
and I cannot find the Realm file default.realm.
Also, when I open up the Realm Browser and access to the directory, all the files above except default.realm.note is grayed-out and I cannot access the Realm data.
Note that I create a Realm instance and save data by realm.write:
try! self.realm.write {
for (_, value) in json { // json is from SwiftyJSON
let pitcher = Player()
player.year.value = value["year"].int
player.name = value["name"].stringValue
self.realm.add(player)
}
}
So how can I access the Realm data file?
Realm's auxiliary files (.note, .lock, etc) are always created next to the .realm file itself. So it seems like something is deleting the realm file without deleting the auxiliary files, which would explain why you can't find it.
Please make sure you're not deleting this Realm file somewhere in your code.

How to pass Core Data objectID and use it with Continuity

Just trying to update some Core Data apps with Continuity and have run into a bit of an issue with using the selected objects ID in the userInfo dictionary to display the correct data on the continuing device.
My first thought was to use the ObjectID, however on the receiving device this would never find a corresponding object in the Core Data store.
As it turns out the URL representation of the objectID contains the UUID of the store itself, and because the two stores UUID's are different this is obviously going to fail.
So I guess I could replace the Core Data store's UUID in the URL with the continuing devices UUID and use this, and no doubt it would work.
The Url seems to be of the following format
Does anyone know what the correct way would be to pass a reference to an object between two devices with core data stores that are synchronised via iCloud?
I'll answer this one myself and see if there are any better answers...
I pass the url of the objectID (from objectID.URIRepresentation) using Continuity API and on the receiving device create a new URL using the following:
url is the url passed in the NSUserActivity.userInfo dictionary
let storeUUID = self.identifierForStore()
// Switch the host component to be the local storeUUID
let newURL = NSURL(scheme: url.scheme!, host: storeUUID, path: url.path!)
func identifierForStore()->NSString? {
if let store = self.persistentStoreCoordinator?.persistentStores[0] as? NSPersistentStore {
return store.identifier
} else {
return nil
}
}
This seems to work just fine - hope it helps someone

Resources