SDK Interface

Configure one installation, authenticate a Sonar user, and control native provider synchronization.

9 min read Updated Sep 21, 2026

Shared Lifecycle

The iOS, Android, and React Native SDKs expose the same lifecycle with platform-appropriate types and naming:

MethodPurpose
configure(appId, environment)Configure the registered mobile application before user authentication
authenticate(clientTokenProvider)Associate this installation with one Sonar user and maintain its renewable session
connect(provider)Request native permissions, connect the provider, and start its initial synchronization
disconnect(provider)Stop the provider and permanently delete its imported Sonar data
sync()Request an immediate incremental synchronization for every connected provider
resync(provider)Safely reread and replay up to two years for one provider without deleting existing data
getProviderState(provider)Read the latest availability, permission, connection, and synchronization state
observeState(callback)Receive state changes and return a subscription that can be cancelled
signOut()Revoke this installation’s SDK session and clear its local credentials

configure is application-scoped. Call authenticate after your customer has signed in and revoke the SDK session with signOut before another customer uses the installation.

Repeating configure with the same application and environment is safe. Changing either value while authenticated requires signOut first. The application registration is the provider allowlist; V1 does not configure another provider subset at runtime. Repeating authenticate for the current session returns already_authenticated, while switching to another user also requires signOut first.

Platform Method Names

The platforms use the same method names and completion boundaries. Enum values follow the normal convention of each language:

OperationSwiftKotlinReact Native
ConfigureSonar.configure(appId:environment:)Sonar.configure(context, appId, environment)Sonar.configure(options)
AuthenticateSonar.authenticate(clientTokenProvider:)Sonar.authenticate(clientTokenProvider)Sonar.authenticate(options)
ConnectSonar.connect(_:)Sonar.connect(provider)Sonar.connect(provider)
DisconnectSonar.disconnect(_:)Sonar.disconnect(provider)Sonar.disconnect(provider)
Immediate syncSonar.sync()Sonar.sync()Sonar.sync()
Safe replaySonar.resync(_:)Sonar.resync(provider)Sonar.resync(provider)
Current stateSonar.providerState(for:)Sonar.getProviderState(provider)Sonar.getProviderState(provider)
Observe stateSonar.observeState(_:)Sonar.observeState(observer)Sonar.observeState(callback)
Sign outSonar.signOut()Sonar.signOut()Sonar.signOut()

Provider values follow the normal convention of each language. Swift exposes .appleHealth; Kotlin exposes HEALTH_CONNECT and SAMSUNG_HEALTH; React Native uses apple_health on iOS and health_connect or samsung_health on Android.

Shared Types

Every platform maps to the following semantic types:

typescript
type NativeProvider = "apple_health" | "health_connect" | "samsung_health";

type SessionStatus =
  | "signed_out"
  | "ready"
  | "refreshing"
  | "temporarily_unavailable"
  | "reauthorization_required";

type ProviderAvailability =
  | "unknown"
  | "available"
  | "unavailable"
  | "install_required"
  | "update_required"
  | "configuration_required";

type PermissionStatus =
  | "not_requested"
  | "requested"
  | "granted"
  | "partial"
  | "denied";

type ConnectionStatus =
  | "disconnected"
  | "connecting"
  | "connected"
  | "disconnecting";

type SynchronizationStatus =
  | "idle"
  | "syncing_recent"
  | "syncing_historical"
  | "syncing_incremental"
  | "temporarily_unavailable";

type RecoveryAction =
  | "none"
  | "authenticate"
  | "connect_provider"
  | "request_permission"
  | "open_settings"
  | "install_provider"
  | "update_provider"
  | "fix_configuration"
  | "sign_out"
  | "retry"
  | "contact_support";

NativeProvider is the shared cross-platform set. Runtime platform validation still applies: Apple Health is available only on iOS, while Health Connect and Samsung Health are available only on Android.

HealthKit does not reveal whether a user denied read access to an individual type. On iOS, requested means the authorization request completed; empty HealthKit results are not reported as denied. Health Connect and Samsung Health can report granted, partial, or denied when their platform APIs expose that distinction.

