Early on, we realized that we needed some way to communicate to the user when data isn’t synced with the server yet. Otherwise, you could end up in a frustrating situation where you see a card you created on your device, but no one else can see it and you don’t know why.

Design

The symbology for sync indicators isn’t tricky. It’s just a loading symbol that rotates while we’re actively syncing.

Sync indicator on a card

The tricky part of its design is figuring out how often to tell the user that data isn’t synced. Plastering our UI with loading symbols would create clutter and wouldn’t necessarily be helpful to users at all. On the other hand, having no indicators might give users false confidence that the data has been uploaded.

In the end, we settled on showing the loading symbol in four places: boards, cards, comments, and attachments. Boards and cards were chosen because they are the primary units of change. Comments because those are important for users to know were sent out. Attachments were chosen because they take much longer to sync than anything else.

Implementation

Essentially, you want to show the indicator in one of three states:

  • Queued (indicator visible but not rotating)
  • Syncing (indicator visible and rotating)
  • Synced (indicator invisible)

We have a database table which tracks models and the data necessary to determine what state each model is in (based on timestamps for queueing, syncing, etc.).

For models with no children (such as attachments), this is a simple task: just query the database for that model’s state.

The real trouble came in with models with children. A card might be syncing because one of its properties changed (like its description), or it might be syncing because one of its children are syncing (such as an attachment). In both cases, we want to show that the card isn’t fully synced, but now our query must involve multiple rows in the database.

We first tried large SQL queries which incorporated all the children IDs, but this instantly proved to be cumbersome and slow. We then setup some fancy recursive SQL to handle the problem, but then ran afoul of old versions of SQLite on Android which didn’t support it.

Ultimately, we took a very pragmatic route: we realized that the only parent models we really cared about were boards and cards. So for each sync state row, we simply store their parent card and board IDs. This makes it a snap to query when a model has any children that are syncing.

Displaying sync state