Only re-deliver query results if results have changed
Code example probably will be best description here.
@Entity
class KAEntry(
@Unique val key: String,
val value: String,
@Id var id: Long = 0
)
class MyApp : Application() {
private lateinit var subs1: DataSubscription
private lateinit var subs2: DataSubscription
private lateinit var boxStore: BoxStore
private set
override fun onCreate() {
super.onCreate()
boxStore = MyObjectBox.builder().name("main").androidContext(this).build()
val box = boxStore.boxFor(KAEntry::class.java)
box.removeAll() // just for reproducibility, to avoid "unique constraint violation" on second run of app
subs1 = box.query().equal(KAEntry_.key, "key1")
.build().subscribe().observer { v ->
Log.i("MyApp", "triggered for key1" )
}
subs2 = box.query().equal(KAEntry_.key, "key2")
.build().subscribe().observer { v ->
Log.i("MyApp", "triggered for key2" )
}
box.put(KAEntry("key1","some value"))
}
}
Problem is that even I've only put "key1" object and therefore only first query should trigger it's observer - both actually get triggered:
2021-04-05 14:22:53.263 25926-25962/com.acme I/MyApp: triggered for key1
2021-04-05 14:22:53.263 25926-25963/com.acme I/MyApp: triggered for key2
Either I don't understand something or this observer on query actually bugged. I can see that triggering observer for only those objects that are changed and are in query result may have some performance impact. Though even in this case I would like to have some way of detecting that change was made to objects that query returns and not any object of this type.
Documentation is slightly vague about this aspect of functionality:
query observers will automatically deliver fresh results whenever changes are made to entities in a box
This doesn't explicitly tell that entities which were result of the query were changed. From looking inside source code I see that query subscribes to entity box and will run every time any changes happen with this box. There is no way to figure out if transaction was actually touching entities query returns or not since native code returns only list of entity types that were affected:
int[] entityTypeIdsAffected = nativeCommit(transaction);
So, this is not a bug, but rather a feature request. I don't know how to change tag though.
Yes, this is the current behavior that if anything in a Box is changed any query for that box will re-deliver results.
This is typical database behavior, e.g. also for Room. Not sure if we want or even can change this.
Well, some databases do that (e.g. Parse DB LiveQuery function promises that). I understand how impactful that can be on the performance, though since I can't see native code implementation it is really a guess. I see several ways to solve this and least problematic one might be to pass around some monotonically increasing transaction ID that observer will see and can compare with every entity of the query result:
subs1 = box.query().equal(KAEntry_.key, "key1")
.build().subscribe().observer { v, transactionId ->
v.filter { myentity ->
box.getLastTransactionIdForEntity(myentity.id) >= transactionId
}
}
This way I have control if I want to pay performance price for only reacting to changes of query results. I can store amount of objects returned in previous attempts if I want address inserts/deletes as well.
Focusing on your use case: what prevents your code from checking if changes were made? E.g. keep the previous value to compare against?
Nothing :) except that usecase that I've put as an example above is oversimplified :) I usually do not query by unique key (or at least not by single value of unique key) and therefore storing/comparing with previous value become less feasible. Also storing this data is potential race condition issue that I don't want to introduce.