Introduction
Data synchronization is a core challenge in mobile development. Mobile apps must handle intermittent connectivity, concurrent updates, and device crashes while keeping user data coherent. This tutorial covers practical sync strategies, merge policies you can apply in client-server and peer-to-peer setups, and an approachable primer on CRDTs (Conflict-free Replicated Data Types) that scale well for offline-first Flutter apps.
Conflict Resolution Strategies
When two actors change the same piece of data, you need a deterministic way to reconcile the changes. Common strategies:
Last-Write-Wins (LWW): Choose the update with the latest timestamp. Easy to implement but can lose updates when clocks skew or causality matters.
Operational Transform (OT): Transform concurrent operations to preserve intent (used in collaborative editors). Complex and stateful.
Application-Level Merge: Use domain rules (e.g., merge lists by IDs, sum counters). Best when domain semantics are known.
CRDTs: Data structures designed to converge automatically without coordination. Preferable for many offline-first use cases.
In mobile development, favor a hybrid approach: simple policies for trivial fields (e.g., LWW for read-only metadata) and application-level or CRDT merges for user-generated content.
Merge Policies In Practice
Pick merge policies based on consistency requirements and the cost of lost updates.
Immutable/Append-Only Data: Append-only logs avoid conflicts. Use sequence numbers or Lamport clocks to order items.
Simple Scalar Fields: LWW or client-priority (client overwrites server) are pragmatic. Include device id and vector timestamp for auditability.
Composite Objects: Merge per-field with fallbacks. Example: for a contact record, merge phone numbers by set-union, but use LWW for the display name only when user explicitly edits it.
Implement merge at the last possible layer to preserve domain intent. In Flutter, perform merges in the model layer before persisting to local DB or sending to server.
CRDT Basics For Mobile
CRDTs provide conflict-free replication by construction. Two common flavors:
State-based (Convergent Replicated Data Types, CvRDT): Each replica periodically exchanges full state and a deterministic merge function computes the join.
Operation-based (Commutative Replicated Data Types, CmRDT): Replicas send operations that are designed to commute; receivers apply them in any order.
CRDT properties important for mobile:
Commutative, associative, idempotent merges: order and duplication do not break convergence.
Designed for partial connectivity: replicas can sync when possible and still converge.
Common CRDT examples useful in apps:
G-Counter (grow-only counter): good for likes or counters.
PN-Counter (positive-negative): supports increments and decrements via two G-Counters.
OR-Set (Observed-Remove Set): add/remove set that avoids tombstone explosion with causal metadata.
Example: a simple state-based G-Counter merge implemented in Dart.
class GCounter {
final Map<String,int> counts;
GCounter([Map<String,int>? c]) : counts = Map.from(c ?? {});
void increment(String id, int n) => counts[id] = (counts[id] ?? 0) + n;
GCounter merge(GCounter other) {
final merged = Map<String,int>.from(counts);
other.counts.forEach((k,v){ merged[k] = math.max(merged[k] ?? 0, v)
This merge is idempotent and commutative, safe to call repeatedly during intermittent sync.
Implementing Sync In Flutter
Architecture patterns:
Offline-First Local Store: Keep a local DB (SQLite, Hive) as the source of truth. Queue outgoing operations and apply incoming merges to the local store.
Sync Engine: A small deterministic engine that applies merge policies and CRDT merges. Run merges on a background isolate to avoid UI jank.
Versioning and Metadata: Attach vector clocks or lamport timestamps and device ids to objects for conflict diagnosis. Do not rely solely on device clock.
Practical tips for Flutter code:
Keep merge logic in plain Dart models (testable, platform-agnostic).
Use background tasks (workmanager, android_alarm_manager) to run periodic syncs.
Surface merge outcomes to users only when manual resolution is needed; otherwise operate automatically and log conflicts for support.
Small pattern for applying LWW with server timestamp fallback in Dart:
class LwwRegister<T> {
final T value;
final DateTime ts;
LwwRegister(this.value, this.ts);
LwwRegister<T> merge(LwwRegister<T> other) =>
(other.ts.isAfter(ts)) ? other : this;
}Use LWW sparingly for non-critical fields and combine with audit metadata.
Vibe Studio

Vibe Studio, powered by Steve’s advanced AI agents, is a revolutionary no-code, conversational platform that empowers users to quickly and efficiently create full-stack Flutter applications integrated seamlessly with Firebase backend services. Ideal for solo founders, startups, and agile engineering teams, Vibe Studio allows users to visually manage and deploy Flutter apps, greatly accelerating the development process. The intuitive conversational interface simplifies complex development tasks, making app creation accessible even for non-coders.
Conclusion
In mobile development with Flutter, adopt pragmatic merge policies: LWW for trivial fields, application-level merges for domain-specific objects, and CRDTs where automatic convergence is necessary. Keep merge logic in your model layer, use local stores for offline resilience, and run deterministic sync engines in background tasks. CRDTs reduce coordination complexity and make eventual consistency predictable—ideal for many offline-first mobile apps.