realm-swift
realm-swift copied to clipboard
Enhancement to Results<T> Queries and Collection Update notifications
Enhancement Request
Goals
Search is a critical part of most apps that contain a lot of data. On top of that, modern user interfaces have created the standard of "live" search and users expect this feature. Live search generally includes the following features:
- As the underlying data changes, either on their current device, or from other devices, the search is automatically updated.
- Preferably with a meaningful animation as data is added to and removed from the results
- As the user types, the data updates automatically to match their query
- Preferably with a meaningful animation as data is added to and removed from the results
Its this last item which is not easy to implement when using Realm.
From an implementation perspective when data is shown in an iOS app using Realm, a Results<Element>
object is generated with a predicate query. Then an object subscribes to the collection notifications and populates a view with the data when initial
notification is fired. Note that this usually doesn't include an animation because its assumed this set of data has no relation to any other data that was on the screen. Animation is intended in the update
notification. There its assumed the data was mostly the same as before and allows the developer to easily animate changes in the view for the user.
Expected Results
Currently, the easiest way to make a live search that supports all the features mentioned above is to take advantage of predicates and Realm collection notifications. When the query from the user changes, a new Results
object is generated with the new predicate and then the view can be updated in the initial
notification. However, the initial update doesn't allow the view to updated in an animated way, or at least, not easily.
It would be fantastic if there was a way to have the conceptual equivalent of an NSMutablePredicate
. When the developer creates a Results
object from an NSMutablePredicate
based query it has only 1 extra feature. The feature is that whenever the predicate is changed, the update
notification is fired rather than the initial
notification. This would allow the developer to easily update the UI in an animated way.
I don't want to imply an implementation. I just used the made-up concept of NSMutablePredicate
as a way to explain the enhancement. Another way of explaining it might be: A way of being able to update the underlying query of a Results
object without having to reset the collection notifications - which causes the initial
notification to be fired instead of the update
notification.
Actual Results
When the user changes their query, the developer has to unsubscribe from notifications changes, create a new Results
object with a new predicate and then subscribe to notifications again. When the initial
notification is made, the developer can update the view in a non-animated way.
Version of Realm and Tooling
Realm version when this enhancement was made: 2.4.2
I think the main difficulty here is just figuring out the API, since this isn't something that can be expressed well with our current abstractions. Implementation-wise handling changing the query isn't too bad since there's no big difference between calculating the changes between two versions of the data with one query and two different queries (although maybe things get weird if you change the query while a notification is already in flight?).
An awful thing that you could (but probably shouldn't) do with the current API is the following:
class Search: Object {
let objects = List<MyModel>()
var token: NotificationToken? = nil
var query: Results? = nil {
didSet {
realm.write {
objects.clear()
if let query = query {
objects.add(query)
token = query.addNotificationBlock { changes in
realm.write {
objects.clear()
objects.add(query)
}
}
}
}
}
}
class func ignoredProperties() -> [String] {
return ["token", "query"]
}
}
func setUp() {
let realm = try! Realm()
let search = Search()
try! realm.write { realm.add(search) }
let token = search.objects.filter("TRUEPREDICATE").addNotificationBlock { change in
// ... do whatever stuff
}
search.query = realm.objects(MyModel.self).filter("initial search")
}
The basic idea is that you store the results of the query in a List and then update your UI based on the changes to the List (with the filter("TRUEPREDICATE")
making it so that the changes aren't just "hey you cleared it and then added a bunch of new items").
@tgoyne thats a really really interesting workaround. I'm going to play around with something like that.
I imagine that if I didn't want to muck up my user's realm or be syncing these Search objects (which are more of a means to an end rather than something I want to keep) I could use a temporary realm on disk on this line
try! realm.write { realm.add(search) }
and use my normal user logged in syncing realm on this line
search.query = realm.objects(MyModel.self).filter("initial search")
*Edit - The performance penalty of copying from one realm to another for every search may not be worth it. I'm guessing that syncing a new object that has links to a bunch of existing objects is not that much overhead.
Thanks again for the idea. I'm going to play with this.
+1 Ability to get change-sets after updating/filtering existing results would be supremely useful.
+1
+1
+1
Any progress on this here? Since filtering any fetched result from your data store is a widespread feature I think this should be reminded very soon. Otherwise the notifications-feature "as is" is not very useful for many developers.