Android
Native Android toolkit that integrates the Zing Fitness experience into your app. The SDK ships as an AAR (coach.zing:fitness-sdk) consumable via GitHub Packages.
Requirements
| Requirement | Value / Notes |
|---|---|
| Minimum Android version | 8.0 (API 26) |
| Compile SDK | 34+ |
| Kotlin | 2.1.0 |
| Dependency injection | Hilt (com.google.dagger:hilt-android) |
| Maven credentials | GitHub Packages PAT with read:packages |
Setup
Add the GitHub Packages Repository
Add the following to your settings.gradle or top-level build.gradle:
// settings.gradle or top-level build.gradle
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven {
url = uri("https://maven.pkg.github.com/Muze-Fitness/fitness-coach-sdk-android")
val localProperties = java.util.Properties()
val file = File(rootDir, "local.properties")
if (file.exists()) {
localProperties.load(file.inputStream())
}
credentials {
username = localProperties.getProperty("sdk_maven_read_username")
?: System.getenv("GITHUB_USER")
password = localProperties.getProperty("sdk_maven_read_token")
?: System.getenv("GITHUB_TOKEN")
}
}
}
}Create local.properties next to your top-level settings.gradle if it does not exist yet and provide the credentials:
sdk_maven_read_username=GITHUB_USERNAME
sdk_maven_read_token=ghp_xxx_with_read_packagesDeclare the Dependency
dependencies {
implementation("coach.zing:fitness-sdk:<latest-version>")
}Replace <latest-version> with the version published in GitHub Packages (e.g. 2.3.1).
Enable Hilt
Add the Hilt plugin and dependencies to your app-level build.gradle:
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.dagger.hilt.android")
kotlin("kapt") // or id("com.google.devtools.ksp")
}
dependencies {
implementation("com.google.dagger:hilt-android:2.56.1")
kapt("com.google.dagger:hilt-android-compiler:2.56.1")
// ksp("com.google.dagger:hilt-android-compiler:2.56.1")
}Create an Application Subclass
Create an Application subclass that extends SdkApplication. The SDK must be
initialized from your Application class — call ZingSdk.init() from onCreate():
@HiltAndroidApp
class FitnessApp : SdkApplication() {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
override fun onCreate() {
super.onCreate()
scope.launch {
ZingSdk.init()
}
}
}Register it in AndroidManifest.xml:
<application
android:name=".FitnessApp"
... />Initialization & Authentication
The SDK follows a three-step lifecycle: init() → login(sdkAuth) → logout().
init()— configure the SDK (theme, feature configuration). Call once, from your Application class.login(sdkAuth)— authenticate and start a session (must be called only whenauthStateisSdkAuthState.LoggedOut).logout()— end the session and clear user data.
Authentication methods
| Method | Class | Description |
|---|---|---|
| Partner Key | SdkAuthentication.ApiKey | API key authentication. partnerUserId is optional and links the session to your own user id. |
| Partner Token | SdkAuthentication.ExternalToken | Pass a JWT minted by your own backend. |
For the backend requirements behind these two modes — the JWKS your server must publish for JWT auth, the required claims, and why the API key is the weaker option — see Authorization.
Login
suspend fun login(sdkAuth: SdkAuthentication)Must be called after init() when authState is SdkAuthState.LoggedOut.
Throws SdkAlreadyLoggedInException if the user is already logged in.
Partner Key — pass your API key directly:
ZingSdk.login(SdkAuthentication.ApiKey(apiKey = "your-api-key"))Pass partnerUserId to link the session to your own user id. If omitted, the SDK generates an id for a new user:
ZingSdk.login(
SdkAuthentication.ApiKey(apiKey = "your-api-key", partnerUserId = "your-user-id")
)Partner Token — pass a JWT minted by your own backend:
ZingSdk.login(SdkAuthentication.ExternalToken(jwtToken = yourAuthRepo.getToken()))Auth state monitoring
Observe ZingSdk.authState: StateFlow<SdkAuthState> to react to authentication
changes:
| State | Description |
|---|---|
SdkAuthState.LoggedOut | No active session |
SdkAuthState.InProgress | Login is in progress |
SdkAuthState.LoggedIn | Session active. Carries the authenticated userId — for ApiKey this is partnerUserId (or the generated id, if omitted); for ExternalToken this is the sub claim of jwtToken. |
Session errors
Set criticalErrorHandler to react when an active session can’t be refreshed
(e.g. the refresh token was revoked server-side):
ZingSdk.criticalErrorHandler = CriticalErrorHandler { error ->
// e.g. log out and prompt re-authentication
}Logout
suspend fun logout()Ends the current session. Throws SdkAlreadyLoggedOutException if the user is
already logged out.
Profile parameters
Update user profile fields directly, without going through SDK UI:
suspend fun setProfileParams(profileParams: ProfileParams)Requires SdkAuthState.LoggedIn.
| Field | Type | Description |
|---|---|---|
name | String? | |
gender | UserGender? | MALE, FEMALE, OTHER |
height | Float? | Centimeters. Always pass metric, regardless of measurementSystem. |
weight | Float? | Kilograms. Always pass metric, regardless of measurementSystem. |
age | Int? | |
measurementSystem | MeasurementUnit? | IMPERIAL, METRIC |
ZingSdk.setProfileParams(
ProfileParams(
name = "Alex",
gender = UserGender.MALE,
height = 180f,
weight = 75f,
age = 30,
measurementSystem = MeasurementUnit.METRIC,
)
)Primary location id
Call setPrimaryLocationId after login to store the member’s primary location id from your organization. The SDK writes it to the user profile and syncs it. Throws IllegalArgumentException when no user is logged in.
suspend fun setPrimaryLocationId(id: String)ZingSdk.setPrimaryLocationId("your-location-id")Full example
class FitnessApp : SdkApplication() {
override fun onCreate() {
super.onCreate()
ZingSdk.init()
}
}
// Start a session (e.g. in your Activity or ViewModel)
fun onStart(scope: CoroutineScope) {
scope.launch {
if (ZingSdk.authState.value is SdkAuthState.LoggedOut) {
ZingSdk.login(SdkAuthentication.ApiKey(apiKey = "your-api-key"))
}
}
}
// End the session
fun onLogout(scope: CoroutineScope) {
scope.launch {
ZingSdk.logout()
// e.g. navigate to login or clear user data
}
}Health Connect
The SDK integrates with Health Connect to read and write activity data, and since 2.3.1 to read sleep and body measurements. The required Health Connect permissions are declared by the SDK itself and merged into your app — you do not need to add them manually:
android.permission.health.READ_STEPS
android.permission.health.READ_HEART_RATE
android.permission.health.READ_EXERCISE
android.permission.health.READ_TOTAL_CALORIES_BURNED
android.permission.health.READ_SLEEP
android.permission.health.READ_WEIGHT
android.permission.health.READ_HEIGHT
android.permission.health.READ_BODY_FAT
android.permission.health.READ_BONE_MASS
android.permission.health.READ_LEAN_BODY_MASS
android.permission.health.READ_BODY_WATER_MASS
android.permission.health.READ_BASAL_METABOLIC_RATE
android.permission.health.WRITE_STEPS
android.permission.health.WRITE_HEART_RATE
android.permission.health.WRITE_EXERCISE
android.permission.health.WRITE_TOTAL_CALORIES_BURNEDDeclare every permission your app requests in the Play Console health apps declaration, including the ones merged from the SDK.
Users who connected Health Connect before 2.3.1 see a one-time prompt on the SDK home screen asking for the sleep and body measurement permissions.
By default, Zing reads and writes Health Connect data while the app is in use, as soon as the user grants the runtime Health Connect permissions. No additional setup is required.
Background sync (optional)
You can additionally enable collecting Health Connect data in the background (e.g. after a reboot, without the app being open). This requires three changes:
1. Add the following permissions to your AndroidManifest.xml:
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_HEALTH" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />2. Declare the SDK’s background sync service and boot receiver inside
<application>:
<service
android:name="coach.zing.fitness.coach.service.ZingSdkHCForegroundService"
android:exported="false"
android:foregroundServiceType="health">
<intent-filter>
<action android:name="coach.zing.fitness.sdk.HC_SYNC" />
</intent-filter>
</service>
<receiver
android:name="coach.zing.fitness.coach.broadcast.SdkHealthSyncBootReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>3. Enable it via Configuration when initializing the SDK:
ZingSdk.init(
configuration = Configuration(
healthConnectBackgroundSync = true,
),
)Background sync is disabled by default (healthConnectBackgroundSync = false).
Theming
Working with themes
Use the theming playground to interactively preview how colors, typography, corner rounding, and images look in the SDK screens before writing any code.
Pass a ZingSdkTheme to ZingSdk.init() to customize the SDK’s appearance. Every field is optional — any unset field falls back to the SDK default.
ZingSdk.init(
theme = ZingSdkTheme(
colors = ZingSdkTheme.Colors(
brandPrimary = 0xFF1A73E8,
brandSecondary = 0xFF34A853,
),
cornerRadius = ZingSdkTheme.CornerRadius(
button = ZingSdkTheme.CornerRadius.SdkRadius.Pill,
),
typography = ZingSdkTheme.Typography(
brand = Typeface.createFromAsset(assets, "fonts/MyBrandFont.ttf"),
system = Typeface.createFromAsset(assets, "fonts/MySystemFont.ttf"),
),
assets = ZingSdkTheme.Assets(
planBackground = R.drawable.my_plan_background,
coachImages = ZingSdkTheme.Assets.CoachAsset(
john = R.drawable.my_coach_john,
jennifer = R.drawable.my_coach_jennifer,
sarah = R.drawable.my_coach_sarah,
chris = R.drawable.my_coach_chris,
),
),
),
)| Field | Type | Description |
|---|---|---|
colors | ZingSdkTheme.Colors | Overrides brand, text, button, and background colors. Values are Long in AARRGGBB format. |
cornerRadius | ZingSdkTheme.CornerRadius | Overrides button border radius. Use SdkRadius.Value(dp) for a fixed radius or SdkRadius.Pill for a pill shape. |
typography | ZingSdkTheme.Typography | Overrides font typefaces for brand and system text. |
assets | ZingSdkTheme.Assets | Overrides images for plan background and coach portraits. |
Working with typography
Pass Typeface objects directly to ZingSdkTheme.Typography.
| Field | Applies to | Default |
|---|---|---|
brand | Headings, display text, counter, coach name | SDK built-in (Outfit) |
system | Body text, UI elements, coach chat | SDK built-in (Roboto) |
Both fields are optional — omitting a field keeps the SDK’s built-in font for that role.
Working with pictures
Pass drawable resource IDs directly to ZingSdkTheme.Assets. The SDK uses the IDs to load images from your app’s resources.
| Field | Used for |
|---|---|
planBackground | Plan section background image |
coachImages.john | Coach John avatar |
coachImages.jennifer | Coach Jennifer avatar |
coachImages.sarah | Coach Sarah avatar |
coachImages.chris | Coach Chris avatar |
All fields are optional — omitting a field falls back to the SDK’s built-in image.
For best quality across device densities, provide density-specific variants. Android automatically picks the correct one:
res/
drawable-mdpi/my_plan_background.png
drawable-hdpi/my_plan_background.png
drawable-xhdpi/my_plan_background.png
drawable-xxhdpi/my_plan_background.png
drawable-xxxhdpi/my_plan_background.pngLaunching Zing Screens
You can open any major Zing screen explicitly via
ZingSdkActivity.launch(context, StartingRoute.<Destination>). Supported
destinations:
| Destination | Description |
|---|---|
StartingRoute.Home() | Home screen. Accepts an optional HomeScreenConfig — see Home screen configuration |
StartingRoute.Onboarding() | Onboarding flow. Accepts navigateToHome — see below |
StartingRoute.CustomWorkout | Custom workout builder |
StartingRoute.AiAssistant | AI coach chat |
StartingRoute.WorkoutPlanDetails | Workout plan details |
StartingRoute.FullSchedule | Full workout schedule |
StartingRoute.ProfileSettings | Profile settings |
StartingRoute.BodyScan | Body scan |
StartingRoute.FitnessTest | Fitness test |
StartingRoute.FlexibilityTest | Flexibility test |
StartingRoute.HealthConnectPermissions | Health Connect permissions request |
Example
button.setOnClickListener {
ZingSdkActivity.launch(this, StartingRoute.AiAssistant)
}Onboarding
StartingRoute.Onboarding starts the onboarding flow and lets you choose what
happens when the user finishes it:
data class Onboarding(val navigateToHome: Boolean = true)| Parameter | Default | Description |
|---|---|---|
navigateToHome | true | When true, completing the onboarding opens the Zing home screen. When false, the SDK closes and the user returns to your app |
Use navigateToHome = false when your app drives its own navigation and only
needs the onboarding to be completed:
ZingSdkActivity.launch(this, StartingRoute.Onboarding(navigateToHome = false))Home screen configuration
StartingRoute.Home accepts a HomeScreenConfig that controls which home
screen elements are shown. The same config is used by the embedded home view:
ZingSdkActivity.launch(
this,
StartingRoute.Home(
HomeScreenConfig(
backButtonIsVisible = true,
askCoachIsVisible = true,
bodyScanIsVisible = true
)
)
)| Parameter | Default | Description |
|---|---|---|
backButtonIsVisible | true | Shows the back button in the home screen toolbar |
askCoachIsVisible | true | Shows the “Ask coach” entry point on the home screen |
bodyScanIsVisible | true | Shows the Body Scan card on the home screen. Available since 2.3.0. |
Embedded Views
Since 2.2.0 the Zing home screen can be embedded directly into your own UI — as a tab, a page, or any part of your layout — instead of being opened as a full-screen activity.
Embedded views live in coach.zing.fitness.coach.embedded.
Login required. An embedded view can only be shown while the SDK is logged in. Rendering it in any other state throws an
IllegalArgumentException. CheckZingSdk.authStatebefore showing the view.
Embedded home view
ZingSdkHomeView renders the Zing home screen inside your layout. It is
available both as an Android View and as a Composable.
Compose
@Composable
fun HomeTab(modifier: Modifier = Modifier) {
val authState by ZingSdk.authState.collectAsState()
if (authState is SdkAuthState.LoggedIn) {
ZingSdkHomeView(
modifier = modifier,
homeScreenViewConfig = HomeScreenConfig(
backButtonIsVisible = true,
askCoachIsVisible = true,
bodyScanIsVisible = true
)
)
}
}Android View
val homeView = ZingSdkHomeView(context)
homeView.setConfig(
HomeScreenConfig(
backButtonIsVisible = true,
askCoachIsVisible = true,
bodyScanIsVisible = true
)
)
container.addView(homeView)Call setConfig() at any time — the view re-renders with the new configuration.
Window insets
Embedded views apply window insets themselves. If your layout already consumes some of them — for example a bottom navigation bar — tell the view about it, so the content isn’t padded twice. There are three ways to do it.
By inset type. Pass a WindowInsetsCompat.Type mask with the insets your
layout already handles:
homeView.setHandledInsetTypes(WindowInsetsCompat.Type.navigationBars())By explicit values. Pass the already-handled insets in pixels:
homeView.setHandledInsets(bottom = bottomBarHeightPx)In Compose. Use the standard Modifier.consumeWindowInsets(...) on the
modifier you pass to the view:
ZingSdkHomeView(
modifier = Modifier.consumeWindowInsets(WindowInsets.navigationBars),
homeScreenViewConfig = HomeScreenConfig()
)