SDK Interface
Configure one installation, authenticate a Sonar user, and control native provider synchronization.
Shared Lifecycle
The iOS, Android, and React Native SDKs expose the same lifecycle with platform-appropriate types and naming:
| Method | Purpose |
|---|---|
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:
| Operation | Swift | Kotlin | React Native |
|---|---|---|---|
| Configure | Sonar.configure(appId:environment:) | Sonar.configure(context, appId, environment) | Sonar.configure(options) |
| Authenticate | Sonar.authenticate(clientTokenProvider:) | Sonar.authenticate(clientTokenProvider) | Sonar.authenticate(options) |
| Connect | Sonar.connect(_:) | Sonar.connect(provider) | Sonar.connect(provider) |
| Disconnect | Sonar.disconnect(_:) | Sonar.disconnect(provider) | Sonar.disconnect(provider) |
| Immediate sync | Sonar.sync() | Sonar.sync() | Sonar.sync() |
| Safe replay | Sonar.resync(_:) | Sonar.resync(provider) | Sonar.resync(provider) |
| Current state | Sonar.providerState(for:) | Sonar.getProviderState(provider) | Sonar.getProviderState(provider) |
| Observe state | Sonar.observeState(_:) | Sonar.observeState(observer) | Sonar.observeState(callback) |
| Sign out | Sonar.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:
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:
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
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.
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";
};authenticatereturns after the user-scoped SDK session is ready.connectreturns after permissions have been requested and Sonar has accepted or already holds the connection.pending_cleanupmeans a prior disconnect is still finishing. Callconnectagain later; the SDK also tries once when it resumes on a later application launch. It does not poll cleanup continuously.disconnectreturns when new synchronization has stopped.disconnectingmeans asynchronous deletion continues.syncandresyncreturn as soon as work is scheduled. Compatible concurrent calls returnalready_runningand join the current run.signOutis 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
| Action | Scope | Existing Sonar data |
|---|---|---|
| Automatic synchronization | Initial import, background opportunities, and incremental changes | Preserved |
sync() | New changes for all connected providers; unfinished history then resumes | Preserved |
resync(provider) | Full recent-to-historical replay for one provider | Preserved and deduplicated |
disconnect(provider) | Stop and delete one provider | Permanently 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:
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:
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:
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:
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;
};| Code | Retryable | Recovery action | Meaning |
|---|---|---|---|
not_configured | No | fix_configuration | configure has not completed |
configuration_conflict | No | sign_out | Changing the configured application or environment requires sign-out first |
authentication_required | No | authenticate | No user is associated with this installation |
authentication_conflict | No | sign_out | Switching the installation to another user requires sign-out first |
client_token_unavailable | Yes | retry | The mobile application’s token provider could not return a token |
reauthorization_required | No | authenticate | The renewable session cannot be recovered |
user_deleted | No | authenticate | The user was deleted from Sonar; the SDK has already cleared its state on this device |
unsupported_provider | No | fix_configuration | The provider is not enabled for this registered application or platform |
provider_unavailable | No | none | The device cannot use this provider |
provider_install_required | No | install_provider | The native provider must be installed |
provider_update_required | No | update_provider | The native provider must be updated |
provider_configuration_required | No | fix_configuration | Application capabilities, declarations, registration, or signature are invalid |
permission_denied | No | open_settings | The platform reports that required permission was denied |
provider_not_connected | No | connect_provider | The requested synchronization scope is not connected |
temporarily_unavailable | Yes | retry | Network, provider, or Sonar work can be retried |
unexpected | No | contact_support | The 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()andresync(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.
Sonar