Skip to Content

Flutter

Flutter plugin that wires the native Zing SDK on Android and iOS (and provides a Dart-facing API for initialization, logout, and launching native screens).

Current platform support

The Flutter plugin currently supports Android and iOS.

PlatformStatusNotes
AndroidFully SupportedAll features
iOSFully SupportedAll features

Requirements

RequirementValue / Notes
Flutter3.3.0+
Android minSdk26 (Android 8.0)
Android compileSdk36+
Dependency injection (Android)Hilt
Maven credentials (Android native SDK)GitHub Packages token
iOS minimum version16.0

Install the plugin

Add the Git dependency to your pubspec.yaml (the plugin is distributed privately and is not published on pub.dev):

dependencies: zing_sdk_initializer: git: url: https://github.com/Muze-Fitness/fitness-coach-sdk-flutter.git ref: v2.3.1

Check the GitHub releases page  for the latest available version.

Then run flutter pub get.

Android setup

To integrate on Android, you must configure your project to support the native Zing SDK dependencies (Hilt + GitHub Packages).

Add Maven repository & credentials

Add the GitHub Packages repository to your Android build (project-level android/build.gradle or settings.gradle):

maven { url = uri("https://maven.pkg.github.com/Muze-Fitness/fitness-coach-sdk-android") val localProperties = java.util.Properties() val localPropertiesFile = File(rootDir, "local.properties") if (localPropertiesFile.exists()) { localProperties.load(localPropertiesFile.inputStream()) } credentials { username = localProperties.getProperty("zing_sdk_username") password = localProperties.getProperty("zing_sdk_token") } }

Ensure your local.properties contains your credentials:

zing_sdk_username=GITHUB_USERNAME zing_sdk_token=ghp_xxx_with_read_packages

Configure Hilt (KSP or KAPT)

The native SDK uses Hilt for dependency injection.

  • Option A: KSP (recommended)
// Project-level (android/build.gradle or settings.gradle) id("com.google.dagger.hilt.android") version "2.56.1" apply false id("com.google.devtools.ksp") version "2.1.20-2.0.0" apply false
// App module (android/app/build.gradle) plugins { id("com.google.devtools.ksp") id("com.google.dagger.hilt.android") } dependencies { implementation("com.google.dagger:hilt-android:2.56.1") ksp("com.google.dagger:hilt-android-compiler:2.56.1") }
  • Option B: KAPT (legacy projects)
// Project-level (android/build.gradle or settings.gradle) id("com.google.dagger.hilt.android") version "2.56.1" apply false
// App module (android/app/build.gradle) plugins { kotlin("kapt") id("com.google.dagger.hilt.android") } dependencies { implementation("com.google.dagger:hilt-android:2.56.1") kapt("com.google.dagger:hilt-android-compiler:2.56.1") }

Custom Application class

Create an Application class extending SdkApplication and annotate it with @HiltAndroidApp:

import coach.zing.fitness.coach.SdkApplication import dagger.hilt.android.HiltAndroidApp @HiltAndroidApp class MyApplication : SdkApplication()

Register it in AndroidManifest.xml:

<application android:name=".MyApplication" ... >

Update MainActivity

Change MainActivity to inherit from FlutterFragmentActivity and add @AndroidEntryPoint:

import dagger.hilt.android.AndroidEntryPoint import io.flutter.embedding.android.FlutterFragmentActivity @AndroidEntryPoint class MainActivity : FlutterFragmentActivity()

Disable the default WorkManager initializer

Add the following provider override inside <application> in AndroidManifest.xml:

<provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge"> <meta-data android:name="androidx.work.WorkManagerInitializer" android:value="androidx.startup" tools:node="remove" /> </provider>

Initialization

Initialize the SDK from Dart during app startup:

import 'package:zing_sdk_initializer/zing_sdk_initializer.dart'; await ZingSdk.instance.init();

Android: register a background setup

Android initialization has an extra step: in addition to calling ZingSdk.instance.init(), register the same setup with registerBackgroundSetup, passing a top-level function marked @pragma('vm:entry-point'). The SDK uses it to bring up a headless isolate and initialize itself whenever your main() doesn’t run — such as restoring an SDK screen’s state or launching the SDK from the background.

Note: registerBackgroundSetup is a safe no-op on iOS, so you can call it unconditionally.

