Name:
interface
Value:
Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.

Page updated May 1, 2026

LegacyYou are viewing Gen 1 documentation. Switch to the latest Gen 2 docs →

Offline-first

For a better understanding of Amplify DataStore's opinionated approach, consult these resources:

To further explore and understand the principles of building offline-first applications, we recommend reviewing the Android guide for building offline-first apps.

We understand the importance of these capabilities to your applications, and while DataStore will no longer receive new features, we want to ensure that you have the necessary resources to transition smoothly. The following sections provide high-level recommendations on alternative solutions.

Remote sync

By regularly synchronizing your local store, it can serve as the primary source for your application, allowing for read operations that can be done offline. Opting to manage offline writes will introduce additional complexity to your application, so it is crucial to consider whether you want to handle this for each individual model.

Live syncing

You can achieve real-time detection of changes to a remote repository by leveraging Amplify.API.subscribe with onCreate, onUpdate, and onDelete, as long as you are connected to the internet. This method is not sufficient on its own since the subscriptions do not activate when the app is offline.

Future<void> initOnlineSync() async {
final onCreate = ModelSubscriptions.onCreate(RemoteModel.classType);
final createSubscription = Amplify.API.subscribe(onCreate);
createSubscription.listen((response) {
_syncItem(response, localRepository.create);
});
final onUpdate = ModelSubscriptions.onUpdate(RemoteModel.classType);
final updateSubscription = Amplify.API.subscribe(onUpdate);
updateSubscription.listen((response) {
_syncItem(response, localRepository.update);
});
final onDelete = ModelSubscriptions.onDelete(RemoteModel.classType);
final deleteSubscription = Amplify.API.subscribe(onDelete);
deleteSubscription.listen((response) {
_syncItem(response, localRepository.delete);
});
}
Future<void> _syncItem(
GraphQLResponse<RemoteModel> response,
Function(LocalModel) syncFunction,
) async {
final remoteModel = response.data;
if (remoteModel == null) return;
final localModel = toLocalModel(remoteModel);
// Calls localRepository's create, update, or delete method
syncFunction.call(localModel);
}

Local cache refresh

Whenever your app reconnects to the internet, whether through launching the app or network updates, perform a complete refresh of your local store to ensure that you receive all the updates that you may have missed.

Future<void> _syncAll() async {
final response = await remoteRepository.readAll();
final remoteItems = response.data?.items;
final localData = remoteItems?.nonNulls.map(toLocalModel);
if (localData == null) return;
// Clear local and save results
localRepository.rebuild(localData);
}

Detect network status

You can reactively detect network status updates by waiting for an Amplify.API.mutate operation to throw an exception or for your subscription observers to send an error.

void _initObservers() {
final onCreate = ModelSubscriptions.onCreate(Todo.classType);
Amplify.API
.subscribe(onCreate)
.listen(_syncCreate, onError: (_) => _offlineDetected());
final onUpdate = ModelSubscriptions.onUpdate(Todo.classType);
Amplify.API
.subscribe(onUpdate)
.listen(_syncUpdate, onError: (_) => _offlineDetected());
final onDelete = ModelSubscriptions.onDelete(Todo.classType);
Amplify.API
.subscribe(onDelete)
.listen(_syncDelete, onError: (_) => _offlineDetected());
}
Future<GraphQLResponse<Todo>> create(Todo model) async {
try {
final request = ModelMutations.create(model);
return Amplify.API.mutate(request: request).response;
} catch (e, st) {
_offlineDetected();
rethrow;
}
}

Additionally, you can proactively detect network status updates via Amplify Hub. This allows you to identify scenarios where your local cache needs to be synced even though you are not actively making network requests.

Amplify.Hub.listen(HubChannel.Api, _onNetworkStatusChanged);
void _onNetworkStatusChanged(ApiHubEvent hubEvent) {
if (hubEvent is SubscriptionHubEvent) {
switch (hubEvent.status) {
case SubscriptionStatus.connected:
_onlineDetected();
break;
case SubscriptionStatus.connecting:
case SubscriptionStatus.pendingDisconnected:
case SubscriptionStatus.disconnected:
case SubscriptionStatus.failed:
_offlineDetected();
default:
break;
}
}
}

Once you have detected that the application is offline, periodically attempt to resync your local cache. This allows you to detect when your app is back online and cleans up any stale data. We recommend implementing a backoff strategy or using a third-party package to efficiently distribute your resync attempts.

Future<void> _offlineDetected() async {
if (_isOffline) return;
_isOffline = true;
final backoffStrategy = ExponentialJitterBackoff();
while (_isOffline) {
await Future.delayed(backoffStrategy.nextDelay());
_isOffline = !await _resync();
}
}

Offline mutations

By storing any changes made locally in a separate pending mutations table and synchronizing them at a later time, you can enable your users to work offline. Your pending mutations table should contain:

  1. Auto incrementing primary key (not your model's normal primary key)
  2. Enum for type of mutation (create, update, or delete)
  3. The state of the model at the time of the action

As you are syncing your pending mutations table, handle your Amplify.API.mutate responses per the following table:

Exception Was ThrownResponse Has DataResponse hasErrorsResultAction
TRUEN/AN/AExceptionPause Syncing
FALSETRUEFALSESuccessContinue Syncing
FALSETRUETRUEPartial SuccessContinue Syncing
FALSEFALSETRUEValidation ErrorContinue Syncing
FALSEFALSEFALSENot a Valid ResultN/A

To incorporate pending mutations table entries into your app before syncing, you must aggregate them with the results of your local store queries.

Manually start and stop sync

DataStore allows for syncing of data to be manually stopped and started. To implement this, you can check if syncing is enabled before running the sync logic and provide a way to toggle the flag. Remember to sync your local store when enabling sync.

bool _syncEnabled = true;
void enableSync() {
final previous = _syncEnabled;
_syncEnabled = true;
if (previous != _syncEnabled) _syncAll();
}
void disableSync() {
_syncEnabled = false;
}