Is there a way in Realm to implement order_by for nested objects - ios

I am trying to retrieve nested objects sorted in a specific order when I retrieve the parent object using realm.objects(). And equally importantly, maintain the sort order if any nested object is modified.
I have searched related issues but they are either dated or not quite the issue I am describing
Here's an example: I have 'user's in Realm, each of which have 'task's (with extraneous fields omitted for clarity)
class task: Object {
#objc dynamic var priority = 10
}
class user: Object {
#objc dynamic var name = ""
let tasks = List<task>()
}
I create a few users and then append tasks for any given user (a table in my UI with section for each User and Tasks for that User as rows in that user's section, SORTED by priority).
Default priority starts at 10, and can be changed in the range from 1-10 at any time.
When I retrieve users:
// (1)
users = realm.objects(user.self).sorted(byKeyPath: "name")
I want to retrieve their tasks sorted by priority.
Note that the priority is modified after the initial retrieval of users (meaning Results's task object's priority is changed under a realm.write()). To clarify, somewhere in my code, for a given user's task, I do the following:
realm.write() {
task1.priority = newPriority
?
Which means that the user's task list should always be sorted by priority and not require repeating (1) above. I can't sort the user.tasks property because it is a "let" variable.
Note that both user and task objects have a sort order.
I could do index(remove/add) on tasks after I update 'priority' above but have not tried this yet. However, rather than do this manually (and assuming it works, and assuming there isn't a delete/add of 'task' happening alongside the reshuflle, ...), isn't that the whole idea behind ORMs, to make such things straightforward?
Anyway to do this? With Extensions on Realm? ??? Open to suggestions on alternative approaches vs nested objects.

Great question and an answer that really shows off Realms live updating capability.
Restating the question
A user has tasks and we want to work with those tasks as an ordered
list, and if there's a change to the order, keep them ordered by the new order
Using the two classes presented in the question, we have a button that calls a function to query realm for a user and store that user in a class var
var myUser: UserClass!
func loadUser() {
if let realm = gGetRealm() { //my code to connect to Realm
let userResults = realm.objects(UserClass.self)
if userResults.count > 0 {
let user = userResults[0]
self.myUser = user
}
}
}
then a button that calls a function to simply print out that users tasks, ordered by priority
func printOrderedTasks() {
let sortedTasks = self.myUser.tasks.sorted(byKeyPath: "priority")
for task in sortedTasks {
print(task)
}
}
So for this example, I created 4 tasks, and added them to a users tasks list and wrote the user to realm, the priority initial order is: 10, 0, 1, 4
Then loadUser loads in and store the first user available and assigned it to the class var.
The printOrderedTasks outputs the tasks, in ascending order. So after loading the user, clicking printOrderedTasks the output is
0
1
4
10
then, using Realm Studio, change the 1 to a 6 and click the printOrderedTasks again and the output is
0
4
6
10
without having to reload anything.
Realm objects are live updating so as long as you have a reference to them in your code, any changes are reflected live.
You could expand upon this by adding an observer to that object and Realm will notify the app of the event, to which you could reload a tableView or let the user know of the change.
Edit:
To take this a step further, the users tasks are also live updating objects and if you set a sort on them, those results maintain their sort.
For example, let's re-write the above code to keep track of a users tasks that maintain a live sort. I've re-written the above code and eliminated the user class var and added a tasks class var. Note that we never need to re-sort the tasks, the sort order is set initially and they will stay sorted from that point forward.
var myUserTasks: Results<TaskClass>!
func loadUserAndGetTasks() {
if let realm = gGetRealm() {
let userResults = realm.objects(UserClass.self)
if userResults.count > 0 {
let user = userResults[0]
self.myUserTasks = user.tasks.sorted(byKeyPath: "priority")
}
}
}
func printTasks() {
for t in self.myUserTasks {
print(t)
}
}
The initial order was 10, 0, 1, 4 as above. If we then change the 1 to a six using Realm Studio, and then run the printTasks function, you'll see the ordering was automagically done because the Results are live updating.
0
4
6
10
The cool thing here is that you don't need to keep resorting the tasks - they maintain their sort order.

#Jay's answer is great as always, but I'd take his suggestion and make it intrinsic to the user object. This is my 'usual' way of handling requirements such as this.
As you say, the user object tasks property is a let, but this is how a Realm object is defined. That's not to say you can't add your own computed properties to control access to the object.
If you only want to see the tasks as an ordered list, then add a property that gives you that, e.g.
extension user
{
var orderedTasks: Results<task>
{
return tasks.sorted(byKeyPath: "priority")
}
}
Then always use that property to access the tasks. Using an extension there is my own style - I just keep the data declarations in the class declaration, and then add an extension with any computed properties or functions - but you can just add the declaration straight into the class definition.

Related

Realm Swift: Question about Query-based public database

I’ve seen all around the documentation that Query-based sync is deprecated, so I’m wondering how should I got about my situation:
In my app (using Realm Cloud), I have a list of User objects with some information about each user, like their username. Upon user login (using Firebase), I need to check the whole User database to see if their username is unique. If I make this common realm using Full Sync, then all the users would synchronize and cache the whole database for each change right? How can I prevent that, if I only want the users to get a list of other users’ information at a certain point, without caching or re-synchronizing anything?
I know it's a possible duplicate of this question, but things have probably changed in four years.
The new MongoDB Realm gives you access to server level functions. This feature would allow you to query the list of existing users (for example) for a specific user name and return true if found or false if not (there are other options as well).
Check out the Functions documentation and there are some examples of how to call it from macOS/iOS in the Call a function section
I don't know the use case or what your objects look like but an example function to calculate a sum would like something like this. This sums the first two elements in the array and returns their result;
your_realm_app.functions.sum([1, 2]) { sum, error in
if let err = error {
print(err.localizedDescription)
return
}
if case let .double(x) = result {
print(x)
}
}

How to handle data deletions in SwiftUI (iOS) without crashing the app

I have a SwiftUI calendaring app with a UI similar to the built-in Calendar.app. I'm getting crashes whenever I try to delete events. The overall lifecycle of my app is as follows:
Download calendar data from server and populate models ([Events], [Users], [Responses] etc)
Transform the source data into a more structured format (see https://stackoverflow.com/a/58583601/2282313)
Render list view of events, each event linking to a Detail View and an Edit modal (very similar to calendar.app)
When an event is deleted, I tell the server to delete the event (if it's a recurring event, the server will delete multiple events), then refresh my data from the server by re-downloading the data, re-populating the models and re-generating the structured data (which causes the list to refresh).
When I do this, I get crashes coming from my calculated values because event data displayed in the detail view is no longer available. For example, I get the array index of a user's RSVP as follows:
var responseIndex: Int {
userData.responses.firstIndex(where: { $0.user == response.user && $0.occurrence == response.occurrence })!
}
I thought this was because I hadn't dismissed the view displaying the deleted event before updating the data, but even if I delay the data refresh until the view is no longer displayed, I still get the crash (SwiftUI seems to keep these views in memory).
What is the right way to handle data deletion? Do I need to keep deleted events in my UserData EnvironmentObject and just mark them as "deleted/hidden" to avoid this issue, or is there a better way to handle it?
There's quite a bit of code involved in this, so it's tricky to provide a sample I'm happy to add relevant bits if asked.
EDIT: I found this article which clarifies something really well: https://jasonzurita.com/swiftui-if-statement/
SwiftUI is perfectly happy to try and render nil views, it just draws nothing. Counter-intuitively, a good way to avoid crashes and make the compiler happy is to set your code up around this.
Original "answer" follows...
I don't know if this is the "right" way to do this, but I ended up making sure that none of my UserData is ever deleted to avoid the crashes. I added a "deleted" bool to my Occurrence (i.e. Event) object, and when I refresh my structured data, I get the latest data from the server, but check to see if any of the old ones are no longer present. Steps are:
Get latest list of occurrences from server
Create a second init() for my structured data which takes the existing data as an argument
Inside the new init(), flatten the structured data, check for deleted items against the new data, update data which hasn't been removed, cull duplicates, then merge in net new data. Once that's done, I call my original init() with the modified data to create new structured data
Code looks like this:
init(occurrences: [Occurrence], existing: [Day]) {
// Create a mutable copy of occurrences (useful so I can delete duplicates)
var occurrences = occurrences
// Flatten the structured data into a plan array of occurrences again
var existingOccurrences = existing.compactMap({ $0.occurrences }).flatMap { $0 }
// Go through existing occurrences and see if they still exist.
existingOccurrences = existingOccurrences.map {
occurrence -> Occurrence in
let occurrenceIndex: Int? = occurrences.firstIndex(where: { $0.id == occurrence.id })
// If the occurrence no longer exists, mark it as "deleted" in the original data
if occurrenceIndex == nil {
var newOccurrence = occurrence
newOccurrence.deleted = true
return newOccurrence
// If it still exists, replace the existing copy with the new copy
// (in case it has changed since the last pull from the server)
// Remove the event from the "new" data so you don't get duplicates
} else {
let newOccurrence = occurrences[occurrenceIndex!]
occurrences.remove(at: occurrenceIndex!)
return newOccurrence
}
}
// Merge the existing data (with deleted items marked) and the updated data (with deleted items removed)
let finalOccurrences = existingOccurrences + occurrences
// Re-initialize the strutured data with the new array of data
self = EventData(occurrences: finalOccurrences)
}
Once this was done, I had to update my code to make sure I'm always using my structured data as the source of truth (which I wasn't doing before because accessing the "source" flat data was often easier, and I've updated my ForEach in my list view to only render a row if deleted is false.
It works! It's perhaps a sub-optimal way to solve the problem, but no more crashes. Still interested to hear better ways to solve the problem.

Value type design pattern to replace class

We are a looking for a value type design pattern in swift that will allow us to create a shopping cart to hold Products. We are currently using a class but that is a reference type and when we try to add two different version of the same product (i.e. with a different colors or sizes), the first item we added gets changed to the second item we added because it points to the same object in memory.
The design pattern needs to be “global” so we can access it from any page in the app. Right now this is our Cart class that stores all the items in the cart. What do we need to do to make this a value type or how does it need to be reengineered to use a struct without a class?
class Cart : NSObject {
var allProductsInCart = [MainProduct]()
override init() {
super.init()
}
class var sharedCart: Cart {
struct Static {
static let instance = Cart()
}
return Static.instance
}
}
The problem we are getting is that we need the products in the cart to be of custom class “MainProduct.” Right now as you can see, they are stored as “MainProduct.” Do we need to switch the products to a struct or other design pattern as well? How would we do that?
Yes, given the desired behavior between a value type vs. reference type you should use a Struct.
A commonly used "pattern" for doing this is called "Redux".
The idea is that you have one, immutable version of the "state" of your app.
It can be accessed from anywhere and the only way to update it is through "actions". These will reconstruct the entire state with the required updates.
ViewControllers and views, etc... subscribe to updates of various parts of the state.
So you could have an AppState that contains a ShoppingCartState. When a product is added to it your CartViewController will be informed of this update and can update its view etc...
There are many different frameworks that are built to use Redux so I won't recommend one as you should find the one that is right for you. But I believe this pattern best suits the usage you are after.

Copying all objects (preserving their relationships) from one Realm to another

tl;dr: I'm trying to copy every single object from one Realm to another, but I get twice as many objects or 4 times as many objects as I should (because of their relationships, I presume). (Edit: I actually get many many more! Check my last edit at the bottom.)
I'm trying to allow my users to backup and restore their Realm databases.
I have a Book class and a ReadingSession class. A Book can have many ReadingSessions:
class Book: Object {
// (…)
var readingSessions: [ReadingSession] {
return linkingObjects(ReadingSession.self, forProperty: "book")
}
}
class ReadingSession: Object {
// (…)
var book: Book?
}
To restore from the backup I tried doing this:
func restoreBackupFile(backupFileToRestore: String) {
// (…) I omitted the NSFileManager related part.
let config = Realm.Configuration(path: "\(tmp)/ReadingLog.realm", readOnly: true)
let backupRealm = try! Realm(configuration: config)
let defaultRealm = try! Realm()
let results = backupRealm.objects(Book)
try! defaultRealm.write {
for result in results {
defaultRealm.create(Book.self, value: result)
}
}
That copied all the Book objects alright, but not the ReadingSessions related to those Books.
So I added some code to copy every ReadingSession too:
// (…)
let bookResults = backupRealm.objects(Book)
let sessionResults = backupRealm.objects(ReadingSession)
try! defaultRealm.write {
for result in bookResults {
defaultRealm.create(Book.self, value: result)
}
for result in sessionResults {
defaultRealm.create(ReadingSession.self, value: result)
}
}
And that gave my defaultRealm 4 times as much books as it should! I used a database with 10 books to test it out, and after running that code my default Realm had 40 books, 20 with the right ReadingSessions associated to them and 20 without any ReadingSessions at all.
I tried copying just the ReadingSessions to see if the related Books would be created too, and then I got twice as many Books as I should, half of them with the right ReadingSessions and half of them without any.
So how can I do what I want? Copy every single object from a Realm to another keeping their relationships intact and without getting duplicates like I am now?
(I know I can just replace the database files, but I want my users to be able to restore data from one database without losing the data from the other.)
Thanks in advance,
Daniel
Edit: I've been experimenting some more and it seems that if I copy just the ReadingSessions it creates a book for each reading session copied, even if they're related to the same book. If I have a book with 60 reading sessions, for instance, that book will be created 60 times. So my problem is even worse than I thought. When I copy the Books they don't come with their related ReadingSessions. When I copy the ReadingSessions they create many repeated books.
When you create new objects on base of objects from another Realm, then linked objects are copied as well and recursed into. But backreferences are just convenience getters on your object and neither known to the schema nor accessible (for Swift), so they are not traversed at all. Even though linked objects are recursed into, they are not de-duplicated at all automatically. So this would loop for circular relations.
You might want to configure a primary key on Book and use create(… update: true) to copy your ReadingSessions. When you copy both, you ensure, that unread books are copied as well, if that's a valid case for your schema.

Class or Struct for a model similar to a relational database?

The application in question is built as follows:
A user selects a job
the job can have many components
each component can have many lineitems.
I am not clear on how this should be structured - should this be class or structs? Seeing that only one job is being processed at a time, I am fairly confident that jobs should be a class. However, when there are multiples of a certain object type, I am not exactly clear on how to form them, like the components and lineitem objects.
The application consists of ViewControllers and TableViewControllers. All the data is fetched from a server in JSON and populated into the appropriate view as needed. Here are the object types as they are currently setup:
A job object:
// Job Object
//
public struct Job {
static var globalId : String?
static var name : String?
static var status : String?
static var client = Client()
static var components = Array<Component>()
// etc..
}
A Component like so:
// JobComponent Object
//
public struct Component {
var name:String? = ""
var fmRecordId : String?
var startTS:NSDate?
var endTS:NSDate?
var notes:String? = ""
var items = Array<Lineitem>()
// etc...
}
and finally, a lineitem:
// Lineitem Object
//
public struct Lineitem {
var fmRecordId = String()
var itemName = String()
var itemNumber = String()
// etc...
}
All of these object are built within a public class called "PL".
When a user selects lineitem and edits it's values, the values are not available outside the VC in which they are edited because the VC isn't referencing the lineitem that is was passed, it is simply copying it. The same happens with components.
A workaround I found was to use the the Job struct PL.Job.self and always modify the components and lineitems like so where i = a desired index in the array:
PL.Job.components[i] to access a component
PL.Job.components[i].items[i] to access a specific item within that component.
However, this doesn't scale very well.
The desired behavior is to be able to pass a reference to a particular instance of an object around rather than pass around the index path of those objects in the PL.Job object.
I am well aware there is something wrong with how this is currently structured, but could someone please point me in the right direction?
A couple of points:
You can only pass class instances by reference. If you want to be able to pass a reference to a particular LineItem or Component or Job, and you want to be able to make changes to that object that are effective everywhere, then you need to define them as classes and not structs. Instances of struct types are always passed by value and not be reference. And when a value type is passed, it is copied, meaning that you create an entirely new copy of the object, and mutating the copy has no effect on the original.
Your Job struct only has static properties - i.e., there will only ever be one globalId, name, status etc. throughout your entire application. If you want to have multiple instances of Job, then these should not be static properties. You say that only one Job will be processed at a time, so maybe that was intentional. Either way, it is still often preferable to create an instance of a Job class that has those properties. It certainly would give you more flexibility later if you decide to make it possible to hold references to multiple jobs in memory, or to allow the user to select between different jobs, or switch between jobs, etc. For example, you may want to allow a user to switch to the Job they were processing earlier without necessarily destroying the Job that they are working on now.
But I think the main point is that you will need to define your objects as classes if you want to be able to pass them by reference. If you modify an object that is passed by reference, all other references to the same object will show the same changes (because, after all, they are just references to the same object). That doesn't work with value types, like structs.

Resources