Realm Swift: Do realm.beginWrite/commitWrite need to be on the same instance of “let realm = Realm()”? - swift

Sorry about the title, i’m not quite sure how to stay this succinctly. In any case, in my (iOS/Swift but not SwfitUI) app, the user can edit an object (for example Team) over a number of screens. When I first start, I create a team using
// Team is subclass of Realm Object
let team = Team()
let realm = try Realm()
// save copy of team into a property or something in memory
realm.beginWrite()
realm.add(team)
}
and then later in the app, after a number of interactions with the user:
var team = // fetch copy of team from memory
let realm = try Realm()
team.name="bar"
// other updates to "team".
try realm.commitWrite()
This seems to work fine, however I was just wondering if this is “OK” to do since I’m calling beginWrite and commitWrite in different calls to let realm = Realm().
I’m sure that the user has not and will not perform any other realm queries/writes during this flow since if the user “cancels” out of editing the “team”, I call realm.cancelWrite.
Lastly, does the answer change when using a synced realm vs. a local realm?
Thanks!
A more complete (yet still simplified) example of what I’m doing looks like this:
class MyViewController: UIViewController {
private var team: Team?
#IBOutlet weak var nameTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
do {
// setup realm configuration up here somewhere
let realm = try Realm()
realm.beginWrite()
let team = Team()
realm.add(team)
} catch {
print("Error: \(error)")
}
}
#objc func submitTapped(_ sender: Any) {
let name = self.nameTextField.text!.trim()
guard let team = self.team else {
return
}
do {
let realm = try Realm()
self.team.name = name
try realm.commitWrite()
} catch {
print ("Error: \(error)")
}
}
}

Related

NSFetchRequestResult returns duplicate data from the database

I have a simple entity with identifier (constraint) and name fields. In ViewController I try to add data to the database without worrying about duplicates and there really is no duplicate data in the database, but when I try to get records, I get twice as many of them. As I found out, this only happens when I try to write something to the database, and regardless of whether the attempt was successful, the data goes to my NSFetchRequestResult. What is the reason for this behavior?
DB content now:
ViewController:
class ViewController: UIViewController {
let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
super.viewDidLoad()
//if comment this loop I won't get duplicates
for i in 0...5 {
let ent = Entity(context: moc)
ent.identifier = Int16(i)
ent.name = "Username"
try? moc.save() //if the code is not executed for the first time, then the attempt is unsuccessful due to identifier constraint
}
let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Entity")
let fetchedEntities = try? moc.fetch(fetch) as? [Entity]
print(fetchedEntities!.count) // Output: 12 (actually only 6 records in the db)
}
}
Change your code where you create the objects to
for i in 0...5 {
let ent = Entity(context: moc)
ent.identifier = Int16(i)
ent.name = "Username"
}
do {
try moc.save()
} catch {
moc.reset()
}
This way you will remove the faulty (duplicate) objects from the context

Realm Async is not opening

I dont know what I am doing wrong. I am trying sync from my MongoDB Realm Cloud to local database for access.
I am following this instruction
https://docs.mongodb.com/realm/ios/sync-data/#ios-sync-data
here is my mongoDB Realm screen shot. It shows the items i want to sync with the partioningKey.
here is my code
override func viewDidLoad() {
super.viewDidLoad()
fetchStoreItems()
}
func fetchStoreItems(){
let user = app.currentUser()
let partitionValue = "store=walmart"
Realm.asyncOpen(configuration: user!.configuration(partitionValue: partitionValue),
callback: { (maybeRealm, error) in
guard error == nil else {
fatalError("Failed to open realm: \(error!)")
}
guard let realm = maybeRealm else {
fatalError("realm is nil!")
}
// realm opened
// All tasks in the realm
let storeItems = maybeRealm!.objects(Item.self)
let tasksThatBeginWithA = storeItems.filter("name beginsWith 'R'")
print("these are the item amount", tasksThatBeginWithA.count)
})
}
Authentication works fine but fetching the data returns empty with this message
Sync: Connection2: Session2: client_reset_config = false, Realm exists = true, async open = false, client reset = false
what am I doing wrong and how can I fix it?
Pretty sure you're accessing the wrong object, but it's a little hard to tell by the question. Items is the highlighted object in the screen shot so it seems that's the one you're after (?)
It looks like your database has two objects
Item
and
items
but your code is accessing the Item one
let storeItems = maybeRealm!.objects(Item.self)
not the items one.

