Tracking the sync progress of multiple objects belonging to parent object

Hi everyone!

We’re starting to introduce real time sync of shopping lists in our app. i.e. device A is adding items, removing, marking as checked… reflected on device B with a little message while the UI is being updated.

My objective is to update the UI on the second device when modifications are made on the shopping list, when the sync starts (show a loader), and hide it when the sync ends.

Here’s where it gets tricky. This top level object, call it “ShoppingList”, actually contains multiple objects (mostly Lists), also managed in the realm.
When the shopping list is being edited (items added or removed, etc.), actually, it’s multiple embedded objects that are being edited. We can’t simply observe the top level object “ShoppingList”. We must observe all embedded objects that may be modified as well.

I tried using syncSession.addProgressNotification and observe this realm, to know when the sync is complete (top level + all lower level objects). But within addProgressNotification, isTransferComplete is called each time 1 single object from this realm is done syncing. I can never know when the shopping list and all its embedded objects are actually done synching.

Any clue on how we could track the sync completion for such an object, containing dozens of other objects also being updated?

Thanks!

I feel like the best way to achieve this would be to refactor all objects within the parent object to EmbeddedObjects.

That’s an interesting thought; allows a single object to be monitored for sync’ing status.

What if there are relationships between objects - many-many or one-many? For example suppose each users shipping list contains a Milk object, and the grocery store wants to know how many shopping lists contain that Milk object so they can plan for restocking. In that sense the Embedded objects won’t work as they are not uniquely managed.

OP - can you provide a bit more info as to what is in these lower level Lists you mentioned in the question? Are those objects all managed objects (like Milk per above) or what’s their use case?

Jay

Hey @Jay,
Thanks for the reply.

You’re right that Embedded objects wouldn’t work on the example you mentioned.

As you suggested, here’s more info as to what’s in these lists and their use cases.

It’s a cooking app that allows you to add recipes (their ingredients) to your shopping list. When you add several recipes, we create an “aggregated” shopping list. As you can see here, it’s organized by aisles, so you can better organize your trip through the supermarket.

Here are the models involved (simplified).

AggregatedShoppingList

@Persisted(primaryKey: true) var _id: String? = ObjectId.generate().stringValue
@Persisted var partition: String = ""
@Persisted var givenName: String?
@Persisted var isDefault: Bool = true
@Persisted var aisles = List<AisleSection>()

AisleSection

@Persisted(primaryKey: true) var _id: String? = ObjectId.generate().stringValue
@Persisted var aisleAlias: String? = nil
@Persisted var localizedName: String? = nil
@Persisted var ingredients = List<IngredientShoppingList>()

IngredientShoppingList

@Persisted(primaryKey: true) var _id: String? = ObjectId.generate().stringValue
@Persisted var ingredientId: String? // This is a reference to an object stored in another realm, that we don't want in this realm
@Persisted var unitsAndQuantities = List<UnitInAggregatedShoppingList>()    
@Persisted var checked: Bool = false

As you would expect, you can “check” items off the list when you’ve already got them. This actually updates the IngredientShoppingList objects referenced in the AisleSections in the AggregatedShoppingList :smiling_face_with_tear:

Now, this is a quick and easy update, I have no issue with that.
What’s messing with me is when the user actually updates the entire list by say remove a recipe or add a new one. Then, dozens of objects are being modified at once: AisleSections, Ingredients,… and since update notifications are received individually (because they’re all unique objects), it’s tricky to know when the update is complete for the whole list.

Note that I’ve tried refactoring to EmbeddedObjects and surprisingly, I found that the notification tracking changes on the AggregateList wasn’t fired when one of its EmbeddedObjects was being modified.

A couple of design questions hit me.

In a users shopping list, why is there information stored within that object regarding the aisle the product is on? Maybe I am not looking at it correctly? The aisle is dynamic. In other words wouldn’t a shopping list have a direct List of ingredients and the ingredients themselves know what aisle they’re on?

For example: a grocery store chain in our area has a couple hundred locations and peanut button is on aisle 2 in our local store but is on aisle 9 in another part of the state. So the ingredient aisle is subject to the store it’s in (dynamic) and not directly related to the ingredient list but the ingredient knows which aisle it’s on in whatever store its in.

My other question is: Lists are one way relationships. If, in the above code, a AggregatedShoppingList is deleted, the only impact would be that… it was deleted. The AisleSections would not be affected and neither would the IngredientShoppingList.

So the only notification that would fire would be that the AggregatedShoppingList was removed - which would be a single event only. Or is there more to it?

Good questions!

You’re right about the dynamic aspect of aisles in grocery stores. We offer this information as a general guidance, but we can’t at this time offer store-by-store information – not until we tap into grocery stores’ APIs to have that kind of information.
Storing a list of ingredients which know which aisles they’re on makes more sense indeed. But I found it easier to build the UI (tableView) with this design: sections are aisles, items in sections are ingredients. Once the object is created as such, no need to go and check each ingredient to see where it goes.

You’re right about the fact that cascading deletes can’t happen in this scenario. That’s why when the AggregatedShoppingList is deleted, I do have to delete its AisleSections and IngredientShoppingLists.
I must therefore observe changes on the said aisles and ingredients.

We obviously don’t know the full use case but perhaps that extra work is causing syncing objects unnecessarily.

To me, it seems like a lot of managed objects are being instantiated and destroyed but are really only tied to a single users list - that really calls for an embedded object instead. Why manage an object if you don’t have to, right?

What about a simpler stucture

class UserClass: Object {
   @Persisted var shoppingList = List<ShoppingItemClass>()
}

and then

class ShoppingItemClass: EmbeddedObject {
   @Persisted var ingredient: Ingredient!
   @Persisted var aisle: AisleSections!
}

With the above, ShoppingItemClass objects are embedded can be freely added and removed from a users list. The properties are one-way relationships to the object they reference so the only thing that’s sync’d is the User class and it’s embedded list - the actual realm objects for Ingredients and Aisles remain unaffected and don’t need to be deleted or sync’d.

1 Like