Trust Signals: Mobile Sensor SDK
This page shows you how to install and use the Trust Signals Mobile Sensor SDK for Android and iOS. The SDK collects contextual data from the user’s device and sends it, through your collection proxy, to the Futurae Collection API.
Prerequisites
- Android: Android 6.0 (API level 23) or later, Kotlin 1.9 or later, and
compileSdk36. - iOS: iOS 16 or later, built with Xcode 16 or later and Swift 5.9 or later.
- A collection proxy on your backend. The SDK sends observations to it, never directly to Futurae. See Collection proxy.
Before you integrate, read Core Concepts, which explains the identifiers you pass to the SDK.
Release information
The release history is published on GitHub for Android and iOS. The API references are at futurae-technologies.github.io/android-trust-signals-sdk for Android and futurae-technologies.github.io/ios-trust-signals-sdk for iOS.
How the SDK works
The Android and iOS SDKs implement the same model:
- Collect on demand. One call collects sensor data and uploads it immediately, for example just before an authentication attempt.
- Collect on schedule. A recurring background job collects and uploads at an interval you set. Schedules survive app restarts.
- Identifiers per collection. Every collection carries its own credentials and identifiers, so one SDK instance can tag different collections differently.
Each collection follows the same path:
- Your app calls
collectAndUpload(), or a scheduled job starts. - The SDK collects data from every source it has permission for, within the collection timeout.
- The SDK sends
POSTto thecollectionUrlyou configured, which is your collection proxy. - Your proxy sets the collection token and the identity fields, and forwards the request to the Collection API, which replies
202 Accepted. - On demand, the SDK returns the collection to your app.
Installation
Android
The SDK is distributed through GitHub Packages. Add the dependency to your app module:
// app/build.gradle.kts
implementation("com.futurae.sdk:trust-signals:<version>")
Replace <version> with the latest release tag. GitHub Packages requires authentication. The repository setup is in the Android SDK README.
iOS
The SDK is distributed through Swift Package Manager. In Xcode, choose File → Add Package Dependencies, enter https://github.com/Futurae-Technologies/ios-trust-signals-sdk.git, set the dependency rule to Exact Version with the latest release tag, and add TrustSignalsSDK to your target. Swift Package Manager needs an exact version for pre-release versions such as 0.1.0-alpha.
Or add it in Package.swift:
.package(url: "https://github.com/Futurae-Technologies/ios-trust-signals-sdk.git", exact: "<version>")
Full setup instructions are in the iOS SDK README.
Permissions
The SDK collects only what your app has permission for. A missing permission means the related data source reports nothing: the rest of the collection proceeds and uploads normally, and only the signals that depend on that data are affected. Decide when and how to ask the user for each permission as part of your app’s own flow.
In the tables below, Proximity means both Latest Proximity and Historical Proximity.
Android
The SDK declares its permissions in its own manifest, and they are merged into your app automatically. Normal permissions need no action. Runtime permissions must be requested by your app before the SDK can use them.
Runtime permissions, which your app must request:
| Permission | API level | Collects | Used by |
|---|---|---|---|
ACCESS_FINE_LOCATION | All | Location, Wi-Fi scan, Bluetooth scan, nearby devices | Proximity, Geolocation, Geovelocity |
ACCESS_COARSE_LOCATION | All | Approximate location, when precise location is denied | Geolocation, Geovelocity |
BLUETOOTH_SCAN | 31 and later | Bluetooth scan, nearby devices | Proximity |
BLUETOOTH_CONNECT | 31 and later | Connected Bluetooth peripherals | Proximity |
NEARBY_WIFI_DEVICES | 33 and later | Wi-Fi scan | Proximity |
READ_PHONE_STATE | All | Phone-call state | Active Call |
Normal permissions, granted automatically:
| Permission | Purpose |
|---|---|
BLUETOOTH, BLUETOOTH_ADMIN | Bluetooth on API level 30 and earlier, declared with maxSdkVersion="30" |
INTERNET | Uploading observations |
ACCESS_WIFI_STATE, CHANGE_WIFI_STATE | Wi-Fi state collection |
ACCESS_NETWORK_STATE, CHANGE_NETWORK_STATE | Network state collection |
FOREGROUND_SERVICE | The location foreground service |
FOREGROUND_SERVICE_LOCATION | The location foreground service type, on API level 34 and later |
iOS
iOS permissions are configured in Info.plist and in your app’s entitlements.
Info.plist key or entitlement | Collects | Used by |
|---|---|---|
NSLocationWhenInUseUsageDescription | GPS location, Wi-Fi network information | Proximity, Geolocation, Geovelocity |
NSBluetoothAlwaysUsageDescription | Bluetooth scan, Bluetooth peripherals, nearby devices | Proximity |
NSLocalNetworkUsageDescription | Nearby devices, through local network discovery | Proximity |
NSBonjourServices | Nearby devices. Required together with NSLocalNetworkUsageDescription. | Proximity |
Access WiFi Information entitlement (com.apple.developer.networking.wifi-info) | SSID of the connected Wi-Fi network | Proximity |
Phone-call state needs no permission on iOS.
Add the purpose strings to Info.plist, describing why your app uses each capability:
<key>NSLocationWhenInUseUsageDescription</key>
<string>Used for Trust Signals location verification.</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Used for Trust Signals Bluetooth device scanning.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Used for Trust Signals nearby device discovery.</string>
Enable the Access WiFi Information capability in Xcode, under Signing & Capabilities.
Nearby-device discovery browses only the Bonjour service types declared under NSBonjourServices. iOS silently skips any type that is not listed, so an incomplete list reduces nearby-device coverage without producing an error. To match the SDK’s full coverage, declare all 38 types:
<key>NSBonjourServices</key>
<array>
<string>_smb._tcp.</string>
<string>_privet._tcp.</string>
<string>_device-info._tcp.</string>
<string>_sftp-ssh._tcp.</string>
<string>_airplay._tcp.</string>
<string>_scanner._tcp.</string>
<string>_mediaremotetv._tcp.</string>
<string>_rdlink._tcp.</string>
<string>_rfb._tcp.</string>
<string>_uscan._tcp.</string>
<string>_companion-link._tcp.</string>
<string>_apple-mobdev2._tcp.</string>
<string>_b._dns-sd._udp.</string>
<string>_afpovertcp._tcp.</string>
<string>_nfs._tcp.</string>
<string>_webdav._tcp.</string>
<string>_ftp._tcp.</string>
<string>_ssh._tcp.</string>
<string>_eppc._tcp.</string>
<string>_http._tcp.</string>
<string>_telnet._tcp.</string>
<string>_printer._tcp.</string>
<string>_ipp._tcp.</string>
<string>_pdl-datastream._tcp.</string>
<string>_riousbprint._tcp.</string>
<string>_daap._tcp.</string>
<string>_dpap._tcp.</string>
<string>_ichat._tcp.</string>
<string>_presence._tcp.</string>
<string>_ica-networking._tcp.</string>
<string>_airport._tcp.</string>
<string>_xserveraid._tcp.</string>
<string>_distcc._tcp.</string>
<string>_apple-sasl._tcp.</string>
<string>_workstation._tcp.</string>
<string>_servermgr._tcp.</string>
<string>_raop._tcp.</string>
<string>_xcs2p._tcp.</string>
</array>
If you use scheduled collections, also enable the Location, Background Fetch and Background Processing background modes, under Signing & Capabilities → Background Modes, and register the SDK’s background task at launch, as described in Registering the background task on iOS.
Initialization
Initialize the SDK once, before calling any other SDK method, at every entry point of your app. App launch, background tasks and push handlers each start in a fresh context with no shared state. On Android these are distinct classes. On iOS they can be distinct processes, and a push extension does not share the app’s local observation cache unless you configure an App Group.
TSConfiguration is set once, at initialization, and applies to every collection:
| Field | Type | Required | Description |
|---|---|---|---|
collectionUrl | String on Android, URL on iOS | Yes | The full URL of your collection proxy endpoint. |
collectionTimeoutMs on Android, collectionTimeout on iOS | Long on Android, Duration on iOS | No | The maximum time to wait for the data sources before returning a partial result. The default is 20 seconds. The timeoutMs argument on Android, or timeout: on iOS, of collect() and collectAndUpload() overrides it for one call. On iOS, scheduled collections always use the configured value. |
collectionReuseWindow on iOS | Duration | No | How old signals may be when a scheduled collection reuses them instead of reading the sensors again, for example when several accounts come due at almost the same time. The default, zero, means no reuse. On-demand calls never reuse signals. |
requestTimeout on iOS | Duration | No | The HTTP timeout for each upload to your proxy, independent of the collection timeout. The default is 15 seconds. |
permissionPolicy on iOS | TSPermissionPolicy | No | Overrides how the SDK resolves permissions, to force specific permission states in tests or demo apps without prompting the user. The default defers to the system. |
On Android, initialize in Application.onCreate():
import com.futurae.sdk.ts.TrustSignalsSDK
import com.futurae.sdk.ts.model.public.TSConfiguration
TrustSignalsSDK.initialize(
context = this,
configuration = TSConfiguration(
collectionUrl = "https://your-backend.example.com/trust-signals/collections"
)
)
On iOS, initialize on app launch, for example in AppDelegate or App.init:
import TrustSignals
await TrustSignalsSDK.initialize(
TSConfiguration(
collectionUrl: URL(string: "https://your-backend.example.com/trust-signals/collections")!
)
)
Registering the background task on iOS
On iOS, scheduled collections run in the background only if your app registers the SDK’s background task. Call registerBackgroundTasks(identifier:) in application(_:didFinishLaunchingWithOptions:), before that method returns. The call is synchronous and independent of initialize(_:), because iOS rejects background task handlers registered after launch completes.
import TrustSignals
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
TrustSignalsSDK.registerBackgroundTasks(identifier: "com.example.mybank.trust-signals")
Task {
await TrustSignalsSDK.initialize(
TSConfiguration(
collectionUrl: URL(string: "https://your-backend.example.com/trust-signals/collections")!
)
)
}
return true
}
}
List the same identifier under BGTaskSchedulerPermittedIdentifiers in Info.plist:
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.example.mybank.trust-signals</string>
</array>
registerBackgroundTasks(identifier:) returns false if the identifier is missing from that list, or if the call came too late. Background collection is then unavailable, while everything else keeps working. Nothing runs in the background until you schedule a collection. Each background launch runs the accounts that are due, then requests the next launch, until you call stopScheduledCollections().
Collecting observations
Credentials and identifiers
Every upload, on demand or scheduled, takes a TSCredentials value on both platforms. All its fields are required:
| Field | Required | Description |
|---|---|---|
serviceId | Yes | Your tenant’s UUID. It must equal the service_id claim of your collection token. |
unitId | Yes | The static identifier of this app, such as mybank-ios. |
accountId | Yes | The end user these observations belong to. Your proxy overwrites it. |
accessToken | Yes | The value the SDK sends as Bearer token to your proxy. Use a placeholder, or your app’s own session credential for your backend. Never the Futurae collection token. |
interactionId | Yes | The app installation, such as your installation identifier. Your proxy overwrites it. |
verificationStatus | Yes | A TSVerificationStatus value: VERIFIED, UNVERIFIED or FRAUD on Android, .verified, .unverified or .fraud on iOS. Your proxy overwrites it. |
What each identifier means, and its format rules, are in Identifiers. The recommended values are in Identifier Strategy.
Both SDKs reject invalid credentials before they read any sensor:
- On Android,
TSCredentialschecks its values when you create it. If an identifier does not match its format rules, oraccessTokenis blank, it throwsIllegalArgumentException, so the invalid value never reachescollectAndUpload()orscheduleCollections(). - On iOS,
collectAndUpload()andscheduleCollections()check the credentials they receive, and reject invalid input with one of theTSErrorcases below.
| iOS error | Cause |
|---|---|
noAccountsProvided | An upload was requested without any credentials. |
blankAccountId | accountId is empty or only whitespace. |
missingAccessToken | accessToken is blank. |
invalidIdentifier(field:reason:) | An identifier breaks one of the format rules. field names it and reason explains the rule. |
On iOS, on-demand calls throw these errors, and scheduled collections deliver them to the error handler.
Both SDKs accept several TSCredentials values in one call, one per account. collectAndUpload() then collects the data once and uploads one collection per account. Every upload is attempted, so a failure for one account does not cancel the others:
- On Android, pass the values as separate arguments. The uploads run in parallel, and failures are reported in
TSUploadException. - On iOS, pass an array. The call returns a
TSUploadOutcome, which lists the accounts that reached the backend and the failure of each one that did not. It throwsTSUploadErroronly when no account got through. That error carries the same per-account failures, and itsrejectedAccountIdsnames the accounts whose credentials were rejected.
Collect and upload on demand
On Android, collectAndUpload() is available as a suspending function, which is the recommended form, and with callbacks. The optional timeoutMs argument overrides the configured collection timeout for one call:
import com.futurae.sdk.ts.TrustSignalsSDK
import com.futurae.sdk.ts.error.TSAuthenticationException
import com.futurae.sdk.ts.error.TSCollectionTimedOutException
import com.futurae.sdk.ts.error.TSNotInitializedException
import com.futurae.sdk.ts.error.TSUploadException
import com.futurae.sdk.ts.model.public.TSCredentials
import com.futurae.sdk.ts.model.public.TSVerificationStatus
val credentials = TSCredentials(
serviceId = "f47ac10b-58cc-4372-a567-0e02b2c3d479",
unitId = "mybank-android",
accountId = "placeholder", // your proxy sets the real value
accessToken = "<your_proxy_credential>", // never the Futurae collection token
interactionId = "placeholder", // your proxy sets the real value
verificationStatus = TSVerificationStatus.VERIFIED // your proxy sets the real value
)
// Coroutine
try {
val collection = TrustSignalsSDK.collectAndUpload(credentials)
// collection.observation contains everything collected
} catch (e: TSUploadException) {
// One or more uploads failed: e.failures maps each failed accountId to its cause
e.failures.forEach { (accountId, error) ->
if (error is TSAuthenticationException) { /* your proxy answered 401 or 403 */ }
}
} catch (e: TSCollectionTimedOutException) {
// Every long-running data source timed out and nothing was collected
} catch (e: TSNotInitializedException) {
// initialize() was not called first
}
// Coroutine, overriding the configured collection timeout for this call only
val quickCollection = TrustSignalsSDK.collectAndUpload(credentials, timeoutMs = 5_000L)
// Callback
TrustSignalsSDK.collectAndUpload(
credentials,
onSuccess = { collection -> /* runs on the main thread */ },
onError = { error -> /* runs on the main thread; a failed upload arrives as TSUploadException */ }
)
On Android, each call returns a TSCollection. It carries a collectionId, the observationTime, the sensorTag, the observation itself, and a timedOut flag. timedOut is true when at least one long-running data source did not finish before the deadline, so the collection is partial.
On iOS, collectAndUpload() is an asynchronous throwing function. Swift requires the TSCredentials arguments in the order shown. The optional timeout: argument overrides the configured collection timeout for one call, and a value of zero or less is ignored:
import TrustSignals
let credentials = TSCredentials(
accountId: "placeholder", // your proxy sets the real value
accessToken: "<your_proxy_credential>", // never the Futurae collection token
serviceId: "f47ac10b-58cc-4372-a567-0e02b2c3d479",
unitId: "mybank-ios",
interactionId: "placeholder", // your proxy sets the real value
verificationStatus: .verified // your proxy sets the real value
)
do {
let collection = try await TrustSignalsSDK.collectAndUpload(credentials)
// collection.observation contains everything collected
} catch TSError.authenticationFailed {
// Your proxy answered 401 or 403
} catch TSError.uploadRejected {
// Your proxy answered with another non-2xx status
} catch TSError.uploadTransportFailed {
// The request did not complete: DNS, TLS or timeout
} catch TSError.collectionTimedOut(let after) {
// No data source finished within `after`
} catch TSError.notInitialized {
// initialize(_:) was not called first, or stopAll() was called
} catch {
// Invalid credentials, see Credentials and identifiers
}
// Override the configured collection timeout for this call only
let quickCollection = try await TrustSignalsSDK.collectAndUpload(credentials, timeout: .seconds(5))
// Several accounts: one sweep of the sensors, one upload per account
let outcome = try await TrustSignalsSDK.collectAndUpload([credentialsForUser1, credentialsForUser2])
Upload errors are thrown to the caller. An on-demand upload that is still running when the app moves to the background completes there.
Collect without uploading
collect() gathers the same data without sending it, for example to inspect what the SDK collects during development:
// Android, coroutine
val collection = TrustSignalsSDK.collect()
val quickCollection = TrustSignalsSDK.collect(timeoutMs = 5_000L) // overrides the configured timeout
// Android, callback
TrustSignalsSDK.collect(
onSuccess = { collection -> /* runs on the main thread */ },
onError = { error -> /* runs on the main thread */ }
)
// iOS
let collection = try await TrustSignalsSDK.collect()
let quickCollection = try await TrustSignalsSDK.collect(timeout: .seconds(5)) // overrides the configured timeout
Scheduled collections
scheduleCollections() starts a recurring background job that collects and uploads at the interval you set. Futurae recommends every 3 hours, see When to collect. Each account has its own schedule: calling scheduleCollections() again for the same account replaces that account’s schedule without affecting others.
On Android, each TSCredentials value gets its own independent periodic job, so you can stop one account’s schedule without affecting the others. The minimum interval is 15 minutes, available as TrustSignalsSDK.MIN_COLLECTION_INTERVAL, and a shorter interval throws IllegalArgumentException:
// Android
import kotlin.time.Duration.Companion.hours
TrustSignalsSDK.scheduleCollections(
collectionInterval = 3.hours,
credentials
)
TrustSignalsSDK.stopScheduledCollections("user-account-id") // stop one account's schedule
TrustSignalsSDK.stopScheduledCollections("account-1", "account-2") // stop several accounts' schedules
TrustSignalsSDK.stopScheduledCollections() // stop every schedule
On iOS, the default interval is 30 minutes. There are three forms:
scheduleCollections(collectionInterval:credentials:)with oneTSCredentials, for one account.- The same call with an array of
TSCredentials, for several accounts on a shared interval. scheduleCollections(_:)with an array ofTSScheduledRequest, for several accounts, each on its own interval.
Calling scheduleCollections() again with refreshed credentials, or with accounts added or removed, updates the schedule without interrupting it. Only a changed interval restarts an account’s timer. The schedules property returns the interval of every scheduled account.
// iOS: one account
await TrustSignalsSDK.scheduleCollections(
collectionInterval: .seconds(10800), // 3 hours
credentials: credentials
)
// iOS: several accounts, each on its own interval
await TrustSignalsSDK.scheduleCollections([
TSScheduledRequest(credentials: credentialsForUser1, collectionInterval: .seconds(10800)),
TSScheduledRequest(credentials: credentialsForUser2, collectionInterval: .seconds(3600)),
])
let active = await TrustSignalsSDK.schedules // [accountId: interval]
await TrustSignalsSDK.stopScheduledCollections(accountId: "user-account-id") // stop one account's schedule
await TrustSignalsSDK.stopScheduledCollections() // stop every schedule; the SDK stays usable
await TrustSignalsSDK.stopAll() // stop all SDK activity and release background resources
After stopAll(), every call throws TSError.notInitialized until you call initialize(_:) again.
Android runs schedules as WorkManager periodic jobs. iOS runs them as background tasks, which need the registration at launch. Both survive app restarts. The operating system decides when background work actually runs, depending on the OS version, battery level and device activity, so expect fewer collections than the interval implies. Register an error handler before you schedule: it is the only way to learn about failures in background collections.
Observe collections on iOS
On iOS you can subscribe to a stream of every completed collection, on demand or scheduled:
for await collection in await TrustSignalsSDK.collections {
// Process each collection as it completes
}
Sending observations through your proxy
The SDK sends every upload to the collectionUrl you set at initialization, which is your collection proxy. The real collection token and the user’s real identifiers are added there, so the app holds only placeholders and, optionally, its own session credential for your backend.
For every request it receives from a Sensor SDK, your proxy must:
- Identify the user. Use your own session mechanism to establish which authenticated user and session the request belongs to. Reject the request if it cannot.
- Set the identity fields. Overwrite
accountId,interactionIdandverificationStatusin the request body with the values your backend holds for that user and session, whatever the SDK sent. - Set the collection token. Replace the
Authorizationheader withBearer <collection_token>, using the token your backend holds. - Add the client IP address. Set the
X-Forwarded-Forheader to the IP address of the device or browser that sent the request. - Forward the request to the Collection API at
https://ts-collection-public.futurae.com/api/v1/collections.
The forwarded request, and the status codes the SDK expects back, are in Collection proxy.
Error handling
Android
On-demand calls throw exceptions to the caller:
| Exception | When it is thrown |
|---|---|
TSUploadException | One or more uploads failed. Its failures map holds the cause for each failed accountId: TSAuthenticationException when your proxy answered 401 or 403, TSUploadRejectedException for another non-2xx status, or TSUploadTransportException when the request failed before a response arrived. |
TSCollectionTimedOutException | Every long-running data source timed out and nothing was collected. If some finished in time, the call returns a partial collection with timedOut set to true. |
TSNotInitializedException | An SDK method was called before initialize(). |
IllegalArgumentException | A TSCredentials value was created with an invalid identifier or a blank accessToken, see Credentials and identifiers. Or scheduleCollections() was given an interval shorter than MIN_COLLECTION_INTERVAL. |
Errors from scheduled collections are delivered, on the main thread, to the handler registered with registerErrorHandler, together with the affected accountId. The schedule is not cancelled when an error occurs.
| Error | On demand | Scheduled |
|---|---|---|
| Network or I/O failure | In TSUploadException.failures, as TSUploadTransportException | Retried up to 3 times by WorkManager, then delivered to the handler as TSUploadTransportException, with the IOException as its cause |
401 or 403 from your proxy | In TSUploadException.failures, as TSAuthenticationException, not retried | Delivered to the handler as TSAuthenticationException, not retried |
| Other non-2xx status | In TSUploadException.failures, as TSUploadRejectedException, not retried | Delivered to the handler as TSUploadRejectedException, not retried |
| Unexpected exception | — | Delivered to the handler, not retried |
After a failed scheduled cycle, the schedule continues at the next interval.
iOS
All SDK errors are cases of TSError, except TSUploadError. collect() and collectAndUpload() throw their errors directly to the caller:
| Error | Cause | Values it carries |
|---|---|---|
authenticationFailed | Your proxy answered 401 or 403. | statusCode, body |
uploadRejected | Your proxy answered with another non-2xx status. | statusCode, body |
uploadTransportFailed | The request failed before a response arrived: DNS, TLS or timeout. | underlying |
collectionTimedOut | No data source finished within the collection timeout. If some finished in time, the collection is returned with timedOut set instead. | after |
notInitialized | An SDK method was called before initialize(_:), or after stopAll(). | — |
noAccountsProvided, blankAccountId, missingAccessToken, invalidIdentifier | The credentials are invalid. See Credentials and identifiers. | field and reason, for invalidIdentifier |
TSUploadError | A call with several accounts, where no account got through. | failures per account, and rejectedAccountIds |
collectorFailed(name:message:) reports a single data source that failed. The rest of the collection still completes, and only that signal is missing.
Errors from scheduled collections are delivered to the handler registered with registerErrorHandler(_:), together with the affected accountId. Failures are reported per account, and one failing account stops neither the others nor the schedule. The handler may be invoked off the main thread, so switch to the main actor before you touch the UI. Pass nil to remove the handler.
The SDK performs no automatic retries and keeps no offline queue, so each upload is a single attempt. After a failed scheduled cycle, the schedule continues at the next interval.
Recovering from authentication errors
An authentication error means your proxy rejected the upload. The Futurae collection token lives on your backend, so your proxy refreshes it, as described in Responding to the SDK, and the app is not involved. On the device, an authentication error usually means your proxy no longer accepts the credential the app passes as accessToken. Renew that credential, then stop only the failing account’s schedule and reschedule it with complete credentials:
import com.futurae.sdk.ts.TrustSignalsSDK
import com.futurae.sdk.ts.error.TSAuthenticationException
import com.futurae.sdk.ts.model.public.TSCredentials
import com.futurae.sdk.ts.model.public.TSVerificationStatus
import kotlin.time.Duration.Companion.hours
TrustSignalsSDK.registerErrorHandler { accountId, error ->
when (error) {
is TSAuthenticationException -> {
TrustSignalsSDK.stopScheduledCollections(accountId)
val credential = renewProxyCredential(accountId) // your own method
TrustSignalsSDK.scheduleCollections(
collectionInterval = 3.hours,
TSCredentials(
serviceId = serviceId,
unitId = unitId,
accountId = accountId,
accessToken = credential,
interactionId = interactionId,
verificationStatus = TSVerificationStatus.VERIFIED
)
)
}
else -> {
// Log or report other errors
}
}
}
The iOS pattern is the same, using registerErrorHandler and TSError.authenticationFailed.
Sample apps
Both SDK repositories include a runnable sample app:
- Android: the
sampledirectory holds a minimal Jetpack Compose app that demonstrates initialization, collect-and-upload for one and for several accounts, and scheduled background collections. It pulls the SDK from GitHub Packages, so it needs your GitHub Packages credentials, and it reads the collection URL from theTS_COLLECTION_URLGradle property. - iOS: the
test-appdirectory holds a minimal SwiftUI app. OpenTrustSignalsTestApp.xcodeprojin Xcode and run it on a simulator or a device. It lets you configure the SDK, then collect, collect and upload, and schedule collections from a single screen.
API overview
The two SDKs are equivalent in capability. Differences are limited to naming conventions, error delivery and a few data points unique to each platform, and may grow as platform constraints evolve.
| Aspect | Android | iOS |
|---|---|---|
| Language | Kotlin / Java | Swift |
| Initialize | initialize(context, config) | initialize(config), awaited |
| Collect without uploading | collect() | collect() |
| Collect and upload | collectAndUpload(), for one or several accounts | collectAndUpload(), for one or several accounts |
| Schedule collections | scheduleCollections(), one job per account | scheduleCollections(), one schedule per account |
| Read active schedules | — | schedules |
| Stop scheduling | stopScheduledCollections(), for one, several or all accounts | stopScheduledCollections(), for one or all accounts |
| Stop all activity | — | stopAll(), which also releases background resources |
| Register an error handler | registerErrorHandler() | registerErrorHandler() |
| Error handler thread | Main thread | May be off the main thread |
| Stream of collections | — | collections |
| Scheduling backend | WorkManager periodic job | Background tasks, registered with registerBackgroundTasks() |
| Upload errors on demand | Thrown as TSUploadException, with a cause per account | Thrown as TSError cases, or as TSUploadError when no account got through |
| Error types | TSUploadException, with causes such as TSAuthenticationException | TSError cases, such as authenticationFailed |
| Platform-only data | Manufacturer, developer mode, installer source, network state. See Data Collection and Privacy. | Device name, vendor identifier, thermal state, low power mode, proximity sensor, screen brightness |
The arguments of each method are described in Initialization and Collecting observations, and every error type is listed in Error handling.