rolla_sdk
The Rolla Health & Fitness SDK as a Flutter package: activity tracking, health metrics, workouts, Rolla Band pairing, Apple Health / Health Connect, Garmin and Oura, and a complete white-label UI. Your app initializes the SDK with a user token and renders one widget, RollaSdkHome; the SDK runs in-process on your app's own Flutter engine and owns everything from there.
It is the same SDK that ships as the native iOS pod (RollaSDK), the Android Maven artifact (com.rolla.sdk:android_release) and the React Native wrapper. The partner documentation for those platforms applies to a Flutter host one-to-one for everything that happens inside your ios/ and android/ projects; this page links to the exact sections rather than repeating them.
Partners only. The SDK talks to the Rolla backend with a Partner ID and credentials issued during onboarding. Contact support@rolla.app if you don't have yours yet. Source is published under a proprietary license, see License.
Versioning
The package is released in lockstep with the native SDK, and the package version names the SDK version it contains.
pub.flutter-io.cn rolla_sdk |
SDK version | Partner docs |
|---|---|---|
0.1.15 (current) |
0.1.15 |
Flutter guide · iOS · Android |
0.1.12, 0.1.11 |
pre-release snapshots (June 2026) | superseded, no longer supported |
- Pin the exact version:
flutter pub add rolla_sdk:0.1.15writesrolla_sdk: 0.1.15. A caret would letflutter pub upgradepick up any later 0.1.x release, and 0.1.x releases can carry breaking changes. - Upgrading is one number: bump the package, run
cd ios && pod installandcd android && ./gradlew --refresh-dependencies, rebuild. See Verify the integration. - The Changelog tab is the SDK changelog. Every "Both platforms" entry applies to a Flutter host, and the iOS / Android sections apply on the respective platform. Entries name the native wrapper surface (
RollaConfiguration,RollaDelegate,RollaListener); in Flutter the same options are parameters ofRollaSDK.initializeWithTokenand the callbacks you pass to it, see Configuration.
Requirements
| Requirement | |
|---|---|
| Flutter / Dart | Flutter 3.35.6 or newer, Dart 3.9.2 or newer |
| iOS | Deployment target 14.0, CocoaPods |
| Android | minSdk 26, compileSdk 36, Kotlin 2.2.0 or newer, JDK 17 to build, core library desugaring |
| Mapbox | A Mapbox public token (pk.), supplied by Rolla with your partner credentials. Route maps stay blank without it |
| Credentials | Partner ID plus sandbox credentials from your Rolla SDK starter package |
| Devices | Bluetooth band pairing and motion-sensor tracking need a physical device; simulators and emulators run everything else |
The floors are set by the package's own Android and iOS build files and its bundled plugins (health for HealthKit / Health Connect, mapbox_maps_flutter, flutter_local_notifications for the desugaring).
Reference integration
A complete working host app lives in rolla-sdk-demo-flutter (ask your Rolla contact for access): the full Info.plist and AndroidManifest.xml, a login and token-refresh flow, explicit branding, and the launch screen below. When this page and your build disagree, the demo is the source of truth.
Installation
1. Add the package
flutter pub add rolla_sdk:0.1.15
dependencies:
rolla_sdk: 0.1.15
No authentication is required to fetch the package. The package does not change which permissions are required — they are declared in your ios/ and android/ projects exactly as in a native app; steps 2 and 3 list what a fresh flutter create scaffold is missing.
2. iOS: deployment target, Info.plist, entitlements
Set the deployment target in ios/Podfile and install the pods:
platform :ios, '14.0'
flutter pub get && cd ios && pod install
Open Runner.xcworkspace, not Runner.xcodeproj.
Add the following to ios/Runner/Info.plist. iOS calls abort() (SIGABRT) the moment the SDK touches Bluetooth, Location, Motion, HealthKit or Photos without the matching usage string; there is no Dart exception to catch. Customize the strings to your app's wording, they are shown in the OS permission dialogs:
<key>MBXAccessToken</key>
<string>YOUR_MAPBOX_PUBLIC_TOKEN</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Bluetooth access lets the app connect to your fitness band and keep syncing data, even when the app isn't open.</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>The app uses Bluetooth to connect to your fitness band and sync health data.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>The app uses your location to track outdoor activities like running and cycling. With 'While Using the App', your route is recorded only when the app is open.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>The app uses your location to accurately track outdoor activities like running and cycling, even when your phone is locked or the app is in the background.</string>
<key>NSMotionUsageDescription</key>
<string>Used to count steps and measure cadence during workouts when no fitness band is connected.</string>
<key>NSHealthShareUsageDescription</key>
<string>The app reads your Apple Health data, such as heart rate, steps, sleep and workouts, to provide personalised health insights and keep your metrics in sync.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Save your activity images to Photos.</string>
<key>UIBackgroundModes</key>
<array>
<string>location</string>
<string>bluetooth-central</string>
</array>
| Key | Required for | Missing it means |
|---|---|---|
NSBluetoothAlwaysUsageDescription, NSBluetoothPeripheralUsageDescription |
Band pairing and sync | Crash at CBCentralManager init |
NSLocationWhenInUseUsageDescription, NSLocationAlwaysAndWhenInUseUsageDescription |
Outdoor and background activity tracking | Crash at CLLocationManager request |
NSMotionUsageDescription |
Smartphone-only workouts | Crash at CMPedometer start |
NSHealthShareUsageDescription |
Apple Health read (the SDK never writes to HealthKit) | Crash at HealthKit authorization |
NSPhotoLibraryAddUsageDescription |
Saving activity share images | Crash at the photo save |
MBXAccessToken |
Route maps | Blank maps |
UIBackgroundModes (location, bluetooth-central) |
Band connection and GPS while backgrounded | Tracking stops when the app leaves the foreground |
Two capabilities live in Runner.entitlements, not in Info.plist: HealthKit (Xcode → Signing & Capabilities → + Capability → HealthKit; needs HealthKit enabled on the App ID) and Bluetooth Central (com.apple.developer.bluetooth-central). Steps and the wording you can lift into your privacy policy: iOS Permissions & Entitlements.
One line in ios/Runner/AppDelegate.swift, after GeneratedPluginRegistrant.register(with: self), makes your app the notification-center delegate so the SDK's notifications show in the foreground and their taps reach it:
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
Optional, iOS 16.1+: the Lock Screen / Dynamic Island Live Activity during workouts. It is a native Widget Extension target in your ios/ project with no Dart involved; follow iOS Live Activities.
3. Android: Gradle, Mapbox token, manifest
android/settings.gradle.kts: Kotlin 2.2.0 or newer (the Flutter scaffold ships an older one), and build with JDK 17:
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.9.1" apply false
id("org.jetbrains.kotlin.android") version "2.2.0" apply false
}
android/app/build.gradle.kts: minSdk 26 and core library desugaring:
android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
defaultConfig {
minSdk = 26
}
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
}
android/app/src/main/res/values/strings.xml, the Android counterpart of MBXAccessToken:
<string name="mapbox_access_token">YOUR_MAPBOX_PUBLIC_TOKEN</string>
android/app/src/main/AndroidManifest.xml: the package's own manifest merges the Bluetooth, location, activity-recognition, foreground-service, notification and boot permissions into your app, so you do not declare those. Three things are yours, because Google reviews the merged manifest under your app's identity:
<manifest …>
<uses-permission android:name="android.permission.INTERNET" />
<!-- Health Connect: the full read set (the SDK's manifest carries only part of it) -->
<uses-permission android:name="android.permission.health.READ_HEART_RATE" />
<uses-permission android:name="android.permission.health.READ_HEART_RATE_VARIABILITY" />
<uses-permission android:name="android.permission.health.READ_STEPS" />
<uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED" />
<uses-permission android:name="android.permission.health.READ_SLEEP" />
<uses-permission android:name="android.permission.health.READ_WEIGHT" />
<uses-permission android:name="android.permission.health.READ_BLOOD_PRESSURE" />
<uses-permission android:name="android.permission.health.READ_EXERCISE" />
<uses-permission android:name="android.permission.health.READ_EXERCISE_ROUTES" />
<uses-permission android:name="android.permission.health.READ_SPEED" />
<uses-permission android:name="android.permission.health.READ_DISTANCE" />
<uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED" />
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_HISTORY" />
<application …>
<activity android:name=".MainActivity" …>
<!-- your existing intent-filters (LAUNCHER, deep links, etc.) -->
<!-- Health Connect "View permissions" rationale entry point; the SDK handles the intent. -->
<intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
</activity>
<!-- Android 14 Health Connect compliance -->
<activity-alias
android:name=".ViewPermissionUsageActivity"
android:exported="true"
android:permission="android.permission.START_VIEW_PERMISSION_USAGE"
android:targetActivity=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
<category android:name="android.intent.category.HEALTH_PERMISSIONS" />
</intent-filter>
</activity-alias>
</application>
<!-- Package visibility: lets the SDK detect the Health Connect app on Android 11+. -->
<queries>
<package android:name="com.google.android.apps.healthdata" />
<intent>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent>
</queries>
</manifest>
Optional permissions the SDK leaves to you: SCHEDULE_EXACT_ALARM for on-time reminders and REQUEST_IGNORE_BATTERY_OPTIMIZATIONS for the OEM battery-manager exemption. Declare what you ship in your privacy policy and the Play Console Data Safety form; the rationale matrix in Android Permissions is written to be lifted into both. Keep Flutter's default launchMode="singleTop" on MainActivity.
4. Verify the integration
flutter run # on a physical device
The app should build on both platforms before you write any integration code. After every rolla_sdk bump, run cd android && ./gradlew --refresh-dependencies: Gradle caches transitive metadata (notably Mapbox) per coordinate, and stale metadata produces confusing resolution errors.
Usage
Your backend obtains the user's tokens from the Rolla auth API (/api/register → /api/login) and hands them to your app. The integration is then two steps: initialize once, render RollaSdkHome.
import 'package:rolla_sdk/rolla_sdk.dart';
Future<void> startRolla(BuildContext context, Session session) async {
final userId = JwtDecoder.extractUserId(session.accessToken)!; // the JWT's `sub` claim, or your own stable id
await RollaSDK.initializeWithToken(
accessToken: session.accessToken, // access_token from POST /api/login
refreshToken: session.refreshToken, // refresh_token
tokenExpiresIn: Duration(seconds: session.expiresIn), // expires_in
userId: userId,
partnerId: 'your-partner-id',
environment: RollaEnvironment.rnd, // sandbox while integrating; .production for release builds
// Let the user leave the SDK: the back button in the SDK's app bar asks you to pop your route.
showBackButton: true,
onRequestDismiss: () => Navigator.of(context).pop(),
// The SDK could not refresh on its own: return a fresh pair from your backend, or null.
onTokenExpired: () async {
try {
final fresh = await myBackend.fetchRollaTokens();
return TokenRefreshResult(
accessToken: fresh.accessToken,
refreshToken: fresh.refreshToken,
expiresIn: Duration(seconds: fresh.expiresIn),
);
} catch (_) {
return null; // nothing fresher available
}
},
// The user signed out inside the SDK, or the session is unrecoverable: leave the SDK.
// onSessionExpired can fire again for later failing requests, so keep the handler idempotent.
onLogout: () => Navigator.of(context).pop(),
onSessionExpired: () => Navigator.of(context).pop(),
);
}
// After initialization completes, with the same userId:
Widget buildRolla(String userId) => RollaSdkHome(userId: userId);
RollaSdkHome is a complete app shell with its own MaterialApp.router, navigation and theming. Push it as a route, make it your authenticated home, or gate it behind a FutureBuilder; in every placement, initialize first, pass the same userId you passed to initializeWithToken, and never wrap it in another MaterialApp. Calling initializeWithToken again disposes the previous instance and rebuilds the SDK, so do not call it on every rebuild. The full launch screen, with error handling and retry, is in Code Integration.
Tokens
The SDK keeps the session alive by itself: it refreshes the access token proactively before tokenExpiresIn elapses and reactively on a 401, and stores the rotated pair in its own secure storage. Your app keeps four duties:
- Pass all three token fields, always the newest pair you have. Without
refreshTokenthe SDK cannot refresh at all and every expiry escalates toonTokenExpired; withouttokenExpiresInit only recovers after the first401.tokenExpiresInandTokenRefreshResult.expiresInareDurations, not seconds. - Answer
onTokenExpiredwith aTokenRefreshResultfrom your backend. The callback runs when a request was rejected and the SDK's own refresh failed or was impossible; the SDK persists what you return and retries the failed request invisibly. Returnnullwhen you have nothing fresher. - Handle
onSessionExpired. It fires when every refresh path came up empty, or a request still fails right after a successful refresh — for example after a server-side revocation: the session is dead. Clear your own session and show your login; it can fire more than once per dead session. It is suppressed during a deliberateRollaSDK.logout(). - Never spend the SDK's refresh token yourself. Refresh tokens are single-use. If your backend also refreshes with the same token, whichever side goes first invalidates it for the other. Give the SDK its own pair, or route all refreshes through one owner.
Push a pair you refreshed elsewhere at any time with RollaSDK.updateToken(accessToken:, refreshToken:, expiresIn:). It returns false when the SDK kept a newer pair it already held (a replayed pair is ignored by design) or when it is not initialized. When the user logs out of your app, call RollaSDK.logout(): it clears the SDK's tokens and disposes the instance. onLogout is the other direction: the user signed out from inside the SDK, which has already cleared its session when the callback runs.
Access tokens last 30 minutes, refresh tokens 30 days. Full lifecycle: Token Management.
Configuration
Everything is a parameter of RollaSDK.initializeWithToken. Unset optional parameters keep the SDK default.
| Parameter | Type | Default | Notes |
|---|---|---|---|
accessToken |
String |
required | JWT from POST /api/login |
userId |
String |
required | Namespaces persisted SDK data per user on shared devices; never sent to the backend. Pass the JWT sub claim or your own stable id |
partnerId |
String |
required | Issued by Rolla |
environment |
RollaEnvironment |
production |
rnd (sandbox) or production; must match where the token was issued |
baseUrl |
String? |
from environment |
Override the backend URL |
refreshToken |
String? |
unset | Enables the SDK's own refresh |
tokenExpiresIn |
Duration? |
unset | Enables proactive refresh |
onTokenExpired |
Future<TokenRefreshResult?> Function()? |
unset | See Tokens |
onLogout |
VoidCallback? |
unset | The user signed out inside the SDK |
onSessionExpired |
VoidCallback? |
unset | The session is unrecoverable |
onRequestDismiss |
VoidCallback? |
unset | The SDK asks you to pop its route (back button, or back from a screen opened with openScreen) |
hideBottomNavigation |
bool |
false |
Hide the Home / Profile tabs |
showBackButton |
bool |
false |
Back button in the SDK app bar; pair with onRequestDismiss |
showOptionsButton |
bool |
true |
Three-dot options action on Home with shortcuts to Data Sources, Goals, Leaderboards and the FAQ |
showGoalsSection |
bool |
false |
The user's goals with an edit action at the bottom of Home |
showAccountSettings |
bool |
false |
Credential screens in Settings (change / reset password, change e-mail, delete account) |
removeRollaBandReferences |
bool |
true |
Generic "fitness device" wording instead of Rolla Band naming and imagery |
isProfileComplete |
bool? |
null |
null: the SDK checks the backend profile and skips its onboarding when name, birthdate, gender, height and weight are set (your backend can set them in advance via POST /api/setprofile); true: your app owns onboarding, skip the check; false: always run it |
language |
RollaLanguage? |
profile-driven | english, german, spanish, croatian, bosnian, serbianLatin, serbianCyrillic, arabic. When set it overrides the user's profile language for the SDK instance's lifetime and is written to the profile so backend content matches |
disabledModules |
Set<RollaDisabledModule> |
{} |
weight, bloodPressure, leaderboards, insights: the module's UI is hidden everywhere |
disabledDataSources |
Set<RollaDataSource> |
{} |
band, garmin, oura, appleHealth, healthConnect: no longer offered for new connections; an already-connected source still renders. The band is a safety floor if you disable everything |
branding |
Branding? |
SDK defaults | See below |
Branding
const Branding(
appName: 'Your App Name', // names your app in consent and permission copy
primaryColor: Color(0xFF1976D2), // seeds the SDK's entire color scheme, light and dark
defaultThemeMode: ThemeMode.system,
headerLogoAsset: null, // your logo, pre-bundled into the SDK by Rolla; null shows no logo
privacyUrl: 'https://example.com/privacy',
// Required by the constructor, not used by the SDK UI:
secondaryColor: Color(0xFF625B71),
accentColor: Color(0xFF7D5260),
brightness: Brightness.light,
)
A Branding you pass replaces the SDK's built-in defaults as a whole rather than merging field by field, so set privacyUrl or the consent screen loses its privacy link, and set headerLogoAsset or the header shows no logo. defaultLocale only applies when language is unset and the user has no profile language yet. Logo assets must be bundled inside the SDK: send Rolla an SVG during onboarding and use the asset path you get back. Details: Configuration.
Beyond the Home screen
Open a specific screen. RollaSDK.openScreen(RollaScreen.insights) navigates the SDK UI to activityHistory, goals, home, insights or resume (the last state) and resolves with a RollaOpenScreenStatus: opened, notInitialized, screenDisabled (module in disabledModules), blockedByGate (a mandatory startup step is in front of the user), uiUnavailable or superseded. The opened screen becomes the SDK root: back calls onRequestDismiss. The call waits up to 15 seconds for RollaSdkHome to mount, so push your SDK route right after it.
Headless calls. After initializeWithToken, and with no RollaSdkHome on screen:
final sync = await RollaSDK.syncHealthData(); // RollaSyncResult: outcome, hasNewData, source, skipReason, syncedData
final battery = await RollaSDK.getBandBatteryLevel(); // BandBatteryResult: status, level
final paired = await RollaSDK.getPairedBandInfo(); // PairedBandResult: status, band (MAC, cached battery/firmware/serial)
They never throw: a sync that could not run reports skipped with a reason (noBandPaired, bandNotConnected, bluetoothPermissionRequired, appleHealthPermissionRequired, healthConnectPermissionRequired, offline, …). Because there is no SDK UI to prompt from, your app owns OS permissions for headless calls; request them first, for example with permission_handler. The one prompt the SDK raises itself is the notification permission, the first time initializeWithToken runs.
Notification taps. The SDK posts its own notifications (inactivity reminder, band battery, the Android workout notifications) and routes a tap to the matching screen itself once initializeWithToken has run and RollaSdkHome is on screen (it waits up to 15 seconds from initialization). On iOS this needs the notification-delegate line from step 2. Hosts that keep the SDK on a pushed route get the app opened, not the route pushed. If your app also uses flutter_local_notifications, the two share one tap callback and the last initialize wins — not supported in 0.1.15.
Not available in Flutter at 0.1.15: the native host event callbacks (activity, band, sync, goals, profile), notificationTarget, warmUpEngine and the engine lifecycle (there is no separate engine), and RollaTransition (your route owns its animation). The complete Dart surface: API Reference.
Troubleshooting
- The app aborts with
signal 6(iOS) when the SDK opens. A usage string is missing fromInfo.plist; add every key from step 2. - The SDK's back button does nothing.
showBackButton: truerenders it,onRequestDismissis what it calls. Pass both. uses-sdk:minSdkVersion … cannot be smaller than version 26. SetminSdk = 26.- D8:
Default interface methods are only supported starting with Android N. Enable core library desugaring. - Maps stay blank.
MBXAccessToken/mapbox_access_tokenis missing or is a secret (sk.) token. flutter pub getversion solving fails. Your toolchain is below Flutter 3.35.6, or one of your own dependencies conflicts with the versionsrolla_sdkpins exactly (mapbox_maps_flutter 2.22.0,health 13.3.1,device_info_plus 12.3.0) or constrains to a major (go_router ^14,flutter_bloc ^8,get_it ^8,flutter_local_notifications ^17,geolocator ^10,share_plus ^10). Align your constraints to these.- Android resolution errors after a bump.
cd android && ./gradlew --refresh-dependencies. - Debug logs for support tickets. iOS: Console.app filtered by subsystem
app.rolla.rollaV2. Android:adb logcat -s RollaSdkPlugin:* Flutter:*.
More symptoms: Flutter Troubleshooting, iOS, Android.
Documentation & support
- Flutter integration guide: rolla-sdk-documentation → flutter, quick start to API reference.
- iOS and Android guides for everything inside your native projects: iOS · Android.
- Auth API (register, log in, refresh, set the profile in advance): sdk-auth-api.
- Support: support@rolla.app, or your partner Slack channel with Rolla. Include the
rolla_sdkversion,flutter --version, platform and OS version, and the verbatim error text.
License
Proprietary. See the License tab; use of the SDK requires a written agreement with Rolla Health & Fitness.