One of my biggest gripes with the Spotify app is the poor offline support, at least in my experience on Android. I have the bulk of my library downloaded for offline listening, so when I have a spotty network connection like when I'm on the Subway, I'd expect that I can still easily access at least my downloaded songs. Not the case. Spotify, it seems, won't use its local cache until it's thoroughly convinced you're offline, which may take several minutes of waiting for requests to time out. Once Spotify is convinced I'm offline, my downloaded songs will then finally load normally.
My guess is that instead of doing it the way the Kinopio does - by reading from the local cache before fetching the remote data - Spotify does it the other way around.
Anyway, nicely done!
However, I wouldn't consider using it without better undo, if i merge a set of cards by mistake it cannot be undone. Maybe store a larger undo history in IndexedDB?
If you just drop a conflicting edit then it's a stretch to call your app "offline". Yes, it "works" but who wants to use an application to at drops you edits?
Beyond that, you get into complex territory but maybe we've all been overthinking the problem space.
The challenge is interpreting what that tree structure should mean.
If you can, let a user decide how to resolve the conflict.
- User logs in, they have a “conflict inbox” of things that need to be resolved.
- Two coworkers make conflicting edits, maybe the manager gets a notification in their conflict inbox and they decide
1. Do you store the tree structure for every table in your app? If you have 20 tables that could be edited offline, do you re-implement it for each table, or try to have a generic implementation? 2. Do you design all your tables around the tree structure, or do you just store it in addition to your "normal" tables? 3. Every piece of code that modifies one of these tables need to do it via the tree structure - if you update your tables directly from any place it could effectively cause conflicts. 4. Do you build separate UI to resolve conflicts for every table? 5. Do you query and cache the tree structure on the device, or does it have to be online to resolve conflicts? 6. Do you expose the tree structure via external APIs, or keep it internal?
I find that "last-write-wins" is sufficient for a large percentage of cases, and much simpler to implement. Or in some cases, just doing conflict detection is sufficient (notify the user that the data has changed between loading and saving, and they need to re-apply their edits).
If you do need conflict resolution on a large scale (many different tables), I'd recommend using data structures designed for that. CRDTs is one example - while it is typically used for automatic conflict resolution, it often stores enough data to allow manual resolution if desired.
What you are describing is just the beginning of conflict handling. The consequences for bad handling are dire: data loss. If conflict handling and resolving was easy (hint: it's not), the article would have mentioned it.
https://github.com/gerdemb/SQLiteChangesetSync
As you noted, detecting conflicts is easy, but handling them is not.
For most products, there isnt a one-size-fits-all answer to how to handle conflicts.
Felt as if it checked online or loaded too many things.
EDIT: in case you were referring to the loading state in the vid in the post, I suspect that's a bug with the iOS loading screen that I didn't have time to fix before this post (it's just idling for most of that). will update later
All the offline-first apps I've built have felt magically fast to load. It really is an incredible experience. I think its likely something to do with parsing/loading the initial view state that is taking a bit. Thats a pretty complicated widget so likely has to load libraries to handle it.