When saving to CoreData from Today Extension, Data only accesable from Widget - NOT from Main Application

I'm writing an Application for iOS with Swift 3.
In my Main iOS App I'm using an NSFetchedResultsController to show saved items as an TableView.
It's (of course) possible to add new items from another ViewController.
-> Thats all working really awesome.
So I thought it would be great if I could add an new item really fast from an TodayWidget.
What I did:
Created an SharedCode Framework and added AppGroup to my Main App and the Today Widget.
Moved my CoreDataStack.swift Class, the .xcdatamodeled and my Item+CoreDataClass.swift and Item+CoreDataProperties.swift files to my SharedCode Framework.
Sublcassed NSPersistentContainer to addforSecurityApplicationGroupIdentifier for my appGroupID
Rewrote my CoreData code in my ViewController to use the created CoreDataStack.shared.managedContext
Testing. AWESOME. My NSFetchedResultsController is working, and adding new Items works as expected. Nice. -> moveing on.
In my Today Widget I'm using an simple NSFetchRequest to get the last entered Item from CoreData. Works perfectly!
Added Buttons to modify the data and saving it to CoreData. Here I'm also using CoreDataStack.shared.managedContext and CoreDataStack.shared.save()
Automatically reloading my Data AND?! AWESOME. All working very nice. Data is saved and new data is shown in the Today Extension. Also when I count the results from my NSFetchRequest the number of Items is increased.
NOW TO MY PROBLEM:
All the data that I'm adding through the Extension is not showing in my main iOS App.
There when I'm fetching the Items from CoreData there are only the ones showing that I created in the main app. -.-
I have no Idea whats going wrong.
It's like I have two different CoreDataStores.
Also after I once added an Item trough the Widget - the Widget does not fetch an Item from the main App. Only the last one entered from the Widget.
- Here is my Code:
CoreDataStack.swift
public class CoreDataStack {
public static let shared = CoreDataStack()
public var errorHandler: (Error) -> Void = {_ in }
//#1
lazy var persistentContainer: PersistentContainer = {
let container = PersistentContainer(name: ServiceConsts.modelName)
var persistentStoreDescriptions: NSPersistentStoreDescription
let storeUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: ServiceConsts.appGroupID)!.appendingPathComponent("\(ServiceConsts.modelName).sqlite")
let description = NSPersistentStoreDescription()
description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true
description.url = storeUrl
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: ServiceConsts.appGroupID)!.appendingPathComponent("\(ServiceConsts.modelName).sqlite"))]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
//#2
public lazy var managedContext: NSManagedObjectContext = {
return self.persistentContainer.viewContext
}()
//#3
// Optional
public lazy var backgroundContext: NSManagedObjectContext = {
return self.persistentContainer.newBackgroundContext()
}()
//#4
public func performForegroundTask(_ block: #escaping (NSManagedObjectContext) -> Void) {
self.managedContext.perform {
block(self.managedContext)
}
}
//#5
public func performBackgroundTask(_ block: #escaping (NSManagedObjectContext) -> Void) {
self.persistentContainer.performBackgroundTask(block)
}
//#6
public func saveContext () {
guard managedContext.hasChanges else { return }
do {
try managedContext.save()
} catch let error as NSError {
print("Unresolved error \(error), \(error.userInfo)")
}
}
}
PersistentContainer.swift
class PersistentContainer: NSPersistentContainer {
internal override class func defaultDirectoryURL() -> URL {
var url = super.defaultDirectoryURL()
if let newURL =
FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: ServiceConsts.appGroupID) {
url = newURL
}
return url
}
}
Can anyone help?
I have really no idea what I'm doing wrong.
Would be so awesome if anyone can give me a tip :)
Thanks <3
I finally fixed my issue <3.
It was so simple.
After hours and hours of testing testing crying and putting an axed through my MacBook ;) I found the thing thats what close killing me.
I testet if a simple Fetch Request in the Main App would get the added Items from the Today Extension. And that was working.
And than I saw it. In my Main App.
The NSFetchedResultController. I used an cache.
So now I notify the Main App when a new Weight was added via the widget - and than I call my refresh function.
func handleRefresh(_ refreshControl: UIRefreshControl) {
NSFetchedResultsController<NSFetchRequestResult>.deleteCache(withName: "weightCache")
do {
try fetchedResultsController.performFetch()
setupView()
self.tableView.reloadData()
} catch let error as NSError {
print("Fetching error: \(error), \(error.userInfo)")
}
refreshControl.endRefreshing()
}
So simple.
Just delete the cache.

