DEV Community

vmodal_ai
vmodal_ai

Posted on

Building an Offline-First Flutter Application

Building an Offline-First Flutter Application

An offline-first application assumes that the network may be unavailable.

Instead of designing:

UI -> Network -> Data
Enter fullscreen mode Exit fullscreen mode

design the application around local data:

             ┌──────────────┐
             │    Flutter   │
             └──────┬───────┘
                    |
              Repository
               /       \
              v         v
       Local Database   API
              ^         |
              |         |
              +----Sync-+
Enter fullscreen mode Exit fullscreen mode

The UI should remain useful even when the network is unavailable.

Why Offline-First?

Benefits include:

  • better user experience
  • faster screen loading
  • resilience to network failures
  • reduced API usage
  • support for intermittent connectivity

Choose a Local Database

Common options include:

  • SQLite-based solutions
  • Drift
  • Isar
  • Hive
  • SharedPreferences for simple preferences

For structured application data, choose a database rather than storing large datasets in preferences.

Repository as the Source of Truth

The UI should normally communicate with a repository:

abstract class ProductRepository {
  Stream<List<Product>> watchProducts();

  Future<void> refreshProducts();
}
Enter fullscreen mode Exit fullscreen mode

The repository can combine local and remote sources.

Read From Local Data First

A common pattern is:

Open screen
   ↓
Read local database
   ↓
Display cached data immediately
   ↓
Refresh from API
   ↓
Store new data locally
   ↓
UI receives updated data
Enter fullscreen mode Exit fullscreen mode

This gives users fast feedback.

Sync Flow

A simple synchronization strategy:

Local data
   ↓
Check connectivity
   ↓
Upload pending changes
   ↓
Download server changes
   ↓
Resolve conflicts
   ↓
Update local database
Enter fullscreen mode Exit fullscreen mode

Do not make the UI responsible for synchronization.

Pending Operations

When the user performs an action offline, store the operation locally.

Example:

class PendingOperation {
  final String id;
  final String type;
  final Map<String, dynamic> payload;

  PendingOperation({
    required this.id,
    required this.type,
    required this.payload,
  });
}
Enter fullscreen mode Exit fullscreen mode

The operation can later be synchronized.

Example Queue

Offline:
  Create order #1001
  Update profile
  Add note

Local queue:
  [Create order]
  [Update profile]
  [Add note]

Network returns:
  ↓
Process queue
  ↓
Server
Enter fullscreen mode Exit fullscreen mode

Conflict Resolution

Conflicts happen when local and server data change independently.

For example:

Device A:
name = "John"

Device B:
name = "Jonathan"

Both update while offline.
Enter fullscreen mode Exit fullscreen mode

Possible strategies include:

  • last-write-wins
  • server-wins
  • client-wins
  • field-level merging
  • manual conflict resolution

Choose a strategy based on the business domain.

Connectivity Is Not the Same as Internet Access

A device may report that it is connected to Wi-Fi while the internet is unavailable.

Therefore, an application should not assume:

Wi-Fi connected = API reachable
Enter fullscreen mode Exit fullscreen mode

The API request itself is the strongest evidence that the server is reachable.

Optimistic UI

For some actions, update the UI immediately:

User taps Like
   ↓
UI changes immediately
   ↓
Save locally
   ↓
Send API request
Enter fullscreen mode Exit fullscreen mode

If the server rejects the operation, reconcile the local state.

Retry Strategy

Do not retry failed requests continuously.

Use:

Attempt 1
   ↓
Wait
   ↓
Attempt 2
   ↓
Longer wait
   ↓
Attempt 3
Enter fullscreen mode Exit fullscreen mode

Exponential backoff reduces unnecessary network traffic.

Architecture

A production offline-first feature can use:

UI
 |
BLoC
 |
Use Case
 |
Repository
 |         v          v
Local DB   Remote API
 |
Sync Engine
Enter fullscreen mode Exit fullscreen mode

The repository coordinates data access while the synchronization service manages consistency.

Testing Offline Behavior

Test at least:

  • first launch without internet
  • cached data display
  • creating data offline
  • editing data offline
  • reconnecting
  • failed synchronization
  • duplicate requests
  • conflict resolution

Common Mistakes

Making every screen call the API directly

This makes offline support extremely difficult.

Treating connectivity state as truth

Connectivity indicators are useful hints, not proof that the backend is reachable.

No synchronization state

Users should be able to understand whether data is:

Synced
Pending
Syncing
Failed
Enter fullscreen mode Exit fullscreen mode

Conclusion

Offline-first architecture changes the role of the local database. It is no longer just a cache; it becomes a central part of the application's data flow.

When implemented correctly, users can continue working even when connectivity is unreliable.

Useful Links

SDK Flutter: https://github.com/v-modal/vmodal_sdk_flutter

SDK Android: https://github.com/v-modal/vmodal_sdk_android

Discord: https://discord.gg/K72z28KUx

Top comments (0)