Abrova
☰ Docs menu · Platform notes
Coming soon. Abrova Sync is coming soon. This page describes how it will work; it can’t be used yet.

Platform Notes

Per-platform behavior notes for the Abrova Sync SDKs — Flutter and React Native wrap native, Web uses IndexedDB, Node.js is server-side, REST has no realtime channel — and the error types each SDK throws.

Last updated: 27 Oct 2026

Behavior, authentication, and capability differences between each AbrovaSync SDK and the REST API.

Per-platform behavior#

PlatformNotes
Flutter & React NativeWrap the native Android and iOS SDKs — best performance with a consistent API across both platforms.
Web SDKBuilt-in offline support via IndexedDB. Offline writes sync automatically when the connection returns.
Native Android & iOSBoth async/await and callback-based APIs. Choose the style that fits your app architecture.
Node.js / Backend SDKServer-side SDK. Requires both apiKey and serverSecret. Once a database has rules they apply to it as to any client (auth.uid is the userId you configure); a database with no rules is open to it, while client SDKs are locked out. Realtime is opt-in (enableRealtime). Batches are atomic, as on every SDK.
REST APIDirect HTTP access from any language. There is no realtime channel or change webhook: poll, or use the Node.js SDK with enableRealtime.

Live updates use an encrypted connection on every platform (Android 0.2.5+, iOS 0.2.4+, Flutter and React Native 0.2.5+, Web, Node.js 0.2.2+).

Server-Side Only (REST API & Node.js).

Never use the server secret in client-side code (mobile/web apps). Anything shipped in an app should be treated as public, and the server secret can mint a user token for anyone in your project. Client apps use the API key only, plus a short-lived user token your backend mints for them — see Security Rules.

Errors#

When a call fails, every SDK throws (or rejects with) an error that carries the HTTP status of the server's answer as statusCode. Use statusCode to tell a refusal from a connection problem:

statusCodeMeaning
403Your security rules refused the request.
404The document does not exist.
400 / 422The server does not accept the request (for example invalid data).
401The user token is missing, expired or not valid.
null / undefinedThe server could not be reached (no network, timeout).

With offline persistence on, a call that cannot reach the server is answered from the cache or queued instead of failing. A refusal by the server is always thrown.

Errors from your security rules also carry a code:

  • query_not_allowed: the rules do not allow a query or a listener, because it could return documents the user may not read. fields names the fields to filter on. See Queries and your security rules.
  • listener_revoked: a listener was stopped because the rules no longer allow it (for example, the user was removed from a team). It receives nothing more and is not restarted.
  • permission_denied: the rules do not allow reading what a listener listens to (Flutter, React Native, Web).
PlatformError typeFields
AndroidAbrovaSyncException.NetworkException, with the subclasses QueryNotAllowedException and ListenerRevokedExceptionstatusCode, code, fields (QueryNotAllowedException)
iOSAbrovaSyncError, with the cases .queryNotAllowed(_, fields:) and .listenerRevokedstatusCode, code, fields
FlutterAbrovaSyncError for calls; AbrovaSyncListenerError for listener errorscode, statusCode, fields; isPermissionDenied (AbrovaSyncError); isQueryNotAllowed, isListenerRevoked (AbrovaSyncListenerError)
React NativeAbrovaSyncErrorcode, statusCode, fields
WebAbrovaSyncErrorcode, reason, statusCode (also status), fields
Node.jsAbrovaSyncError, with the subclasses QueryNotAllowedError and ListenerRevokedErrorcode, statusCode (also status), serverCode, fields (QueryNotAllowedError)
dart
// Flutter
try {
  final open = await todos.where('teamId', QueryOperator.equal, teamId).get();
} on AbrovaSyncError catch (e) {
  if (e.code == AbrovaSyncErrorCode.queryNotAllowed) {
    print('Filter on: ${e.fields}');
  } else if (e.isPermissionDenied) {
    print('Refused by the security rules');
  } else if (e.statusCode == null) {
    print('Could not reach the server');
  }
}
typescript
// React Native
import { AbrovaSyncError } from '@abrova/sync-react-native';

try {
  const open = await todos.where('teamId', '==', teamId).get();
} catch (e) {
  if (e instanceof AbrovaSyncError && e.code === AbrovaSyncError.QUERY_NOT_ALLOWED) {
    console.log('Filter on:', e.fields);
  } else if (e instanceof AbrovaSyncError && e.statusCode === 403) {
    console.log('Refused by the security rules');
  }
}
typescript
// Web
import { AbrovaSyncError } from '@abrova/sync-web';

try {
  const open = await todos.where('teamId', '==', teamId).get();
} catch (e) {
  if (e instanceof AbrovaSyncError && e.reason === 'query_not_allowed') {
    console.log('Filter on:', e.fields);
  } else if (e instanceof AbrovaSyncError && e.statusCode === 403) {
    console.log('Refused by the security rules');
  } else if (e instanceof AbrovaSyncError && e.statusCode === undefined) {
    console.log('Could not reach the server');
  }
}
typescript
// Node.js
import { AbrovaSyncError, QueryNotAllowedError } from '@abrova/sync-node';

try {
  const open = await todos.where('teamId', '==', teamId).get();
} catch (e) {
  if (e instanceof QueryNotAllowedError) {
    console.log('Filter on:', e.fields);
  } else if (e instanceof AbrovaSyncError && e.statusCode === 403) {
    console.log('Refused by the security rules');
  } else if (e instanceof AbrovaSyncError && e.statusCode === undefined) {
    console.log('Could not reach the server');
  }
}
kotlin
// Android
try {
    val open = todos.where("teamId", QueryOperator.EQUAL, teamId).get()
} catch (e: AbrovaSyncException.QueryNotAllowedException) {
    Log.w("Todos", "Filter on: ${e.fields}")
} catch (e: AbrovaSyncException.NetworkException) {
    when (e.statusCode) {
        403 -> Log.w("Todos", "Refused by the security rules")
        null -> Log.w("Todos", "Could not reach the server")
        else -> Log.w("Todos", "Failed: ${e.message}")
    }
}
swift
// iOS
do {
    let open = try await todos.query().where("teamId", .equal, teamId).get()
} catch AbrovaSyncError.queryNotAllowed(_, let fields) {
    print("Filter on: \(fields)")
} catch let error as AbrovaSyncError {
    switch error.statusCode {
    case 403: print("Refused by the security rules")
    case nil: print("Could not reach the server")
    default: print("Failed: \(error.localizedDescription)")
    }
}

Listener errors arrive in the listener's error callback instead of being thrown. See Realtime Listeners.

Was this page helpful?
Report a problem