Deleting an object in Realm and keeping tableview in sync

I am trying to understand what I am doing wrong here.
Have two basic viewControllers: a tableView and a detailView.
I am subscribing to notification to reload the tableView if the data is modified. I can modify a record from the detailViewController, and the changes get reflected. Even delete a record from the tableView, and it works.
BUT I would also like to add a delete button on the detailView, but then the viewController and Realm gets out of sync and crashes. 'RLMException', reason: 'Object has been deleted or invalidated.'. The next time I load the app the record is deleted so it works.
How can I delete an object in a different viewController and keep the tablieView controller updated?
class TableViewController: UITableViewController {
var token:NotificationToken?
var expenses: Results<Expense> {
get {
let realm = try! Realm()
return realm.objects(Expense.self)
}
override func viewDidLoad() {
super.viewDidLoad()
let realm = try! Realm()
token = realm.addNotificationBlock { [weak self] notification, realm in
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
class DetailViewController {
func deleteButtonTapped() {
let realm = try! Realm()
if let expense = self.expense {
try! realm.write {
realm.delete(expense)
}
// crashes during load
performSegue(withIdentifier: "unwindToExpensesTable", sender: self)
}
}
Make sure you aren't retaining any references to the deleted Expense object. After deleting the object clear out any reference you may have in DetailViewController, also make sure it isn't in an array somewhere that you might access it again.

Realm causing program to behave unexpectedly. (OS X and Swift)

So I am creating a fairly simple program using Realm as my database. I am fairly new to programing in Swift (or any OS X or iOS environment.) In my program when a button is pressed IBAction func createInvoice I want a few things to happen, I want to count the previous rows in the database and create an invoice number, I want to write new data to the database and I want to call a new view and view controller and pass along the invoice number. My code works except for one thing when using Realm the new view controller is called (override func prepareForSegue) before the invoice number is created so a 0 value is passed along to the new view controller.
If I create a dummy invoice number value such as let invoicenumber = 42 everything works perfectly. It seems that Realm is causing things to happen 'out of order' How can I make the veiwcontroller wait for a value before loading?
#IBAction func createInvoice(sender: AnyObject) {
let realm = Realm()
let invoicepull = Invoice()
let invoicecount = realm.objects(Invoice)
let invoicenraw = invoicecount.count
let a = 100
let invoicenumber = a + invoicenraw
var invoicefile = Invoice()
invoicefile.inumber = invoicenumber
invoicefile.cnumber = clientcombo.stringValue
invoicefile.cost = owed.doubleValue
invoicefile.paid = paid.doubleValue
invoicefile.sevicecode = service.stringValue
invoicefile.dateofservice = NSDate()
// Save your object
realm.beginWrite()
realm.add(invoicefile)
realm.commitWrite()
//Sent notification
performSegueWithIdentifier("cinvoiceseuge", sender: nil)
println("Inside Action")
println(invoicenumber)
dismissViewController(self)
}
override func prepareForSegue(segue: NSStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "cinvoiceseuge") {
//Checking identifier is crucial as there might be multiple
// segues attached to same view
var detailVC = segue.destinationController as! invociegenerator;
detailVC.toPass = invoicenumber
println("Inside Sugue")
println(invoicenumber)
}
}
If createInvoice is happening on a different thread than prepareForSegue, you'll have to refresh the realm (Realm().refresh()) before accessing your invoicenumber variable (which I assume is of type RealmSwift.Object).
I have solved this issue, thanks to the help of #Shmidt by using Realm's built in notification center. To use the notifications you can use this basic structure.
var notificationToken: NotificationToken?
deinit{
let realm = Realm()
if let notificationToken = notificationToken{
realm.removeNotification(notificationToken)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let realm = Realm()
notificationToken = realm.addNotificationBlock { note, realm in
println("The realm is complete")
}
...
}
One small other error in my code was let invoicenumber = a + invoicenraw I needed to drop the let as it is a variable and not a constant.