Configure and Authenticate

This example uses the React Native spelling of the shared interface:

typescript
Sonar.configure({
  appId: "app_01K4M7D9R3W2V8Y6T5Q1N0PABC",
  environment: "sandbox",
});

await Sonar.authenticate({
  clientTokenProvider: async (context) => {
    const response = await customerApi.createSonarClientToken({
      appId: context.appId,
      installationId: context.installationId,
    });
    return response.client_token;
  },
});

The token provider calls your authenticated backend. Your API key remains on that backend and never enters the application. The SDK calls the provider again only when its renewable session cannot be recovered directly with Sonar.

Connect and Observe

typescript
const unsubscribe = Sonar.observeState((state) => {
  renderNativeHealthState(state);
});

const result = await Sonar.connect("health_connect");

connect checks availability, presents the operating system’s permission flow when needed, records the installation-owned provider connection, and starts automatic synchronization. Partial permission grants produce a connected provider with permission detail rather than failing the entire connection. When the user connects on another phone, the newest installation takes the connection over with its data, and the older one reads disconnected. disconnect removes the provider and all its data from Sonar; to stop using some data while keeping it, change the user’s consolidation settings from your backend.

Call unsubscribe() when the owning application lifecycle ends. State observations do not contain health records or Sonar credentials.

Method Results

Calls return after the requested lifecycle transition has been recorded or scheduled. They do not wait for historical synchronization or server-side normalization.

typescript
type AuthenticationResult = {
  status: "authenticated" | "already_authenticated";
  session: SessionStatus;
};

type ConnectResult = {
  status: "connected" | "already_connected" | "pending_cleanup";
  state: ProviderState;
};

type DisconnectResult = {
  status: "disconnecting" | "disconnected";
  state: ProviderState;
};

type SyncResult = {
  status: "started" | "already_running";
  providers: NativeProvider[];
};

type ResyncResult = {
  status: "started" | "already_running";
  provider: NativeProvider;
};

type SignOutResult = {
  status: "signed_out";
};
  • authenticate returns after the user-scoped SDK session is ready.
  • connect returns after permissions have been requested and Sonar has accepted or already holds the connection. pending_cleanup means a prior disconnect is still finishing. Call connect again later; the SDK also tries once when it resumes on a later application launch. It does not poll cleanup continuously.
  • disconnect returns when new synchronization has stopped. disconnecting means asynchronous deletion continues.
  • sync and resync return as soon as work is scheduled. Compatible concurrent calls return already_running and join the current run.
  • signOut is idempotent and returns after credentials and user-specific local state have been cleared.

The SDK serializes lifecycle mutations for each provider. disconnect(provider) stops the active run and forgets unsent progress for that provider before scheduling deletion. signOut() takes precedence over local work, stops active synchronization, and clears credentials and user-specific progress without deleting data already accepted by Sonar. Connect and disconnect requests for the same provider are applied in invocation order.

Automatic, Immediate, and Full Synchronization

ActionScopeExisting Sonar data
Automatic synchronizationInitial import, background opportunities, and incremental changesPreserved
sync()New changes for all connected providers; unfinished history then resumesPreserved
resync(provider)Full recent-to-historical replay for one providerPreserved and deduplicated
disconnect(provider)Stop and delete one providerPermanently removed

Immediate Sync

Use sync() when the mobile app knows the user expects current data, such as when opening a health dashboard after a workout:

typescript
const result = await Sonar.sync();
// result.status is "started" or "already_running"

The call requests work and returns without waiting for historical synchronization or server-side normalization. Concurrent calls join the active run.

Safe Resynchronization

Use resync(provider) to recover missing data, replace an invalid native cursor, or import record types added by a newer SDK:

typescript
const result = await Sonar.resync("health_connect");
// result.status is "started" or "already_running"

Resynchronization resets only that provider’s device-local read position. It keeps the provider connected, preserves data already stored by Sonar, reads the recent 30 days first, and continues backwards for up to two years. Replayed records are deduplicated during processing.

The SDK persists the request and resumes after an application restart. Repeated requests join the same replay instead of creating concurrent work.

Resynchronization is not deletion

