Offline-eerst formulier conflictoplossing voor Kotlin + SQLite
Leer offline-eerst conflictresolutie voor formulieren: heldere merge-regels, een eenvoudige Kotlin + SQLite syncflow en praktische UX-patronen voor bewerkingsconflicten.

Wat gebeurt er eigenlijk als twee mensen offline bewerken
Offline-eerst formulieren laten mensen gegevens bekijken en bewerken, zelfs als het netwerk traag of niet beschikbaar is. In plaats van te wachten op de server schrijft de app wijzigingen eerst naar een lokale SQLite-database en synchroniseert later.
Dat voelt direct, maar het schept een eenvoudige realiteit: twee apparaten kunnen hetzelfde record veranderen zonder elkaars wijzigingen te kennen.
Een typisch conflict ziet er zo uit: een veldtechnicus opent een werkorder op een tablet in een kelder zonder signaal. Ze zetten de status op "Done" en voegen een notitie toe. Tegelijkertijd werkt een supervisor op een andere telefoon dezelfde werkorder bij, wijst iemand anders toe en past de vervaldatum aan. Beiden tikken op Opslaan. Beide opslagen slagen lokaal. Niemand deed iets verkeerds.
Als er eindelijk gesynchroniseerd wordt, moet de server beslissen wat het "echte" record is. Als je conflicten niet expliciet afhandelt, eindig je meestal met een van deze uitkomsten:
- Last write wins: de latere sync overschrijft eerdere wijzigingen en iemand verliest data.
- Hard failure: sync weigert één update en de app toont een weinig behulpzame foutmelding.
- Dubbele records: het systeem maakt een tweede kopie om overschrijven te vermijden en rapportage wordt rommelig.
- Stille merge: het systeem combineert wijzigingen, maar mixt velden op manieren die gebruikers niet verwachten.
Conflicten zijn geen bug. Ze zijn het voorspelbare gevolg van mensen laten werken zonder live verbinding — juist dat is het punt van offline-eerst.
Het doel is tweeledig: data beschermen en de app eenvoudig houden in gebruik. Dat betekent meestal duidelijke merge-regels (vaak op veldniveau) en een gebruikerservaring die mensen alleen onderbreekt als het echt nodig is. Als twee bewerkingen verschillende velden raken, kun je vaak stil samenvoegen. Als twee mensen hetzelfde veld op verschillende manieren aanpassen, moet de app dat zichtbaar maken en iemand helpen de juiste uitkomst te kiezen.
Kies een conflictstrategie die bij je data past
Conflicten zijn eerst een productkeuze, geen puur technisch probleem: wat betekent "correct" wanneer twee mensen hetzelfde record hebben veranderd vóór sync?
Drie strategieën dekken de meeste offline apps:
- Last write wins (LWW): accepteer de nieuwste edit en overschrijf de oudere.
- Handmatige review: pauzeer en vraag een mens welke waarde behouden moet blijven.
- Veld-niveau merge: combineer wijzigingen per veld en vraag alleen wanneer twee mensen hetzelfde veld aanraakten.
LWW kan prima zijn wanneer snelheid belangrijker is dan perfecte nauwkeurigheid en de kosten van fouten laag zijn. Denk aan interne notities, niet-kritieke tags of een draftstatus die later weer bewerkt kan worden.
Handmatige review is veiliger voor velden met grote impact waar de app niet moet gokken: juridische tekst, compliance-bevestigingen, loon- en factuurbedragen, bankgegevens, medicatie-instructies en alles dat aansprakelijkheid kan veroorzaken.
Veld-niveau merge is meestal de beste default voor formulieren waar verschillende rollen verschillende delen bijwerken. Een supportmedewerker past het adres aan terwijl sales de verlengdatum bijwerkt. Een per-veld merge behoudt beide wijzigingen zonder iemand te storen. Maar als beide gebruikers de verlengdatum wijzigden, moet dat veld een beslissing triggeren.
Voordat je iets implementeert, schrijf op wat "correct" betekent voor je bedrijf. Een korte checklist helpt:
- Welke velden moeten altijd de meest actuele real-world waarde tonen (zoals de huidige
status)? - Welke velden zijn historisch en mogen nooit overschreven worden (zoals een ingestuurd-tijd)?
- Wie mag elk veld veranderen (rol, eigendom, goedkeuringen)?
- Wat is de bron van waarheid als waarden verschillen (apparaat, server, managergoedkeuring)?
- Wat gebeurt er als je het fout kiest (kleine ergernis versus financiële of juridische impact)?
Als deze regels helder zijn, heeft de sync-code één taak: ze afdwingen.
Definieer merge-regels per veld, niet per scherm
Wanneer er een conflict ontstaat, raakt het zelden het hele formulier gelijkmatig. De ene gebruiker kan een telefoonnummer aanpassen terwijl de ander een notitie toevoegt. Als je het hele record als alles-of-niets behandelt, dwing je mensen om goed werk opnieuw te doen.
Veld-niveau merge is voorspelbaarder omdat elk veld een bekend gedrag heeft. De UX blijft rustig en snel.
Een eenvoudige manier om te beginnen is velden te scheiden in "meestal veilig" en "meestal onveilig" om automatisch te mergen.
Meestal veilig om automatisch te mergen: notities en interne opmerkingen, tags, attachments (vaak union), en tijdstempels zoals "last contacted" (vaak de nieuwste houden).
Meestal onveilig om automatisch te mergen: status/state, assignee/eigenaar, totalen/prijzen, goedkeuringsvlaggen en voorraadtellingen.
Kies vervolgens een prioriteitsregel per veld. Veelvoorkomende keuzes zijn server wins, client wins, rol wins (bijvoorbeeld manager overridet agent), of een deterministische tie-breaker zoals de nieuwste serverversie.
De kernvraag is wat er gebeurt als beide zijden hetzelfde veld veranderden. Kies per veld één gedrag:
- Auto-merge met een duidelijke regel (bijvoorbeeld tags zijn een union)
- Beide waarden bewaren (bijvoorbeeld notities aan elkaar plakken met auteur en tijd)
- Markeren voor review (bijvoorbeeld
statusenassigneevereisen een keuze)
Voorbeeld: twee supportmedewerkers bewerken offline hetzelfde ticket. Rep A verandert status van "Open" naar "Pending". Rep B verandert notes en voegt de tag "refund" toe. Bij sync kun je veilig notes en tags mergen, maar je moet status niet stil samenvoegen. Vraag alleen naar status, terwijl de rest al gemerged is.
Om later discussies te voorkomen, documenteer elke regel in één zin per veld:
- "
notes: bewaar beide, plak nieuwste achteraan, inclusief auteur en tijd." - "
tags: union, verwijderen alleen als het expliciet aan beide kanten verwijderd is." - "
status: als beide kanten gewijzigd, vereist user choice." - "
assignee: manager wins, anders server wins."
Die eén-zinsregel wordt de bron van waarheid voor je Kotlin-code, SQLite-queries en de conflict-UI.
Basis datamodel: versies en auditvelden in SQLite
Als je wilt dat conflicten voorspelbaar aanvoelen, voeg dan een kleine set metadata-kolommen toe aan elke gesynchroniseerde tabel. Zonder die kolommen kun je niet zien of je naar een verse edit, een oude kopie of twee edits die een merge nodig hebben kijkt.
Een praktisch minimum voor elk server-gesynchroniseerd record:
id(stabiele primaire sleutel): hergebruik deze nooitversion(integer): neemt toe bij elke succesvolle write op de serverupdated_at(timestamp): wanneer het record voor het laatst gewijzigd isupdated_by(text of user id): wie de laatste wijziging maakte
Op het apparaat voeg je lokale-only velden toe om veranderingen bij te houden die nog niet door de server bevestigd zijn:
dirty(0/1): lokale wijzigingen bestaanpending_sync(0/1): in de rij om te uploaden, maar nog niet bevestigdlast_synced_at(timestamp): de laatste keer dat deze rij overeenkwam met de serversync_error(text, optioneel): laatste foutreden om in de UI te tonen
Optimistic concurrency is de eenvoudigste regel die stille overschrijvingen voorkomt: elke update bevat de versie waarvan je denkt dat je die bewerkt (expected_version). Als het serverrecord nog op die versie staat, wordt de update geaccepteerd en retourneert de server de nieuwe versie. Zo niet, dan is het een conflict.
Voorbeeld: Gebruiker A en Gebruiker B hebben allebei version = 7 gedownload. A synchroniseert eerst; de server verhoogt naar 8. Wanneer B probeert te syncen met expected_version = 7, wijst de server af met een conflict zodat B’s app mergeert in plaats van te overschrijven.
Voor een goed conflictscherm, sla het gedeelde startpunt op: waar de gebruiker oorspronkelijk van begon te bewerken. Twee gangbare benaderingen:
- Sla een snapshot van het laatst gesynchroniseerde record op (één JSON-kolom of een parallelle tabel).
- Sla een changelog op (rij-per-edit of veld-per-edit).
Snapshots zijn eenvoudiger en vaak genoeg voor formulieren. Changelogs zijn zwaarder, maar kunnen precies uitleggen wat er veranderde, veld per veld.
Hoe dan ook, de UI moet drie waarden per veld kunnen tonen: de edit van de gebruiker, de huidige waarde op de server en het gedeelde startpunt.
Record snapshots vs changelogs: kies één aanpak
Bij het synchroniseren van offline-eerst formulieren kun je de hele record uploaden (een snapshot) of een lijst met bewerkingen (een changelog). Beide werken met Kotlin en SQLite, maar ze gedragen zich anders wanneer twee mensen hetzelfde record bewerken.
Optie A: Volledige-record snapshots
Bij snapshots schrijft elke save de nieuwste volledige staat (alle velden). Bij sync stuur je het record plus een versienummer. Als de server ziet dat de versie oud is, heb je een conflict.
Dit is eenvoudig te bouwen en snel te lezen, maar creëert vaak grotere conflicten dan nodig. Als Gebruiker A het telefoonnummer aanpast terwijl Gebruiker B het adres wijzigt, kan een snapshot-benadering het als één groot conflict behandelen, ook al overlappen de wijzigingen niet.
Optie B: Changelogs (operaties)
Bij changelogs sla je op wat er veranderd is, niet het hele record. Elke lokale wijziging wordt een operatie die je kunt replayen bovenop de nieuwste serverstatus.
Operaties die vaak makkelijker te mergen zijn:
- Zet een veldwaarde (zet
emailnaar een nieuwe waarde) - Voeg een notitie toe (voegt een nieuw notitie-item toe)
- Voeg een tag toe (voegt één tag toe aan een set)
- Verwijder een tag (verwijdert één tag uit een set)
- Markeer een checkbox als voltooid (zet
isDonetrue met een timestamp)
Operationele logs kunnen conflicten verminderen omdat veel acties elkaar niet overlappen. Notities toevoegen conflicteert zelden met iemand anders die een andere notitie toevoegt. Tag toevoegingen en verwijderingen kunnen samenkomen als set-math. Voor single-value velden heb je nog steeds per-veld regels nodig wanneer twee verschillende edits concurreren.
De afweging is complexiteit: stabiele operatie-ID's, ordering (lokale volgorde en servertijd), en regels voor operaties die niet commuteerbaar zijn.
Opruimen: compacten na succesvolle sync
Operationele logs groeien, dus plan hoe je ze verkleint.
Een gebruikelijke aanpak is per-record compactie: zodra alle operaties tot een bekende serverversie erkend zijn, vouw je ze samen tot een nieuwe snapshot en verwijder je die oudere operaties. Bewaar een korte tail alleen als je undo, auditing of makkelijker debuggen nodig hebt.
Stappenplan sync flow voor Kotlin + SQLite
Een goede sync-strategie gaat vooral over strikt zijn in wat je verstuurt en wat je accepteert terug. Het doel is simpel: overschrijf nooit per ongeluk nieuwere data en maak conflicten duidelijk wanneer je niet veilig kunt mergen.
Een praktische flow:
-
Schrijf elke wijziging eerst naar SQLite. Sla wijzigingen op in een lokale transactie en markeer het record als
pending_sync = 1. Slalocal_updated_aten de laatst bekendeserver_versionop. -
Stuur een patch, geen heel record. Als de connectiviteit terug is, stuur het record-id plus alleen de velden die veranderd zijn, samen met
expected_version. -
Laat de server mismatchende versies afwijzen. Als de serverversie niet overeenkomt met
expected_version, retourneert de server een conflict-payload (serverrecord, voorgestelde wijzigingen en welke velden verschillen). Als versies overeenkomen, past de server de patch toe, verhoogt de versie en retourneert het bijgewerkte record. -
Pas eerst auto-merge toe, vraag daarna de gebruiker. Voer veld-niveau merge-regels uit. Behandel veilige velden zoals notities anders dan gevoelige velden zoals
status, prijs ofassignee. -
Commit het uiteindelijke resultaat en wis pending-flags. Of het nu automatisch gemerged of handmatig opgelost is, schrijf het definitieve record terug naar SQLite, werk
server_versionbij, zetpending_sync = 0en registreer genoeg auditdata om later uit te leggen wat er gebeurde.
Voorbeeld: twee salesmedewerkers bewerken offline dezelfde order. Rep A verandert de leverdatum. Rep B verandert het telefoonnummer van de klant. Met patches kan de server beide wijzigingen schoon accepteren. Als beiden de leverdatum veranderden, toon je één duidelijke beslissing in plaats van te dwingen tot het opnieuw invullen van het hele formulier.
Houd de UI-belofte consistent: "Saved" moet betekenen lokaal opgeslagen. "Synced" moet een aparte, expliciete staat zijn.
UX-patronen voor het oplossen van conflicten in formulieren
Conflicten moeten uitzondering zijn, niet de normale stroom. Begin met automatische merges voor wat veilig is, en vraag de gebruiker alleen als een beslissing echt nodig is.
Maak conflicten zeldzaam met veilige defaults
Als twee mensen verschillende velden bewerken, merge automatisch zonder een modal te tonen. Bewaar beide wijzigingen en toon een kleine "Updated after sync" melding.
Reserveer prompts voor echte botsingen: hetzelfde veld dat op beide apparaten gewijzigd is, of een wijziging die afhankelijk is van een ander veld (zoals status plus statusreden).
Als je moet vragen, maak het snel af
Een conflictscherm moet twee dingen beantwoorden: wat is er veranderd en wat wordt opgeslagen. Vergelijk waarden naast elkaar: "Your edit", "Their edit" en "Saved result". Als slechts twee velden conflicteren, toon niet het hele formulier. Spring direct naar die velden en houd de rest read-only.
Beperk de acties tot wat mensen echt nodig hebben:
- Keep mine
- Keep theirs
- Edit final
- Review field-by-field (alleen indien nodig)
Partiële merges zijn waar UX rommelig kan worden. Markeer alleen de conflicterende velden en label de bron duidelijk ("Yours" en "Theirs"). Preselecteer de veiligste optie zodat de gebruiker kan bevestigen en doorgaan.
Zet verwachtingen zodat gebruikers zich niet opgesloten voelen. Vertel wat er gebeurt als ze weggaan: bijvoorbeeld "We bewaren jouw versie lokaal en proberen later opnieuw te synchroniseren" of "Dit record blijft in Needs review totdat je kiest." Maak die staat zichtbaar in de lijst zodat conflicten niet verloren raken.
Als je deze flow in AppMaster bouwt, geldt dezelfde UX-aanpak: merge veilige velden eerst automatisch en toon daarna alleen een gefocuste reviewstap wanneer specifieke velden botsen.
Lastige gevallen: deletes, duplicaten en "missende" records
De meeste sync-problemen die willekeurig lijken komen door drie situaties: iemand verwijdert terwijl iemand anders bewerkt, twee apparaten maken offline hetzelfde ding aan, of een record verdwijnt en verschijnt later weer. Deze hebben expliciete regels nodig omdat LWW mensen vaak verrast.
Verwijderen versus bewerken: wie wint?
Bepaal of een delete sterker is dan een edit. In veel zakelijke apps wint delete omdat gebruikers verwachten dat een verwijderd record verwijderd blijft.
Een praktische set regels:
- Als een record op enig apparaat verwijderd is, behandel het als verwijderd overal, zelfs als er later edits zijn.
- Als deletes omkeerbaar moeten zijn, zet dan "delete" om in een gearchiveerde status in plaats van een harde delete.
- Als er een edit binnenkomt voor een verwijderd record, bewaar de edit in de geschiedenis voor audit, maar herstel het record niet.
Offline creatiecollisies en dubbele concepten
Offline-eerst formulieren maken vaak tijdelijke ID's (zoals UUID) voordat de server een definitief ID toewijst. Duplicaten ontstaan wanneer gebruikers twee drafts maken voor hetzelfde reële object (dezelfde bon, hetzelfde ticket, hetzelfde item).
Als je een stabiele natural key hebt (bonnummer, barcode, e-mail plus datum), gebruik die om botsingen te detecteren. Als je die niet hebt, accepteer dat duplicaten gebeuren en bied later een eenvoudige merge-optie.
Implementatietip: sla zowel local_id als server_id op in SQLite. Wanneer de server reageert, schrijf een mapping en bewaar die minstens totdat je zeker weet dat er geen in de wachtrij staande wijzigingen meer naar de lokale ID verwijzen.
Voorkomen van "resurrectie" na sync
Resurrectie gebeurt wanneer Apparaat A een record verwijdert, maar Apparaat B offline is en later een oude kopie uploadt als upsert, waardoor het record wordt hersteld.
De oplossing is een tombstone. In plaats van de rij meteen te verwijderen, markeer je het als verwijderd met deleted_at (vaak ook deleted_by en delete_version). Tijdens sync behandel je tombstones als echte wijzigingen die oudere niet-verwijderde staten kunnen overschrijven.
Bepaal hoe lang je tombstones bewaart. Als gebruikers weken offline kunnen zijn, bewaar ze langer dan dat. Verwijder pas nadat je zeker weet dat actieve apparaten voorbij die delete gesynchroniseerd hebben.
Als je undo ondersteunt, behandel undo als een andere wijziging: verwijder deleted_at en verhoog de versie.
Veelvoorkomende fouten die dataverlies of frustratie veroorzaken
Veel sync-fouten ontstaan door kleine aannames die goede data stilletjes overschrijven.
Fout 1: Vertrouwen op apparaat-tijd om bewerkingen te ordenen
Telefoons kunnen een verkeerde klok hebben, tijdzones veranderen en gebruikers kunnen de tijd handmatig aanpassen. Als je bewerkingen ordent op apparaat-tijd, pas je uiteindelijk wijzigingen in de verkeerde volgorde toe.
Geef de voorkeur aan server-uitgegeven versies (monotoon toenemende serverVersion) en behandel client-tijdstempels als alleen-weergave. Als je toch tijd moet gebruiken, voeg dan beveiligingen toe en reconcilieer op de server.
Fout 2: Per ongeluk LWW op gevoelige velden
LWW lijkt simpel totdat het gevoelige velden raakt die niet door de laatste sync mogen worden "gewonnen". status, totalen, goedkeuringen en toewijzingen hebben meestal expliciete regels nodig of handmatige review.
Een veiligheidschecklist voor risicovolle velden:
- Behandel statustransities als een state machine, niet als vrije tekst.
- Herbereken totalen uit regelitems. Merge geen totalen als ruwe nummers.
- Voor tellers, merge door deltas toe te passen, niet door één winnaar te kiezen.
- Voor eigendom of toewijzing, eis expliciete bevestiging bij conflicten.
Fout 3: Nieuwere serverwaarden overschrijven met verouderde cache
Dit gebeurt als de client een oude snapshot bewerkt en vervolgens het volledige record uploadt. De server accepteert het en nieuwere server-side wijzigingen verdwijnen.
Los dit op in de vorm van wat je stuurt: stuur alleen veranderde velden (of een changelog), plus de basisversie die je bewerkte. Als de basisversie achterloopt, wijst de server af of dwingt een merge af.
Fout 4: Geen "wie wijzigde wat" geschiedenis
Bij conflicten willen gebruikers één antwoord: wat heb ik veranderd en wat de ander? Zonder editor-identity en per-veld wijzigingsgeschiedenis wordt je conflictscherm giswerk.
Sla updatedBy, server-side update-tijd als je die hebt, en minstens een lichte per-veld audittrail op.
Fout 5: Conflict-UI die dwingt tot volledige-record vergelijking
Mensen het hele record laten vergelijken is uitputtend. De meeste conflicten zijn slechts één tot drie velden. Toon alleen de conflicterende velden, preselecteer de veiligste optie en laat de gebruiker de rest automatisch accepteren.
Als je formulieren bouwt in een no-code tool zoals AppMaster, streef naar hetzelfde resultaat: los conflicten op op veldniveau zodat gebruikers één duidelijke keuze maken in plaats van door het hele formulier te moeten scrollen.
Korte checklist en volgende stappen
Als je wilt dat offline-bewerkingen veilig aanvoelen, behandel conflicten als een normale staat, niet als een fout. De beste resultaten komen van duidelijke regels, herhaalbare tests en een UX die uitlegt wat er gebeurde in begrijpelijke taal.
Voordat je meer features toevoegt, zorg dat deze basics vastliggen:
- Voor elk recordtype: wijs een merge-regel per veld toe (LWW, keep max/min, append, union of altijd vragen).
- Bewaar een door de server gecontroleerde versie plus een
updated_atdie je controleert en valideer ze tijdens sync. - Doe een twe aparato test waarbij beide offline hetzelfde record bewerken, en sync in beide orders (A dan B, B dan A). De uitkomst moet voorspelbaar zijn.
- Test de hard conflicts: delete vs edit, en edit vs edit op verschillende velden.
- Maak de status duidelijk: toon Synced, Pending upload en Needs review.
Prototype de volledige flow end-to-end met één echt formulier, niet een demo-scherm. Gebruik een realistisch scenario: een veldtechnicus werkt een job-notitie bij op een telefoon terwijl een dispatcher dezelfde jobtitel op een tablet aanpast. Als ze verschillende velden aanraken, merge automatisch en toon een kleine "Updated from another device" hint. Als ze hetzelfde veld aanraken, routeer naar een eenvoudig reviewscherm met twee keuzes en een duidelijke preview.
Wanneer je klaar bent om de volledige mobiele app en de backend-API's samen te bouwen, kan AppMaster (appmaster.io) helpen. Je kunt data modelleren, businesslogica definiëren en web- en native mobiele UI's bouwen op één plek, en dan deployen of broncode exporteren zodra je sync-regels vaststaan.
FAQ
Een conflict gebeurt wanneer twee apparaten dezelfde server-gebonden record aanpassen terwijl ze offline zijn (of voordat één van beide gesynchroniseerd heeft), en de server later ziet dat beide updates gebaseerd zijn op een oudere versie. Het systeem moet dan beslissen wat de uiteindelijke waarde wordt voor elk veld dat verschillend is.
Begin met veld-niveau merge als standaard voor de meeste bedrijfsformulieren, omdat verschillende rollen vaak verschillende velden bijwerken en je zo beide wijzigingen kunt behouden zonder iemand te storen. Gebruik handmatige review alleen voor velden die echte schade kunnen veroorzaken als je het fout raadt (geld, goedkeuringen, compliance). Gebruik last write wins alleen voor velden met laag risico waar het verliezen van een oudere wijziging acceptabel is.
Als twee bewerkingen verschillende velden raken, kun je meestal automatisch samenvoegen en de UI stil houden. Als twee bewerkingen hetzelfde veld naar verschillende waarden veranderen, moet dat veld een beslissing triggeren — elke automatische keuze kan iemand verrassen. Houd de beslissing beperkt door alleen de conflicterende velden te tonen, niet het hele formulier.
Behandel version als de monotone teller van de server voor het record en eis dat de client bij elke update een expected_version meestuurt. Als de huidige serverversie niet overeenkomt, wijs dan af met een conflictreactie in plaats van te overschrijven. Deze ene regel voorkomt “stille dataverlies” zelfs wanneer twee apparaten in verschillende volgorde syncen.
Een praktisch minimum is een stabiel id, een door de server gecontroleerde version, en door de server gecontroleerde updated_at/updated_by zodat je kunt uitleggen wat er veranderd is. Op het apparaat track je of de rij gewijzigd is en wacht om te uploaden (bijvoorbeeld pending_sync) en houd je de laatst gesynchroniseerde serverversie bij. Zonder deze velden kun je conflicten niet betrouwbaar detecteren of een nuttig oplossingsscherm tonen.
Stuur alleen de velden die veranderd zijn (een patch) plus de basis expected_version. Volledige record-uploads veranderen kleine, niet-overlappende wijzigingen in onnodige conflicten en vergroten de kans dat een nieuwere serverwaarde met verouderde gecachte data overschreven wordt. Patches maken ook duidelijker welke velden merge-regels nodig hebben.
Een snapshot is eenvoudiger: je bewaart de nieuwste volledige record en vergelijkt die later met de server. Een change log is flexibeler: je bewaart operaties zoals “zet veld” of “voeg notitie toe” en replayt die op de nieuwste serverstatus, wat vaak beter merge-gedrag geeft voor notities, tags en andere additieve updates. Kies snapshots voor snelheid van implementatie; kies change logs als merges vaak voorkomen en je duidelijker wilt zien wie wat veranderde.
Bepaal van tevoren of een delete sterker is dan een edit, want gebruikers verwachten consistent gedrag. Voor veel bedrijfsapps is een veilige default om deletes als “tombstones” te behandelen (markeer verwijderd met deleted_at en een versie) zodat een oudere offline upsert het record niet per ongeluk terugbrengt. Als je omkeerbaarheid nodig hebt, gebruik dan een “archived” status in plaats van een harde delete.
Vertrouw niet op apparaat-tijd om bewerkingen te ordenen, want klokken lopen uiteen en tijdzones veranderen. Gebruik serverversies voor orde en conflictchecks. Vermijd last-write-wins op gevoelige velden zoals status, assigned persoon en totalen; geef die expliciete regels of handmatige review. Laat gebruikers niet een volledig-record vergelijkingsscherm zien wanneer slechts één of twee velden botsen, want dat veroorzaakt fouten en frustratie.
Houd de belofte dat “Saved” betekent lokaal opgeslagen en toon een aparte status voor “Synced” zodat gebruikers begrijpen wat er gebeurt. Als je dit in AppMaster bouwt, streef dan naar dezelfde structuur: definieer per-veld merge-regels als onderdeel van de productlogica, merge veilige velden automatisch en routeer alleen echte veld-collisies naar een kleine reviewstap. Test met twee apparaten die hetzelfde record offline bewerken en syncen in beide volgorde om te bevestigen dat uitkomsten voorspelbaar zijn.


