Reputation: 87
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.
Upvotes: 3
Views: 734
Reputation: 35659
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.
Upvotes: 1
Reputation: 1610
@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.
Upvotes: 1