Skip to Content

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

RequirementValue / Notes
Minimum Android version8.0 (API 26)
Compile SDK34+
Kotlin2.1.0
Dependency injectionHilt (com.google.dagger:hilt-android)
Maven credentialsGitHub 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_packages

Declare 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().

  1. init() — configure the SDK (theme, feature configuration). Call once, from your Application class.
  2. login(sdkAuth) — authenticate and start a session (must be called only when authState is SdkAuthState.LoggedOut).
  3. logout() — end the session and clear user data.

Authentication methods

MethodClassDescription
Partner KeySdkAuthentication.ApiKeyAPI key authentication. partnerUserId is optional and links the session to your own user id.
Partner TokenSdkAuthentication.ExternalTokenPass 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:

StateDescription
SdkAuthState.LoggedOutNo active session
SdkAuthState.InProgressLogin is in progress
SdkAuthState.LoggedInSession 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.

FieldTypeDescription
nameString?
genderUserGender?MALE, FEMALE, OTHER
heightFloat?Centimeters. Always pass metric, regardless of measurementSystem.
weightFloat?Kilograms. Always pass metric, regardless of measurementSystem.
ageInt?
measurementSystemMeasurementUnit?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_BURNED

Declare 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, ), ), ), )
FieldTypeDescription
colorsZingSdkTheme.ColorsOverrides brand, text, button, and background colors. Values are Long in AARRGGBB format.
cornerRadiusZingSdkTheme.CornerRadiusOverrides button border radius. Use SdkRadius.Value(dp) for a fixed radius or SdkRadius.Pill for a pill shape.
typographyZingSdkTheme.TypographyOverrides font typefaces for brand and system text.
assetsZingSdkTheme.AssetsOverrides images for plan background and coach portraits.

Working with typography

Pass Typeface objects directly to ZingSdkTheme.Typography.

FieldApplies toDefault
brandHeadings, display text, counter, coach nameSDK built-in (Outfit)
systemBody text, UI elements, coach chatSDK 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.

FieldUsed for
planBackgroundPlan section background image
coachImages.johnCoach John avatar
coachImages.jenniferCoach Jennifer avatar
coachImages.sarahCoach Sarah avatar
coachImages.chrisCoach 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.png

Launching Zing Screens

You can open any major Zing screen explicitly via ZingSdkActivity.launch(context, StartingRoute.<Destination>). Supported destinations:

DestinationDescription
StartingRoute.Home()Home screen. Accepts an optional HomeScreenConfig — see Home screen configuration
StartingRoute.Onboarding()Onboarding flow. Accepts navigateToHome — see below
StartingRoute.CustomWorkoutCustom workout builder
StartingRoute.AiAssistantAI coach chat
StartingRoute.WorkoutPlanDetailsWorkout plan details
StartingRoute.FullScheduleFull workout schedule
StartingRoute.ProfileSettingsProfile settings
StartingRoute.BodyScanBody scan
StartingRoute.FitnessTestFitness test
StartingRoute.FlexibilityTestFlexibility test
StartingRoute.HealthConnectPermissionsHealth 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)
ParameterDefaultDescription
navigateToHometrueWhen 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 ) ) )
ParameterDefaultDescription
backButtonIsVisibletrueShows the back button in the home screen toolbar
askCoachIsVisibletrueShows the “Ask coach” entry point on the home screen
bodyScanIsVisibletrueShows 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. Check ZingSdk.authState before 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() )
Last updated on