import 'package:flutter/widgets.dart'; import 'package:zing_sdk_initializer/zing_sdk_initializer.dart'; /// Used both on app startup and in the headless background isolate. /// Must be a top-level function annotated with `@pragma('vm:entry-point')`. @pragma('vm:entry-point') Future<void> zingSdkSetup() async { WidgetsFlutterBinding.ensureInitialized(); await ZingSdk.instance.init(); } Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await zingSdkSetup(); // Android only: lets the SDK run the same setup in a headless isolate. // No-op on iOS. await ZingSdk.instance.registerBackgroundSetup(zingSdkSetup); runApp(const MyApp()); }

Initialization errors

CodeDescription
already_initializedCalled init() more than once
native_init_failedNative SDK failed to initialize (internal error)

Configuration

Pass an SdkConfiguration to customize SDK behavior:

await ZingSdk.instance.init( configuration: SdkConfiguration( coachesAvailability: CoachesAvailability.userGenderBased, genderAvailability: GenderAvailability.binary, ), );
FieldTypeDefaultDescription
coachesAvailabilityCoachesAvailabilityallCoachesallCoaches — all coaches are shown; userGenderBased — only coaches matching the user’s gender.
genderAvailabilityGenderAvailabilityallall — all gender options; binary — male/female only.
healthBackgroundSyncboolfalseEnables background sync of health data — Health Connect on Android and Apple Health background delivery on iOS. See Health Connect.

Theme

Pass an SdkTheme to apply branding overrides. Any unset field falls back to SDK defaults.

await ZingSdk.instance.init( theme: SdkTheme( colors: SdkColors( brandPrimary: Color(0xFF1A73E8), brandSecondary: Color(0xFF34A853), ), cornersRounding: SdkCornerRounding( button: SdkRadius.pill(), ), typography: SdkTypography( brand: 'YourBrandFont', system: 'YourSystemFont', ), ), );
FieldTypeDescription
colorsSdkColorsOverrides brand, text, button and background colors.
cornersRoundingSdkCornerRoundingOverrides corner radius for SDK surfaces. Use SdkRadius.value(n) for a fixed radius or SdkRadius.pill() for a pill shape.
typographySdkTypographyOverrides font families for brand and system text.
blurStyleSdkBlurStyleiOS blur style. Android ignores this field.

To change the theme after initialization, call setTheme. That call replaces the theme and leaves configuration and the current session unchanged. See Working with themes.

Login

After initialization, call login() with an SdkAuthentication value to authenticate the user and start a session.

API Key

Use this mode when backend integration is not available. Pass partnerUserId to link the session to your own user id — if omitted, the SDK generates an id for a new user:

await ZingSdk.instance.login( SdkAuthentication.apiKey( ios: 'your-ios-api-key', android: 'your-android-api-key', partnerUserId: 'your-user-id', ), );

External Token

Use this mode when your backend manages user authentication and issues JWT tokens. Obtain a token from your backend, then pass it directly to login():

await ZingSdk.instance.login( SdkAuthentication.externalToken(await yourAuthRepo.getToken()), );

The JWT must contain a sub claim with the partner user ID.

Call login() only on the first session, when the auth state is SdkAuthStateLoggedOut. On subsequent sessions, init() automatically restores the previous session — you do not need to call login() again.

Login errors

CodeDescription
not_initializedSDK has not been initialized yet
login_failedLogin failed (already logged in, login already in progress, or token retrieval failed)

Logout

Logout and clear the user session.

await ZingSdk.instance.logout();

Logout errors

CodeDescription
not_initializedSDK has not been initialized yet
logout_failedLogout failed (no active session)

Critical errors

Register a CriticalErrorCallback to be notified when an active session can’t be refreshed (e.g. the refresh token was revoked server-side). Log the user out of your app and call login() again with fresh credentials:

