Over the last year, several people have asked me add support this and that synchronization alternative. It sounds easy enough but it’s actually quite a complicated thing. As I’m now implementing support for Org-mode, I thought I’d write a bit about the process.
Getting notified about changes
To allow for editing tasks both in the app and in the files directly, the synchronization service will have to monitor both the database and the fileystem. This is no problem as Android provides ContentObserver and FileObserver. To avoid disrupting the UI, they’ll run in a separate thread.
A chart of the service logic might look as follows:
So acting on changes is actually not that hard. The service that handles the monitors and the sync thread just starts with the app, or on boot-up, and runs forever. I have already implemented it and tested it to see if it would have any impact on battery life over the last week. The answer is none at all:
Synchronizing the database and filesystem
I should probably explain how the database is structured. The app stores your tasks in the database because that makes ListViews, searches etc lightning fast. You also get automatically updating UI and so on for free. As you know, a task belongs to a list. A list can have entries in the following tables:
- TaskLists (called list in chart)
- RemoteTaskList (called DB entry in chart)
What you see in the app is the TaskList entry. This stores the name of the list, what sorting it should use and whether tasks should be displayed as todo items. A RemoteTaskList is connected to the TaskList and contains the information necessary for synchronization. For GTasks, this is the ID assigned by Google’s servers to the list and when it was last updated. For Org-mode, it will be the filename and time of last synchronization.
A RemoteTaskList might exist without a corresponding TaskList if this TaskList has been deleted. This is how the app knows that it should tell the server to delete the list as well. So, keeping the filesystem in sync with the database will look like something as follows:
Yeah… That’s a big chart. And it doesn’t even contain the logic for syncing the tasks in the database which have corresponding database tables:
- Tasks (called task in the chart)
- RemoteTask (called DB entry in the chart)
The reasoning is the same: the RemoteTask table contains the information specific to each sync source, such as the filesystem or GTasks. So most notably it contain an ID and an update time. For file synchronization it also co ntains the text representation of the task at last synchronization. Because tasks belong to a list, which is exported as a whole to a file, it is impossible to keep track of individual changes using timestamps alone. I might change the title of some task in the file (called a node), but this can not be distinguished from editing ALL the tasks (nodes) by looking at the modification time of the file alone.
In case the task has changed both in the app and in the file, the app’s change will win. One could imagine doing some kind of diff here, but I’ll keep it simple first.
A necessary compromise is to explicitly generate and save IDs in the nodes. So a node in a file will look like:
* TODO Task title
# NONSENSEID: 99DFA930
DEADLINE: <2014-02-14>
An example of a task saved with an id for
the app's benefit.
If you are a hardcore user of Org-mode already, you might realize that this might not really be suitable for you. In that case, stay tuned for the coming NoNonsenseOrg app. Otherwise I recommend using a special file for sync purposes.
For users not already using Org-mode, this is not that big of a deal. The id comment makes it possible to keep the same node in sync across several devices. NoNonsenseOrg will store it’s data natively as files and so will not require id-lines.