To remove a provider’s imported data, call disconnect(provider). A destructive rebuild is an explicit disconnect followed by connect after deletion completes.

Provider State

getProviderState and observeState expose a combined provider view:

typescript
type ProviderState = {
  provider: NativeProvider;
  availability: ProviderAvailability;
  permission: PermissionStatus;
  connection: ConnectionStatus;
  synchronization: SynchronizationStatus;
  recoveryAction: RecoveryAction;
  lastSuccessfulSyncAt: string | null;
  backfill: BackfillProgress | null;
  observedAt: string;
};

type BackfillProgress = {
  phase: "recent" | "history" | "complete";
  daysDone: number;
  daysTotal: number;
  fraction: number;
};

type SDKState = {
  session: SessionStatus;
  providers: ProviderState[];
};

observeState immediately emits the restored local SDKState, then emits changes from permission checks, synchronization, session refresh, and authoritative Sonar responses. getProviderState returns the current SDK view for one configured provider.

backfill is null until the provider is connected. It then reports recent and historical import progress; fraction ranges from 0 to 1. observedAt tells the mobile app when the provider view was last updated. The SDK refreshes authoritative connection and session state with Sonar when connectivity permits. Local state never grants permission or authorizes an upload.

Errors

Every rejected public method returns a structured SDK error:

typescript
type SonarSdkErrorCode =
  | "not_configured"
  | "configuration_conflict"
  | "authentication_required"
  | "authentication_conflict"
  | "client_token_unavailable"
  | "reauthorization_required"
  | "user_deleted"
  | "unsupported_provider"
  | "provider_unavailable"
  | "provider_install_required"
  | "provider_update_required"
  | "provider_configuration_required"
  | "permission_denied"
  | "provider_not_connected"
  | "temporarily_unavailable"
  | "unexpected";

type SonarSdkError = {
  code: SonarSdkErrorCode;
  message: string;
  retryable: boolean;
  recoveryAction: RecoveryAction;
  provider: NativeProvider | null;
};
CodeRetryableRecovery actionMeaning
not_configuredNofix_configurationconfigure has not completed
configuration_conflictNosign_outChanging the configured application or environment requires sign-out first
authentication_requiredNoauthenticateNo user is associated with this installation
authentication_conflictNosign_outSwitching the installation to another user requires sign-out first
client_token_unavailableYesretryThe mobile application’s token provider could not return a token
reauthorization_requiredNoauthenticateThe renewable session cannot be recovered
user_deletedNoauthenticateThe user was deleted from Sonar; the SDK has already cleared its state on this device
unsupported_providerNofix_configurationThe provider is not enabled for this registered application or platform
provider_unavailableNononeThe device cannot use this provider
provider_install_requiredNoinstall_providerThe native provider must be installed
provider_update_requiredNoupdate_providerThe native provider must be updated
provider_configuration_requiredNofix_configurationApplication capabilities, declarations, registration, or signature are invalid
permission_deniedNoopen_settingsThe platform reports that required permission was denied
provider_not_connectedNoconnect_providerThe requested synchronization scope is not connected
temporarily_unavailableYesretryNetwork, provider, or Sonar work can be retried
unexpectedNocontact_supportThe SDK cannot classify the failure safely

Retryable synchronization failures leave the native read position unchanged. The SDK tries that work again during a later foreground, observer, or operating-system background opportunity. Applications should render state and recovery guidance rather than scheduling uploads themselves. A public method rejects only when its requested work cannot be accepted or scheduled; failures during later background work are delivered through observeState.

Recovery Rules

  • Temporary provider, network, and Sonar failures preserve synchronization progress and retry during a later SDK run.
  • An expired or revoked SDK session requests a new client token through the mobile application’s provider.
  • The newest installation to connect a provider owns it; the previous one stops syncing and reads disconnected.
  • sync() and resync(provider) require an authenticated SDK session and at least one connected provider in their requested scope.
  • signOut() clears credentials and user-specific local synchronization state without deleting imported Sonar data.

Continue to SDK Authentication, review Supported Native Data, or read SDK Synchronization and Provider Lifecycle.