ZingSdk.instance.criticalErrorCallback = _MyCriticalErrorCallback(); class _MyCriticalErrorCallback implements CriticalErrorCallback { @override void onCriticalError(PlatformException error) { // error.code is 'auth_error' or 'unknown' } }
CodeDescription
auth_errorThe session could not be refreshed and is unrecoverable
unknownUnknown critical error

Auth State

Monitor authentication state changes via the authState stream. userId on SdkAuthStateAuthenticated is the authenticated user’s id — for SdkAuthentication.apiKey this is partnerUserId (or the SDK-generated id, if omitted); for SdkAuthentication.externalToken this is the sub claim of the JWT:

ZingSdk.instance.authState.listen((state) { switch (state) { case SdkAuthStateLoggedOut(): // User is not authenticated case SdkAuthStateInProgress(): // Authentication is in progress case SdkAuthStateAuthenticated(:final userId): // User is authenticated } });

Health Connect (Android)

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 native SDK and merged into your app automatically — 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 android/app/src/main/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 SdkConfiguration by passing healthBackgroundSync: true:

await ZingSdk.instance.init( configuration: const SdkConfiguration( healthBackgroundSync: true, ), );

Background sync is disabled by default (healthBackgroundSync: 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 fully composed SdkTheme to customize all visual aspects of the SDK. Every field is optional — any unset field falls back to the SDK default.

await ZingSdk.instance.init( theme: SdkTheme( colors: SdkColors( brandPrimary: Color(0xFF1A73E8), brandSecondary: Color(0xFF34A853), headingPrimary: Color(0xFF1D212C), headingPrimaryInv: Color(0xFFFFFFFF), fgPrimary: Color(0xFF1D212C), fgSecondary: Color(0xFF6B7280), buttonPrimary: Color(0xFF1A73E8), buttonSecondary: Color(0xFF34A853), bgPrimary: Color(0xFFFFFFFF), bgSecondary: Color(0xFFF3F4F6), ), blurStyle: SdkBlurStyle.systemMaterial, cornersRounding: SdkCornerRounding( button: SdkRadius.pill(), input: SdkRadius.value(20), hero: SdkRadius.value(32), modal: SdkRadius.value(48), cardSm: SdkRadius.value(8), cardMd: SdkRadius.value(16), cardLg: SdkRadius.value(24), ), typography: SdkTypography( brand: 'YourBrandFont', system: 'YourSystemFont', ), ), );

SdkCornerRounding accepts a radius for each SDK surface:

FieldUsed forNotes
buttonButtons
inputText inputsiOS only
heroHome screen top cardiOS only
modalBottom sheets and modal containersiOS only
cardSmSmall cardsiOS only
cardMdMedium cardsiOS only
cardLgLarge cardsiOS only

All fields are optional — omitting a field keeps the SDK default.

SdkColors fields match the native color tokens. Every field is applied on iOS. Android applies the same fields except brandTertiary, buttonLightYellow, buttonLightOrchid, buttonLightBlue, buttonIconTransparent, overlayBlackDark, overlayBlackMedium, overlayCadetMedium, and the textBody* colors. blurStyle is iOS only.

Call setTheme when the theme should change after init:

await ZingSdk.instance.setTheme( SdkTheme( colors: SdkColors( brandPrimary: Color(0xFF1A73E8), brandSecondary: Color(0xFF34A853), ), ), );
CodeDescription
not_initializedSDK has not been initialized yet
set_theme_failedThe native SDK rejected the theme update

Working with pictures

The SDK automatically loads custom images from the host app’s platform resources using fixed asset names. No Flutter-level code is required — just add the images with the correct names to your platform project.

Android

Add drawable files to android/app/src/main/res/drawable/. For best quality, provide density-specific variants — Android will automatically pick the correct one for the device screen:

res/ drawable-mdpi/zing_plan_background.png drawable-hdpi/zing_plan_background.png drawable-xhdpi/zing_plan_background.png drawable-xxhdpi/zing_plan_background.png drawable-xxxhdpi/zing_plan_background.png
Drawable nameUsed for
zing_plan_backgroundPlan section background image
zing_coach_johnCoach John avatar
zing_coach_jenniferCoach Jennifer avatar
zing_coach_sarahCoach Sarah avatar
zing_coach_chrisCoach Chris avatar

iOS

Add image assets to Assets.xcassets using the same names:

Asset nameUsed for
zing_plan_backgroundPlan section background image
zing_coach_johnCoach John avatar
zing_coach_jenniferCoach Jennifer avatar
zing_coach_sarahCoach Sarah avatar
zing_coach_chrisCoach Chris avatar
zing_coach_manelCoach Manel avatar
zing_coach_adrianaCoach Adriana avatar

All image names are optional — the SDK falls back to its built-in assets for any name that is not found.

Working with typography

Pass SdkTypography inside SdkTheme to override the SDK’s built-in fonts. Two font roles are available:

FieldApplies toDefault
brandHeadings, display text, counter, coach nameSDK built-in (Outfit)
systemBody text, UI elements, coach chatSDK built-in (SF Pro / Roboto)

Both fields are optional — omitting a field keeps the SDK’s built-in font for that role.

The value must be the name the font is registered under in the host app’s platform resources. Register your font first, then pass the same name to SdkTypography:

PlatformHow to registerName to pass
AndroidPlace .ttf/.otf in android/app/src/main/res/font/Filename without extension (e.g. "inter")
iOSAdd the font file to Xcode and declare it under UIAppFonts in Info.plistPostScript family name (e.g. "Inter")
await ZingSdk.instance.init( theme: SdkTheme( typography: SdkTypography( brand: 'MyBrandFont', // registered as res/font/mybrandfont.ttf on Android system: 'MySystemFont', // registered as PostScript name on iOS ), ), );

User profile

Supply user profile data to personalize the SDK experience. Log in before calling setProfileParams — the call fails with set_profile_params_failed when no user is logged in.

Passing name or gender skips the matching onboarding screens; the other fields pre-fill them. After onboarding, the params update the profile directly.

ProfileParams

All fields are optional. Omit a field to leave the existing value unchanged.

FieldTypeNotes
nameString?Display name
genderUserGender?male, female, other; preferNotToSay is iOS only
heightdouble?Centimeters (cm), regardless of measurementSystem
weightdouble?Kilograms (kg), regardless of measurementSystem
ageint?Age in full years
measurementSystemMeasurementSystem?metric or imperial — selects the units shown in the SDK UI only

Setting the profile

await ZingSdk.instance.setProfileParams( const ProfileParams( name: 'Username', gender: UserGender.female, height: 178, weight: 75, age: 30, measurementSystem: MeasurementSystem.metric, ), );

Profile errors

CodeDescription
not_initializedSDK has not been initialized yet
set_profile_params_failedProfile update was rejected (for example, no user is logged in)

Primary location

Call setPrimaryLocationId after login to store the member’s primary location id from your organisation. The SDK writes it to the user profile and syncs it.

await ZingSdk.instance.setPrimaryLocationId('your-location-id');
CodeDescription
not_initializedSDK has not been initialized yet
set_primary_location_id_failedThe id was missing, or no user is logged in

Launch native screens

await ZingSdk.instance.openScreen(AiAssistantRoute());

Supported routes:

  • HomeRoute() — accepts an optional HomeScreenConfiguration, see Home screen configuration
  • OnboardingRoute() — runs the onboarding flow; when it completes, the SDK closes and the user returns to your app
  • CustomWorkoutRoute()
  • AiAssistantRoute()
  • WorkoutPlanDetailsRoute()
  • FullScheduleRoute()
  • ProfileSettingsRoute()
  • BodyScanRoute()
  • FlexibilityTestRoute()
  • FitnessTestRoute()

Home screen configuration

HomeRoute accepts a HomeScreenConfiguration that controls which home screen elements are shown. The same configuration is used by ZingHomeView:

await ZingSdk.instance.openScreen( HomeRoute( configuration: HomeScreenConfiguration( showCloseButton: true, showAskCoachButton: true, showBodyScanWidget: true, ), ), );
ParameterDefaultDescription
showCloseButtonrequiredShows the close button in the home screen toolbar. HomeRoute() without a configuration uses true
showAskCoachButtontrueShows the “Ask coach” entry point on the home screen
showBodyScanWidgettrueShows the Body Scan card on the home screen. The Fitness Test and Flexibility Test cards stay visible

Launch screen errors

CodeDescription
not_initializedSDK has not been initialized yet
missing_routeNo route argument provided
unknown_routeUnrecognized route identifier
no_activityNo activity or root view controller available to present the screen
not_logged_inNo active user session; call login before opening a screen (iOS only)
launch_failedScreen launch failed

Embedded home screen

Since 2.2.0 the Zing home screen can be embedded directly into your widget tree — as a tab, a page, or any part of your layout — instead of being opened as a full-screen native screen.

ZingHomeView is a single Flutter widget that renders the native home screen on both Android and iOS. On any other platform it renders nothing.

Login required. ZingHomeView can only be shown while the SDK is logged in. Rendering it in any other state throws an IllegalArgumentException on Android. Check the authState stream before showing the view.

const ZingHomeView( configuration: HomeScreenConfiguration(showCloseButton: false), )

The widget takes the same HomeScreenConfiguration as HomeRoute.

Notes

  • Configure auth/tokens as described in Authorization before launching UI.
Last updated on