# KinesteX SDK Documentation — Full Text
> Complete KinesteX SDK documentation. Source: https://www.kinestex.com/docs
This file concatenates every documentation section as Markdown for AI agents and LLMs. Code samples are included for all supported platforms (Swift, Kotlin, React Native, Flutter, HTML/JavaScript, React TypeScript).
---
## Getting Started
KinesteX offers a powerful motion tracking SDK that seamlessly integrates into your platform. Choose between ready-made workouts, plans, challenges, AI assessments, or create custom experiences with our advanced motion tracking.
### Overview
Our white-label solution supports any camera-enabled device across Android, iOS, web, and gaming platforms, with SDKs for React Native, Flutter, Kotlin, Swift, Java, PWA, and JavaScript for quick integration.
KinesteX's real-time AI motion tracking boosts engagement, retention, and revenue while providing valuable data insights to create personalized, growth-driving experiences.
#### Plug and Play Integration
Pre-built experiences ready to integrate. Customize colors, fonts, and certain UI/UX elements while we handle the core workout experience, motion tracking, and user flow.
##### Complete UX (Main)
Full experience with personalized plan selection, user survey, and AI-generated workout schedules.
##### Plan View
Display a specific workout plan with schedule and multiple workouts.
##### Workout View
Launch directly into a specific workout by name or ID.
##### Challenge View
Gamified exercise challenges with leaderboards and rep tracking.
##### Leaderboard View
Display real-time leaderboards for challenge exercises as a standalone view.
##### AI Experiences
Interactive fitness games like Balloon Pop, Color Chase, and Alien Squat Shooter.
##### Personalized Plan
AI-generated workout plans tailored to user biometrics, fitness level, and assessment results.
##### Admin Workout Editor
Embedded view for creating and managing workouts and exercises. Receive events for workout/exercise creation, selection, and updates. Flutter only.
#### Custom Integration
Build everything yourself with full control over UI/UX. Use our Content APIs to fetch workout data and the Camera Component for real-time motion analysis.
##### Custom Workout
Create personalized workout sequences with custom exercises, reps, durations, and rest periods. Full control over exercise order and timing.
##### Camera Component
Access raw pose analysis data to build your own custom workout UI. Receive real-time rep counts, form feedback, and body tracking data.
##### Content Fetching APIs
Fetch workout plans, exercises, and content data via REST APIs. Build your own content browsing and workout selection experience.
### Requirements
**Platform Versions:**
• iOS 14.0+
• Android API 26+
• Web browser support (Chrome, Safari, Firefox, Edge)
**Prerequisites:**
• API key from KinesteX
• Camera permissions
• Internet connection
### Get API Key
To get demo access and your API key, fill out the contact form on our website.
[Contact Us](/#contact-form)
---
## Installation
Follow these steps to install the KinesteX SDK in your project.
**Step 4: iOS 15+ Device Orientation Fix (Required)** — React Native
react-native-webview doesn't support iOS 15+ motion permissions by default. Apply this patch to enable device orientation tracking:
_4.1: Install patch-package_
```bash
npm install patch-package postinstall-postinstall --save-dev
```
_4.2: Add postinstall script to package.json_
```json
{
"scripts": {
"postinstall": "patch-package"
}
}
```
_4.3: Patch RNCWebViewImpl.m_
```objectivec
// Edit: node_modules/react-native-webview/apple/RNCWebViewImpl.m
// Find requestMediaCapturePermissionForOrigin (line ~1312)
// Add this method after it:
- (void)webView:(WKWebView *)webView
requestDeviceOrientationAndMotionPermissionForOrigin:(WKSecurityOrigin *)origin
initiatedByFrame:(WKFrameInfo *)frame
decisionHandler:(void (^)(WKPermissionDecision))decisionHandler {
(void)webView;
(void)origin;
(void)frame;
decisionHandler(WKPermissionDecisionGrant);
}
```
_4.4: Save the patch_
```bash
npx patch-package react-native-webview
```
**Step 5: Expo Android Camera Fix** — React Native
For Expo on Android, request app-level camera permission before displaying KinesteX:
_5.1: Install expo-camera_
```bash
npx expo install expo-camera
```
_5.2: Configure app.json_
```json
{
"expo": {
"android": {
"permissions": ["android.permission.CAMERA"]
},
"ios": {
"infoPlist": {
"NSCameraUsageDescription": "For AI Motion Tracking"
}
}
}
}
```
_5.3: Request permission before showing KinesteX_
```typescript
import { Camera } from 'expo-camera';
import { useState, useEffect } from 'react';
const [hasPermission, setHasPermission] = useState(false);
useEffect(() => {
(async () => {
const { status } = await Camera.requestCameraPermissionsAsync();
setHasPermission(status === 'granted');
})();
}, []);
// Only render KinesteX when hasPermission is true
```
### Step 1: Add Dependencies
Add the KinesteX SDK to your project using your platform's package manager.
**Install SDK**
_Swift (iOS)_
```swift
1. In Xcode: File > Add Package Dependencies...
2. Enter URL: https://github.com/KinesteX/KinesteX-AI-Kit.git
3. Click "Add Package"
```
_Kotlin (Android)_
```kotlin
// 1. Add JitPack repository in settings.gradle.kts
dependencyResolutionManagement {
repositories {
maven { url = uri("https://jitpack.io") }
}
}
// 2. Add dependency in app/build.gradle.kts
dependencies {
implementation("com.github.KinesteX:KinesteX-SDK-Kotlin:2.0.0")
}
// 3. Sync project with Gradle files
```
_React Native_
```jsx
// 1. Install packages
npm install kinestex-sdk-react-native react-native-webview
// 2. Install iOS dependencies
cd ios && pod install && cd ..
// For Expo projects:
npx expo install react-native-webview
```
_Flutter_
```dart
# 1. Add to pubspec.yaml
dependencies:
kinestex_sdk_flutter: ^latest
permission_handler: ^11.3.1
# 2. Install packages
flutter pub get
# 3. Install iOS dependencies
cd ios && pod install && cd ..
```
_HTML / JavaScript_
```html
```
_React (TypeScript)_
```tsx
npm i kinestex-sdk-react-ts
```
### Step 2: Configure Permissions
Add the required permissions for camera and motion sensors.
**Required Permissions**
_Swift (iOS)_
```swift
NSCameraUsageDescription
Camera access is required for AI-powered workout tracking
NSMotionUsageDescription
Motion sensors help position your device correctly for workouts
```
_Kotlin (Android)_
```kotlin
```
_React Native_
```jsx
NSCameraUsageDescription
Camera access is required for AI-powered workout tracking
NSMotionUsageDescription
Motion sensors help position your device correctly for workouts
```
_Flutter_
```dart
NSCameraUsageDescription
Camera access is required for AI-powered workout tracking
NSMotionUsageDescription
Motion sensors help position your device correctly for workouts
```
_HTML / JavaScript_
```html
```
_React (TypeScript)_
```tsx
Browser will prompt users for camera access automatically. No additional permissions are needed.
```
### Step 3: Import the SDK
Import the KinesteX SDK in your code to start using it.
**Import Statement**
_Swift (iOS)_
```swift
import KinesteXAIKit
```
_Kotlin (Android)_
```kotlin
import com.kinestex.kinestexsdkkotlin.KinesteXSDK
```
_React Native_
```jsx
import KinestexSDK from 'kinestex-sdk-react-native';
import { IntegrationOption, IPostData, PlanCategory } from 'kinestex-sdk-react-native';
```
_Flutter_
```dart
import 'package:kinestex_sdk_flutter/kinestex_sdk_flutter.dart';
```
_HTML / JavaScript_
```html
// Define the iframe element and create a URL to be later used to load the iframe
const webView = document.getElementById('webView');
const srcURL = 'https://ai.kinestex.com';
```
_React (TypeScript)_
```tsx
import {
IntegrationOption,
KinesteXSDK,
type IPostData,
type KinesteXSDKCamera,
} from "kinestex-sdk-react-ts";
```
---
## Configuration
Initialize the SDK with your API credentials.
**Step 1: Initialize the SDK**
_Swift (iOS)_
```swift
import KinesteXAIKit
@State var showKinesteX = false // Controls KinesteX SDK visibility
@State var isLoading = false // Optional: Controls custom loading screen
// Initialize with your API key, company name, and unique user ID
let kinestex = KinesteXAIKit(
apiKey: "YOUR_API_KEY",
companyName: "YOUR_COMPANY",
userId: "unique-user-id"
)
// Optional: UserDetails to customize workout intensity
let user = UserDetails(
age: 20,
height: 170,
weight: 70,
gender: .Male,
lifestyle: .Active
)
```
_Kotlin (Android)_
```kotlin
import com.kinestex.kinestexsdkkotlin.KinesteXSDK
// Initialize the SDK in your Application class as early as possible
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
KinesteXSDK.initialize(
context = this,
apiKey = "YOUR_API_KEY",
companyName = "YOUR_COMPANY",
userId = "unique-user-id"
)
}
}
```
_React Native_
```jsx
// 1. Create a reference to KinesteXSDK component
const kinestexSDKRef = useRef(null);
// 2. Create postData object to initialize KinesteX session
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID', // unique per user
company: 'YOUR_COMPANY',
style: {
style: 'dark', // 'dark' or 'light'
loadingBackgroundColor: '000000', // hex without #
},
customParameters: {
// language: 'es' // customize language
},
// Optional: UserDetails for workout intensity
age: 25,
height: 180, // cm
weight: 75, // kg
gender: 'Male',
lifestyle: Lifestyle.Active,
};
// 3. Handle real-time messages from KinesteX
const handleMessage = (type: string, data: { [key: string]: any }) => {
switch (type) {
case 'exit_kinestex':
console.log('User exited');
setShowKinesteX(false);
break;
case 'plan_unlocked':
console.log('Plan unlocked:', data);
break;
default:
console.log('Message:', type, data);
break;
}
};
```
_Flutter_
```dart
import 'package:kinestex_sdk_flutter/kinestex_sdk_flutter.dart';
// Initialize the SDK in your main function as early as possible.
await KinesteXAIFramework.initialize(
apiKey: "YOUR_API_KEY",
companyName: "YOUR_COMPANY",
userId: "unique-user-id",
);
```
_HTML / JavaScript_
```html
// 1. Define the config data to be sent to the iframe
const postData = {
userId: "YOUR_USER_ID", // Unique identifier for the user
company: "YOUR_COMPANY", // Your organization's name from kinestex admin dashboard
key: "YOUR_API_KEY", // Your API Key from kinestex admin dashboard
// Optional parameters for customization
style: "dark", // dark or light theme
// user related customization
age: 30, // User's age (optional)
height: 175, // User's height in cm (optional)
weight: 70, // User's weight in kg (optional)
gender: "Female", // Gender (optional)
};
// 2. Sending the messages to the iframe will be done through postMessage API
function sendMessage() {
if (webView.contentWindow) {
webView.contentWindow.postMessage(postData, srcURL); // post initial data to start session
} else {
setTimeout(() => {
try {
webView.contentWindow.postMessage(postData, srcURL); // post initial data to start session
} catch {
webView.contentWindow.postMessage(postData, srcURL); // retry sending message
}
}, 100);
}
}
// 3. Whenever you want to display the view:
webView.src = srcURL; // Update this URL for specific features
webView.onload = () => {
sendMessage();
};
// 4. Handle the loading confirmation received from the iframe to send the initial data once more (iOS specific verification)
window.addEventListener("message", (event) => {
if (event.origin !== "https://ai.kinestex.com") return; // prevent listening for messages from other sources for security
const message = JSON.parse(event.data);
if (message.type === 'kinestex_loaded') {
sendMessage();
}
});
```
_React (TypeScript)_
```tsx
// 1. Create a reference to KinesteXSDK component
const kinestexSDKRef = useRef(null);
// 2. Create postData object to initialize KinesteX session
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID', // unique per user
company: 'YOUR_COMPANY',
style: {
style: 'dark', // 'dark' or 'light'
loadingBackgroundColor: '000000', // hex without #
},
customParameters: {
// language: 'es' // customize language
},
// Optional: UserDetails for workout intensity
age: 25,
height: 180, // cm
weight: 75, // kg
gender: 'Male',
lifestyle: Lifestyle.Active,
};
// 3. Handle real-time messages from KinesteX
const handleMessage = (type: string, data: { [key: string]: any }) => {
switch (type) {
case 'exit_kinestex':
console.log('User exited');
setShowKinesteX(false);
break;
case 'plan_unlocked':
console.log('Plan unlocked:', data);
break;
default:
console.log('Message:', type, data);
break;
}
};
```
**Step 2: Register Application Class** — Kotlin (Android)
Register your custom Application class in AndroidManifest.xml:
_AndroidManifest.xml_
```xml
```
**Step 3: Request Camera Permission** — Kotlin (Android)
Before a user starts a workout, request camera permission at the app level. Your activity or fragment must implement the PermissionHandler interface:
_MainActivity with PermissionHandler_
```kotlin
class MainActivity : AppCompatActivity(), PermissionHandler {
// Initialize the webview
private var kinesteXWebView: GenericWebView? = null
// Optional: Pass user details to adjust exercises and estimate calories
// Note: User details are only used on the device for customization during the session
private var userDetails = UserDetails(
age = 20,
height = 170,
weight = 180,
gender = Gender.MALE,
lifestyle = Lifestyle.ACTIVE
)
// Register permission launcher
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
// Pass permission result to KinesteX webview
kinesteXWebView?.handlePermissionResult(isGranted)
}
// Override to display system dialog for camera access
override fun requestCameraPermission() {
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
```
**Step 4: Handle SDK Messages** — Kotlin (Android)
Implement a callback function to handle messages from the SDK:
_Message Handler_
```kotlin
private fun handleWebViewMessage(message: WebViewMessage) {
when (message) {
is WebViewMessage.ExitKinestex -> {
// Dismiss KinesteX view when user clicks exit button
}
// Handle other messages
else -> {
Log.d("KinesteX", message.toString())
}
}
}
```
**Step 2: Request Camera Permission** — Flutter
Request camera permission before launching KinesteX. Ensure you've added the necessary permissions in AndroidManifest.xml and Info.plist. Add the following to your iOS Podfile:
_2.1: ios/Podfile_
```ruby
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
## dart: PermissionGroup.camera
'PERMISSION_CAMERA=1',
]
end
end
end
```
_2.2: Request Permission in Dart_
```dart
void _checkCameraPermission() async {
if (await Permission.camera.request() != PermissionStatus.granted) {
_showCameraAccessDeniedAlert();
}
}
void _showCameraAccessDeniedAlert() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Camera Permission Denied"),
content: const Text("Camera access is required for this app to function properly."),
actions: [
TextButton(
child: const Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
```
**Step 3: Initialize on App Launch** — Flutter
Initialize KinesteX in your main function for WebView warmup:
_main.dart_
```dart
Future main() async {
await KinesteXAIFramework.initialize(
apiKey: YOUR_API_KEY,
companyName: YOUR_COMPANY_NAME,
userId: YOUR_USER_ID,
);
runApp(
const MaterialApp(
home: MyHomePage(),
),
);
}
```
**Step 4: Deinitialize on App Closure** — Flutter
Call dispose when closing the app:
_Dispose Method_
```dart
@override
void dispose() {
disposeKinesteXAIFramework();
super.dispose();
}
```
**Step 5: Setup Recommendations** — Flutter
Use a ValueNotifier to manage KinesteX presentation and handle callback messages:
_State Management & Message Handler_
```dart
import 'package:kinestex_sdk_flutter/kinestex_sdk_flutter.dart';
// Add a ValueNotifier to manage presentation
ValueNotifier showKinesteX = ValueNotifier(false);
// Handle callback messages from KinesteX
void handleWebViewMessage(WebViewMessage message) {
if (message is ExitKinestex) {
setState(() {
showKinesteX.value = false;
});
} else {
print("Message received: ${message.data}");
}
}
```
---
## Integration Options
Choose the integration option that best fits your use case. Each option provides a different level of UI customization.
### Plug and Play Integration
Pre-built experiences ready to integrate. Customize colors, fonts, and certain UI/UX elements while we handle the core workout experience, motion tracking, and user flow.
#### Complete UX (Main)
Display the full KinesteX experience with personalized workout plan selection based on category. Includes user survey, assessment, and personalized schedule generation. This is the easiest integration option.
**Available Plan Categories:**
| Plan Category | Key |
|---------------|-----|
| Strength | Strength |
| Cardio | Cardio |
| Weight Management | Weight Management |
| Rehabilitation | Rehabilitation |
| Custom | Custom |
**Define Plan Category**
First, define the plan category for personalized fitness goals:
_Swift (iOS)_
```swift
// Plan category for personalized fitness goals
@State private var planCategory: PlanCategory = .Cardio
```
_Kotlin (Android)_
```kotlin
// Plan category for personalized fitness goals
private var planCategory = PlanCategory.Cardio
```
_React Native_
```jsx
// Plan category for personalized fitness goals
const planCategory = PlanCategory.Cardio; // Strength, Cardio, etc.;
```
_Flutter_
```dart
// Plan category for personalized fitness goals
PlanCategory planCategory = PlanCategory.Cardio;
```
_HTML / JavaScript_
```html
// Plan category for personalized fitness goals
const planCategory = "Cardio";
```
_React (TypeScript)_
```tsx
// Plan category for personalized fitness goals
const planCategory = PlanCategory.Cardio; // Strength, Cardio, etc.
```
**Display Category View**
Display the category-based view with real-time message handling:
_Swift (iOS)_
```swift
kinestex.createCategoryView(
planCategory: planCategory,
user: user, // optional: can be nil
isLoading: $isLoading,
customParams: ["style": "dark"], // dark or light theme
onMessageReceived: { message in
switch message {
case .kinestex_launched(let data):
print("KinesteX Launched: \(data)")
case .finished_workout(let data):
print("Workout Finished: \(data)")
case .exit_kinestex(let data):
showKinesteX = false // Dismiss the view
default:
print("Received \(message)")
break
}
}
)
// OPTIONAL: Display loading screen during view initialization
.overlay(
Group {
if showAnimation {
Text("Aifying workouts...")
.foregroundColor(.black)
.font(.caption)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.white)
}
}
)
.onChange(of: isLoading) { newValue in
withAnimation(.easeInOut(duration: 2.5)) {
showAnimation = !newValue
}
}
```
_Kotlin (Android)_
```kotlin
KinesteXSDK.createMainView(
context = this,
planCategory = planCategory,
user = userDetails, // optional user details
data = mapOf("style" to "dark"),
isLoading = viewModel.isLoading,
onMessageReceived = { message ->
when (message) {
is WebViewMessage.KinestexLaunched ->
println("KinesteX Launched")
is WebViewMessage.FinishedWorkout ->
println("Workout Finished: ${message.data}")
is WebViewMessage.ExitKinestex -> finish()
else -> println("Received: $message")
}
},
permissionHandler = this
)
```
_React Native_
```jsx
// postData structure
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
planCategory: planCategory,
style: {
style: 'dark', // or 'light'
},
};
{
switch (type) {
case 'kinestex_launched':
console.log('KinesteX Launched:', data);
break;
case 'finished_workout':
console.log('Workout Finished:', data);
break;
case 'exit_kinestex':
setShowKinesteX(false);
break;
}
}}
/>
```
_Flutter_
```dart
KinesteXAIFramework.createMainView(
isShowKinestex: showKinesteX,
planCategory: planCategory,
customParams: {"style": "dark"},
isLoading: ValueNotifier(false),
onMessageReceived: (message) {
switch (message.type) {
case 'kinestex_launched':
print('KinesteX Launched');
break;
case 'finished_workout':
print('Workout Finished: ${message.data}');
break;
case 'exit_kinestex':
setState(() => showKinesteX = false);
break;
}
},
)
```
_HTML / JavaScript_
```html
// Add planCategory to postData
const postData = {
// ... all initial fields
planCategory: planCategory, // "Strength", "Cardio", etc.
};
const srcURL = "https://ai.kinestex.com";
webView.src = srcURL;
webView.onload = () => {
sendMessage();
};
// Handle messages
window.addEventListener('message', (event) => {
const { type, data } = event.data;
switch (type) {
case 'kinestex_launched':
console.log('KinesteX Launched:', data);
break;
case 'exit_kinestex':
// Hide the iframe
break;
}
});
```
_React (TypeScript)_
```tsx
import { IntegrationOption, KinesteXSDK, PlanCategory, type IPostData } from 'kinestex-sdk-react-ts';
// postData structure
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
planCategory: PlanCategory.Cardio,
style: {
style: 'dark',
},
};
{
switch (type) {
case 'kinestex_launched':
console.log('KinesteX Launched:', data);
break;
case 'finished_workout':
console.log('Workout Finished:', data);
break;
case 'exit_kinestex':
setShowKinesteX(false);
break;
}
}}
/>
```
**Complete Example**
Full implementation example with all required setup:
_Swift (iOS)_
```swift
import SwiftUI
import KinesteXAIKit
struct MainViewIntegration: View {
@State private var showKinesteX = false
@State private var isLoading = false
// Replace with your KinesteX credentials
let kinesteXKit = KinesteXAIKit(
apiKey: "YOUR API KEY",
companyName: "YOUR COMPANY NAME",
userId: "YOUR USER ID"
)
// Plan category for personalized fitness goals
@State private var planCategory: PlanCategory = .Cardio
var body: some View {
VStack {
Text("KinesteX Main View")
.font(.title)
.padding()
Spacer()
Button(action: {
showKinesteX.toggle()
}) {
Text("Open Main View")
.font(.title3)
.foregroundColor(.white)
.bold()
.padding()
.frame(maxWidth: .infinity)
.background(Color.green.cornerRadius(10))
.padding(.horizontal)
}
Spacer()
}
.fullScreenCover(isPresented: $showKinesteX) {
kinestex.createCategoryView(
planCategory: planCategory,
user: nil,
isLoading: $isLoading,
customParams: ["style": "light"],
onMessageReceived: { message in
switch message {
case .exit_kinestex(_):
showKinesteX = false
default:
print("Message received: \(message)")
}
}
)
}
}
}
#Preview {
MainViewIntegration()
}
```
_Kotlin (Android)_
```kotlin
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.mutableStateOf
import com.kinestex.kinestexsdkkotlin.GenericWebView
import com.kinestex.kinestexsdkkotlin.KinesteXSDK
import com.kinestex.kinestexsdkkotlin.PlanCategory
import com.kinestex.kinestexsdkkotlin.PermissionHandler
import com.kinestex.kinestexsdkkotlin.WebViewMessage
import kotlinx.coroutines.flow.MutableStateFlow
class MainViewActivity : ComponentActivity(), PermissionHandler {
private val viewModel = MainViewModel()
// OPTIONAL: UserDetails to customize workout intensity and calorie estimation
// Note: User details are only used on-device during the session
private val userDetails = UserDetails(
age = 30,
height = 180,
weight = 75,
gender = Gender.MALE,
lifestyle = Lifestyle.ACTIVE
)
// Custom data for the WebView
private val data = mutableMapOf()
// Store reference to the KinesteX WebView
private var kinesteXWebView: GenericWebView? = null
// Register permission launcher
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
// Pass permission result to KinesteX webview
kinesteXWebView?.handlePermissionResult(isGranted)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
data["style"] = "light"
setContent {
val webView = KinesteXSDK.createMainView(
context = this,
planCategory = PlanCategory.Cardio,
user = userDetails,
data = data,
isLoading = viewModel.isLoading,
onMessageReceived = ::handleWebViewMessage,
permissionHandler = this
) as GenericWebView
kinesteXWebView = webView
webView.Render()
}
}
private fun handleWebViewMessage(message: WebViewMessage) {
when (message) {
is WebViewMessage.ExitKinestex -> finish()
is WebViewMessage.KinestexLaunched -> viewModel.isLoading.value = false
else -> Toast.makeText(this, "Received: $message", Toast.LENGTH_SHORT).show()
}
}
// When request is sent, display system dialog for camera access
override fun requestCameraPermission() {
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
class MainViewModel {
val isLoading = MutableStateFlow(true)
}
```
_React Native_
```jsx
import React, { useState } from 'react';
import { View, Button, StyleSheet } from 'react-native';
import KinestexSDK, { IntegrationOption, IPostData, PlanCategory } from '@kinestex/sdk-react-native';
const MainViewIntegration = () => {
const [showKinesteX, setShowKinesteX] = useState(false);
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
planCategory: PlanCategory.Cardio,
style: {
style: 'dark',
},
};
return (
{showKinesteX ? (
{
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
}}
/>
) : (
setShowKinesteX(true)}
/>
)}
);
};
const styles = StyleSheet.create({
container: { flex: 1 }
});
export default MainViewIntegration;
```
_Flutter_
```dart
import 'package:flutter/material.dart';
import 'package:kinestex_sdk_flutter/kinestex_sdk.dart';
import 'package:permission_handler/permission_handler.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await KinesteXAIFramework.initialize(
apiKey: "your_api_key",
companyName: "your_company_name",
userId: "your_user_id",
);
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State createState() => _MyAppState();
}
class _MyAppState extends State {
@override
void dispose() {
disposeKinesteXAIFramework();
super.dispose();
}
Future disposeKinesteXAIFramework() async {
await KinesteXAIFramework.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'KinesteX Main View',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
ValueNotifier showKinesteX = ValueNotifier(false);
@override
void initState() {
super.initState();
_checkCameraPermission();
}
void _checkCameraPermission() async {
if (await Permission.camera.request() != PermissionStatus.granted) {
_showCameraAccessDeniedAlert();
}
}
void _showCameraAccessDeniedAlert() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Camera Permission Denied"),
content: const Text("Camera access is required for this app to function properly."),
actions: [
TextButton(
child: const Text("OK"),
onPressed: () => Navigator.of(context).pop(),
),
],
);
},
);
}
void handleWebViewMessage(WebViewMessage message) {
if (message is ExitKinestex) {
setState(() {
showKinesteX.value = false;
});
}
}
Widget createMainView() {
return Center(
child: KinesteXAIFramework.createMainView(
isShowKinestex: showKinesteX,
planCategory: PlanCategory.Cardio,
customParams: {
"style": "dark",
},
isLoading: ValueNotifier(false),
onMessageReceived: handleWebViewMessage,
),
);
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: showKinesteX,
builder: (context, isShowKinesteX, child) {
return isShowKinesteX
? SafeArea(
child: createMainView(),
)
: Scaffold(
body: Center(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 40,
vertical: 20,
),
backgroundColor: Colors.green,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: () {
showKinesteX.value = true;
},
child: const Text(
'Start Main View',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
);
},
);
}
}
```
_HTML / JavaScript_
```html
KinesteX: Complete User Experience
Start KinesteX
```
_React (TypeScript)_
```tsx
import React, { useState } from 'react';
import {
IntegrationOption,
KinesteXSDK,
PlanCategory,
type IPostData,
} from 'kinestex-sdk-react-ts';
const MainViewIntegration: React.FC = () => {
const [showKinesteX, setShowKinesteX] = useState(false);
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
planCategory: PlanCategory.Cardio,
style: {
style: 'dark',
},
};
return (
{showKinesteX ? (
{
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
}}
/>
) : (
setShowKinesteX(true)}>
Open Main View
)}
);
};
export default MainViewIntegration;
```
#### Workout View
Personalized Workouts: Anytime, Anywhere.
- **Tailored for All Levels**: Workouts for strength, flexibility, or relaxation
- **Time-Saving**: Quick, efficient sessions with zero hassle
- **Engaging**: Keep users motivated with fresh, personalized routines
- **Easy Integration**: Add workouts seamlessly with minimal effort
You can find workouts in our [workout library](https://workout-view.kinestex.com/?tab=workouts), or create your own workouts in our [admin portal](https://admin.kinestex.com).
**Workout Integration**
Display a workout by name or ID:
_Swift (iOS)_
```swift
kinestex.createWorkoutView(
workout: selectedWorkout, // workout name or ID
user: nil,
isLoading: $isLoading,
customParams: ["style": "dark", "language": "en"], // dark or light theme
onMessageReceived: { message in
switch message {
case .exit_kinestex(_):
showKinesteX = false // dismiss the view
default:
print("Received \(message)")
break
}
}
)
```
_Kotlin (Android)_
```kotlin
KinesteXSDK.createWorkoutView(
context = this,
workout = selectedWorkout, // workout name or ID
user = userDetails, // optional user details
data = mapOf("style" to "dark", "language" to "en"),
isLoading = viewModel.isLoading,
onMessageReceived = { message ->
when (message) {
is WebViewMessage.ExitKinestex -> finish()
else -> println("Received: $message")
}
},
permissionHandler = this
)
```
_React Native_
```jsx
// postData structure
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
style: {
style: 'dark',
},
};
{
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
}}
/>
```
_Flutter_
```dart
KinesteXAIFramework.createWorkoutView(
isShowKinestex: showKinesteX,
workoutName: selectedWorkout, // workout name or ID
customParams: {"style": "dark", "language": "en"},
isLoading: ValueNotifier(false),
onMessageReceived: (message) {
if (message.type == 'exit_kinestex') {
setState(() => showKinesteX = false);
}
},
)
```
_HTML / JavaScript_
```html
// Specify workout ID in the URL
const srcURL = "https://ai.kinestex.com/workout/YOUR_WOROUT_ID";
webView.src = srcURL;
```
_React (TypeScript)_
```tsx
import { IntegrationOption, KinesteXSDK, type IPostData } from 'kinestex-sdk-react-ts';
// postData structure
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
style: {
style: 'dark',
},
};
{
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
}}
/>
```
**Complete Example**
Full implementation example with workout selection:
_Swift (iOS)_
```swift
import SwiftUI
import KinesteXAIKit
struct WorkoutIntegrationView: View {
@State private var showKinesteX = false
@State private var isLoading = false
// Initialize KinesteXAIKit
// Replace with your KinesteX credentials
let kinesteXKit = KinesteXAIKit(
apiKey: "YOUR API KEY",
companyName: "YOUR COMPANY NAME",
userId: "YOUR USER ID"
)
// Replace with the name or ID of the workout
let workoutName = "Fitness Lite"
var body: some View {
VStack {
Text("KinesteX Workout Integration")
.font(.title)
.padding()
Spacer()
Button(action: {
showKinesteX.toggle()
}) {
Text("Start \(workoutName) Workout")
.font(.title3)
.foregroundColor(.white)
.bold()
.padding()
.frame(maxWidth: .infinity)
.background(Color.green.cornerRadius(10))
.padding(.horizontal)
}
.padding()
Spacer()
}
.fullScreenCover(isPresented: $showKinesteX) {
kinesteXKit.createWorkoutView(
workout: workoutName,
user: nil,
isLoading: $isLoading,
customParams: ["style": "dark"],
onMessageReceived: { message in
switch message {
case .exit_kinestex(_):
showKinesteX = false
default:
print("Message received: \(message)")
}
}
)
}
}
}
#Preview {
WorkoutIntegrationView()
}
```
_Kotlin (Android)_
```kotlin
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.kinestex.kinestexsdkkotlin.GenericWebView
import com.kinestex.kinestexsdkkotlin.KinesteXSDK
import com.kinestex.kinestexsdkkotlin.PermissionHandler
import com.kinestex.kinestexsdkkotlin.WebViewMessage
import kotlinx.coroutines.flow.MutableStateFlow
class WorkoutViewActivity : ComponentActivity(), PermissionHandler {
private val viewModel = WorkoutViewModel()
// OPTIONAL: UserDetails to customize workout intensity and calorie estimation
// Note: User details are only used on-device during the session
private val userDetails = UserDetails(
age = 30,
height = 180,
weight = 75,
gender = Gender.MALE,
lifestyle = Lifestyle.ACTIVE
)
// Custom data for the WebView
private val data = mutableMapOf()
// Store reference to the KinesteX WebView
private var kinesteXWebView: GenericWebView? = null
// Register permission launcher
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
// Pass permission result to KinesteX webview
kinesteXWebView?.handlePermissionResult(isGranted)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
data["style"] = "dark"
setContent {
val webView = KinesteXSDK.createWorkoutView(
context = this,
workout = "Fitness Lite",
user = userDetails,
data = data,
isLoading = viewModel.isLoading,
onMessageReceived = ::handleWebViewMessage,
permissionHandler = this
) as GenericWebView
kinesteXWebView = webView
webView.Render()
}
}
private fun handleWebViewMessage(message: WebViewMessage) {
when (message) {
is WebViewMessage.ExitKinestex -> finish()
is WebViewMessage.KinestexLaunched -> viewModel.isLoading.value = false
else -> Toast.makeText(this, "Received: $message", Toast.LENGTH_SHORT).show()
}
}
// When request is sent, display system dialog for camera access
override fun requestCameraPermission() {
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
class WorkoutViewModel {
val isLoading = MutableStateFlow(true)
}
```
_React Native_
```jsx
import React, { useState } from 'react';
import { View, Button, StyleSheet } from 'react-native';
import KinestexSDK, { IntegrationOption, IPostData } from '@kinestex/sdk-react-native';
const WorkoutIntegration = () => {
const [showKinesteX, setShowKinesteX] = useState(false);
const selectedWorkout = "Fitness Lite";
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
style: {
style: 'dark',
},
};
return (
{showKinesteX ? (
{
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
}}
/>
) : (
setShowKinesteX(true)}
/>
)}
);
};
const styles = StyleSheet.create({
container: { flex: 1 }
});
export default WorkoutIntegration;
```
_Flutter_
```dart
import 'package:flutter/material.dart';
import 'package:kinestex_sdk_flutter/kinestex_sdk.dart';
import 'package:permission_handler/permission_handler.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await KinesteXAIFramework.initialize(
apiKey: "your_api_key",
companyName: "your_company_name",
userId: "your_user_id",
);
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State createState() => _MyAppState();
}
class _MyAppState extends State {
@override
void dispose() {
disposeKinesteXAIFramework();
super.dispose();
}
Future disposeKinesteXAIFramework() async {
await KinesteXAIFramework.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'KinesteX Workout',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
ValueNotifier showKinesteX = ValueNotifier(false);
@override
void initState() {
super.initState();
_checkCameraPermission();
}
void _checkCameraPermission() async {
if (await Permission.camera.request() != PermissionStatus.granted) {
_showCameraAccessDeniedAlert();
}
}
void _showCameraAccessDeniedAlert() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Camera Permission Denied"),
content: const Text("Camera access is required for this app to function properly."),
actions: [
TextButton(
child: const Text("OK"),
onPressed: () => Navigator.of(context).pop(),
),
],
);
},
);
}
void handleWebViewMessage(WebViewMessage message) {
if (message is ExitKinestex) {
setState(() {
showKinesteX.value = false;
});
}
}
Widget createWorkoutView() {
return Center(
child: KinesteXAIFramework.createWorkoutView(
isShowKinestex: showKinesteX,
workoutName: "Fitness Lite",
customParams: {
"style": "dark",
},
isLoading: ValueNotifier(false),
onMessageReceived: handleWebViewMessage,
),
);
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: showKinesteX,
builder: (context, isShowKinesteX, child) {
return isShowKinesteX
? SafeArea(
child: createWorkoutView(),
)
: Scaffold(
body: Center(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 40,
vertical: 20,
),
backgroundColor: Colors.green,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: () {
showKinesteX.value = true;
},
child: const Text(
'Start Workout',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
);
},
);
}
}
```
_HTML / JavaScript_
```html
KinesteX: Workout
Start KinesteX
```
_React (TypeScript)_
```tsx
import React, { useState } from 'react';
import {
IntegrationOption,
KinesteXSDK,
type IPostData,
} from 'kinestex-sdk-react-ts';
const WorkoutIntegration: React.FC = () => {
const [showKinesteX, setShowKinesteX] = useState(false);
const selectedWorkout = "Fitness Lite";
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
style: {
style: 'dark',
},
};
return (
{showKinesteX ? (
{
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
}}
/>
) : (
setShowKinesteX(true)}>
Start {selectedWorkout}
)}
);
};
export default WorkoutIntegration;
```
#### Plan View
Display a specific workout plan with multiple workouts. Shows plan overview, schedule, and individual workout access.
**Key Features of Our Workout Plans:**
- **Goal-Oriented**: Supports strength, flexibility, and wellness goals
- **Seamless Experience**: From recommendations to real-time feedback
- **Customizable**: Brand-aligned app design
- **Quick Integration**: Easy setup for advanced fitness solutions
You can find plans in our [workout library](https://workout-view.kinestex.com/?tab=plans), or create your own plans in our [admin portal](https://admin.kinestex.com).
**Plan Integration**
Display a workout plan by name or ID:
_Swift (iOS)_
```swift
kinestex.createPlanView(
plan: selectedPlan, // name or ID of the plan (string)
user: nil, // OPTIONAL: provide user details
isLoading: $isLoading,
customParams: ["style": "dark"], // dark or light theme
onMessageReceived: { message in
switch message {
case .exit_kinestex(_):
showKinesteX = false // dismiss the view
default:
print("Received \(message)")
break
}
}
)
```
_Kotlin (Android)_
```kotlin
KinesteXSDK.createPlanView(
context = this,
plan = selectedPlan, // name or ID of the plan
user = userDetails, // optional user details
data = mapOf("style" to "dark"),
isLoading = viewModel.isLoading,
onMessageReceived = { message ->
when (message) {
is WebViewMessage.ExitKinestex -> finish()
else -> println("Received: $message")
}
},
permissionHandler = this
)
```
_React Native_
```jsx
// postData structure
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
style: {
style: 'dark',
},
};
{
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
}}
/>
```
_Flutter_
```dart
KinesteXAIFramework.createPlanView(
isShowKinestex: showKinesteX,
planName: selectedPlan, // name or ID of the plan
customParams: {"style": "dark"},
isLoading: ValueNotifier(false),
onMessageReceived: (message) {
if (message.type == 'exit_kinestex') {
setState(() => showKinesteX = false);
}
},
)
```
_HTML / JavaScript_
```html
// Specify plan ID in the URL
const srcURL = "https://ai.kinestex.com/plan/YOUR_PLAN_ID";
webView.src = srcURL;
```
_React (TypeScript)_
```tsx
import { IntegrationOption, KinesteXSDK, type IPostData } from 'kinestex-sdk-react-ts';
// postData structure
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
style: {
style: 'dark',
},
};
{
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
}}
/>
```
**Complete Example**
Full implementation example with plan selection:
_Swift (iOS)_
```swift
import SwiftUI
import KinesteXAIKit
struct PlanViewIntegration: View {
@State private var showKinesteX = false
@State private var isLoading = false
@State private var selectedPlan = "livggb4P6zoD94VsTBB6"
let kinestex = KinesteXAIKit(
apiKey: "YOUR API KEY",
companyName: "YOUR COMPANY NAME",
userId: "YOUR USER ID"
)
var body: some View {
VStack {
Text("Select a Plan")
.font(.title)
.padding()
Spacer()
Button(action: {
showKinesteX.toggle()
}) {
Text("Start \(selectedPlan)")
.font(.title3)
.foregroundColor(.white)
.bold()
.padding()
.frame(maxWidth: .infinity)
.background(Color.green.cornerRadius(10))
.padding(.horizontal)
}
Spacer()
}
.fullScreenCover(isPresented: $showKinesteX) {
kinestex.createPlanView(
plan: selectedPlan,
user: nil,
isLoading: $isLoading,
customParams: ["style": "light"],
onMessageReceived: { message in
switch message {
case .exit_kinestex(_):
showKinesteX = false
default:
print("Message: \(message)")
}
}
)
}
}
}
#Preview {
PlanViewIntegration()
}
```
_Kotlin (Android)_
```kotlin
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.kinestex.kinestexsdkkotlin.GenericWebView
import com.kinestex.kinestexsdkkotlin.KinesteXSDK
import com.kinestex.kinestexsdkkotlin.PermissionHandler
import com.kinestex.kinestexsdkkotlin.WebViewMessage
import kotlinx.coroutines.flow.MutableStateFlow
class PlanViewActivity : ComponentActivity(), PermissionHandler {
private val viewModel = PlanViewModel()
// OPTIONAL: UserDetails to customize workout intensity and calorie estimation
// Note: User details are only used on-device during the session
private val userDetails = UserDetails(
age = 30,
height = 180,
weight = 75,
gender = Gender.MALE,
lifestyle = Lifestyle.ACTIVE
)
// Custom data for the WebView
private val data = mutableMapOf()
// Store reference to the KinesteX WebView
private var kinesteXWebView: GenericWebView? = null
// Register permission launcher
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
// Pass permission result to KinesteX webview
kinesteXWebView?.handlePermissionResult(isGranted)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
data["style"] = "light"
setContent {
val webView = KinesteXSDK.createPlanView(
context = this,
plan = "livggb4P6zoD94VsTBB6", // name or ID of the plan
user = userDetails,
data = data,
isLoading = viewModel.isLoading,
onMessageReceived = ::handleWebViewMessage,
permissionHandler = this
) as GenericWebView
kinesteXWebView = webView
webView.Render()
}
}
private fun handleWebViewMessage(message: WebViewMessage) {
when (message) {
is WebViewMessage.ExitKinestex -> finish()
is WebViewMessage.KinestexLaunched -> viewModel.isLoading.value = false
else -> Toast.makeText(this, "Received: $message", Toast.LENGTH_SHORT).show()
}
}
// When request is sent, display system dialog for camera access
override fun requestCameraPermission() {
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
class PlanViewModel {
val isLoading = MutableStateFlow(true)
}
```
_React Native_
```jsx
import React, { useState } from 'react';
import { View, Button, StyleSheet } from 'react-native';
import KinestexSDK, { IntegrationOption, IPostData } from '@kinestex/sdk-react-native';
const PlanViewIntegration = () => {
const [showKinesteX, setShowKinesteX] = useState(false);
const selectedPlan = "livggb4P6zoD94VsTBB6"; // plan ID
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
style: {
style: 'dark',
},
};
return (
{showKinesteX ? (
{
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
}}
/>
) : (
setShowKinesteX(true)}
/>
)}
);
};
const styles = StyleSheet.create({
container: { flex: 1 }
});
export default PlanViewIntegration;
```
_Flutter_
```dart
import 'package:flutter/material.dart';
import 'package:kinestex_sdk_flutter/kinestex_sdk.dart';
import 'package:permission_handler/permission_handler.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await KinesteXAIFramework.initialize(
apiKey: "your_api_key",
companyName: "your_company_name",
userId: "your_user_id",
);
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State createState() => _MyAppState();
}
class _MyAppState extends State {
@override
void dispose() {
disposeKinesteXAIFramework();
super.dispose();
}
Future disposeKinesteXAIFramework() async {
await KinesteXAIFramework.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'KinesteX Plan',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
ValueNotifier showKinesteX = ValueNotifier(false);
@override
void initState() {
super.initState();
_checkCameraPermission();
}
void _checkCameraPermission() async {
if (await Permission.camera.request() != PermissionStatus.granted) {
_showCameraAccessDeniedAlert();
}
}
void _showCameraAccessDeniedAlert() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Camera Permission Denied"),
content: const Text("Camera access is required for this app to function properly."),
actions: [
TextButton(
child: const Text("OK"),
onPressed: () => Navigator.of(context).pop(),
),
],
);
},
);
}
void handleWebViewMessage(WebViewMessage message) {
if (message is ExitKinestex) {
setState(() {
showKinesteX.value = false;
});
}
}
Widget createPlanView() {
return Center(
child: KinesteXAIFramework.createPlanView(
isShowKinestex: showKinesteX,
planName: "Full Body Fitness",
customParams: {
"style": "dark",
},
isLoading: ValueNotifier(false),
onMessageReceived: handleWebViewMessage,
),
);
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: showKinesteX,
builder: (context, isShowKinesteX, child) {
return isShowKinesteX
? SafeArea(
child: createPlanView(),
)
: Scaffold(
body: Center(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 40,
vertical: 20,
),
backgroundColor: Colors.green,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: () {
showKinesteX.value = true;
},
child: const Text(
'Start Plan',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
);
},
);
}
}
```
_HTML / JavaScript_
```html
KinesteX: Plan
Start KinesteX
```
_React (TypeScript)_
```tsx
import React, { useState } from 'react';
import {
IntegrationOption,
KinesteXSDK,
type IPostData,
} from 'kinestex-sdk-react-ts';
const PlanViewIntegration: React.FC = () => {
const [showKinesteX, setShowKinesteX] = useState(false);
const selectedPlan = "Full Body Fitness";
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
style: {
style: 'dark',
},
};
return (
{showKinesteX ? (
{
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
}}
/>
) : (
setShowKinesteX(true)}>
Start {selectedPlan}
)}
);
};
export default PlanViewIntegration;
```
#### Challenge View
Exciting Challenges: Drive Engagement and Motivation.
- **Fun and Competitive**: Quick challenges with leaderboards for friendly competition
- **Boost Activity**: Keep fitness exciting and rewarding for users
- **Easy Integration**: Add dynamic challenges effortlessly to your app
You can find exercises in our [exercise library](https://workout-view.kinestex.com/?tab=exercises), or create your own exercises in our [admin portal](https://admin.kinestex.com).
**Challenge Integration**
Display a challenge by exercise name or ID:
_Swift (iOS)_
```swift
kinestex.createChallengeView(
exercise: challengeExercise, // exercise name or ID
duration: 100, // duration of challenge in seconds
user: nil, // Optionally pass user details
showLeaderboard: true, // showLeaderboard prompts a user to enter a challenge at the end (true by default)
isLoading: $isLoading,
customParams: ["style": "dark"], // dark or light theme
onMessageReceived: { message in
switch message {
case .exit_kinestex(let data):
showKinesteX = false // dismiss the view
default:
print("Received \(message)")
break
}
}
)
```
_Kotlin (Android)_
```kotlin
KinesteXSDK.createChallengeView(
context = this,
exercise = challengeExercise, // exercise name or ID
countdown = 100, // duration of challenge in seconds
user = userDetails, // optional user details
data = mapOf("style" to "dark"),
showLeaderboard = true, // show leaderboard at end (default true)
isLoading = viewModel.isLoading,
onMessageReceived = { message ->
when (message) {
is WebViewMessage.ExitKinestex -> finish()
else -> println("Received: $message")
}
},
permissionHandler = this
)
```
_React Native_
```jsx
// postData structure with challenge fields
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
exercise: 'Squats', // exercise name or ID
countdown: 100, // duration in seconds
showLeaderboard: true, // show leaderboard at end
style: {
style: 'dark',
},
};
{
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
}}
/>
```
_Flutter_
```dart
KinesteXAIFramework.createChallengeView(
isShowKinestex: showKinesteX,
exercise: challengeExercise, // exercise name or ID
countdown: 100, // seconds
showLeaderboard: true,
customParams: {"style": "dark"},
isLoading: ValueNotifier(false),
onMessageReceived: (message) {
if (message.type == 'exit_kinestex') {
setState(() => showKinesteX = false);
}
},
)
```
_HTML / JavaScript_
```html
// Add challenge params to postData
const postData = {
// ... all initial fields
exercise: challengeExercise, // exercise name or ID
showLeaderboard: true, // show leaderboard at end
countdown: 100, // duration in seconds
};
const srcURL = "https://ai.kinestex.com/challenge";
webView.src = srcURL;
webView.onload = () => {
sendMessage();
};
```
_React (TypeScript)_
```tsx
import { IntegrationOption, KinesteXSDK, type IPostData } from 'kinestex-sdk-react-ts';
// postData structure with challenge fields
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
exercise: 'Squats', // exercise name or ID
countdown: 100, // duration in seconds
showLeaderboard: true, // show leaderboard at end
style: {
style: 'dark',
},
};
{
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
}}
/>
```
**Complete Example**
Full implementation example with challenge setup:
_Swift (iOS)_
```swift
import SwiftUI
import KinesteXAIKit
struct ChallengeIntegrationView: View {
@State private var showKinesteX = false
@State private var isLoading = false
// Initialize KinesteXAIKit
// Replace with your KinesteX credentials
let kinesteXKit = KinesteXAIKit(
apiKey: "YOUR API KEY",
companyName: "YOUR COMPANY NAME",
userId: "YOUR USER ID"
)
// Challenge parameters
let challengeExercise = "Squats"
let challengeDuration = 100 // Duration in seconds
let showLeaderboardAfterChallenge = true
var body: some View {
VStack {
Text("KinesteX Challenge Integration")
.font(.title)
.padding()
Spacer()
Button(action: {
showKinesteX.toggle()
}) {
Text("Start \(challengeExercise) Challenge (\(challengeDuration)s)")
.font(.title3)
.foregroundColor(.white)
.bold()
.padding()
.frame(maxWidth: .infinity)
.background(Color.red.cornerRadius(10))
.padding(.horizontal)
}
.padding()
Spacer()
}
.fullScreenCover(isPresented: $showKinesteX) {
kinesteXKit.createChallengeView(
exercise: challengeExercise,
duration: challengeDuration,
showLeaderboard: showLeaderboardAfterChallenge,
user: nil,
isLoading: $isLoading,
customParams: ["style": "dark"],
onMessageReceived: { message in
switch message {
case .exit_kinestex(_):
showKinesteX = false
default:
print("Message received: \(message)")
}
}
)
}
}
}
#Preview {
ChallengeIntegrationView()
}
```
_Kotlin (Android)_
```kotlin
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.kinestex.kinestexsdkkotlin.GenericWebView
import com.kinestex.kinestexsdkkotlin.KinesteXSDK
import com.kinestex.kinestexsdkkotlin.PermissionHandler
import com.kinestex.kinestexsdkkotlin.WebViewMessage
import kotlinx.coroutines.flow.MutableStateFlow
class ChallengeViewActivity : ComponentActivity(), PermissionHandler {
private val viewModel = ChallengeViewModel()
// OPTIONAL: UserDetails to customize workout intensity and calorie estimation
// Note: User details are only used on-device during the session
private val userDetails = UserDetails(
age = 30,
height = 180,
weight = 75,
gender = Gender.MALE,
lifestyle = Lifestyle.ACTIVE
)
// Custom data for the WebView
private val data = mutableMapOf()
// Store reference to the KinesteX WebView
private var kinesteXWebView: GenericWebView? = null
// Register permission launcher
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
// Pass permission result to KinesteX webview
kinesteXWebView?.handlePermissionResult(isGranted)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
data["style"] = "dark"
setContent {
val webView = KinesteXSDK.createChallengeView(
context = this,
exercise = "Squats",
countdown = 60,
user = userDetails,
data = data,
showLeaderboard = true,
isLoading = viewModel.isLoading,
onMessageReceived = ::handleWebViewMessage,
permissionHandler = this
) as GenericWebView
kinesteXWebView = webView
webView.Render()
}
}
private fun handleWebViewMessage(message: WebViewMessage) {
when (message) {
is WebViewMessage.ExitKinestex -> finish()
is WebViewMessage.KinestexLaunched -> viewModel.isLoading.value = false
else -> Toast.makeText(this, "Received: $message", Toast.LENGTH_SHORT).show()
}
}
// When request is sent, display system dialog for camera access
override fun requestCameraPermission() {
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
class ChallengeViewModel {
val isLoading = MutableStateFlow(true)
}
```
_React Native_
```jsx
import React, { useState } from 'react';
import { View, Button, StyleSheet } from 'react-native';
import KinestexSDK, { IntegrationOption, IPostData } from '@kinestex/sdk-react-native';
const ChallengeIntegration = () => {
const [showKinesteX, setShowKinesteX] = useState(false);
const challengeExercise = "Squats";
const challengeDuration = 100;
// Include challenge fields in postData
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
exercise: challengeExercise, // exercise name or ID
countdown: challengeDuration, // duration in seconds
showLeaderboard: true, // show leaderboard at end
style: {
style: 'dark',
},
};
return (
{showKinesteX ? (
{
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
}}
/>
) : (
setShowKinesteX(true)}
/>
)}
);
};
const styles = StyleSheet.create({
container: { flex: 1 }
});
export default ChallengeIntegration;
```
_Flutter_
```dart
import 'package:flutter/material.dart';
import 'package:kinestex_sdk_flutter/kinestex_sdk.dart';
import 'package:permission_handler/permission_handler.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await KinesteXAIFramework.initialize(
apiKey: "your_api_key",
companyName: "your_company_name",
userId: "your_user_id",
);
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State createState() => _MyAppState();
}
class _MyAppState extends State {
@override
void dispose() {
disposeKinesteXAIFramework();
super.dispose();
}
Future disposeKinesteXAIFramework() async {
await KinesteXAIFramework.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'KinesteX Challenge',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
ValueNotifier showKinesteX = ValueNotifier(false);
String challengeExercise = "Squats";
int challengeDuration = 100;
@override
void initState() {
super.initState();
_checkCameraPermission();
}
void _checkCameraPermission() async {
if (await Permission.camera.request() != PermissionStatus.granted) {
_showCameraAccessDeniedAlert();
}
}
void _showCameraAccessDeniedAlert() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Camera Permission Denied"),
content: const Text("Camera access is required for this app to function properly."),
actions: [
TextButton(
child: const Text("OK"),
onPressed: () => Navigator.of(context).pop(),
),
],
);
},
);
}
void handleWebViewMessage(WebViewMessage message) {
if (message is ExitKinestex) {
setState(() {
showKinesteX.value = false;
});
}
}
Widget createChallengeView() {
return Center(
child: KinesteXAIFramework.createChallengeView(
isShowKinestex: showKinesteX,
exercise: challengeExercise,
countdown: challengeDuration,
showLeaderboard: true,
customParams: {
"style": "dark",
},
isLoading: ValueNotifier(false),
onMessageReceived: handleWebViewMessage,
),
);
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: showKinesteX,
builder: (context, isShowKinesteX, child) {
return isShowKinesteX
? SafeArea(
child: createChallengeView(),
)
: Scaffold(
body: Center(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 40,
vertical: 20,
),
backgroundColor: Colors.green,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: () {
showKinesteX.value = true;
},
child: Text(
'Start $challengeExercise Challenge (${challengeDuration}s)',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
);
},
);
}
}
```
_HTML / JavaScript_
```html
KinesteX: Challenge
Start KinesteX
```
_React (TypeScript)_
```tsx
import React, { useState } from 'react';
import {
IntegrationOption,
KinesteXSDK,
type IPostData,
} from 'kinestex-sdk-react-ts';
const ChallengeIntegration: React.FC = () => {
const [showKinesteX, setShowKinesteX] = useState(false);
const challengeExercise = "Squats";
const challengeDuration = 100;
// Include challenge fields in postData
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
exercise: challengeExercise, // exercise name or ID
countdown: challengeDuration, // duration in seconds
showLeaderboard: true, // show leaderboard at end
style: {
style: 'dark',
},
};
return (
{showKinesteX ? (
{
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
}}
/>
) : (
setShowKinesteX(true)}>
Start {challengeExercise} Challenge ({challengeDuration}s)
)}
);
};
export default ChallengeIntegration;
```
#### Leaderboard View (Challenge)
Ready-made Leaderboard: Boost User Engagement and Motivation.
- **Adaptive Design**: The leaderboard automatically adapts to your KinesteX UI and can be fully customized in the admin dashboard
- **Real-time Updates**: Whenever a new ranking is available, the leaderboard automatically refreshes to show the latest standings
**Leaderboard Integration**
Display the leaderboard for a specific exercise:
_Swift (iOS)_
```swift
kinestex.createLeaderboardView(
exercise: "Squats", // Specify the exercise id or title
username: "", // if you know the username: highlight the user by specifying their username
isLoading: $isLoading,
customParams: [
"style": "dark", // light or dark theme (default is dark)
"isHideHeaderMain": true // OPTIONAL: hide the exit button from the leaderboard
],
onMessageReceived: { message in
switch message {
case .exit_kinestex(_):
showKinesteX = false
default:
break
}
}
)
```
_Kotlin (Android)_
```kotlin
KinesteXSDK.createLeaderboardView(
context = this,
exercise = "Squats", // exercise name or ID
username = "John", // highlight username in leaderboard if known
data = mapOf(
"style" to "dark",
"isHideHeaderMain" to true // hide exit button
),
isLoading = viewModel.isLoading,
onMessageReceived = { message ->
when (message) {
is WebViewMessage.ExitKinestex -> finish()
else -> println("Received: $message")
}
},
permissionHandler = this
)
```
_React Native_
```jsx
// postData structure
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
exercise: 'Squats', // exercise name or ID
style: {
style: 'dark',
},
};
{
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
}}
/>
```
_Flutter_
```dart
KinesteXAIFramework.createLeaderboardView(
isShowKinestex: showKinesteX,
exercise: "Squats",
username: "", // highlight user if known
customParams: {
"style": "dark",
"isHideHeaderMain": true, // hide exit button
},
isLoading: ValueNotifier(false),
onMessageReceived: (message) {
if (message.type == 'exit_kinestex') {
setState(() => showKinesteX = false);
}
},
)
```
_HTML / JavaScript_
```html
// Add to postData
const postData = {
// ... all initial fields
exercise: "Squats", // exercise name or ID
};
const userId = "unique-user-id"; // OPTIONAL: userId to highlight in leaderboard
const srcURL = `https://ai.kinestex.com/leaderboard/?userId=${userId}`;
webView.src = srcURL;
webView.onload = () => {
sendMessage();
};
```
_React (TypeScript)_
```tsx
import { IntegrationOption, KinesteXSDK, type IPostData } from 'kinestex-sdk-react-ts';
// postData structure
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
exercise: 'Squats', // exercise ID or name
style: {
style: 'dark',
},
};
{
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
}}
/>
```
#### AI Experiences
AI-powered movement-based games and clinical assessments with real-time motion tracking.
**AI Games:**
| Game | Exercise ID |
|------|-------------|
| Balloon Pop | balloonpop |
| Color Memory | colorchase |
| Alien Squat Shooter | aliensquatshooter |
**Clinical Assessments:**
| Assessment | Exercise ID |
|------------|-------------|
| Timed Up and Go (TUG) | tug |
| Gait Speed Test | gaitspeedtest |
| Sit-to-Stand | sittostand |
| Functional Reach Test | functionalreachtest |
| Single Leg Stance Test | singlelegstancetest |
| Five Times Sit-to-Stand | fivetimessts |
| Side-by-Side Stand | sidebysidestand |
| Semi-Tandem Stand | semitandemstand |
| Full Tandem Stand | fulltandem |
| Shoulder Range of Motion | romshoulder |
**Experience View**
_Swift (iOS)_
```swift
// Launch an AI game or balance assessment
// Pass the exercise ID from the tables above
kinestex.createExperienceView(
experience: "assessment", // experience type
exercise: "balloonpop", // exercise ID from table
user: nil,
isLoading: $isLoading,
customParams: ["style": "dark"],
onMessageReceived: { message in
switch message {
case .exit_kinestex(_):
showKinesteX = false
default:
break
}
}
)
```
_Kotlin (Android)_
```kotlin
// Launch an AI game or balance assessment
val data = mutableMapOf()
data["style"] = "dark"
data["exercise"] = "balloonpop" // exercise ID from table
KinesteXSDK.createExperiencesView(
context = this,
experience = "assessment",
user = userDetails, // optional user details
data = data,
isLoading = viewModel.isLoading,
onMessageReceived = { message ->
if (message is WebViewMessage.ExitKinestex) finish()
},
permissionHandler = this
)
```
_React Native_
```jsx
// postData structure
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
exercise: 'balloonpop', // exercise ID from table
style: {
style: 'dark',
},
};
```
_Flutter_
```dart
KinesteXAIFramework.createExperienceView(
isShowKinestex: showKinesteX,
experience: "assessment",
exercise: "balloonpop", // exercise ID from table
customParams: {"style": "dark"},
isLoading: ValueNotifier(false),
onMessageReceived: (message) {
handleWebViewMessage(message);
},
)
```
_HTML / JavaScript_
```html
const postData = {
// ... all initial fields
exercise: "balloonpop", // exercise ID from table
};
const srcURL = "https://ai.kinestex.com/experiences/assessment";
webView.src = srcURL;
webView.onload = () => {
sendMessage();
};
```
_React (TypeScript)_
```tsx
import { IntegrationOption, KinesteXSDK, type IPostData } from 'kinestex-sdk-react-ts';
// postData structure
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
exercise: 'balloonpop', // exercise ID from table
style: {
style: 'dark',
},
};
```
**Complete Example**
_Swift (iOS)_
```swift
import SwiftUI
import KinesteXAIKit
struct ExperienceIntegrationView: View {
@State private var showKinesteX = false
@State private var isLoading = false
// Initialize KinesteXAIKit
// Replace with your KinesteX credentials
let kinesteXKit = KinesteXAIKit(
apiKey: "YOUR API KEY",
companyName: "YOUR COMPANY NAME",
userId: "YOUR USER ID"
)
// Parameters for the experience
let experienceName = "assessment" // Name of the AI experience
let experienceExercise = "balloonpop" // Exercise ID from table
var body: some View {
VStack {
Text("KinesteX Experience Integration")
.font(.title)
.padding()
Spacer()
Button(action: {
showKinesteX.toggle()
}) {
Text("Start '\(experienceName.capitalized)' Experience")
.font(.title3)
.foregroundColor(.white)
.bold()
.padding()
.frame(maxWidth: .infinity)
.background(Color.teal.cornerRadius(10))
.padding(.horizontal)
}
.padding()
Spacer()
}
.fullScreenCover(isPresented: $showKinesteX) {
kinesteXKit.createExperienceView(
experience: experienceName,
exercise: experienceExercise,
user: nil,
isLoading: $isLoading,
customParams: ["style": "dark"],
onMessageReceived: { message in
switch message {
case .exit_kinestex(_):
showKinesteX = false
default:
print("Message received: \(message)")
}
}
)
}
}
}
#Preview {
ExperienceIntegrationView()
}
```
_Kotlin (Android)_
```kotlin
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.kinestex.kinestexsdkkotlin.GenericWebView
import com.kinestex.kinestexsdkkotlin.KinesteXSDK
import com.kinestex.kinestexsdkkotlin.PermissionHandler
import com.kinestex.kinestexsdkkotlin.WebViewMessage
import kotlinx.coroutines.flow.MutableStateFlow
class ExperienceActivity : ComponentActivity(), PermissionHandler {
private val viewModel = ExperienceViewModel()
// OPTIONAL: UserDetails to customize workout intensity and calorie estimation
// Note: User details are only used on-device during the session
private val userDetails = UserDetails(
age = 30,
height = 180,
weight = 75,
gender = Gender.MALE,
lifestyle = Lifestyle.ACTIVE
)
// Custom data for the WebView
private val data = mutableMapOf()
// Store reference to the KinesteX WebView
private var kinesteXWebView: GenericWebView? = null
// Register permission launcher
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
// Pass permission result to KinesteX webview
kinesteXWebView?.handlePermissionResult(isGranted)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
data["style"] = "dark"
data["exercise"] = "balloonpop" // AI game or balance assessment ID
setContent {
val webView = KinesteXSDK.createExperiencesView(
context = this,
experience = "assessment",
user = userDetails,
data = data,
isLoading = viewModel.isLoading,
onMessageReceived = ::handleWebViewMessage,
permissionHandler = this
) as GenericWebView
kinesteXWebView = webView
webView.Render()
}
}
private fun handleWebViewMessage(message: WebViewMessage) {
when (message) {
is WebViewMessage.ExitKinestex -> finish()
is WebViewMessage.KinestexLaunched -> viewModel.isLoading.value = false
else -> Toast.makeText(this, "Received: $message", Toast.LENGTH_SHORT).show()
}
}
// When request is sent, display system dialog for camera access
override fun requestCameraPermission() {
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
class ExperienceViewModel {
val isLoading = MutableStateFlow(true)
}
```
_React Native_
```jsx
import React, { useState } from 'react';
import { View, Button } from 'react-native';
import KinestexSDK, { IntegrationOption, IPostData } from '@kinestex/sdk-react-native';
export default function ExperienceScreen() {
const [showKinesteX, setShowKinesteX] = useState(false);
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
exercise: 'balloonpop', // AI game or balance assessment ID
style: {
style: 'dark',
},
};
const handleMessage = (type: string, data: any) => {
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
};
if (showKinesteX) {
return (
);
}
return (
setShowKinesteX(true)} />
);
}
```
_Flutter_
```dart
import 'package:flutter/material.dart';
import 'package:kinestex_sdk_flutter/kinestex_sdk.dart';
import 'package:permission_handler/permission_handler.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await KinesteXAIFramework.initialize(
apiKey: "your_api_key",
companyName: "your_company_name",
userId: "your_user_id",
);
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State createState() => _MyAppState();
}
class _MyAppState extends State {
@override
void dispose() {
disposeKinesteXAIFramework();
super.dispose();
}
Future disposeKinesteXAIFramework() async {
await KinesteXAIFramework.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'KinesteX Experience',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
ValueNotifier showKinesteX = ValueNotifier(false);
@override
void initState() {
super.initState();
_checkCameraPermission();
}
void _checkCameraPermission() async {
if (await Permission.camera.request() != PermissionStatus.granted) {
_showCameraAccessDeniedAlert();
}
}
void _showCameraAccessDeniedAlert() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Camera Permission Denied"),
content: const Text("Camera access is required for this app to function properly."),
actions: [
TextButton(
child: const Text("OK"),
onPressed: () => Navigator.of(context).pop(),
),
],
);
},
);
}
void handleWebViewMessage(WebViewMessage message) {
if (message is ExitKinestex) {
setState(() {
showKinesteX.value = false;
});
}
}
Widget createExperienceView() {
return Center(
child: KinesteXAIFramework.createExperienceView(
isShowKinestex: showKinesteX,
experience: "assessment",
exercise: "balloonpop",
customParams: {
"style": "dark",
},
isLoading: ValueNotifier(false),
onMessageReceived: handleWebViewMessage,
),
);
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: showKinesteX,
builder: (context, isShowKinesteX, child) {
return isShowKinesteX
? SafeArea(
child: createExperienceView(),
)
: Scaffold(
body: Center(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 40,
vertical: 20,
),
backgroundColor: Colors.green,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: () {
showKinesteX.value = true;
},
child: const Text(
'Start AI Experience',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
);
},
);
}
}
```
_HTML / JavaScript_
```html
KinesteX: AI Experience
Start KinesteX
```
_React (TypeScript)_
```tsx
import React, { useState } from 'react';
import {
IntegrationOption,
KinesteXSDK,
type IPostData,
} from 'kinestex-sdk-react-ts';
export default function ExperienceScreen() {
const [showKinesteX, setShowKinesteX] = useState(false);
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
exercise: 'balloonpop', // AI game or balance assessment ID
style: {
style: 'dark',
},
};
const handleMessage = (type: string, data: Record) => {
if (type === 'exit_kinestex') {
setShowKinesteX(false);
}
};
if (showKinesteX) {
return (
);
}
return setShowKinesteX(true)}>Start AI Experience ;
}
```
#### Personalized Plan View
AI-Generated Personalized Workout Plans.
- **Personalized**: Tailored to height, weight, age, gender, activity level, and fitness assessment results
- **Goal-Oriented**: Supports strength, flexibility, and wellness goals
- **Seamless Experience**: From recommendations to real-time feedback
- **Customizable**: Brand-aligned app design
- **Quick Integration**: Easy setup for advanced fitness solutions
**Personalized Plan Integration**
_Swift (iOS)_
```swift
kinestex.createPersonalizedPlanView(
user: nil, // OPTIONAL: provide user details
isLoading: $isLoading,
customParams: ["style": "dark"], // dark or light theme (customizable in admin portal)
onMessageReceived: { message in
switch message {
case .exit_kinestex(_):
showKinesteX = false // dismiss the view
default:
print("Received \(message)")
break
}
}
)
```
_Kotlin (Android)_
```kotlin
KinesteXSDK.createPersonalizedPlanView(
context = this,
user = userDetails, // optional user details
data = mapOf("style" to "dark"), // customizable in admin portal
isLoading = viewModel.isLoading,
onMessageReceived = { message ->
when (message) {
is WebViewMessage.ExitKinestex -> finish()
else -> println("Received: $message")
}
},
permissionHandler = this
)
```
_React Native_
```jsx
// postData structure
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
style: {
style: 'dark',
},
};
```
_Flutter_
```dart
KinesteXAIFramework.createPersonalizedPlanView(
isShowKinestex: showKinesteX,
customParams: {"style": "dark"},
isLoading: ValueNotifier(false),
onMessageReceived: (message) {
handleWebViewMessage(message);
},
)
```
_HTML / JavaScript_
```html
KinesteX: Personalized Plan
Start Personalized Plan
```
_React (TypeScript)_
```tsx
import { IntegrationOption, KinesteXSDK, type IPostData } from 'kinestex-sdk-react-ts';
// postData structure
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
style: {
style: 'dark',
},
};
```
#### Admin Workout Editor
Embedded view for creating and managing workouts and exercises. As users interact with the editor, your application will receive events that you can handle to trigger custom logic.
**Available only for Flutter.**
**Custom Query Options:**
| Option | Description |
|--------|-------------|
| hidePlansTab | Hide the plans tab in the dashboard |
| tab | Default tab: "workouts", "exercises", or "plans" |
| isSelectableMenu | Show select button on cards, triggers `_selected` events |
**Available Events:**
*General Events:*
- `kinestex_loaded` - Application fully loaded
- `kinestex_launched` - Successful authentication
- `error_occurred` - Authentication error
*Exercise Events:*
- `exercise_opened` - Exercise detail page opened
- `exercise_selection_opened` - Exercise list page opened
- `exercise_selected` - Exercise selected from menu
- `exercise_saved` - Exercise created/updated
- `exercise_removed` - Exercise removed from workout
*Workout Events:*
- `workout_opened` - Workout detail page opened
- `workout_selection_opened` - Workout list page opened
- `workout_selected` - Workout selected from menu
- `workout_saved` - Workout created/updated
*Plan Events:*
- `plan_opened` - Plan detail page opened
- `plan_selection_opened` - Plan list page opened
- `plan_selected` - Plan selected from menu
- `plan_saved` - Plan created/updated
**Admin Workout Editor Integration**
Create the admin workout editor view for managing workouts and exercises:
_Flutter_
```dart
KinesteXAIFramework.createAdminWorkoutEditor(
// Use an organization name to differentiate between different orgs.
// If you don't plan to use multiple orgs, you can use your company name.
organization: "your_organization_name",
isShowKinestex: showKinesteX,
// OPTIONAL: show/hide content on the admin dashboard
customQueries: {
"hidePlansTab": true, // will hide the plans tabs in the dashboard
"tab": "workouts", // will default to workouts tab. Options: "exercises", "plans"
"isSelectableMenu": true // show select Button, triggers _selected events
},
isLoading: ValueNotifier(false),
onMessageReceived: (message) {
handleWebViewMessage(message);
},
)
```
**Complete Example**
Full implementation with event handling:
_Flutter_
```dart
import 'package:flutter/material.dart';
import 'package:kinestex_sdk_flutter/kinestex_sdk.dart';
import 'package:permission_handler/permission_handler.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await KinesteXAIFramework.initialize(
apiKey: "your_api_key",
companyName: "your_company_name",
userId: "your_user_id",
);
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State createState() => _MyAppState();
}
class _MyAppState extends State {
@override
void dispose() {
disposeKinesteXAIFramework();
super.dispose();
}
Future disposeKinesteXAIFramework() async {
await KinesteXAIFramework.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'KinesteX Admin Editor',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
ValueNotifier showKinesteX = ValueNotifier(false);
@override
void initState() {
super.initState();
_checkCameraPermission();
}
void _checkCameraPermission() async {
if (await Permission.camera.request() != PermissionStatus.granted) {
_showCameraAccessDeniedAlert();
}
}
void _showCameraAccessDeniedAlert() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Camera Permission Denied"),
content: const Text("Camera access is required for this app to function properly."),
actions: [
TextButton(
child: const Text("OK"),
onPressed: () => Navigator.of(context).pop(),
),
],
);
},
);
}
void handleWebViewMessage(WebViewMessage message) {
if (message is ExitKinestex) {
setState(() {
showKinesteX.value = false;
});
} else if (message is WorkoutSaved) {
print('Workout saved: ${message.data["workout_id"]}');
} else if (message is ExerciseSaved) {
print('Exercise saved: ${message.data["exercise_id"]}');
} else if (message is WorkoutSelected) {
print('Workout selected: ${message.data["workout_title"]}');
} else if (message is ExerciseSelected) {
print('Exercise selected: ${message.data["exercise_title"]}');
} else if (message is PlanSaved) {
print('Plan saved: ${message.data["plan_id"]}');
} else if (message is ErrorOccurred) {
print('Error: ${message.data["error_message"]}');
}
}
Widget createAdminWorkoutEditorView() {
return Center(
child: KinesteXAIFramework.createAdminWorkoutEditor(
organization: "your_organization_name",
isShowKinestex: showKinesteX,
customQueries: {
"hidePlansTab": false,
"tab": "workouts",
"isSelectableMenu": true,
},
isLoading: ValueNotifier(false),
onMessageReceived: handleWebViewMessage,
),
);
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: showKinesteX,
builder: (context, isShowKinesteX, child) {
return isShowKinesteX
? SafeArea(
child: createAdminWorkoutEditorView(),
)
: Scaffold(
body: Center(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 40,
vertical: 20,
),
backgroundColor: Colors.green,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: () {
showKinesteX.value = true;
},
child: const Text(
'Open Admin Workout Editor',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
);
},
);
}
}
```
### Custom Integration
Build everything yourself with full control over UI/UX. Use our Camera Component for real-time motion analysis and create custom workout sequences.
#### Custom Workout
Create and execute personalized workout sequences with custom exercises, repetitions, durations, and rest periods. Define your own workout flow with full control over exercise order and timing.
**How it works:**
1. Initialize the SDK with custom workout integration option
2. Pass your custom workout exercises array
3. Wait for `all_resources_loaded` message
4. Send `workout_activity_action: start` to begin
**WorkoutSequenceExercise Parameters:**
- `exerciseId` - Exercise ID from KinesteX API or admin panel
- `reps` - Number of repetitions
- `duration` - Duration in seconds (null = unlimited time for reps)
- `includeRestPeriod` - Include rest period before exercise
- `restDuration` - Rest duration in seconds
**Tip:** To create sets, duplicate the same exercise in the array multiple times.
**Custom Workout Setup**
_Swift (iOS)_
```swift
@State var workoutAction: [String: Any]? = nil
let exercises = [
WorkoutSequenceExercise(
exerciseId: "jz73VFlUyZ9nyd64OjRb",
reps: 15,
duration: nil,
includeRestPeriod: true,
restDuration: 20
),
WorkoutSequenceExercise(
exerciseId: "ZVMeLsaXQ9Tzr5JYXg29",
reps: 10,
duration: 30,
includeRestPeriod: true,
restDuration: 15
),
WorkoutSequenceExercise(
exerciseId: "gJGOiZhCvJrhEP7sTy78",
reps: 20,
duration: nil,
includeRestPeriod: false,
restDuration: 0
)
]
kinestex.createCustomWorkoutView(
exercises: exercises,
user: userDetails,
style: nil,
isLoading: $isLoading,
workoutAction: $workoutAction,
onMessageReceived: { message in
if case .custom_type(let value) = message,
let type = value["type"] as? String,
type == "all_resources_loaded" {
// Start workout when resources are ready
workoutAction = ["workout_activity_action": "start"]
}
}
)
```
_Kotlin (Android)_
```kotlin
// Define custom workout exercises
val customExercises = listOf(
WorkoutSequenceExercise(
exerciseId = "jz73VFlUyZ9nyd64OjRb",
reps = 15,
duration = null,
includeRestPeriod = true,
restDuration = 20
),
WorkoutSequenceExercise(
exerciseId = "ZVMeLsaXQ9Tzr5JYXg29",
reps = 10,
duration = 30,
includeRestPeriod = true,
restDuration = 15
)
)
KinesteXSDK.createCustomWorkoutView(
context = this,
customWorkouts = customExercises,
user = userDetails, // optional user details
isLoading = viewModel.isLoading,
customParams = mapOf("style" to "dark"),
onMessageReceived = { message ->
if (message is WebViewMessage.AllResourcesLoaded) {
// Start workout when ready
}
},
permissionHandler = this
)
```
_React Native_
```jsx
// Step 1: Define custom workout exercises
const customWorkoutExercises: WorkoutSequenceExercise[] = [
{
exerciseId: "jz73VFlUyZ9nyd64OjRb", // exercise id from kinestex api
reps: 15, // number of reps
duration: null, // null = unlimited time for reps
includeRestPeriod: true, // include rest before exercise
restDuration: 20, // rest duration in seconds
},
{
exerciseId: "ZVMeLsaXQ9Tzr5JYXg29",
reps: 10,
duration: 30,
includeRestPeriod: true,
restDuration: 15,
},
// Duplicate exercise to create a set
{
exerciseId: "ZVMeLsaXQ9Tzr5JYXg29",
reps: 10,
duration: 30,
includeRestPeriod: true,
restDuration: 15,
},
];
// Step 2: Configure postData with customWorkoutExercises
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
customWorkoutExercises: customWorkoutExercises, // pass exercises in postData
style: {
style: 'dark',
},
};
// Step 3: Handle SDK messages
const handleMessage = (type: string, data: any) => {
switch (type) {
case 'all_resources_loaded':
// SDK is ready - show the view and start workout
setAllResourcesLoaded(true);
kinestexSDKRef.current?.sendAction("workout_activity_action", "start");
break;
case 'workout_exit_request':
setAllResourcesLoaded(false);
setShowKinestex(false);
break;
case 'exit_kinestex':
setAllResourcesLoaded(false);
break;
}
};
// Step 4: Render with conditional visibility
{showKinestex && (
)}
```
_Flutter_
```dart
// Define custom workout exercises
final customWorkoutExercises = [
WorkoutSequenceExercise(
exerciseId: "jz73VFlUyZ9nyd64OjRb",
reps: 15,
duration: null,
includeRestPeriod: true,
restDuration: 20,
),
WorkoutSequenceExercise(
exerciseId: "ZVMeLsaXQ9Tzr5JYXg29",
reps: 10,
duration: 30,
includeRestPeriod: true,
restDuration: 15,
),
];
KinesteXAIFramework.createCustomWorkoutView(
isShowKinestex: showKinesteX,
customWorkouts: customWorkoutExercises,
customParams: {"style": "dark"},
isLoading: ValueNotifier(false),
onMessageReceived: (message) {
if (message.type == "all_resources_loaded") {
// Start workout when ready
}
},
)
```
_HTML / JavaScript_
```html
// Define custom workout exercises
const customWorkoutExercises = [
{
exerciseId: "jz73VFlUyZ9nyd64OjRb",
reps: 15,
duration: null,
includeRestPeriod: true,
restDuration: 20,
},
{
exerciseId: "ZVMeLsaXQ9Tzr5JYXg29",
reps: 10,
duration: 30,
includeRestPeriod: true,
restDuration: 15,
},
];
const config = {
...postData,
customWorkoutExercises: customWorkoutExercises,
};
const srcURL = "https://ai.kinestex.com/custom-workout";
webView.src = srcURL;
webView.onload = () => {
sendMessage();
};
// Listen for ready signal
window.addEventListener("message", (e) => {
const msg = JSON.parse(e.data);
if (msg.type === 'all_resources_loaded') {
// Start workout
webView.contentWindow.postMessage(
{ action: "workout_activity_action", value: "start" },
srcURL
);
}
});
```
_React (TypeScript)_
```tsx
import { useRef, useState } from 'react';
import {
IntegrationOption,
KinesteXSDK,
type IPostData,
type KinesteXSDKCamera,
} from 'kinestex-sdk-react-ts';
// Define custom workout exercises
interface WorkoutSequenceExercise {
exerciseId: string;
reps: number | null;
duration: number | null;
includeRestPeriod: boolean;
restDuration: number;
}
const customWorkoutExercises: WorkoutSequenceExercise[] = [
{
exerciseId: "jz73VFlUyZ9nyd64OjRb",
reps: 15,
duration: null,
includeRestPeriod: true,
restDuration: 20,
},
];
// postData structure with customWorkoutExercises
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
customWorkoutExercises: customWorkoutExercises, // pass exercises in postData
style: {
style: 'dark',
},
};
const ref = useRef(null);
{
if (type === 'all_resources_loaded') {
ref.current?.sendAction("workout_activity_action", "start");
}
}}
/>
```
##### Complete Example
Full implementation with state management, loading indicators, and proper message handling.
**Complete Implementation**
_Swift (iOS)_
```swift
import SwiftUI
import KinesteXAIKit
struct CustomWorkoutView: View {
@State private var showKinesteX = false
@State private var isLoading = false
@State private var allResourcesLoaded = false
@State var workoutAction: [String: Any]? = nil
// Initialize KinesteXAIKit with your credentials
let kinestex = KinesteXAIKit(
apiKey: "YOUR_API_KEY",
companyName: "YOUR_COMPANY_NAME",
userId: "YOUR_USER_ID"
)
// Define custom workout exercises
let exercises = [
WorkoutSequenceExercise(
exerciseId: "jz73VFlUyZ9nyd64OjRb",
reps: 15,
duration: nil,
includeRestPeriod: true,
restDuration: 20
),
WorkoutSequenceExercise(
exerciseId: "ZVMeLsaXQ9Tzr5JYXg29",
reps: 10,
duration: 30,
includeRestPeriod: true,
restDuration: 15
),
WorkoutSequenceExercise(
exerciseId: "gJGOiZhCvJrhEP7sTy78",
reps: 20,
duration: nil,
includeRestPeriod: false,
restDuration: 0
)
]
var body: some View {
VStack {
Text("Custom Workout")
.font(.title)
.padding()
Spacer()
Button(action: {
showKinesteX.toggle()
}) {
Text("Start Custom Workout")
.font(.title3)
.foregroundColor(.white)
.bold()
.padding()
.frame(maxWidth: .infinity)
.background(Color.green.cornerRadius(10))
.padding(.horizontal)
}
Spacer()
}
.fullScreenCover(isPresented: $showKinesteX) {
kinestex.createCustomWorkoutView(
exercises: exercises,
user: nil,
style: nil,
isLoading: $isLoading,
workoutAction: $workoutAction,
onMessageReceived: { message in
switch message {
case .custom_type(let value):
guard let type = value["type"] as? String else { return }
if type == "all_resources_loaded" {
allResourcesLoaded = true
// Start workout when resources are ready
workoutAction = ["workout_activity_action": "start"]
}
case .exit_kinestex(_):
showKinesteX = false
allResourcesLoaded = false
case .workout_overview(let data):
print("Workout overview: \(data)")
case .error_occurred(let data):
print("Error: \(data)")
default:
print("Message received: \(message)")
}
}
)
}
}
}
#Preview {
CustomWorkoutView()
}
```
_React Native_
```jsx
import { StyleSheet, View, Button, Text } from "react-native";
import { useEffect, useRef, useState } from "react";
import { Camera } from "expo-camera";
import { SafeAreaView } from "react-native-safe-area-context";
import KinestexSDK from "kinestex-sdk-react-native";
import {
KinesteXSDKCamera,
WorkoutSequenceExercise,
IPostData,
IntegrationOption
} from "kinestex-sdk-react-native/src/types";
export default function CustomWorkoutScreen() {
const kinestexSDKRef = useRef(null);
const [permission, setPermission] = useState(false);
const [allResourcesLoaded, setAllResourcesLoaded] = useState(false);
const [showKinestex, setShowKinestex] = useState(true);
// Define workout sequence of exercises
const customWorkoutExercises: WorkoutSequenceExercise[] = [
{
exerciseId: "jz73VFlUyZ9nyd64OjRb",
reps: 15,
duration: null, // unlimited time to complete reps
includeRestPeriod: true,
restDuration: 20,
},
{
exerciseId: "ZVMeLsaXQ9Tzr5JYXg29",
reps: 10,
duration: 30,
includeRestPeriod: true,
restDuration: 15,
},
// Duplicate to create a set
{
exerciseId: "ZVMeLsaXQ9Tzr5JYXg29",
reps: 10,
duration: 30,
includeRestPeriod: true,
restDuration: 15,
},
{
exerciseId: "gJGOiZhCvJrhEP7sTy78",
reps: 20,
duration: null,
includeRestPeriod: false,
restDuration: 0,
},
];
// Configuration data with customWorkoutExercises
const postData: IPostData = {
key: 'YOUR_API_KEY',
company: 'YOUR_COMPANY_NAME',
userId: "user-123",
customWorkoutExercises: customWorkoutExercises, // pass exercises in postData
style: {
style: "dark",
},
};
// Request camera permission
useEffect(() => {
(async () => {
const { status } = await Camera.requestCameraPermissionsAsync();
if (status === "granted") {
setPermission(true);
}
})();
}, []);
// Handle messages from SDK
const handleMessage = (type: string, data: { [key: string]: any }) => {
switch (type) {
case "exit_kinestex":
console.log("User wishes to exit");
setAllResourcesLoaded(false);
break;
case "workout_exit_request":
console.log("Workout exit request:", data);
setAllResourcesLoaded(false);
setShowKinestex(false);
break;
case "all_resources_loaded":
console.log("All resources loaded");
setAllResourcesLoaded(true);
// Start the workout
kinestexSDKRef.current?.sendAction("workout_activity_action", "start");
break;
case "workout_overview":
console.log("Workout overview:", data);
break;
case "error_occurred":
console.log("Error:", data);
break;
default:
console.log("Message:", type, data);
break;
}
};
if (!permission) {
return (
Camera permission required
);
}
return (
{!showKinestex
? "KinesteX is not activated"
: allResourcesLoaded
? "All resources loaded"
: "KinesteX is loading in background"}
{showKinestex ? (
) : (
setShowKinestex(true)}
/>
)}
);
}
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: "black",
},
statusText: {
padding: 8,
textAlign: "center",
color: "white",
},
launchContainer: {
flex: 1,
alignItems: "center",
justifyContent: "center",
backgroundColor: "black",
},
sdkContainer: {
flex: 1,
},
hiddenSdkContainer: {
width: 0,
height: 0,
overflow: "hidden",
},
});
```
_React (TypeScript)_
```tsx
import React, { useRef, useState } from 'react';
import {
IntegrationOption,
KinesteXSDK,
type IPostData,
type KinesteXSDKCamera,
} from 'kinestex-sdk-react-ts';
// Define workout sequence exercise type
interface WorkoutSequenceExercise {
exerciseId: string;
reps: number | null;
duration: number | null;
includeRestPeriod: boolean;
restDuration: number;
}
const CustomWorkoutScreen: React.FC = () => {
const ref = useRef(null);
const [allResourcesLoaded, setAllResourcesLoaded] = useState(false);
const [showKinestex, setShowKinestex] = useState(true);
const customWorkoutExercises: WorkoutSequenceExercise[] = [
{
exerciseId: "jz73VFlUyZ9nyd64OjRb",
reps: 15,
duration: null,
includeRestPeriod: true,
restDuration: 20,
},
{
exerciseId: "ZVMeLsaXQ9Tzr5JYXg29",
reps: 10,
duration: 30,
includeRestPeriod: true,
restDuration: 15,
},
];
// Include customWorkoutExercises in postData
const postData: IPostData = {
key: 'YOUR_API_KEY',
company: 'YOUR_COMPANY_NAME',
userId: "user-123",
customWorkoutExercises: customWorkoutExercises,
style: { style: "dark" },
};
const handleMessage = (type: string, data: Record) => {
switch (type) {
case "all_resources_loaded":
setAllResourcesLoaded(true);
ref.current?.sendAction("workout_activity_action", "start");
break;
case "workout_exit_request":
case "exit_kinestex":
setAllResourcesLoaded(false);
setShowKinestex(false);
break;
}
};
return (
{showKinestex ? (
) : (
setShowKinestex(true)}>
Show Kinestex Again
)}
);
};
export default CustomWorkoutScreen;
```
_Flutter_
```dart
import 'package:flutter/material.dart';
import 'package:kinestex_sdk_flutter/kinestex_sdk.dart';
import 'package:permission_handler/permission_handler.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await KinesteXAIFramework.initialize(
apiKey: "your_api_key",
companyName: "your_company_name",
userId: "your_user_id",
);
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State createState() => _MyAppState();
}
class _MyAppState extends State {
@override
void dispose() {
disposeKinesteXAIFramework();
super.dispose();
}
Future disposeKinesteXAIFramework() async {
await KinesteXAIFramework.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'KinesteX Custom Workout',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
// The KinesteX view is ALWAYS mounted. We only toggle visibility.
final ValueNotifier showKinesteX = ValueNotifier(false);
// Signals when SDK reports all resources loaded.
final ValueNotifier allResourcesLoaded = ValueNotifier(false);
// Optional: SDK loading notifier.
final ValueNotifier sdkLoading = ValueNotifier(false);
bool permissionGranted = false;
late final List customWorkoutExercises;
@override
void initState() {
super.initState();
_checkCameraPermission();
customWorkoutExercises = const [
WorkoutSequenceExercise(
exerciseId: "jz73VFlUyZ9nyd64OjRb",
reps: 15,
duration: null,
includeRestPeriod: true,
restDuration: 20,
),
WorkoutSequenceExercise(
exerciseId: "ZVMeLsaXQ9Tzr5JYXg29",
reps: 10,
duration: 30,
includeRestPeriod: true,
restDuration: 15,
),
// Duplicate to create a set
WorkoutSequenceExercise(
exerciseId: "ZVMeLsaXQ9Tzr5JYXg29",
reps: 10,
duration: 30,
includeRestPeriod: true,
restDuration: 15,
),
WorkoutSequenceExercise(
exerciseId: "gJGOiZhCvJrhEP7sTy78",
reps: 20,
duration: null,
includeRestPeriod: false,
restDuration: 0,
),
];
}
void _checkCameraPermission() async {
if (await Permission.camera.request() != PermissionStatus.granted) {
setState(() => permissionGranted = false);
_showCameraAccessDeniedAlert();
} else {
setState(() => permissionGranted = true);
}
}
void _showCameraAccessDeniedAlert() {
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Camera Permission Denied"),
content: const Text(
"Camera access is required for this app to function properly.",
),
actions: [
TextButton(
child: const Text("OK"),
onPressed: () => Navigator.of(context).pop(),
),
],
);
},
);
});
}
void handleWebViewMessage(WebViewMessage message) {
if (message is ExitKinestex) {
setState(() {
allResourcesLoaded.value = false;
showKinesteX.value = false;
});
return;
}
try {
final data = message.data;
final type = data['type'];
switch (type) {
case 'all_resources_loaded':
allResourcesLoaded.value = true;
showKinesteX.value = true;
KinesteXAIFramework.sendAction(
"workout_activity_action",
"start",
);
break;
case 'workout_exit_request':
allResourcesLoaded.value = false;
showKinesteX.value = false;
break;
case 'error_occurred':
final errorMsg = data['message']?.toString() ?? 'Unknown error';
print('Error from KinesteX SDK: $errorMsg');
break;
default:
break;
}
} catch (_) {
allResourcesLoaded.value = false;
showKinesteX.value = false;
}
}
Widget createCustomWorkoutView() {
return Center(
child: KinesteXAIFramework.createCustomWorkoutView(
customWorkouts: customWorkoutExercises,
isShowKinestex: showKinesteX,
isLoading: sdkLoading,
onMessageReceived: handleWebViewMessage,
),
);
}
Widget buildCornerIndicator() {
return SafeArea(
child: Padding(
padding: const EdgeInsets.only(top: 8, right: 8),
child: ValueListenableBuilder(
valueListenable: allResourcesLoaded,
builder: (context, loaded, _) {
return ValueListenableBuilder(
valueListenable: showKinesteX,
builder: (context, visible, __) {
Color bg = Colors.black.withOpacity(0.75);
IconData icon = Icons.hourglass_bottom;
String label = 'KinesteX loading...';
if (!permissionGranted) {
bg = Colors.red.withOpacity(0.85);
icon = Icons.videocam_off;
label = 'Camera permission required';
} else if (!loaded) {
bg = Colors.orange.withOpacity(0.85);
icon = Icons.downloading;
label = 'Loading in background';
} else if (loaded && visible) {
bg = Colors.green.withOpacity(0.85);
icon = Icons.check_circle;
label = 'All resources loaded';
} else if (loaded && !visible) {
bg = Colors.grey.withOpacity(0.85);
icon = Icons.visibility_off;
label = 'KinesteX hidden';
}
return Container(
constraints: const BoxConstraints(maxWidth: 260),
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 10,
),
decoration: BoxDecoration(
color: bg,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: Colors.white, size: 18),
const SizedBox(width: 8),
Expanded(
child: Text(
label,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
),
if (!permissionGranted)
TextButton(
onPressed: _checkCameraPermission,
child: const Text(
'Grant',
style: TextStyle(color: Colors.white),
),
)
else if (loaded && !visible)
TextButton(
onPressed: () {
showKinesteX.value = true;
KinesteXAIFramework.sendAction(
"workout_activity_action",
"start",
);
},
child: const Text(
'Show',
style: TextStyle(color: Colors.white),
),
)
else if (!loaded)
const SizedBox(
height: 16,
width: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
),
],
),
);
},
);
},
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
ValueListenableBuilder(
valueListenable: showKinesteX,
builder: (context, isVisible, _) {
return IgnorePointer(
ignoring: !isVisible,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 250),
opacity: isVisible ? 1.0 : 0.0,
child: createCustomWorkoutView(),
),
);
},
),
Positioned(top: 0, right: 0, child: buildCornerIndicator()),
],
),
);
}
}
```
_HTML / JavaScript_
```html
KinesteX: Custom Workout
Click button to start custom workout
Start Custom Workout
```
#### Camera Component
KinesteX Motion Recognition: Real-Time Engagement.
- **Interactive Tracking**: Advanced motion recognition for immersive fitness experiences
- **Real-Time Feedback**: Instantly track reps, spot mistakes, and calculate calories burned
- **Boost Motivation**: Keep users engaged with detailed exercise feedback
- **Custom Integration**: Adapt camera placement to fit your app's design
**Important — what to pass for `currentExercise` and `exercises`:** we **recommend exercise IDs** — they're stable, human-readable, and you already have them when listing exercises from the Content API. Set `exerciseFetchType: "exercise_id"` to use them. The Camera Component also accepts **model IDs** (the default, kept for backward compatibility — but they require an extra round-trip through the Content API to look up) and **exercise titles** (case-sensitive — convenient for prototyping, but title matching depends on locale-normalization and can mis-match similar exercises, so prefer IDs in production). See the **Fetching Exercises** section below.
**Before showing the camera UI**, wait for both `model_warmedup` and `models_loaded` events to fire. See the **Preloading & Events** section below.
**Quick Start**
_Swift (iOS)_
```swift
// Model IDs come from the Content API or admin dashboard.
// See "Camera: Model IDs" subsection.
@State var currentExercise = "3"
kinestex.createCameraView(
exercises: ["3"], // preload every model ID you may switch to
currentExercise: $currentExercise,
user: nil,
isLoading: $isLoading,
onMessageReceived: { message in
switch message {
case .reps(let value):
reps = value["value"] as? Int ?? 0
case .mistake(let value):
mistake = value["value"] as? String ?? "--"
default:
break
}
}
)
```
_Kotlin (Android)_
```kotlin
// Model IDs come from the Content API or admin dashboard.
// See "Camera: Model IDs" subsection.
val cameraView = KinesteXSDK.createCameraComponent(
context = this,
currentExercise = "3",
exercises = listOf("3"),
user = userDetails,
isLoading = viewModel.isLoading,
onMessageReceived = { message ->
when (message) {
is WebViewMessage.Reps ->
(message.data["value"] as? Int)?.let { reps = it }
is WebViewMessage.Mistake ->
(message.data["value"] as? String)?.let { mistake = it }
else -> {}
}
},
permissionHandler = this
)
```
_React Native_
```jsx
// Model IDs come from the Content API or admin dashboard.
// See "Camera: Model IDs" subsection.
// postData seeds the SDK with INITIAL values. To change them at runtime,
// call methods on kinestexSDKRef (e.g. changeExercise) — see "Camera: Controls".
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
currentExercise: '3',
exercises: ['3'], // preload every model ID you may switch to
style: { style: 'dark' },
};
{
if (type === 'successful_repeat') setReps(data.value);
if (type === 'mistake') setMistake(data.value);
}}
/>
```
_Flutter_
```dart
// Model IDs come from the Content API or admin dashboard.
// See "Camera: Model IDs" subsection.
KinesteXAIFramework.createCameraComponent(
isShowKinestex: showKinesteX,
exercises: ["3"],
currentExercise: "3",
isLoading: ValueNotifier(false),
onMessageReceived: (message) {
if (message.type == 'successful_repeat') {
setState(() => reps = message.data['value']);
}
if (message.type == 'mistake') {
setState(() => mistake = message.data['value']);
}
},
)
```
_HTML / JavaScript_
```html
// Model IDs come from the Content API or admin dashboard.
// See "Camera: Model IDs" subsection.
const postData = {
// ... your initial fields (key, userId, company, etc.)
currentExercise: "3",
exercises: ["3"],
};
const srcURL = "https://ai.kinestex.com/camera";
webView.src = srcURL;
webView.onload = () => sendMessage(postData);
window.addEventListener("message", (event) => {
if (event.origin !== "https://ai.kinestex.com") return;
const msg = JSON.parse(event.data);
if (msg.type === 'successful_repeat') console.log('Rep:', msg.value);
if (msg.type === 'mistake') console.log('Mistake:', msg.data?.value);
});
```
_React (TypeScript)_
```tsx
// Model IDs come from the Content API or admin dashboard.
// See "Camera: Model IDs" subsection.
import { useRef } from 'react';
import {
IntegrationOption,
KinesteXSDK,
type IPostData,
type KinesteXSDKCamera,
} from 'kinestex-sdk-react-ts';
const ref = useRef(null);
// postData seeds the SDK with INITIAL values. To change them at runtime,
// call methods on the ref (e.g. ref.current?.changeExercise) — see "Camera: Controls".
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
currentExercise: '3',
exercises: ['3'],
style: { style: 'dark' },
};
{
if (type === 'successful_repeat') setReps(data.value as number);
if (type === 'mistake') setMistake(data.value as string);
}}
/>
```
##### Model IDs
> **Recommendation:** prefer **exercise IDs** — they're easier to get (you already have them when listing exercises from the Content API) and don't require an extra round-trip just to look up a numeric model ID. Set `exerciseFetchType: "exercise_id"` and pass `exercise.id` directly. See the [Fetching Exercises](#camera-component-fetching-exercises) section.
Model IDs are still supported (it's the default when `exerciseFetchType` is omitted) and useful when you already have one handy — e.g. from a stored `WorkoutModel.sequence` or the admin dashboard. They are numeric values like `"3"` for Squats or `"394"` for Jumping Jack.
**Three ways to get a model ID:**
1. **Content API** — call `fetchExercises()` (Swift / Kotlin / Flutter SDK) or `GET /api/v1/exercises` (REST). Each `ExerciseModel` has a `model_id` field. See the [Content API](/docs/content-api) section for full details. *(Note: this is the extra round-trip that exercise IDs let you skip.)*
2. **Admin dashboard** — open the exercise in [admin.kinestex.com](https://admin.kinestex.com); the model ID is shown at the top of the page header.
3. **Workout sequences** — when iterating `WorkoutModel.sequence`, each `ExerciseModel` entry exposes its own `model_id`.
**Fetch a Model ID**
_Swift (iOS)_
```swift
Task {
let result = await kinestex.fetchExercises(limit: 10)
if case .success(let response) = result,
let exercise = response.exercises.first {
// exercise.modelId is what the Camera Component expects
currentExercise = exercise.modelId
}
}
```
_Kotlin (Android)_
```kotlin
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
KinesteXSDK.api.fetchAPIContentData(
contentType = ContentType.EXERCISE,
limit = 10
)
}
if (result is APIContentResult.Exercises) {
// exercise.modelId is what the Camera Component expects
currentExercise = result.exercises.firstOrNull()?.modelId ?: ""
}
}
```
_React Native_
```jsx
const res = await fetch(
'https://admin.kinestex.com/api/v1/exercises?limit=10',
{ headers: { 'x-api-key': API_KEY, 'x-company-name': COMPANY_NAME } },
);
const { exercises } = await res.json();
// exercise.model_id is what the Camera Component expects.
// Pass it as initial value, or switch later via ref.current?.changeExercise(...).
const modelId = exercises[0].model_id;
```
_Flutter_
```dart
final result = await KinesteXAIFramework.apiService.fetchContent(
contentType: ContentType.exercise,
limit: 10,
);
// exercise.modelId is what the Camera Component expects
final modelId = result.exercises.first.modelId;
```
_HTML / JavaScript_
```html
const res = await fetch(
'https://admin.kinestex.com/api/v1/exercises?limit=10',
{ headers: { 'x-api-key': API_KEY, 'x-company-name': COMPANY_NAME } },
);
const { exercises } = await res.json();
// exercise.model_id is what the Camera Component expects
const modelId = exercises[0].model_id;
```
_React (TypeScript)_
```tsx
const res = await fetch(
'https://admin.kinestex.com/api/v1/exercises?limit=10',
{ headers: { 'x-api-key': API_KEY, 'x-company-name': COMPANY_NAME } },
);
const { exercises } = await res.json() as { exercises: ExerciseModel[] };
// exercise.model_id is what the Camera Component expects
const modelId = exercises[0].model_id;
```
##### Fetching Exercises (by ID or Title)
Besides model IDs, the Camera Component can fetch exercises by **exercise ID** (recommended) or **exercise title**. Set the `exerciseFetchType` parameter to choose the form:
| `exerciseFetchType` | Meaning | What goes in `exercises` / `currentExercise` |
|---|---|---|
| `"exercise_id"` ✅ **recommended** | Exercise IDs from the Content API — easiest to use, no extra round-trip | e.g. `"squats_v2"` |
| `"model_id"` *(default)* | Numeric model IDs — kept for backward compatibility; requires a Content API lookup to obtain | `"3"`, `"394"` |
| `"exercise_title"` | Exercise titles (case-sensitive) — handy for prototyping, but title matching depends on locale-normalization and can mis-match similar exercises | `"Squats"`, `"Jumping Jack"` |
Omitting `exerciseFetchType` keeps the default model-ID behavior — no migration needed for existing integrations.
**Where to pass it:**
- **React Native (SDK v1.3.1+):** directly in `postData`, alongside `exercises` and `currentExercise`.
- **Swift, Kotlin, Flutter, HTML/JS, React (TypeScript):** inside `customParams` / `customParameters` along with `exercises` and `currentExercise`.
**Keep one form per session:** use the same form for both `exercises` and `currentExercise`, and for any later switches.
**Fetch by Exercise Title**
_Swift (iOS)_
```swift
// exerciseFetchType goes inside customParams
kinestex.createCameraView(
exercises: ["Squats", "Jumping Jack"],
currentExercise: $currentExercise, // e.g. "Squats"
customParams: [
"exerciseFetchType": "exercise_title" // "model_id" (default) | "exercise_id" | "exercise_title"
]
)
```
_Kotlin (Android)_
```kotlin
// exerciseFetchType goes inside customParams
KinesteXSDK.createCameraComponent(
context = this,
currentExercise = "Squats",
exercises = listOf("Squats", "Jumping Jack"),
customParams = mapOf(
"exerciseFetchType" to "exercise_title" // "model_id" (default) | "exercise_id" | "exercise_title"
),
permissionHandler = this
)
```
_React Native_
```jsx
// React Native v1.3.1+: exerciseFetchType is direct in postData
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
currentExercise: 'Squats',
exercises: ['Squats', 'Jumping Jack'],
exerciseFetchType: 'exercise_title', // "model_id" (default) | "exercise_id" | "exercise_title"
};
```
_Flutter_
```dart
// exerciseFetchType goes inside customParams
KinesteXAIFramework.createCameraComponent(
isShowKinestex: showKinesteX,
exercises: ["Squats", "Jumping Jack"],
currentExercise: "Squats",
customParams: {
"exerciseFetchType": "exercise_title", // "model_id" (default) | "exercise_id" | "exercise_title"
},
)
```
_HTML / JavaScript_
```html
// exerciseFetchType goes inside customParams
const postData = {
// ... your initial fields (key, userId, company, etc.)
currentExercise: "Squats",
exercises: ["Squats", "Jumping Jack"],
customParams: {
exerciseFetchType: "exercise_title", // "model_id" (default) | "exercise_id" | "exercise_title"
},
};
```
_React (TypeScript)_
```tsx
// exerciseFetchType goes inside customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
currentExercise: 'Squats',
exercises: ['Squats', 'Jumping Jack'],
customParameters: {
exerciseFetchType: 'exercise_title', // "model_id" (default) | "exercise_id" | "exercise_title"
},
};
```
##### Loading More Exercises at Runtime (React Native v1.3.1+)
**Availability:** React Native SDK `v1.3.1+` only. Other platforms must pass the full `exercises` list at initialization.
In React Native you can fetch and cache **additional** exercise models **after the session has started**, without re-mounting the camera. This is done by calling `sendAction` with the `"load_models"` action and an `extras` object containing the new identifiers.
**Three-step flow:**
1. Send the `load_models` action with the identifiers you want to add.
2. Wait for the `models_loaded` event — `data.modelIds` echoes the identifiers that resolved.
3. Switch the active exercise with `changeExercise(...)` (do **not** repeat `exerciseFetchType`).
If `exercises` is missing or empty, the SDK posts back `{ "type": "error_occurred", "message": "load_models: no exercises provided" }`. Per-identifier failures arrive as separate `error_occurred` messages; any identifiers that did resolve are still listed in `modelIds` and are switchable.
**Load More Models, Then Switch**
_React Native_
```jsx
// Step 1 — fetch additional models at runtime
sdkRef.current?.sendAction(
"workout_activity_action",
"load_models",
{
exercises: ["Jumping Jack", "Lunges"],
exerciseFetchType: "exercise_title", // match the form you used initially
}
);
// Step 2 — wait for models_loaded, then switch
const handleMessage = (type: string, data: { [key: string]: any }) => {
if (type === "models_loaded" && data.modelIds?.includes("Jumping Jack")) {
// Step 3 — switch the active exercise. Do NOT repeat exerciseFetchType here.
sdkRef.current?.changeExercise("Jumping Jack");
}
if (type === "error_occurred") {
console.warn("KinesteX error:", data.message);
}
};
```
##### Preloading & Events
Two events fire as the component initializes. Wait for **both** before revealing the camera UI to the user.
| Event | Meaning |
|---|---|
| `model_warmedup` | Pose-tracking (MediaPipe) model is ready |
| `models_loaded` | All exercise models in `exercises` have downloaded |
**Pattern:** mount the camera hidden (e.g. `opacity: 0`) with a loader on top. Reveal once both events have fired.
**Wait for Both Events**
_Swift (iOS)_
```swift
@State private var modelWarmedUp = false
@State private var modelsLoaded = false
var isReady: Bool { modelWarmedUp && modelsLoaded }
kinestex.createCameraView(
exercises: ["3"],
currentExercise: $currentExercise,
user: nil,
isLoading: $isLoading,
onMessageReceived: { message in
if case .custom_type(let value) = message,
let type = value["type"] as? String {
if type == "model_warmedup" { modelWarmedUp = true }
if type == "models_loaded" { modelsLoaded = true }
}
}
)
.opacity(isReady ? 1 : 0)
```
_Kotlin (Android)_
```kotlin
var modelWarmedUp = false
var modelsLoaded = false
val cameraView = KinesteXSDK.createCameraComponent(
context = this,
currentExercise = "3",
exercises = listOf("3"),
user = null,
isLoading = viewModel.isLoading,
onMessageReceived = { message ->
if (message is WebViewMessage.CustomType) {
when (message.data["type"] as? String) {
"model_warmedup" -> modelWarmedUp = true
"models_loaded" -> modelsLoaded = true
}
cameraView.alpha = if (modelWarmedUp && modelsLoaded) 1f else 0f
}
},
permissionHandler = this
)
```
_React Native_
```jsx
const [modelWarmedUp, setModelWarmedUp] = useState(false);
const [modelsLoaded, setModelsLoaded] = useState(false);
const isReady = modelWarmedUp && modelsLoaded;
{
if (type === 'model_warmedup') setModelWarmedUp(true);
if (type === 'models_loaded') setModelsLoaded(true);
}}
/>
```
_Flutter_
```dart
bool modelWarmedUp = false;
bool modelsLoaded = false;
KinesteXAIFramework.createCameraComponent(
isShowKinestex: showKinesteX,
exercises: ["3"],
currentExercise: "3",
isLoading: ValueNotifier(false),
onMessageReceived: (message) {
if (message.type == 'model_warmedup') {
setState(() => modelWarmedUp = true);
}
if (message.type == 'models_loaded') {
setState(() => modelsLoaded = true);
}
},
)
```
_HTML / JavaScript_
```html
let modelWarmedUp = false, modelsLoaded = false;
const reveal = () => {
if (modelWarmedUp && modelsLoaded) iframe.style.opacity = '1';
};
iframe.style.opacity = '0';
window.addEventListener("message", (event) => {
if (event.origin !== "https://ai.kinestex.com") return;
const msg = JSON.parse(event.data);
if (msg.type === 'model_warmedup') { modelWarmedUp = true; reveal(); }
if (msg.type === 'models_loaded') { modelsLoaded = true; reveal(); }
});
```
_React (TypeScript)_
```tsx
const [modelWarmedUp, setModelWarmedUp] = useState(false);
const [modelsLoaded, setModelsLoaded] = useState(false);
const isReady = modelWarmedUp && modelsLoaded;
{
if (type === 'model_warmedup') setModelWarmedUp(true);
if (type === 'models_loaded') setModelsLoaded(true);
}}
/>
```
##### Controls
**Switching exercises in real time.** Set `currentExercise` to any model ID from the `exercises` array — the camera component swaps tracking immediately.
**Control commands.** Send any of these strings as `currentExercise` to control the session:
| Command | Effect |
|---|---|
| `"Pause Exercise"` | Pauses motion tracking; rep counter freezes |
| `"Pause Audio"` | Mutes voice feedback |
| `"Resume Audio"` | Re-enables voice feedback |
| `"Workout Overview"` | Triggers a summary snapshot for the current session |
| `"Stop Camera"` | ⚠️ **Destructive** — releases camera + models, fires `stop_camera`. Not recoverable without re-mounting the component (see warning below) |
To resume tracking after a pause, set `currentExercise` back to a real model ID from `exercises`.
> ⚠️ **`"Stop Camera"` is destructive — avoid it for normal flows.** It tears down the camera, MediaPipe, and all loaded exercise models, then fires the `stop_camera` event. **You cannot recover from it without unmounting and re-creating the entire camera component.** Only send it when the user is permanently leaving the camera screen. For temporary pauses, use `"Pause Exercise"` instead.
**Switch Exercise and Send Control Commands**
_Swift (iOS)_
```swift
// Switch exercise (Swift uses two-way binding via @State)
currentExercise = "394"
// Pause / resume tracking
currentExercise = "Pause Exercise"
currentExercise = "3"
```
_Kotlin (Android)_
```kotlin
// Switch exercise
KinesteXSDK.updateCurrentExercise("394")
// Pause / resume tracking
KinesteXSDK.updateCurrentExercise("Pause Exercise")
KinesteXSDK.updateCurrentExercise("3")
```
_React Native_
```jsx
// Switch exercise
kinestexSDKRef.current?.changeExercise("394");
// Pause / resume tracking
kinestexSDKRef.current?.changeExercise("Pause Exercise");
kinestexSDKRef.current?.changeExercise("3");
```
_Flutter_
```dart
// All controls go through your updateExercise ValueNotifier
updateExercise.value = "394";
updateExercise.value = "Pause Exercise";
updateExercise.value = "3";
```
_HTML / JavaScript_
```html
// Switch exercise
webView.contentWindow.postMessage(
{ currentExercise: "394" },
srcURL
);
// Pause / resume tracking
webView.contentWindow.postMessage({ currentExercise: "Pause Exercise" }, srcURL);
webView.contentWindow.postMessage({ currentExercise: "3" }, srcURL);
```
_React (TypeScript)_
```tsx
// Switch exercise
ref.current?.changeExercise("394");
// Pause / resume tracking
ref.current?.changeExercise("Pause Exercise");
ref.current?.changeExercise("3");
```
##### Customization
Pass any of these fields in `customParams` (Swift / Kotlin / Flutter) or directly in `postData` (HTML/JS, React Native, React TS) at initialization.
| Field | Type | Description |
|---|---|---|
| `restSpeeches` | `string[]` | Audio phrases to preload (from `ExerciseModel.rest_speech`) |
| `videoURL` | `string` | Use a video file instead of the live camera |
| `landmarkColor` | `string` | Pose overlay color in hex with `#` (default `#14FF00`) |
| `showSilhouette` | `boolean` | Show "get into frame" silhouette (default `true`) |
| `includeRealtimeAccuracy` | `boolean` | (Beta) Stream live position-confidence alongside reps |
| `includePoseData` | `string[]` | Any of `"angles"`, `"poseLandmarks"`, `"worldLandmarks"`. **Performance impact** — only enable for custom calculations |
##### Event Reference
All events the Camera Component emits to the host app:
| Event | Payload | When |
|---|---|---|
| `model_warmedup` | `{ message }` | Pose model is ready |
| `models_loaded` | `{ message }` | All exercise models in `exercises` finished downloading |
| `person_in_frame` | `{ message }` | User entered the silhouette frame |
| `successful_repeat` | `{ exercise, value, accuracy }` | A rep was counted (`value` = total reps so far) |
| `mistake` | `{ value }` | Form mistake detected |
| `correct_position_accuracy` | `{ accuracy }` | (Beta) Live position confidence — only when `includeRealtimeAccuracy: true` |
| `pose_landmarks` | `{ poseLandmarks }` | Per-frame screen-space landmarks — only when `includePoseData` includes `"poseLandmarks"` |
| `world_landmarks` | `{ worldLandmarks }` | Per-frame world-space landmarks — only when `includePoseData` includes `"worldLandmarks"` |
| `speech_fetch_complete` | `{ successCount, failureCount }` | All `restSpeeches` finished loading |
| `error_occurred` | `{ message }` or `{ data, error }` | Any error (model fetch fail, phrase fail, etc.) |
| `warning` | `{ data }` | Non-fatal config issue (e.g. no model IDs provided) |
| `stop_camera` | `{ message }` | Confirms the `"Stop Camera"` command finished |
##### Pose Data
When `includePoseData` contains `"poseLandmarks"` or `"worldLandmarks"`, the camera emits per-frame events with raw skeleton data. **Only enable this if you're doing custom calculations — there is a performance cost.**
**Two coordinate spaces:**
- `poseLandmarks` — values 0–1, normalized to the camera frame.
- `worldLandmarks` — meters, relative to the hips (best Z accuracy).
Each landmark has `{ x, y, z, visibility }` (all 0–1).
**Available landmarks** (same names in both spaces): `nose`, `leftEyeInner`, `leftEye`, `leftEyeOuter`, `rightEyeInner`, `rightEye`, `rightEyeOuter`, `leftEar`, `rightEar`, `mouthLeft`, `mouthRight`, `leftShoulder`, `rightShoulder`, `leftElbow`, `rightElbow`, `leftWrist`, `rightWrist`, `leftPinky`, `rightPinky`, `leftIndex`, `rightIndex`, `leftThumb`, `rightThumb`, `leftHip`, `rightHip`, `leftKnee`, `rightKnee`, `leftAnkle`, `rightAnkle`, `leftHeel`, `rightHeel`, `leftFootIndex`, `rightFootIndex`.
**Available angles** (when `"angles"` is included — both 2D and 3D versions are emitted): `leftKneeAngle`, `rightKneeAngle`, `leftHipAngle`, `rightHipAngle`, `leftShoulderAngle`, `rightShoulderAngle`, `leftElbowAngle`, `rightElbowAngle`, `leftWristAngle`, `rightWristAngle`, `leftAnkleAngle`, `rightAnkleAngle`, `leftArmpitAngle`, `rightArmpitAngle`.
##### Complete Example
Minimal working example with **Next** / **Previous** buttons that cycle between exercises and a live rep counter in the UI.
**Complete Implementation**
_Swift (iOS)_
```swift
import SwiftUI
import KinesteXAIKit
struct CameraScreen: View {
let kinestex = KinesteXAIKit(
apiKey: "YOUR_API_KEY",
companyName: "YOUR_COMPANY_NAME",
userId: "YOUR_USER_ID"
)
// 3 = Squats, 394 = Jumping Jack
let exerciseIds = ["3", "394"]
@State private var index = 0
@State private var currentExercise = "3"
@State private var reps = 0
@State private var isLoading = false
var body: some View {
VStack(spacing: 16) {
Text("Reps: \(reps)")
.font(.title)
.padding(.top)
kinestex.createCameraView(
exercises: exerciseIds,
currentExercise: $currentExercise,
user: nil,
isLoading: $isLoading,
onMessageReceived: { message in
if case .reps(let value) = message {
reps = value["value"] as? Int ?? 0
}
}
)
HStack(spacing: 24) {
Button("Previous") { switchTo(index - 1) }
Button("Next") { switchTo(index + 1) }
}
.padding(.bottom)
}
}
private func switchTo(_ newIndex: Int) {
index = (newIndex + exerciseIds.count) % exerciseIds.count
currentExercise = exerciseIds[index]
reps = 0
}
}
```
_Kotlin (Android)_
```kotlin
import android.Manifest
import android.os.Bundle
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.kinestex.kinestexsdkkotlin.GenericWebView
import com.kinestex.kinestexsdkkotlin.KinesteXSDK
import com.kinestex.kinestexsdkkotlin.PermissionHandler
import com.kinestex.kinestexsdkkotlin.WebViewMessage
import kotlinx.coroutines.flow.MutableStateFlow
class CameraActivity : AppCompatActivity(), PermissionHandler {
// 3 = Squats, 394 = Jumping Jack
private val exerciseIds = listOf("3", "394")
private var index = 0
private val isLoading = MutableStateFlow(false)
private lateinit var camera: GenericWebView
private lateinit var tvReps: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera)
tvReps = findViewById(R.id.tvReps)
camera = KinesteXSDK.createCameraComponent(
context = this,
currentExercise = exerciseIds[0],
exercises = exerciseIds,
user = null,
isLoading = isLoading,
onMessageReceived = { msg ->
if (msg is WebViewMessage.Reps) {
val v = msg.data["value"] as? Int ?: 0
runOnUiThread { tvReps.text = "Reps: $v" }
}
},
permissionHandler = this
) as GenericWebView
findViewById(R.id.cameraContainer).addView(camera)
findViewById(R.id.btnPrev).setOnClickListener { switchTo(index - 1) }
findViewById(R.id.btnNext).setOnClickListener { switchTo(index + 1) }
}
private fun switchTo(newIndex: Int) {
index = (newIndex + exerciseIds.size) % exerciseIds.size
KinesteXSDK.updateCurrentExercise(exerciseIds[index])
runOnUiThread { tvReps.text = "Reps: 0" }
}
override fun requestCameraPermission() {
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted -> camera.handlePermissionResult(granted) }
.launch(Manifest.permission.CAMERA)
}
}
```
_React Native_
```jsx
import { useRef, useState } from 'react';
import { View, Text, Button } from 'react-native';
import KinestexSDK from 'kinestex-sdk-react-native';
import {
IntegrationOption,
KinesteXSDKCamera,
IPostData,
} from 'kinestex-sdk-react-native/src/types';
// 3 = Squats, 394 = Jumping Jack
const exerciseIds = ['3', '394'];
export default function CameraScreen() {
const ref = useRef(null);
const [index, setIndex] = useState(0);
const [reps, setReps] = useState(0);
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
currentExercise: exerciseIds[0],
exercises: exerciseIds,
style: { style: 'dark' },
};
const switchTo = (newIndex: number) => {
const wrapped = (newIndex + exerciseIds.length) % exerciseIds.length;
setIndex(wrapped);
setReps(0);
ref.current?.changeExercise(exerciseIds[wrapped]);
};
return (
Reps: {reps}
{
if (type === 'successful_repeat') setReps(data.value);
}}
/>
switchTo(index - 1)} />
switchTo(index + 1)} />
);
}
```
_Flutter_
```dart
import 'package:flutter/material.dart';
import 'package:kinestex_sdk_flutter/kinestex_sdk.dart';
class CameraScreen extends StatefulWidget {
const CameraScreen({super.key});
@override
State createState() => _CameraScreenState();
}
class _CameraScreenState extends State {
// 3 = Squats, 394 = Jumping Jack
final exerciseIds = const ['3', '394'];
int index = 0;
int reps = 0;
final showKinesteX = ValueNotifier(true);
final updateExercise = ValueNotifier('3');
void switchTo(int newIndex) {
final wrapped = (newIndex + exerciseIds.length) % exerciseIds.length;
setState(() {
index = wrapped;
reps = 0;
});
updateExercise.value = exerciseIds[wrapped];
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text('Reps: $reps', style: const TextStyle(fontSize: 24)),
),
Expanded(
child: ValueListenableBuilder(
valueListenable: updateExercise,
builder: (context, value, _) {
return KinesteXAIFramework.createCameraComponent(
isShowKinestex: showKinesteX,
exercises: exerciseIds,
currentExercise: value ?? exerciseIds[0],
updatedExercise: value,
isLoading: ValueNotifier(false),
onMessageReceived: (m) {
if (m.type == 'successful_repeat') {
setState(() => reps = m.data['value'] ?? 0);
}
},
);
},
),
),
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
onPressed: () => switchTo(index - 1),
child: const Text('Previous'),
),
ElevatedButton(
onPressed: () => switchTo(index + 1),
child: const Text('Next'),
),
],
),
),
],
),
);
}
}
```
_HTML / JavaScript_
```html
Reps: 0
Previous
Next
```
_React (TypeScript)_
```tsx
import { useRef, useState } from 'react';
import {
IntegrationOption,
KinesteXSDK,
type IPostData,
type KinesteXSDKCamera,
} from 'kinestex-sdk-react-ts';
// 3 = Squats, 394 = Jumping Jack
const exerciseIds = ['3', '394'];
export default function CameraScreen() {
const ref = useRef(null);
const [index, setIndex] = useState(0);
const [reps, setReps] = useState(0);
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'YOUR_USER_ID',
company: 'YOUR_COMPANY_NAME',
currentExercise: exerciseIds[0],
exercises: exerciseIds,
style: { style: 'dark' },
};
const switchTo = (newIndex: number) => {
const wrapped = (newIndex + exerciseIds.length) % exerciseIds.length;
setIndex(wrapped);
setReps(0);
ref.current?.changeExercise(exerciseIds[wrapped]);
};
return (
Reps: {reps}
{
if (type === 'successful_repeat') setReps(data.value as number);
}}
/>
switchTo(index - 1)}>Previous
switchTo(index + 1)}>Next
);
}
```
#### Admin Workout Editor
Opens the KinesteX admin editor for content management. Use this to provide admins or content creators with direct access to workout and exercise management tools.
**Parameters:**
- `organization` (required): Your organization identifier
- `contentType` (optional): `.workout`, `.plan`, or `.exercise`
- `contentId` (optional): Specific content ID to edit
- `customQueries` (optional): Additional query parameters
**Note:** This feature is currently available for Swift SDK only.
**Admin Workout Editor Setup**
_Swift (iOS)_
```swift
// Open main admin dashboard
kinestex.createAdminWorkoutEditor(
organization: "YourOrg",
isLoading: $isLoading,
onMessageReceived: { message in
switch message {
case .exit_kinestex(_):
showEditor = false
default:
print("Message received: \(message)")
}
}
)
// Open specific workout for editing
kinestex.createAdminWorkoutEditor(
organization: "YourOrg",
contentType: .workout,
contentId: "workout123",
isLoading: $isLoading,
onMessageReceived: { message in /* handle messages */ }
)
// Open specific exercise for editing
kinestex.createAdminWorkoutEditor(
organization: "YourOrg",
contentType: .exercise,
contentId: "exercise456",
customQueries: ["language": "en"],
isLoading: $isLoading,
onMessageReceived: { message in /* handle messages */ }
)
```
##### Complete Example
Full implementation with state management and navigation to the admin editor.
**Complete Admin Editor Implementation**
_Swift (iOS)_
```swift
import SwiftUI
import KinesteXAIKit
struct AdminEditorView: View {
@State private var showEditor = false
@State private var isLoading = false
// Initialize KinesteXAIKit with your credentials
let kinestex = KinesteXAIKit(
apiKey: "YOUR_API_KEY",
companyName: "YOUR_COMPANY_NAME",
userId: "YOUR_USER_ID"
)
var body: some View {
VStack {
Text("Admin Editor")
.font(.title)
.padding()
Spacer()
Button(action: {
showEditor.toggle()
}) {
Text("Open Admin Editor")
.font(.title3)
.foregroundColor(.white)
.bold()
.padding()
.frame(maxWidth: .infinity)
.background(Color.blue.cornerRadius(10))
.padding(.horizontal)
}
Spacer()
}
.fullScreenCover(isPresented: $showEditor) {
kinestex.createAdminWorkoutEditor(
organization: "YourOrg",
contentType: nil,
contentId: nil,
customQueries: nil,
isLoading: $isLoading,
onMessageReceived: { message in
switch message {
case .exit_kinestex(_):
showEditor = false
case .error_occurred(let data):
print("Error: \(data)")
default:
print("Message received: \(message)")
}
}
)
}
}
}
#Preview {
AdminEditorView()
}
```
---
## Data Points
PostMessage events sent from KinesteX SDK for integration with native apps and external systems. Events are sent in real-time, work safely offline, and provide comprehensive tracking for workouts, exercises, and health assessments.
### Receiving Data
Each platform has a specific pattern for receiving data from KinesteX.
**SDK Platforms (Swift, Kotlin, React Native, React-TS, Flutter):** Use the callback function provided by the SDK with typed message enums.
**HTML/JS:** Set up a message event listener manually since there's no SDK wrapper.
**Platform-Specific Data Handlers**
_Flutter_
```dart
void handleWebViewMessage(WebViewMessage message) {
if (message is KinestexLaunched) {
print("KinesteX launched at: ${message}");
} else if (message is ExitKinestex) {
print("Exited KinesteX at: ${message} seconds.");
} else if (message is PlanUnlocked) {
print("Plan Unlocked: ${message}");
} else if (message is WorkoutOpened) {
print("Workout Opened: ${message}");
} else if (message is WorkoutStarted) {
print("Workout Started: ${message}");
} else if (message is ExerciseCompleted) {
print("Exercise: ${message}");
} else if (message is TotalActiveSeconds) {
print("Active seconds: ${message}");
} else if (message is LeftCameraFrame) {
print("User left camera frame at time: ${message}");
} else if (message is ReturnedCameraFrame) {
print("User returned to camera frame at time: ${message}");
} else if (message is WorkoutOverview) {
print("Workout Overview: ${message}");
} else if (message is ExerciseOverview) {
print("Exercise Overview: ${message}");
} else if (message is WorkoutCompleted) {
print("Workout Completed: ${message}");
} else {
print("Other data points: ${message.data}");
}
}
```
_React Native_
```jsx
const handleMessage = (type: string, data: { [key: string]: any }) => {
switch (type) {
case "kinestex_launched":
console.log('Launched at:', data);
break;
case "exit_kinestex":
console.log('Exited, time spent:', data.time_spent);
break;
case "workout_overview":
console.log('Workout stats:', data);
break;
case "exercise_completed":
console.log('Exercise done:', data.exercise_title);
break;
case "workout_completed":
console.log('Workout finished:', data);
break;
case "error_occurred":
console.error('Error:', data);
break;
default:
console.log('Other message type:', type, data);
break;
}
};
```
_React (TypeScript)_
```tsx
const handleMessage = (type: string, data: { [key: string]: any }) => {
switch (type) {
case "kinestex_launched":
console.log('Launched at:', data);
break;
case "exit_kinestex":
console.log('Exited, time spent:', data.time_spent);
break;
case "workout_overview":
console.log('Workout stats:', data);
break;
case "exercise_completed":
console.log('Exercise done:', data.exercise_title);
break;
case "workout_completed":
console.log('Workout finished:', data);
break;
case "error_occurred":
console.error('Error:', data);
break;
default:
console.log('Other message type:', type, data);
break;
}
};
```
_Kotlin (Android)_
```kotlin
private fun handleWebViewMessage(message: WebViewMessage) {
when (message) {
is WebViewMessage.KinestexLaunched -> {
println("KinesteX launched at: $message")
}
is WebViewMessage.FinishedWorkout -> {
println("Finished Workout: $message")
}
is WebViewMessage.ErrorOccurred -> {
println("Error Occurred: $message")
}
is WebViewMessage.ExerciseCompleted -> {
println("Exercise Completed: $message")
}
is WebViewMessage.ExitKinestex -> {
println("Exited KinesteX at: $message")
}
is WebViewMessage.WorkoutOpened -> {
println("Workout Opened: $message")
}
is WebViewMessage.WorkoutStarted -> {
println("Workout Started: $message")
}
is WebViewMessage.PlanUnlocked -> {
println("Plan Unlocked: $message")
}
is WebViewMessage.WorkoutOverview -> {
println("Workout Overview: $message")
}
is WebViewMessage.ExerciseOverview -> {
println("Exercise Overview: $message")
}
is WebViewMessage.WorkoutCompleted -> {
println("Workout Completed: $message")
}
// Camera Component Specific
is WebViewMessage.Reps -> {
println("Reps: $message")
}
is WebViewMessage.Mistake -> {
println("Mistake: $message")
}
is WebViewMessage.CustomType -> {
println("Any other message: $message")
}
}
}
```
_Swift (iOS)_
```swift
// onMessageReceived callback passes WebViewMessage enum
// Available message types:
// kinestex_launched([String: Any]) - KinesteX View launched
// finished_workout([String: Any]) - Workout completed
// error_occurred([String: Any]) - Errors (e.g., missing camera)
// exercise_completed([String: Any]) - Exercise finished
// exit_kinestex([String: Any]) - User exits KinesteX view
// workout_opened([String: Any]) - Workout description viewed
// workout_started([String: Any]) - Workout begins
// plan_unlocked([String: Any]) - Workout plan unlocked
// custom_type([String: Any]) - Unrecognized messages
// reps([String: Any]) - Successful repetitions
// mistake([String: Any]) - Detected mistakes
// left_camera_frame([String: Any]) - User left camera frame
// returned_camera_frame([String: Any]) - User returned to frame
// workout_overview([String: Any]) - Workout summary
// exercise_overview([String: Any]) - Exercise summary
// workout_completed([String: Any]) - Workout done, overview exited
```
_HTML / JavaScript_
```html
// HTML/JS requires manual event listener setup
window.addEventListener("message", (event) => {
// Security: only accept messages from KinesteX
if (event.origin !== "https://ai.kinestex.com") return;
try {
const message = JSON.parse(event.data);
switch (message.type) {
case "kinestex_launched":
console.log("Launched:", message.data);
break;
case "exit_kinestex":
console.log("Exited, time spent:", message.time_spent);
break;
case "workout_overview":
console.log("Workout stats:", message.data);
break;
case "exercise_completed":
console.log("Exercise done:", message.data);
break;
case "error_occurred":
console.error("Error:", message.data || message.message);
break;
default:
console.log("Message:", message.type, message);
}
} catch (e) {
console.error("Failed to parse message:", e);
}
});
```
### Application Lifecycle
Events for app startup, loading, and exit.
| Event | Data Fields | Description |
|-------|-------------|-------------|
| kinestex_launched | data: string ("dd mm yyyy hh:mm:ss") | KinesteX application is launched |
| kinestex_loaded | date: string (ISO format) | KinesteX fully loaded and ready |
| exit_kinestex | date: Date, time_spent: string ("hh:mm:ss") | User exits with total time spent |
| main_page_opened | date: string (ISO format) | Main/home page is opened |
| home_page_opened | date: string (ISO format) | Home Page integration screen opened. Fires exactly once per mount |
| streak_extended | data: object | User's daily streak was extended by completing a qualifying activity (workout, challenge, plan day, assessment) |
**streak_extended Data Structure:**
```
{
current_streak: number, // Current streak count (days)
longest_streak: number, // User's longest streak ever
last_activity_date: string // ISO date of the activity that extended the streak
}
```
### Workout Events
Events for workout lifecycle and statistics.
| Event | Data Fields | Description |
|-------|-------------|-------------|
| workout_opened | title: string, id: string, date: string | Workout details page opened |
| workout_started | id: string, date: string | Workout session started |
| workout_started (alt) | workoutId: string | Alternative workout start format |
| workout_completed | workout: string, date: string | Workout finished, user exits overview |
| workout_ended | id: string, exit_type: string, date: string | Workout session ended (see exit_type values below) |
| workout_overview | data: object | Complete workout summary statistics |
**workout_ended exit_type values:**
| Value | Meaning |
|-------|---------|
| complete | User finished the entire workout including outro |
| exit | User abandoned the workout mid-session |
| outro | User exited from the outro/cooldown screen after completing all exercises |
**workout_overview Data Structure:**
```
{
workout_title: string, // Workout name
workout_id: string, // Unique workout ID
target_duration_seconds: number, // Target workout duration (seconds)
workout_duration_seconds: number, // Total wall-clock session time
// (includes rest, transitions, pauses).
// For challenges/assessments this equals
// total_time_spent (no wall-clock concept)
total_time_spent: number, // Active exercise time only (seconds)
completed_reps_count: number, // Total completed reps
target_reps_count: number, // Total target reps
calories_burned: number, // Calories (2 decimal places)
completion_percentage: number, // Completion % (2 decimals)
total_mistakes: number, // Total mistake count
accuracy_score: number, // Overall accuracy (0-100)
efficiency_score: number, // Efficiency metric (0-100)
total_exercise: number, // Number of exercises
actual_hold_time_seconds: number, // Time in correct position
target_hold_time_seconds: number // Target hold time
}
```
**Note:** Use `workout_duration_seconds` to display or log the full session time (including rest periods). Use `total_time_spent` if you only need active exercise time.
**Handling Workout Overview**
_Swift (iOS)_
```swift
case .workout_overview(let data):
if let calories = data["calories_burned"] as? Double,
let completion = data["completion_percentage"] as? Double {
print("Burned \(calories) cal, \(completion)% complete")
}
```
_Kotlin (Android)_
```kotlin
is WebViewMessage.WorkoutOverview -> {
val calories = message.caloriesBurned
val completion = message.completionPercentage
Log.d("Workout", "Burned $calories cal, $completion% complete")
}
```
_React Native_
```jsx
case "workout_overview":
const { calories_burned, completion_percentage, accuracy_score } = data;
console.log(`Workout: ${completion_percentage}% complete`);
console.log(`Calories: ${calories_burned}, Accuracy: ${accuracy_score}`);
break;
```
_Flutter_
```dart
if (message is WorkoutOverview) {
print("Calories: ${message.caloriesBurned}");
print("Completion: ${message.completionPercentage}%");
print("Accuracy: ${message.accuracyScore}");
}
```
_HTML / JavaScript_
```html
case "workout_overview":
const stats = message.data;
console.log("Workout:", stats.workout_title);
console.log("Calories:", stats.calories_burned);
console.log("Accuracy:", stats.accuracy_score);
break;
```
_React (TypeScript)_
```tsx
case "workout_overview":
const { calories_burned, completion_percentage, accuracy_score } = data;
console.log(`Workout: ${completion_percentage}% complete`);
console.log(`Calories: ${calories_burned}, Accuracy: ${accuracy_score}`);
break;
```
### Exercise Events
Events for individual exercise tracking.
| Event | Description |
|-------|-------------|
| exercise_completed | Individual exercise completed |
| exercise_overview | All exercises summary (array) |
**exercise_completed Data Structure:**
```
{
exercise_title: string, // Exercise name
time_spent: number, // Seconds spent
repeats: number, // Reps completed
total_reps: number, // Required reps
total_duration: number, // Countdown time
perfect_hold_position: number, // Time in perfect hold position (seconds).
// 0 for non-hold exercises
calories: number, // Calories burned
exercise_id: string, // Exercise ID
exercise_index: number, // 1-based position of the completed exercise
total_exercises: number, // Total number of exercises in the workout
mistakes: Array<{ // Mistakes made
mistake: string,
count: number
}>,
average_accuracy?: number // Average accuracy (0-1, optional)
}
```
**exercise_overview Item Structure:**
```
{
exercise_title: string, // Exercise name
exercise_id: string, // Unique exercise ID
time_spent: number, // Time on exercise (seconds)
perfect_hold_position: number, // Time in correct position (timer-based)
repeats: number, // Reps completed
total_required_reps: number, // Target reps
total_required_time: number, // Target time (seconds)
calories: number, // Calories (2 decimal places)
mistakes: Array<{ // Detailed mistake breakdown
mistake: string,
count: number
}>,
mistake_count: number, // Total mistakes for exercise
accuracy_reps?: number[], // Per-rep accuracy scores (optional)
average_accuracy?: number // Average accuracy 0-100 (optional)
}
```
### Camera & Frame Events
Events for camera tracking and frame detection.
| Event | Data Fields | Description |
|-------|-------------|-------------|
| left_camera_frame | date: string ("dd mm yyyy hh:mm:ss") | User left camera view |
| returned_camera_frame | date: string ("dd mm yyyy hh:mm:ss") | User returned to camera view |
| check_frame_completed | message: string ("Person stepped into frame") | Frame check completed |
| camera_selector_opened | message: array (available cameras) | Camera selector opened |
| camera_selected | id: string, label: string, isMirrorCamera: boolean | Camera selected by user |
### Plans & Programs
Events for workout plans and programs.
| Event | Data Fields | Description |
|-------|-------------|-------------|
| plan_unlocked | id: string, img: string, title: string, date: string | Plan unlocked/selected |
| plan_opened | id: string | Plan result screen rendered. Fires for both goal-based and personalized plans |
| plan_onboarding_plan_created | data: { plan_id: string, plan_type: string } | A new plan was created from onboarding/assessment. Does NOT fire on revisits to an existing personalized plan |
| plan_progression_saved | data: object | Plan day progression was saved successfully after a plan workout finished |
| plan_progression_failed | data: object | Plan progression save failed (network/server error) |
| personalized_plan_exit | workout: string, date: string | Exit from personalized plan |
| remind_me_later_clicked | - | User tapped "Remind me later" on the Assessment screen inside the plan-onboarding flow. Only fires from the plan-onboarding page |
**Note on plan tracking:** When you launch a plan workout directly via the SDK, you can pass `planId`, `planType`, and `progressWorkoutId` in the initial PostMessage configuration so the SDK associates the workout session with the correct plan. After the workout finishes, you'll receive either `plan_progression_saved` (success) or `plan_progression_failed` (error). See [Plan Context configuration](/docs/customization-parameters#plan-context) for details.
### Challenge Events
Events for challenge mode.
| Event | Data Fields | Description |
|-------|-------------|-------------|
| challenge_started | exerciseId: string | Challenge exercise started |
| challenge_completed | repCount: number, mistakes: number | Challenge completed |
| challenge_exit | workout: string, date: string | Exit from challenge |
### Leaderboard Events
Events for leaderboard functionality.
| Event | Data Fields | Description |
|-------|-------------|-------------|
| highlighted_user | data: object | User's leaderboard position |
**highlighted_user Data Structure:**
```
{
username: string, // User's username
score: number, // User's score
position: number // Leaderboard position (1-based)
}
```
### Navigation Events
Events for app navigation.
| Event | Data Fields | Description |
|-------|-------------|-------------|
| kinestex_home_exit | workout: string, date: string | Exit from KinesteX home |
| navigation_back | data: { exercise_index: number, total_exercises: number } | User navigated back to a previous exercise during a workout |
### Feedback Events
Events dispatched when a user submits feedback.
| Event | Description |
|-------|-------------|
| feedback_submitted | User submitted training rating or per-exercise feedback |
**Training Feedback Payload (source: "training_feedback"):**
```
{
type: "feedback_submitted",
source: "training_feedback",
rating: number, // User rating (e.g., 1-5)
is_like: boolean, // Whether the user liked the workout
description: string, // Optional text feedback
workout_id: string, // Workout ID
workout_title: string // Workout name
}
```
**Per-Exercise Feedback Payload (source: "exercise_feedback"):**
```
{
type: "feedback_submitted",
source: "exercise_feedback",
workout_id: string, // Workout ID
workout_title: string, // Workout name
feedbacks: Array<{ // Per-exercise feedback entries
exercise_id: string, // Exercise ID
exercise_title: string, // Exercise name
is_like: boolean, // Whether the user liked the exercise
description: string // Optional text feedback
}>
}
```
### Session & Upload Events
Events related to workout session saving and motion recording uploads. These events are only dispatched when \`shouldSendStats: true\` is passed in the SDK configuration.
| Event | Data Fields | Description |
|-------|-------------|-------------|
| workout_session_saved | data: object | Workout session successfully saved to backend |
| session_save_complete | - | Motion recording uploads finished successfully |
| motion_upload_progress | data: { completed: number, total: number } | Motion recording upload progress |
| motion_upload_error | data: { error: string } | Motion recording upload failed or timed out |
| workout_completion_overlay_dismissed | - | User dismissed the workout completion celebration overlay |
**workout_session_saved Data Structure:**
```
{
session_id: number, // Backend-assigned session ID
workout_title: string, // Name of the workout
accuracy_score: number, // Overall accuracy (0-100)
efficiency_score: number, // Efficiency score (0-100)
completion_percentage: number, // How much of the workout was completed (0-100)
completed_reps_count: number, // Total reps completed
calories_burned: number // Estimated calories burned
}
```
**motion_upload_progress Data Structure:**
```
{
completed: number, // Number of exercise recordings uploaded so far
total: number // Total number of exercise recordings to upload
}
```
### Error & Status Events
Events for errors, warnings, and active time tracking.
| Event | Data Fields | Description |
|-------|-------------|-------------|
| error_occurred | data: string | General error message |
| error_occurred | message: string | Alternative error format |
| error_occurred | data: string, error: any | Error with details |
| warning | data: string | Warning message |
| total_active_seconds | number | Active workout time (sent every 5s, pauses when user leaves camera frame) |
| ios_video_fallback_activated | reason, videoUrl, readyState, userAgent | iOS detected a stuck video decoder and switched to image-based playback. The exercise still runs in a degraded-but-functional mode (no native video controls) |
**ios_video_fallback_activated Payload:**
```
{
type: "ios_video_fallback_activated",
reason: string, // e.g. "video_stuck_at_metadata"
videoUrl: string, // URL of the affected video
readyState: number, // HTMLMediaElement readyState at the time of fallback
userAgent: string // Device user agent string
}
```
Use this event for analytics or to surface a notice in your UI when iOS playback degrades. Only listen for it if you need visibility into iOS playback issues — no integrator action is required.
### Assessment Exit Events
Events dispatched when a user exits an assessment before completing it.
| Event | Data Fields | Description |
|-------|-------------|-------------|
| assessment_exit | exerciseId: string | User exited the assessment early (before results) |
| assessment_exit_results | exerciseId: string | User exited the assessment from the results screen |
**assessment_exit Payload:**
```
{
type: "assessment_exit",
data: {
exerciseId: string // The assessment exercise identifier
}
}
```
**assessment_exit_results Payload:**
```
{
type: "assessment_exit_results",
data: {
exerciseId: string // The assessment exercise identifier
}
}
```
Use \`assessment_exit\` to detect when users abandon an assessment mid-session, and \`assessment_exit_results\` to detect when they leave after viewing their results.
### Assessment Overview
AI-powered health assessments with two event types:
- **assessment_overview**: Sent when results page loads
- **assessment_completed**: Sent when user clicks restart or finish
**Common Fields (All Assessments):**
| Field | Type | Description |
|-------|------|-------------|
| type | string | "assessment_overview" or "assessment_completed" |
| assessmentType | string | Assessment identifier (e.g., "tug", "sls") |
| date | Date | Timestamp when completed |
| time | number | Total assessment time (seconds) |
| steps | number? | Estimated step count (walking assessments) |
**Assessment Types:**
- **Mobility**: TUG (tug), Gait Speed Test (gaitspeedtest)
- **Balance**: SLS (sls), SBSS (sbss), STSS (stss), Full Tandem (fulltandem)
- **Functional**: STS (sts), Five Times STS (fivetimessts), FRT (frt)
- **Range of Motion**: Shoulder ROM (romshoulder)
- **Games**: Balloon Pop (balloonpop), Color Chase (colorchase), Alien Squat Shooter (aliensquatshooter)
**Risk Levels:**
| Value | Meaning |
|-------|---------|
| low | Good performance, minimal fall/balance risk |
| moderate | Some limitations, may benefit from training |
| high | Significant limitations, balance training recommended |
### Mobility Assessments
**TUG (Timed Up and Go) - assessmentType: "tug"**
Stand-walk-turn-return-sit timing test.
| Field | Type | Description |
|-------|------|-------------|
| time | number | Total completion time (seconds) |
| steps | number | Estimated step count |
| standingUpTime | number | Time to stand from seated (seconds) |
| sittingDownTime | number | Time to sit at end (seconds) |
| walkingForwardTime | number | Time walking to 3m marker (seconds) |
| walkingBackwardTime | number | Time walking back (seconds) |
| turningTime | number | Time spent turning (seconds) |
| backBendingAngleSitting | number[] | Back angles during sitting countdown (degrees) |
| backBendingAngleStanding | number[] | Back angles during movement (degrees) |
| averageSpeedMs_tug | number | Average walking speed. Formula: 6m / time |
| avgBackBendingSitting | number? | Average back angle sitting (degrees) |
| avgBackBendingStanding | number? | Average back angle standing (degrees) |
---
**Gait Speed Test - assessmentType: "gaitspeedtest"**
Walking speed measurement over 4 meters.
| Field | Type | Description |
|-------|------|-------------|
| time | number | Total test time (seconds) |
| steps | number | Estimated step count |
| standingTime | number | Time in standing phase (seconds) |
| walkingTime | number | Active walking time (seconds) |
| averageGaitSpeed | number | Gait speed. Formula: 4m / time |
### Balance Assessments
**SLS (Single Leg Stand) - assessmentType: "sls"**
| Field | Type | Description |
|-------|------|-------------|
| rightTime | number | Duration on right leg (seconds) |
| leftTime | number | Duration on left leg (seconds) |
| symmetryScore_sls | number? | Leg symmetry % (0-100). Formula: (min/max) * 100 |
| riskLevel_sls | string | "low", "moderate", or "high" |
**Risk Calculation:**
- Low: Min time >=20s AND symmetry good (difference <=5s)
- Moderate: Min time >=10s AND average >=15s
- High: All other cases
---
**SBSS (Side-by-Side Stand) - assessmentType: "sbss"**
| Field | Type | Description |
|-------|------|-------------|
| timeInProperPosition_sbss | number | Time in correct stance (max 10s) |
| maxShoulderShift_sbss | number | Max lateral shoulder shift (% of shoulder width) |
| maxHipShift_sbss | number | Max lateral hip shift (% of hip width) |
| feetMoved_sbss | boolean | Whether feet moved |
| riskLevel_sbss | string | "low", "moderate", or "high" |
**Risk Calculation:**
- High: Feet moved OR (time <7s AND sway >=30%)
- Moderate: Time >=7s AND sway <30%
- Low: Time >=9.5s AND sway <15%
---
**STSS (Semi-Tandem Stand) - assessmentType: "stss"**
| Field | Type | Description |
|-------|------|-------------|
| timeInProperPosition_stss | number | Time in correct stance (max 10s) |
| maxShoulderShift_stss | number | Max shoulder shift (% of width) |
| maxHipShift_stss | number | Max hip shift (% of projection) |
| feetMoved_stss | boolean | Whether feet moved |
| riskLevel_stss | string | "low", "moderate", or "high" |
Risk calculation same as SBSS.
---
**Full Tandem Stand - assessmentType: "fulltandem"**
| Field | Type | Description |
|-------|------|-------------|
| time | number | Total test time (seconds) |
| timeInProperPosition_fulltandem | number | Time in heel-to-toe stance (max 10s) |
| maxShoulderShift_fulltandem | number | Max shoulder shift (%) |
| maxHipShift_fulltandem | number | Max hip shift (%) |
| feetMoved_fulltandem | boolean | Whether feet moved |
| testFailed_fulltandem | boolean | Whether test was terminated early |
| terminationReason_fulltandem | string? | Reason for early termination |
| riskLevel_fulltandem | string | "low", "moderate", or "high" |
**Risk Calculation:**
- Low: Time >=10s AND no feet movement
- Moderate: Time >=5s
- High: Time <5s
### Functional Assessments
**STS (30-Second Sit-to-Stand) - assessmentType: "sts"**
| Field | Type | Description |
|-------|------|-------------|
| reps | number | Total reps completed in 30 seconds |
| averageSittingTime | number | Average time sitting per rep (seconds) |
| averageStandingTime | number | Average time standing per rep (seconds) |
| avgTimePerRep_sts | number? | Average seconds per rep. Formula: 30 / reps |
| repTimeVariance_minRepTime | number? | Fastest rep time (seconds) |
| repTimeVariance_maxRepTime | number? | Slowest rep time (seconds) |
| repTimeVariance_minRepIndex | number? | Which rep was fastest (1-indexed) |
| repTimeVariance_maxRepIndex | number? | Which rep was slowest (1-indexed) |
---
**Five Times STS - assessmentType: "fivetimessts"**
| Field | Type | Description |
|-------|------|-------------|
| time | number | Total completion time for 5 reps (seconds) |
| averageSittingTime | number | Average time sitting (seconds) |
| averageStandingTime | number | Average time standing (seconds) |
---
**FRT (Functional Reach Test) - assessmentType: "frt"**
| Field | Type | Description |
|-------|------|-------------|
| reach | number | Maximum forward reach (cm) |
| maxHeelLift | number | Maximum heel lift detected (cm) |
| heelLiftCount | number | Count of heel lift violations |
| legLiftCount | number | Count of leg lift violations |
| testCompleted | boolean | Whether test finished normally |
| endReason | string? | Reason for early termination |
| riskLevel_frt | string? | "low", "moderate", or "high" |
**End Reason Values:**
- "Feet moved out of zone"
- "User left zone"
- "User turned forward"
- "Arm dropped completely"
- "Reference lost"
- "Unhandled state"
**Risk Calculation:**
- High: Test not completed OR reach <=15cm
- Moderate: Reach 15-25cm
- Low: Reach >25cm
### Range of Motion Assessments
**Shoulder ROM - assessmentType: "romshoulder"**
A quick range-of-motion check for the shoulders. The user stands facing the camera and lifts one arm at a time straight out to the side (shoulder abduction), as high as comfortably possible. The system tracks the peak abduction angle reached by each arm, compares left and right sides, and flags meaningful asymmetry.
Useful for tracking recovery, spotting asymmetry, and observing mobility improvements session over session.
| Field | Type | Description |
|-------|------|-------------|
| romMovement | string | Type of ROM movement assessed. Primary value: "shoulder_abduction" |
| romMaxLeft | number | Maximum ROM achieved on the left side (degrees, rounded to 0 decimals, range 0-180+) |
| romMaxRight | number | Maximum ROM achieved on the right side (degrees, rounded to 0 decimals, range 0-180+) |
| romMinLeft | number | Minimum ROM recorded on the left side (degrees, rounded to 0 decimals, range 0-180+) |
| romMinRight | number | Minimum ROM recorded on the right side (degrees, rounded to 0 decimals, range 0-180+) |
| romSymmetryDelta | number | Absolute difference between max left and max right (degrees). Formula: abs(romMaxLeft - romMaxRight) |
| romSymmetryFlagDegrees | number | Asymmetry threshold (degrees). Defaults to 10 if not provided |
| romAsymmetric | boolean | true if romSymmetryDelta > romSymmetryFlagDegrees, else false |
**Notes:**
- All angle measurements are in degrees.
- Numeric values are rounded to 0 decimal places.
- `romAsymmetric` is computed by comparing `romSymmetryDelta` against `romSymmetryFlagDegrees`.
### Game Assessments
**Balloon Pop - assessmentType: "balloonpop"**
| Field | Type | Description |
|-------|------|-------------|
| gameScore | number | Total balloons popped |
| averageReactionTime | string | Average time to pop (seconds) |
| maxIdleTime | string | Max time without action (seconds) |
| averageSpeed_balloonpop | number | Balloons/second. Formula: gameScore / 30 |
| masteryTitle_balloonpop | string | Achievement tier |
**Mastery Titles:**
| Score | Title |
|-------|-------|
| >=30 | pop_tastic_hero |
| >=25 | magic_popper |
| >=20 | super_duper_popper |
| >=15 | sparkly_popper |
| >=10 | giggly_popper |
| >=5 | bouncy_bubbler |
| <5 | tiny_popper |
---
**Color Chase - assessmentType: "colorchase"**
| Field | Type | Description |
|-------|------|-------------|
| gameScore | number | Total score achieved |
| levelReached | number | Highest level completed |
| totalDuration | string | Total game duration (seconds) |
| averageReactionTime | string | Average reaction time per tap (seconds) |
| maxIdleTime | string | Max time between taps (seconds) |
| masteryTitle_colorchase | string | Achievement tier |
**Mastery Titles:**
| Level | Title |
|-------|-------|
| >=10 | color_chase_legend |
| >=8 | magic_color_wizard |
| >=6 | super_color_star |
| >=4 | shiny_sequencer |
| >=2 | rainbow_chaser |
| >=1 | color_buddy |
| <1 | little_color_finder |
---
**Alien Squat Shooter - assessmentType: "aliensquatshooter"**
| Field | Type | Description |
|-------|------|-------------|
| gameScore | number | Total aliens destroyed |
| squatsPerformed | number | Number of squats completed |
| totalDuration | string | Total game duration (seconds) |
| averageSquatRate | string | Squats per second |
| maxTimeBetweenSquats | string | Max idle between squats (seconds) |
| averageAlienDestroyTime | string | Average time to destroy alien (seconds) |
| masteryTitle_aliensquatshooter | string | Achievement tier |
**Mastery Titles:**
| Score | Title |
|-------|-------|
| >=25 | alien_annihilator |
| >=20 | cosmic_blaster |
| >=15 | star_shooter |
| >=10 | galactic_gunner |
| >=5 | space_squatter |
| <5 | rookie_defender |
---
**Health Benefits (All Games)**
Included in payload for all game types.
```
healthBenefits: {
heartDiseaseReduction: number, // Estimated % reduction (max 15)
diabetesReduction: number, // Estimated % reduction (max 20)
obesityReduction: number, // Estimated % reduction (max 10)
depressionReduction: number // Estimated % reduction (max 15)
}
```
**Health Benefit Calculation:**
`reduction = min((gameScore / 100) * maxReduction, maxReduction)`
### Event Flow Examples
**Complete Workout Flow:**
1. `kinestex_launched` - Application starts
2. `workout_opened` - User views workout details
3. `workout_started` - Workout begins
4. `returned_camera_frame` / `left_camera_frame` - Frame tracking
5. Multiple `exercise_completed` - Each exercise finished
6. `workout_overview` - Summary statistics
7. `exercise_overview` - All exercises summary
8. `workout_completed` - User exits statistics
9. `exit_kinestex` - Application closed
**Challenge Flow:**
1. `challenge_started` - Challenge begins
2. `exercise_completed` - Exercise finished
3. `challenge_completed` - Challenge complete
4. `challenge_exit` - Exit from challenge
**Assessment Flow:**
1. `kinestex_launched` - Application starts
2. Assessment performed (user follows on-screen instructions)
3. `assessment_overview` - Results page loads with all metrics
4. `assessment_completed` - User clicks restart or finish
5. `assessment_exit_results` - User exits from results screen (or `assessment_exit` if they exit early)
6. `exit_kinestex` - Application closed
---
## Customization Parameters
This section describes all available customization parameters that can be passed to the KinesteX SDK to customize the user experience. Parameters are organized by category, with examples showing which have direct SDK support vs. requiring `customParams`.
**Parameter Passing Methods:**
- **Direct SDK Support**: Pass directly to SDK initialization or view creation methods
- **customParams / customParameters**: Additional parameters passed via a custom parameters object
- **HTML/JS postData**: All parameters passed as flat object via postMessage API
### Required Parameters
These parameters are mandatory for successful SDK initialization.
| Parameter | Type | Description |
|-----------|------|-------------|
| userId | string | Unique identifier for the user. Must be at least 2 characters. Used for tracking progress, analytics, and personalization |
| company | string | Company name associated with the API key. Determines theme defaults and content access |
| key | string | API key for authentication. Required for all API calls and content access |
**SDK Support:** All platforms support these as direct parameters.
**Required Parameters Setup**
_Swift (iOS)_
```swift
// Direct SDK support
let kinestex = KinesteXAIKit(
apiKey: "YOUR_API_KEY",
companyName: "YOUR_COMPANY",
userId: "unique-user-id"
)
```
_Kotlin (Android)_
```kotlin
// Direct SDK support
KinesteXSDK.initialize(
context = this,
apiKey = "YOUR_API_KEY",
companyName = "YOUR_COMPANY",
userId = "unique-user-id"
)
```
_React Native_
```jsx
// Direct support in postData
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'unique-user-id',
company: 'YOUR_COMPANY',
};
```
_Flutter_
```dart
// Direct SDK support
await KinesteXAIFramework.initialize(
apiKey: "YOUR_API_KEY",
companyName: "YOUR_COMPANY",
userId: "unique-user-id",
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "unique-user-id",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
};
```
_React (TypeScript)_
```tsx
// Direct support in postData
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'unique-user-id',
company: 'YOUR_COMPANY',
};
```
### User Profile
Parameters that define user characteristics for personalized content and recommendations.
| Parameter | Type | Description | Effect |
|-----------|------|-------------|--------|
| age | number | User's age in years | Affects workout intensity recommendations and exercise selection |
| gender | string | User's gender (male, female, other) | Affects BMI calculations and content personalization |
| height | number | User's height (in cm or inches based on locale) | Used for BMI calculation and exercise calibration |
| weight | number | User's weight (in kg or lbs based on locale) | Used for BMI calculation and calorie estimations |
| fitness_level | string | User's fitness level | Determines workout difficulty and progression |
| lifestyle | string | User's lifestyle type (e.g., sedentary, active) | Affects personalized plan recommendations |
| body_parts | string[] | Target body parts for workouts | Filters and prioritizes exercises targeting specific areas |
| plan_type | string | Type of workout plan | Determines the structure and focus of generated plans |
**SDK Support:** Most platforms have direct support via UserDetails object or postData fields.
**User Profile Configuration**
_Swift (iOS)_
```swift
// Direct SDK support via UserDetails
let user = UserDetails(
age: 30,
height: 180,
weight: 75,
gender: .Male,
lifestyle: .Active
)
kinestex.createView(
user: user,
// ... other params
)
```
_Kotlin (Android)_
```kotlin
// Direct SDK support via UserDetails
val userDetails = UserDetails(
age = 30,
height = 180,
weight = 75,
gender = Gender.MALE,
lifestyle = Lifestyle.ACTIVE
)
KinesteXSDK.createView(
user = userDetails,
// ... other params
)
```
_React Native_
```jsx
// Direct support in postData
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
// User profile - direct support
age: 30,
height: 180, // cm
weight: 75, // kg
gender: 'Male',
lifestyle: Lifestyle.Active,
};
```
_Flutter_
```dart
// Direct SDK support via UserDetails
final userDetails = UserDetails(
age: 30,
height: 180,
weight: 75,
gender: Gender.male,
lifestyle: Lifestyle.active,
);
KinesteXAIFramework.createView(
user: userDetails,
// ... other params
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
// User profile
age: 30,
height: 180,
weight: 75,
gender: "Male",
lifestyle: "active",
};
```
_React (TypeScript)_
```tsx
// Direct support in postData
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
// User profile - direct support
age: 30,
height: 180, // cm
weight: 75, // kg
gender: 'Male',
lifestyle: Lifestyle.Active,
};
```
### Theme & Appearance
Control the visual appearance of the application.
| Parameter | Type | Default | Description | Effect |
|-----------|------|---------|-------------|--------|
| style | "dark" | "dark" | Applies style to the UI | Changes the entire UI color scheme to dark or light mode | Theme mode |
| themeName | string | Company name | Custom theme identifier | Loads a specific theme configuration (e.g., branded themes) |
**Note:** Theme can also be set via URL parameter `?style=dark` or `?style=light`
**SDK Support:**
- **Swift:** Direct support via `IStyle` class passed to view creation methods (hex values with #)
- **Flutter:** Direct support via `IStyle` class passed to view creation methods
- **Kotlin:** Direct support via `IStyle` data class passed to view creation methods (hex values without #)
- **React Native/React:** Direct support via `style` object in postData
- **HTML/JS:** Direct in postData object
**Theme Configuration**
_Swift (iOS)_
```swift
// Direct SDK support via IStyle class
let customStyle = IStyle(
style: "light",
themeName: "CustomBrand"
)
kinestex.createWorkoutView(
workout: "Fitness Lite",
user: user,
style: customStyle,
isLoading: $isLoading,
onMessageReceived: { /* ... */ }
)
```
_Kotlin (Android)_
```kotlin
// Direct SDK support via IStyle class
KinesteXSDK.createWorkoutView(
context = this,
workoutName = "Fitness Lite",
style = IStyle(
style = "light",
themeName = "Your Theme Name from Admin Dashboard (default company name)"
),
isLoading = viewModel.isLoading,
onMessageReceived = { message ->
handleWebViewMessage(message)
},
permissionHandler = this
)
```
_React Native_
```jsx
// Direct support via style object
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
style: {
style: 'light', // 'dark' or 'light'
loadingBackgroundColor: 'FFFFFF', // hex without #
},
customParameters: {
themeName: 'CustomBrand', // via customParameters
},
};
```
_Flutter_
```dart
// Direct SDK support via IStyle class
KinesteXAIFramework.createWorkoutView(
workoutName: "Fitness Lite",
isShowKinestex: showKinesteX,
isLoading: ValueNotifier(false),
style: IStyle(
style: 'light', // 'dark' or 'light'
themeName: 'CustomBrand',
),
onMessageReceived: (message) {
handleWebViewMessage(message);
},
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
style: "light",
themeName: "CustomBrand",
};
```
_React (TypeScript)_
```tsx
// Direct support via style object
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
style: {
style: 'light', // 'dark' or 'light'
loadingBackgroundColor: 'FFFFFF', // hex without #
},
customParameters: {
themeName: 'CustomBrand', // via customParameters
},
};
```
### Language & Localization
Configure the language for all UI text, voice prompts, and content.
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| language | string | "en" | Language code for localization |
| voiceActor | string | - | Specific voice actor for audio feedback |
| content_gender | string | - | Gender preference for content/instructors shown |
**Supported Languages:**
| Code | Language | RTL Support |
|------|----------|-------------|
| en | English | No |
| es | Spanish | No |
| fr | French | No |
| de | German | No |
| nl | Dutch | No |
| it | Italian | No |
| pt | Portuguese | No |
| ru | Russian | No |
| ar | Arabic | Yes |
| he | Hebrew | Yes |
| hi | Hindi | No |
| bn | Bengali | No |
| id | Indonesian | No |
| da | Danish | No |
| el | Greek | No |
| nl | Dutch | No |
| zh | Chinese (Simplified) | No |
**SDK Support:** Requires customParams on most platforms.
**Language Configuration**
_Swift (iOS)_
```swift
// Via customParams
kinestex.createView(
customParams: [
"language": "es",
"content_gender": "female"
],
// ... other params
)
```
_Kotlin (Android)_
```kotlin
// Via customParams
KinesteXSDK.createView(
customParams = mapOf(
"language" to "es",
"content_gender" to "female"
),
// ... other params
)
```
_React Native_
```jsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
language: 'es',
content_gender: 'female',
},
};
```
_Flutter_
```dart
// Via customParams
KinesteXAIFramework.createView(
customParams: {
"language": "es",
"content_gender": "female",
},
// ... other params
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
language: "es",
content_gender: "female",
};
```
_React (TypeScript)_
```tsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
language: 'es',
content_gender: 'female',
},
};
```
### Workout Configuration
Parameters for configuring workout behavior and progression.
| Parameter | Type | Description | Effect |
|-----------|------|-------------|--------|
| planC | string | Plan configuration identifier | Specifies which workout plan to load |
| exercises | string[] | Array of exercise identifiers | Defines the specific exercises to include in a session |
| currentExercise | string | Current exercise identifier | Sets the active exercise for camera component |
| completed_exercises | string[] | Previously completed exercises | Allows resuming a workout from a specific point |
| start_from_exercise | string | Exercise to start from | Skips to a specific exercise in the workout |
| start_from_rest | boolean | Start from rest period | If true, begins at rest screen before the specified exercise |
| resetPlanProgress | boolean | Reset all plan progress | Clears all saved progress for the user's plans |
| on_start_url | string | URL to call on workout start | Webhook URL triggered when workout begins |
**Note:** Exercise IDs can be retrieved from the [Content API](/docs/content-api).
**Workout Configuration**
_Swift (iOS)_
```swift
// Direct support for some, customParams for others
kinestex.createCameraView(
exercises: ["Squats", "Lunges", "Pushups"], // direct
currentExercise: $currentExercise, // direct
customParams: [
"start_from_exercise": "Lunges",
"start_from_rest": true,
"resetPlanProgress": false
]
)
```
_Kotlin (Android)_
```kotlin
// Direct support for some, customParams for others
KinesteXSDK.createCameraComponent(
exercises = listOf("Squats", "Lunges", "Pushups"), // direct
currentExercise = "Squats", // direct
customParams = mapOf(
"start_from_exercise" to "Lunges",
"start_from_rest" to true
)
)
```
_React Native_
```jsx
// Direct support in postData
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
// Direct support
exercises: ['Squats', 'Lunges', 'Pushups'],
currentExercise: 'Squats',
// Via customParameters
customParameters: {
start_from_exercise: 'Lunges',
start_from_rest: true,
resetPlanProgress: false,
},
};
```
_Flutter_
```dart
// Direct support for some, customParams for others
KinesteXAIFramework.createCameraComponent(
exercises: ["Squats", "Lunges", "Pushups"], // direct
currentExercise: "Squats", // direct
customParams: {
"start_from_exercise": "Lunges",
"start_from_rest": true,
},
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
exercises: ["Squats", "Lunges", "Pushups"],
currentExercise: "Squats",
start_from_exercise: "Lunges",
start_from_rest: true,
resetPlanProgress: false,
};
```
_React (TypeScript)_
```tsx
// Direct support in postData
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
// Direct support
exercises: ['Squats', 'Lunges', 'Pushups'],
currentExercise: 'Squats',
// Via customParameters
customParameters: {
start_from_exercise: 'Lunges',
start_from_rest: true,
},
};
```
### Camera & Pose Detection
Fine-tune the camera and pose detection system.
| Parameter | Type | Default | Description | Effect |
|-----------|------|---------|-------------|--------|
| shouldAskCamera | boolean | true | Prompt for camera permission | Shows camera permission dialog before starting |
| shouldShowCameraSelector | boolean | false | Show camera selection UI | Allows user to choose between available cameras |
| shouldShowOpenCameraSettings | boolean | false | Show settings button | Displays button to open device camera settings |
| cameraId | string | - | Specific camera device ID | Forces use of a specific camera |
| cameraLabel | string | - | Camera label for display | Shows custom label for the selected camera |
| minPoseDetectionConfidence | number | 0.5 | Minimum detection confidence (0-1) | Lower values detect poses more easily but may be less accurate |
| minTrackingConfidence | number | 0.5 | Minimum tracking confidence (0-1) | Affects how persistently poses are tracked between frames |
| minPosePresenceConfidence | number | 0.5 | Minimum presence confidence (0-1) | Threshold for determining if a person is in frame |
| mediapipeModel | "full" "heavy" "light" | "full" | MediaPipe model variant | light: Faster, less accurate. full: Balanced. heavy: Most accurate, slower |
| defaultDelegate | "GPU" "CPU" | Auto | Processing delegate | Forces GPU or CPU processing for pose detection |
| landmarkColor | string | "#14FF00" | Pose landmark color | Changes the color of skeleton overlay (hex color) |
| showSilhouette | boolean | true | Show body silhouette | Displays silhouette guide overlay during exercises |
| includePoseData | boolean | - | Include raw pose data | Sends pose landmark data in postMessage events |
| includePoseBorders | boolean | true | Show pose boundary guides | Displays borders indicating optimal pose positioning |
| includeRealtimeAccuracy | boolean | - | Send real-time accuracy | Broadcasts accuracy scores in real-time via postMessage |
| videoFit | "contain" | "cover" | Camera feed display mode | When set to "contain", shows the full camera frame instead of zooming in to fill the area. Useful for maximizing available space for motion tracking |
**Note:** `defaultDelegate` can also be set via URL parameter `?delegate=GPU` or `?delegate=CPU`
**Camera & Pose Detection Settings**
_Swift (iOS)_
```swift
// Via customParams
kinestex.createCameraView(
exercises: exerciseList,
currentExercise: $currentExercise,
customParams: [
"landmarkColor": "#FF5500",
"showSilhouette": true,
"mediapipeModel": "heavy",
"defaultDelegate": "GPU",
"includePoseData": true,
"includeRealtimeAccuracy": true,
"shouldShowCameraSelector": true,
"videoFit": "contain" // show full camera frame
]
)
```
_Kotlin (Android)_
```kotlin
// Via customParams
KinesteXSDK.createCameraComponent(
exercises = exerciseList,
currentExercise = "Squats",
customParams = mapOf(
"landmarkColor" to "#FF5500",
"showSilhouette" to true,
"mediapipeModel" to "heavy",
"defaultDelegate" to "GPU",
"includePoseData" to true,
"includeRealtimeAccuracy" to true,
"videoFit" to "contain" // show full camera frame
)
)
```
_React Native_
```jsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
exercises: ['Squats', 'Lunges'],
currentExercise: 'Squats',
customParameters: {
landmarkColor: '#FF5500',
showSilhouette: true,
mediapipeModel: 'heavy',
defaultDelegate: 'GPU',
includePoseData: true,
includeRealtimeAccuracy: true,
shouldShowCameraSelector: true,
videoFit: 'contain', // show full camera frame
},
};
```
_Flutter_
```dart
// Via customParams
KinesteXAIFramework.createCameraComponent(
exercises: ["Squats", "Lunges"],
currentExercise: "Squats",
customParams: {
"landmarkColor": "#FF5500",
"showSilhouette": true,
"mediapipeModel": "heavy",
"defaultDelegate": "GPU",
"includePoseData": true,
"includeRealtimeAccuracy": true,
"videoFit": "contain", // show full camera frame
},
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
exercises: ["Squats", "Lunges"],
currentExercise: "Squats",
landmarkColor: "#FF5500",
showSilhouette: true,
mediapipeModel: "heavy",
defaultDelegate: "GPU",
includePoseData: true,
includeRealtimeAccuracy: true,
shouldShowCameraSelector: true,
videoFit: "contain", // show full camera frame
};
```
_React (TypeScript)_
```tsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
exercises: ['Squats', 'Lunges'],
currentExercise: 'Squats',
customParameters: {
landmarkColor: '#FF5500',
showSilhouette: true,
mediapipeModel: 'heavy',
defaultDelegate: 'GPU',
includePoseData: true,
includeRealtimeAccuracy: true,
videoFit: 'contain', // show full camera frame
},
};
```
### UI Controls
Control visibility and behavior of UI elements.
| Parameter | Type | Default | Description | Effect |
|-----------|------|---------|-------------|--------|
| isHideHeaderMain | boolean | false | Hide main header | Removes the top navigation header |
| hideFeelingDialog | boolean | false | Hide feeling dialog | Skips the post-workout feeling prompt |
| hideMusicIcon | boolean | - | Hide music control | Removes the music toggle button |
| hideMistakesFeedback | boolean | - | Hide mistake feedback | Disables on-screen form correction prompts |
| showModalWarmUp | boolean | - | Show warm-up modal | Displays warm-up recommendation before workout |
| showSettings | boolean | - | Show settings button | Displays settings access in the UI |
| isDrawingPose | boolean | - | Enable pose drawing | Activates skeleton visualization on camera feed |
| isOnboarding | boolean | true | Enable onboarding flow | Shows/hides the initial onboarding experience |
| hideCompletionOverlay | boolean | false | Hide workout completion overlay | Skips the workout completion summary screen shown at the end of a workout |
| preventGestureControl | boolean | false | Disable gesture control | Prevents users from pausing and resuming workouts using hand gestures |
| disableGuide | boolean | false | Disable onboarding guide | Suppresses the guide entirely for embedded contexts where onboarding isn't needed |
| disableCookies | boolean | false | Disable all cookies | Automatically declines all cookies, hides the cookie management button and consent link. Useful for GDPR compliance or privacy-sensitive environments |
| hideStatisticsHeader | boolean | false | Hide statistics header | Hides the header on the workout statistics/results screen |
| nativeParentScroll | boolean | false | Native scroll delegation | Delegates scroll behavior of the statistics screen to the native parent container instead of using internal scroll. Enable if your native app manages its own scrolling |
**UI Controls Configuration**
_Swift (iOS)_
```swift
// Via customParams
kinestex.createView(
customParams: [
"isHideHeaderMain": true,
"hideFeelingDialog": true,
"hideMusicIcon": true,
"hideMistakesFeedback": false,
"isOnboarding": false,
"hideCompletionOverlay": true,
"preventGestureControl": true,
"disableGuide": true,
"disableCookies": true,
"hideStatisticsHeader": false,
"nativeParentScroll": false
]
)
```
_Kotlin (Android)_
```kotlin
// Via customParams
KinesteXSDK.createView(
customParams = mapOf(
"isHideHeaderMain" to true,
"hideFeelingDialog" to true,
"hideMusicIcon" to true,
"hideMistakesFeedback" to false,
"isOnboarding" to false,
"hideCompletionOverlay" to true,
"preventGestureControl" to true,
"disableGuide" to true,
"disableCookies" to true,
"hideStatisticsHeader" to false,
"nativeParentScroll" to false
)
)
```
_React Native_
```jsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
isHideHeaderMain: true,
hideFeelingDialog: true,
hideMusicIcon: true,
hideMistakesFeedback: false,
isOnboarding: false,
hideCompletionOverlay: true,
preventGestureControl: true,
disableGuide: true,
disableCookies: true,
hideStatisticsHeader: false,
nativeParentScroll: false,
},
};
```
_Flutter_
```dart
// Via customParams
KinesteXAIFramework.createView(
customParams: {
"isHideHeaderMain": true,
"hideFeelingDialog": true,
"hideMusicIcon": true,
"hideMistakesFeedback": false,
"isOnboarding": false,
"hideCompletionOverlay": true,
"preventGestureControl": true,
"disableGuide": true,
"disableCookies": true,
"hideStatisticsHeader": false,
"nativeParentScroll": false,
},
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
isHideHeaderMain: true,
hideFeelingDialog: true,
hideMusicIcon: true,
hideMistakesFeedback: false,
isOnboarding: false,
hideCompletionOverlay: true,
preventGestureControl: true,
disableGuide: true,
disableCookies: true,
hideStatisticsHeader: false,
nativeParentScroll: false,
};
```
_React (TypeScript)_
```tsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
isHideHeaderMain: true,
hideFeelingDialog: true,
hideMusicIcon: true,
hideMistakesFeedback: false,
isOnboarding: false,
hideCompletionOverlay: true,
preventGestureControl: true,
disableGuide: true,
disableCookies: true,
hideStatisticsHeader: false,
nativeParentScroll: false,
},
};
```
### Challenge Mode
Configure challenge-specific parameters for direct challenge launches.
| Parameter | Type | Description | Effect |
|-----------|------|-------------|--------|
| exercise | string | Challenge exercise identifier | Specifies which exercise to use for the challenge |
| countdown | number | Countdown duration in seconds | Sets the preparation countdown before challenge starts |
| reps | number | Target repetition count | Sets the goal number of reps for the challenge |
| gameTotalRounds | number | Total number of rounds (Color Chase only) | Controls how many rounds the Color Chase game lasts. Default is 10. Rounds beyond 10 are procedurally generated with increasing difficulty |
**Balloon Pop — rounds mode (special case):**
When you launch the **Balloon Pop** game and pass BOTH `reps` and `countdown`, the game switches from the default 30-second timer mode into a **rounds mode**:
- `reps` = number of rounds
- `countdown` = number of balloons spawned per round
The game ends after all rounds are completed. The HUD shows a round counter instead of a timer, and the difficulty stage indicator is hidden.
| Configuration | Behavior |
|---------------|----------|
| `reps: 4, countdown: 2` | 4 rounds × 2 balloons = 8 total pops |
| `reps: 3, countdown: 3` | 3 rounds × 3 balloons = 9 total pops |
| `reps: 6, countdown: 1` | 6 rounds × 1 balloon = 6 total pops (very easy) |
| `reps` not provided | Default timer mode (30s, escalating difficulty) |
**Challenge Mode Configuration**
_Swift (iOS)_
```swift
// Via customParams for challenge integration
kinestex.createChallengeView(
exercise: exerciseId, // direct
customParams: [
"countdown": 5,
"reps": 20,
// Color Chase only:
"gameTotalRounds": 15
// Balloon Pop rounds-mode example:
// "reps": 4, "countdown": 2 → 4 rounds × 2 balloons
]
)
```
_Kotlin (Android)_
```kotlin
// Via customParams for challenge integration
KinesteXSDK.createChallengeView(
exercise = exerciseId, // direct
customParams = mapOf(
"countdown" to 5,
"reps" to 20,
// Color Chase only:
"gameTotalRounds" to 15
// Balloon Pop rounds-mode example:
// "reps" to 4, "countdown" to 2 → 4 rounds × 2 balloons
)
)
```
_React Native_
```jsx
// Via postData for challenge integration
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
exercise: 'exerciseId',
countdown: 5,
reps: 20,
// Color Chase only:
gameTotalRounds: 15,
// Balloon Pop rounds-mode example:
// reps: 4, countdown: 2 → 4 rounds × 2 balloons
},
};
```
_Flutter_
```dart
// Via customParams for challenge integration
KinesteXAIFramework.createChallengeView(
exercise: exerciseId, // direct
customParams: {
"countdown": 5,
"reps": 20,
// Color Chase only:
"gameTotalRounds": 15,
// Balloon Pop rounds-mode example:
// "reps": 4, "countdown": 2 → 4 rounds × 2 balloons
},
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
exercise: exerciseId,
countdown: 5,
reps: 20,
// Color Chase only:
gameTotalRounds: 15,
// Balloon Pop rounds-mode example:
// reps: 4, countdown: 2 → 4 rounds × 2 balloons
};
```
_React (TypeScript)_
```tsx
// Via postData for challenge integration
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
exercise: 'exerciseId',
countdown: 5,
reps: 20,
// Color Chase only:
gameTotalRounds: 15,
// Balloon Pop rounds-mode example:
// reps: 4, countdown: 2 → 4 rounds × 2 balloons
},
};
```
### Complete UX Customization
Customize the Complete UX (Main View) home page experience.
| Parameter | Type | Description | Effect |
|-----------|------|-------------|--------|
| challenges_home | array | Custom challenges/games for home screen | Defines the two challenge/game options shown on the Complete UX home page |
**challenges_home Configuration:**
The `challenges_home` parameter allows you to customize the challenges and games displayed on the home screen of the Complete UX experience. You must pass exactly **2 objects** in the array.
**Array Object Structure:**
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| id | string | Yes | Exercise ID for challenges, or game ID for games |
| name | string | Yes | Display name shown in the UI |
| isGame | boolean | Yes | Set to `true` for games, `false` for challenges |
**Available Games:**
- `balloonpop` - Balloon Pop game
- `aliensquatshooter` - Alien Squat Shooter game
- `colorchase` - Color Chase game
**Combination Options:**
- Two challenges (both with `isGame: false`)
- Two games (both with `isGame: true`)
- One challenge + one game (mixed `isGame` values)
**Note:** You can pass any exercise ID as a challenge. When specifying a game, ensure `isGame` is set to `true`.
**Complete UX Home Page Customization**
_Swift (iOS)_
```swift
// Customize challenges on Complete UX home page
let exerciseId = "your-exercise-id" // Get from Content API
let exerciseName = "Your Exercise Name"
kinestex.createMainView(
customParams: [
"challenges_home": [
["id": exerciseId, "name": exerciseName, "isGame": false],
["id": "balloonpop", "name": "Balloon Pop", "isGame": true]
]
]
)
```
_Kotlin (Android)_
```kotlin
// Customize challenges on Complete UX home page
val exerciseId = "your-exercise-id" // Get from Content API
val exerciseName = "Your Exercise Name"
KinesteXSDK.createMainView(
customParams = mapOf(
"challenges_home" to listOf(
mapOf("id" to exerciseId, "name" to exerciseName, "isGame" to false),
mapOf("id" to "balloonpop", "name" to "Balloon Pop", "isGame" to true)
)
)
)
```
_React Native_
```jsx
// Customize challenges on Complete UX home page
const exerciseId = 'your-exercise-id'; // Get from Content API
const exerciseName = 'Your Exercise Name';
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
challenges_home: [
{ id: exerciseId, name: exerciseName, isGame: false },
{ id: 'balloonpop', name: 'Balloon Pop', isGame: true },
],
},
};
```
_Flutter_
```dart
// Customize challenges on Complete UX home page
final exerciseId = "your-exercise-id"; // Get from Content API
final exerciseName = "Your Exercise Name";
KinesteXAIFramework.createMainView(
customParams: {
"challenges_home": [
{"id": exerciseId, "name": exerciseName, "isGame": false},
{"id": "balloonpop", "name": "Balloon Pop", "isGame": true},
],
},
);
```
_HTML / JavaScript_
```html
// Customize challenges on Complete UX home page
const exerciseId = "your-exercise-id"; // Get from Content API
const exerciseName = "Your Exercise Name";
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
challenges_home: [
{ id: exerciseId, name: exerciseName, isGame: false },
{ id: "balloonpop", name: "Balloon Pop", isGame: true },
],
};
```
_React (TypeScript)_
```tsx
// Customize challenges on Complete UX home page
const exerciseId = 'your-exercise-id'; // Get from Content API
const exerciseName = 'Your Exercise Name';
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
challenges_home: [
{ id: exerciseId, name: exerciseName, isGame: false },
{ id: 'balloonpop', name: 'Balloon Pop', isGame: true },
],
},
};
```
### Leaderboard
Configure leaderboard functionality.
| Parameter | Type | Default | Description | Effect |
|-----------|------|---------|-------------|--------|
| showLeaderboard | boolean | - | Show leaderboard UI | Enables/disables leaderboard visibility |
| username | string | - | Display name for leaderboard | Sets the user's name shown on leaderboards |
| autoSubmitLeaderboard | boolean | false | Silent leaderboard submission (Challenge only) | When true, Challenge results are submitted to the leaderboard without showing the submission modal. The display name is read from the previously stored `leaderboard_username` (or falls back to the user ID) |
**Note:** Username is automatically saved to localStorage for future sessions.
**`autoSubmitLeaderboard` use case:** Use this only for Challenge integrations where you want every completion silently posted to the leaderboard (e.g., when your host app already manages display names and doesn't want a second prompt). Leave it unset (or `false`) to keep the standard modal-based flow.
**Leaderboard Configuration**
_Swift (iOS)_
```swift
// Via customParams
kinestex.createView(
customParams: [
"showLeaderboard": true,
"username": "FitnessPro123",
"autoSubmitLeaderboard": true // Challenge-only: skip submit modal
]
)
```
_Kotlin (Android)_
```kotlin
// Via customParams
KinesteXSDK.createView(
customParams = mapOf(
"showLeaderboard" to true,
"username" to "FitnessPro123",
"autoSubmitLeaderboard" to true // Challenge-only: skip submit modal
)
)
```
_React Native_
```jsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
showLeaderboard: true,
username: 'FitnessPro123',
autoSubmitLeaderboard: true, // Challenge-only: skip submit modal
},
};
```
_Flutter_
```dart
// Via customParams
KinesteXAIFramework.createView(
customParams: {
"showLeaderboard": true,
"username": "FitnessPro123",
"autoSubmitLeaderboard": true, // Challenge-only: skip submit modal
},
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
showLeaderboard: true,
username: "FitnessPro123",
autoSubmitLeaderboard: true, // Challenge-only: skip submit modal
};
```
_React (TypeScript)_
```tsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
showLeaderboard: true,
username: 'FitnessPro123',
autoSubmitLeaderboard: true, // Challenge-only: skip submit modal
},
};
```
### Loading Screen
Customize the loading screen appearance (includes native overlay color that is displayed during initial loading phase).
| Parameter | Type | Default | Description | Effect |
|-----------|------|---------|-------------|--------|
| loadingStickmanColor | string | Theme default | Stickman animation color | Changes the color of the loading animation character |
| loadingBackgroundColor | string | Theme default | Background color | Sets the loading screen background color |
| loadingTextColor | string | Theme default | Text color | Sets the color of loading text and messages |
**SDK Support:**
- **Swift:** Direct support via `IStyle` class (hex values with #)
- **Flutter:** Direct support via `IStyle` class (hex values without #)
- **Kotlin:** Direct support via `IStyle` data class (hex values without #)
- **React Native/React:** Direct support via `style` object (hex values without #)
- **HTML/JS:** Direct in postData object
**Native Loading Overlay (Swift):**
The SDK displays a native overlay on top of the WebView until content is fully loaded, preventing users from seeing a blank screen.
- Overlay automatically hides when `KinestexLoaded` message is received
- Overlay color priority:
1. Uses `loadingBackgroundColor` if set (hex color with #)
2. Uses white (#FFFFFF) if `style = "light"`
3. Uses black (#000000) if `style = "dark"` (default)
**Style Properties:**
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| style | String? | "dark" | Base theme style ("dark" or "light") |
| themeName | String? | null | Custom theme name |
| loadingStickmanColor | String? | null | Color for the loading animation stickman (hex without #) |
| loadingBackgroundColor | String? | null | Background color during loading (hex without #) |
| loadingTextColor | String? | null | Text color during loading (hex without #) |
**Loading Screen Customization**
_Swift (iOS)_
```swift
// Direct SDK support via IStyle class
let customStyle = IStyle(
style: "dark",
loadingBackgroundColor: "#1A1A2E",
loadingStickmanColor: "#FF6B00",
loadingTextColor: "#FFFFFF"
)
kinestex.createWorkoutView(
workout: "Fitness Lite",
user: user,
style: customStyle,
isLoading: $isLoading,
onMessageReceived: { /* ... */ }
)
```
_Kotlin (Android)_
```kotlin
// Direct SDK support via IStyle class
KinesteXSDK.createWorkoutView(
context = this,
workoutName = "Fitness Lite",
style = IStyle(
style = "dark",
loadingBackgroundColor = "1A1A2E", // hex value without #
loadingStickmanColor = "FF6B00",
loadingTextColor = "FFFFFF"
),
isLoading = viewModel.isLoading,
onMessageReceived = { message ->
handleWebViewMessage(message)
},
permissionHandler = this
)
```
_React Native_
```jsx
// Direct support via style object
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
style: {
style: 'dark',
loadingBackgroundColor: '1A1A2E', // hex without #
loadingTextColor: 'FFFFFF',
},
customParameters: {
loadingStickmanColor: '#FF6B00', // via customParams
},
};
```
_Flutter_
```dart
// Direct SDK support via IStyle class
KinesteXAIFramework.createWorkoutView(
workoutName: "Fitness Lite",
isShowKinestex: showKinesteX,
isLoading: ValueNotifier(false),
style: IStyle(
style: 'dark',
loadingBackgroundColor: '1A1A2E', // hex without #
loadingStickmanColor: 'FF6B00',
loadingTextColor: 'FFFFFF',
),
onMessageReceived: (message) {
handleWebViewMessage(message);
},
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
loadingStickmanColor: "#FF6B00",
loadingBackgroundColor: "#1A1A2E",
loadingTextColor: "#FFFFFF",
};
```
_React (TypeScript)_
```tsx
// Direct support via style object
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
style: {
style: 'dark',
loadingBackgroundColor: '1A1A2E', // hex without #
loadingTextColor: 'FFFFFF',
},
customParameters: {
loadingStickmanColor: '#FF6B00', // via customParams
},
};
```
### Motion Tracking Settings
Control AI-powered motion tracking behavior.
| Parameter | Type | Default | Description | Effect |
|-----------|------|---------|-------------|--------|
| motionTrackingSettingOn | boolean | - | Show motion tracking toggle | Displays the AI tracking on/off setting |
| motionTrackingEnabled | boolean | - | Enable motion tracking | Session-level override for AI tracking (clears localStorage preference) |
| motionDataEnabled | boolean | true | Record per-frame motion data | When set to `false`, the SDK does NOT collect per-frame pose landmark data during the session. Session replay will be empty for affected sessions, but all other workout functionality is unaffected |
**Note:** When `motionTrackingEnabled` is explicitly set, it clears any saved user preference and uses the provided value for the session.
**`motionDataEnabled` — only use this if you know why it's necessary.** This flag exists for memory-constrained scenarios (e.g., long workouts with many unique exercise videos on older iOS devices where peak memory pressure can cause the WebView to crash). Disabling motion data reduces memory usage at the cost of losing session replay. The recorder state resets on every verification, so the flag does not leak across sessions. Default behavior is unchanged if you omit the parameter.
**Motion Tracking Settings**
_Swift (iOS)_
```swift
// Via customParams
kinestex.createView(
customParams: [
"motionTrackingSettingOn": true,
"motionTrackingEnabled": true,
"motionDataEnabled": false // Only set when memory-constrained
]
)
```
_Kotlin (Android)_
```kotlin
// Via customParams
KinesteXSDK.createView(
customParams = mapOf(
"motionTrackingSettingOn" to true,
"motionTrackingEnabled" to true,
"motionDataEnabled" to false // Only set when memory-constrained
)
)
```
_React Native_
```jsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
motionTrackingSettingOn: true,
motionTrackingEnabled: true,
motionDataEnabled: false, // Only set when memory-constrained
},
};
```
_Flutter_
```dart
// Via customParams
KinesteXAIFramework.createView(
customParams: {
"motionTrackingSettingOn": true,
"motionTrackingEnabled": true,
"motionDataEnabled": false, // Only set when memory-constrained
},
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
motionTrackingSettingOn: true,
motionTrackingEnabled: true,
motionDataEnabled: false, // Only set when memory-constrained
};
```
_React (TypeScript)_
```tsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
motionTrackingSettingOn: true,
motionTrackingEnabled: true,
motionDataEnabled: false, // Only set when memory-constrained
},
};
```
### Debug & Development
Parameters for debugging and development purposes.
| Parameter | Type | Default | Description | Effect |
|-----------|------|---------|-------------|--------|
| showDebugRecording | boolean | false | Show debug recording UI | Displays recording controls for debugging |
| showNetworkDebugTool | boolean | - | Show network debug panel | Displays network request monitoring tool |
| newModelId | string | - | Test model identifier | Loads a specific ML model version for testing |
**Note:** `showDebugRecording` can also be enabled via URL parameter `?debug=true`
**Debug & Development Settings**
_Swift (iOS)_
```swift
// Via customParams
kinestex.createView(
customParams: [
"showDebugRecording": true,
"showNetworkDebugTool": true,
"newModelId": "pose_model_v2_beta"
]
)
```
_Kotlin (Android)_
```kotlin
// Via customParams
KinesteXSDK.createView(
customParams = mapOf(
"showDebugRecording" to true,
"showNetworkDebugTool" to true,
"newModelId" to "pose_model_v2_beta"
)
)
```
_React Native_
```jsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
showDebugRecording: true,
showNetworkDebugTool: true,
newModelId: 'pose_model_v2_beta',
},
};
```
_Flutter_
```dart
// Via customParams
KinesteXAIFramework.createView(
customParams: {
"showDebugRecording": true,
"showNetworkDebugTool": true,
"newModelId": "pose_model_v2_beta",
},
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
showDebugRecording: true,
showNetworkDebugTool: true,
newModelId: "pose_model_v2_beta",
};
```
_React (TypeScript)_
```tsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
showDebugRecording: true,
showNetworkDebugTool: true,
newModelId: 'pose_model_v2_beta',
},
};
```
### Custom Workout
Configure custom workout sequences. For complete custom workout implementation, see [Custom Integration](/docs/integration/custom-integration).
| Parameter | Type | Description | Effect |
|-----------|------|-------------|--------|
| customWorkoutExercises | array | Array of exercise configurations | Defines a custom sequence of exercises with their parameters |
| restSpeeches | string[] | Rest period audio identifiers | Custom audio to play during rest periods |
| currentRestSpeech | string | Current rest speech identifier | Sets the active rest period audio |
| videoURL | string | Custom video URL | URL for custom exercise demonstration video |
**Custom Workout Flow:**
1. Pass `customWorkoutExercises` during initial verification
2. Send `workout_activity_action: "start"` message to begin the workout
3. The system will navigate to the workout flow automatically
**Custom Workout Parameters**
_Swift (iOS)_
```swift
// Via customParams for additional settings
let customExercises = [
WorkoutSequenceExercise(
exerciseId: "exercise-id-1",
reps: 15,
duration: nil,
includeRestPeriod: true,
restDuration: 20
)
]
kinestex.createCustomWorkoutView(
customWorkouts: customExercises, // direct
customParams: [
"restSpeeches": ["rest_speech_1", "rest_speech_2"],
"videoURL": "https://example.com/demo.mp4"
]
)
```
_Kotlin (Android)_
```kotlin
// Via customParams for additional settings
val customExercises = listOf(
WorkoutSequenceExercise(
exerciseId = "exercise-id-1",
reps = 15,
duration = null,
includeRestPeriod = true,
restDuration = 20
)
)
KinesteXSDK.createCustomWorkoutView(
customWorkouts = customExercises, // direct
customParams = mapOf(
"restSpeeches" to listOf("rest_speech_1", "rest_speech_2"),
"videoURL" to "https://example.com/demo.mp4"
)
)
```
_React Native_
```jsx
// Direct support in postData
const customWorkoutExercises = [
{
exerciseId: 'exercise-id-1',
reps: 15,
duration: null,
includeRestPeriod: true,
restDuration: 20,
},
];
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customWorkoutExercises: customWorkoutExercises, // direct
customParameters: {
restSpeeches: ['rest_speech_1', 'rest_speech_2'],
videoURL: 'https://example.com/demo.mp4',
},
};
```
_Flutter_
```dart
// Via customParams for additional settings
final customExercises = [
WorkoutSequenceExercise(
exerciseId: "exercise-id-1",
reps: 15,
duration: null,
includeRestPeriod: true,
restDuration: 20,
),
];
KinesteXAIFramework.createCustomWorkoutView(
customWorkouts: customExercises, // direct
customParams: {
"restSpeeches": ["rest_speech_1", "rest_speech_2"],
"videoURL": "https://example.com/demo.mp4",
},
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const customWorkoutExercises = [
{
exerciseId: "exercise-id-1",
reps: 15,
duration: null,
includeRestPeriod: true,
restDuration: 20,
},
];
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
customWorkoutExercises: customWorkoutExercises,
restSpeeches: ["rest_speech_1", "rest_speech_2"],
videoURL: "https://example.com/demo.mp4",
};
```
_React (TypeScript)_
```tsx
// Direct support in postData
const customWorkoutExercises: WorkoutSequenceExercise[] = [
{
exerciseId: 'exercise-id-1',
reps: 15,
duration: null,
includeRestPeriod: true,
restDuration: 20,
},
];
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customWorkoutExercises: customWorkoutExercises, // direct
customParameters: {
restSpeeches: ['rest_speech_1', 'rest_speech_2'],
videoURL: 'https://example.com/demo.mp4',
},
};
```
### Workout Activity Actions
Control workout state programmatically via postMessage using the `workout_activity_action` property.
| Action | Description |
|--------|-------------|
| pause_workout | Pauses the workout (video, timer, and tracking) |
| resume_workout | Resumes a paused workout |
| mute_workout | Mutes all audio (speech, sounds, and music) |
| unmute_workout | Unmutes all audio |
| mute_speech | Mutes only speech feedback (sounds still play) |
| unmute_speech | Unmutes speech feedback |
**Note:** These actions are sent via postMessage to the KinesteX iframe/webview after the workout has started.
**Workout Activity Actions**
_Swift (iOS)_
```swift
// Send workout activity action using @State binding
// First, declare the state variable and pass it to the view:
// @State var workoutAction: [String: Any]? = nil
// kinestex.createWorkoutView(..., workoutAction: $workoutAction, ...)
// Pause the workout
workoutAction = ["workout_activity_action": "pause_workout"]
// Resume the workout
workoutAction = ["workout_activity_action": "resume_workout"]
// Mute all audio
workoutAction = ["workout_activity_action": "mute_workout"]
// Unmute all audio
workoutAction = ["workout_activity_action": "unmute_workout"]
// Mute only speech (sounds still play)
workoutAction = ["workout_activity_action": "mute_speech"]
// Unmute speech
workoutAction = ["workout_activity_action": "unmute_speech"]
```
_Kotlin (Android)_
```kotlin
// Send workout activity action
// Pause the workout
KinesteXSDK.sendAction("workout_activity_action", "pause_workout")
// Resume the workout
KinesteXSDK.sendAction("workout_activity_action", "resume_workout")
// Mute all audio
KinesteXSDK.sendAction("workout_activity_action", "mute_workout")
// Unmute all audio
KinesteXSDK.sendAction("workout_activity_action", "unmute_workout")
// Mute only speech (sounds still play)
KinesteXSDK.sendAction("workout_activity_action", "mute_speech")
// Unmute speech
KinesteXSDK.sendAction("workout_activity_action", "unmute_speech")
```
_React Native_
```jsx
// Send workout activity action
const kinestexSDKRef = useRef(null);
// Pause the workout
kinestexSDKRef.current?.sendAction("workout_activity_action", "pause_workout");
// Resume the workout
kinestexSDKRef.current?.sendAction("workout_activity_action", "resume_workout");
// Mute all audio
kinestexSDKRef.current?.sendAction("workout_activity_action", "mute_workout");
// Unmute all audio
kinestexSDKRef.current?.sendAction("workout_activity_action", "unmute_workout");
// Mute only speech (sounds still play)
kinestexSDKRef.current?.sendAction("workout_activity_action", "mute_speech");
// Unmute speech
kinestexSDKRef.current?.sendAction("workout_activity_action", "unmute_speech");
```
_Flutter_
```dart
// Send workout activity action
// Pause the workout
KinesteXAIFramework.sendAction("workout_activity_action", "pause_workout");
// Resume the workout
KinesteXAIFramework.sendAction("workout_activity_action", "resume_workout");
// Mute all audio
KinesteXAIFramework.sendAction("workout_activity_action", "mute_workout");
// Unmute all audio
KinesteXAIFramework.sendAction("workout_activity_action", "unmute_workout");
// Mute only speech (sounds still play)
KinesteXAIFramework.sendAction("workout_activity_action", "mute_speech");
// Unmute speech
KinesteXAIFramework.sendAction("workout_activity_action", "unmute_speech");
```
_HTML / JavaScript_
```html
// Send workout activity action via postMessage
const iframe = document.getElementById('kinestex-iframe');
// Pause the workout
iframe.contentWindow.postMessage({
workout_activity_action: "pause_workout"
}, "*");
// Resume the workout
iframe.contentWindow.postMessage({
workout_activity_action: "resume_workout"
}, "*");
// Mute all audio
iframe.contentWindow.postMessage({
workout_activity_action: "mute_workout"
}, "*");
// Unmute all audio
iframe.contentWindow.postMessage({
workout_activity_action: "unmute_workout"
}, "*");
// Mute only speech (sounds still play)
iframe.contentWindow.postMessage({
workout_activity_action: "mute_speech"
}, "*");
// Unmute speech
iframe.contentWindow.postMessage({
workout_activity_action: "unmute_speech"
}, "*");
```
_React (TypeScript)_
```tsx
// Send workout activity action
import { useRef } from 'react';
import { type KinesteXSDKCamera } from 'kinestex-sdk-react-ts';
const ref = useRef(null);
// Pause the workout
ref.current?.sendAction("workout_activity_action", "pause_workout");
// Resume the workout
ref.current?.sendAction("workout_activity_action", "resume_workout");
// Mute all audio
ref.current?.sendAction("workout_activity_action", "mute_workout");
// Unmute all audio
ref.current?.sendAction("workout_activity_action", "unmute_workout");
// Mute only speech (sounds still play)
ref.current?.sendAction("workout_activity_action", "mute_speech");
// Unmute speech
ref.current?.sendAction("workout_activity_action", "unmute_speech");
```
### Navigation
Control navigation and routing behavior.
| Parameter | Type | Description | Effect |
|-----------|------|-------------|--------|
| instantRedirect | string | Path to redirect to | Immediately navigates to specified route after verification |
**Note:** Path will be normalized (leading `/` added if missing).
**Navigation Control**
_Swift (iOS)_
```swift
// Via customParams
kinestex.createView(
customParams: [
"instantRedirect": "/workout/start"
]
)
```
_Kotlin (Android)_
```kotlin
// Via customParams
KinesteXSDK.createView(
customParams = mapOf(
"instantRedirect" to "/workout/start"
)
)
```
_React Native_
```jsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
instantRedirect: '/workout/start',
},
};
```
_Flutter_
```dart
// Via customParams
KinesteXAIFramework.createView(
customParams: {
"instantRedirect": "/workout/start",
},
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
instantRedirect: "/workout/start",
};
```
_React (TypeScript)_
```tsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
instantRedirect: '/workout/start',
},
};
```
### Assessment Configuration
Parameters specific to assessment modes (TUG test, balance tests, etc.). For complete assessment data structures, see [Data Points](/docs/data-points).
| Parameter | Type | Default | Description | Effect |
|-----------|------|---------|-------------|--------|
| tugMinRequiredSpace | number | - | Minimum space requirement | Sets the required distance (in meters) for TUG assessment |
**Note:** For balance assessments (semitandemstand, fulltandem, sidebysidestand), the system automatically uses the "heavy" MediaPipe model for better accuracy.
**Assessment Configuration**
_Swift (iOS)_
```swift
// Via customParams for TUG assessment
kinestex.createAssessmentView(
exercise: "tugtest", // direct
customParams: [
"tugMinRequiredSpace": 3
]
)
```
_Kotlin (Android)_
```kotlin
// Via customParams for TUG assessment
KinesteXSDK.createAssessmentView(
exercise = "tugtest", // direct
customParams = mapOf(
"tugMinRequiredSpace" to 3
)
)
```
_React Native_
```jsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
exercise: 'tugtest',
tugMinRequiredSpace: 3,
},
};
```
_Flutter_
```dart
// Via customParams
KinesteXAIFramework.createAssessmentView(
exercise: "tugtest", // direct
customParams: {
"tugMinRequiredSpace": 3,
},
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
exercise: "tugtest",
tugMinRequiredSpace: 3,
};
```
_React (TypeScript)_
```tsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
exercise: 'tugtest',
tugMinRequiredSpace: 3,
},
};
```
### Audio Configuration
Control audio playback settings.
| Parameter | Type | Default | Description | Effect |
|-----------|------|---------|-------------|--------|
| enableM4a | boolean | false | Force M4A audio format | Forces the audio system to use M4A format instead of default |
**Audio Configuration**
_Swift (iOS)_
```swift
// Via customParams
kinestex.createView(
customParams: [
"enableM4a": true
]
)
```
_Kotlin (Android)_
```kotlin
// Via customParams
KinesteXSDK.createView(
customParams = mapOf(
"enableM4a" to true
)
)
```
_React Native_
```jsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
enableM4a: true,
},
};
```
_Flutter_
```dart
// Via customParams
KinesteXAIFramework.createView(
customParams: {
"enableM4a": true,
},
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
enableM4a: true,
};
```
_React (TypeScript)_
```tsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
enableM4a: true,
},
};
```
### Session & Data Saving
Control session saving and data upload behavior.
| Parameter | Type | Default | Description | Effect |
|-----------|------|---------|-------------|--------|
| shouldSendStats | boolean | false | Enable session saving | When enabled, workout sessions are automatically saved to the backend upon completion — including per-exercise stats, accuracy scores, calories, and motion recording data |
**Session Saving Configuration**
_Swift (iOS)_
```swift
// Via customParams
kinestex.createWorkoutView(
workout: "Full Body Burn",
customParams: [
"shouldSendStats": true
]
)
```
_Kotlin (Android)_
```kotlin
// Via customParams
KinesteXSDK.createWorkoutView(
workout = "Full Body Burn",
customParams = mapOf(
"shouldSendStats" to true
)
)
```
_React Native_
```jsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
shouldSendStats: true,
},
};
```
_Flutter_
```dart
// Via customParams
KinesteXAIFramework.createWorkoutView(
workout: "Full Body Burn",
customParams: {
"shouldSendStats": true,
},
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
shouldSendStats: true,
};
```
_React (TypeScript)_
```tsx
// Via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
shouldSendStats: true,
},
};
```
### Plan Context
Pass plan progression context so the SDK associates a directly-launched workout with the correct plan. After the workout finishes, the SDK posts `plan_progression_saved` (or `plan_progression_failed` on error). See [Plans & Programs events](/docs/data-points#plans-programs).
| Parameter | Type | Description |
|-----------|------|-------------|
| planId | string | The ID of the plan the workout belongs to |
| planType | string | Type of plan (e.g., goal-based plan type, or "personalized") |
| progressWorkoutId | string | The plan-day workout ID used to record progression |
**When to use:** Only when you launch a workout **directly** via the SDK (not through the in-app plan UI) and want it to count toward plan progression. The in-app plan flow handles this automatically — you don't need to pass these fields if the user navigates from the plan dashboard.
**Note:** If you launch a workout without these fields, it is treated as a standalone session and won't update plan progression.
**Plan Context Configuration**
_Swift (iOS)_
```swift
// Pass plan context when launching a workout directly
kinestex.createWorkoutView(
workout: "Day 3 Workout",
customParams: [
"planId": "plan_abc123",
"planType": "personalized",
"progressWorkoutId": "plan_day_3"
]
)
```
_Kotlin (Android)_
```kotlin
// Pass plan context when launching a workout directly
KinesteXSDK.createWorkoutView(
workout = "Day 3 Workout",
customParams = mapOf(
"planId" to "plan_abc123",
"planType" to "personalized",
"progressWorkoutId" to "plan_day_3"
)
)
```
_React Native_
```jsx
// Pass plan context via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
planId: 'plan_abc123',
planType: 'personalized',
progressWorkoutId: 'plan_day_3',
},
};
```
_Flutter_
```dart
// Pass plan context via customParams
KinesteXAIFramework.createWorkoutView(
workoutName: "Day 3 Workout",
customParams: {
"planId": "plan_abc123",
"planType": "personalized",
"progressWorkoutId": "plan_day_3",
},
);
```
_HTML / JavaScript_
```html
// Direct in postData object
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
planId: "plan_abc123",
planType: "personalized",
progressWorkoutId: "plan_day_3",
};
```
_React (TypeScript)_
```tsx
// Pass plan context via customParameters
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
planId: 'plan_abc123',
planType: 'personalized',
progressWorkoutId: 'plan_day_3',
},
};
```
### Plan Onboarding Prefill
Pre-fill or skip onboarding survey questions for personalized plans. All fields are optional — only provided fields will be pre-filled and their corresponding screens will be skipped. If all answers are provided for a goal-based plan, the user goes straight to results.
**Availability:** This parameter is used with \`createCustomComponentView\` (Swift, Kotlin, Flutter) or the \`CUSTOM_COMPONENT\` integration option (React Native) with \`route: "plan-onboarding"\`.
| Parameter | Type | Description |
|-----------|------|-------------|
| route | string | Must be \`"plan-onboarding"\` for this feature to work |
| planOnboardingPrefill | object | Object containing prefill values for onboarding survey |
| planOnboardingPrefill.goal | string | User's fitness goal (e.g., "weight_loss") |
| planOnboardingPrefill.healthIssues | string[] | List of health issues (e.g., ["back_pain"]) |
| planOnboardingPrefill.injuries | string[] | List of injuries (empty array for none) |
| planOnboardingPrefill.duration | number | Preferred workout duration in minutes |
| planOnboardingPrefill.lifestyle | string | User's activity level (e.g., "sedentary", "active") |
| planOnboardingPrefill.assessmentOnly | boolean | When \`true\`, all survey screens (goal, health issues, injuries, duration, lifestyle) are skipped and the user lands directly on the fitness assessment. Any previously stored plan ID is cleared so a fresh personalized plan is generated after the assessment completes. All other prefill fields are ignored when this is set |
**\`assessmentOnly\` use case:** Use this for **reassessment flows** — when the user already has a profile (goal, lifestyle, etc.) recorded in your host app and you only want them to perform a fresh fitness assessment to regenerate their personalized plan. Don't combine it with the other prefill fields; they will be ignored.
**\`remind_me_later_clicked\` event:** When the user is on the Assessment screen inside the plan-onboarding flow and taps "Remind me later", the SDK posts a \`remind_me_later_clicked\` PostMessage so your host app can dismiss the SDK and schedule a follow-up prompt. This event is only fired from the plan-onboarding page.
**Plan Onboarding Prefill Configuration**
_Swift (iOS)_
```swift
// Use createCustomComponentView with route "plan-onboarding"
kinestex.createCustomComponentView(
route: "plan-onboarding",
customParams: [
"planOnboardingPrefill": [
"goal": "weight_loss",
"healthIssues": ["back_pain"],
"injuries": [],
"duration": 30,
"lifestyle": "sedentary"
]
]
)
// Reassessment-only flow (skips the entire survey)
kinestex.createCustomComponentView(
route: "plan-onboarding",
customParams: [
"planOnboardingPrefill": [
"assessmentOnly": true
]
]
)
```
_Kotlin (Android)_
```kotlin
// Use createCustomComponentView with route "plan-onboarding"
KinesteXSDK.createCustomComponentView(
route = "plan-onboarding",
customParams = mapOf(
"planOnboardingPrefill" to mapOf(
"goal" to "weight_loss",
"healthIssues" to listOf("back_pain"),
"injuries" to emptyList(),
"duration" to 30,
"lifestyle" to "sedentary"
)
)
)
```
_React Native_
```jsx
// Use CUSTOM_COMPONENT integration with route "plan-onboarding"
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
planOnboardingPrefill: {
goal: 'weight_loss',
healthIssues: ['back_pain'],
injuries: [],
duration: 30,
lifestyle: 'sedentary',
},
},
};
```
_Flutter_
```dart
// Use createCustomComponentView with route "plan-onboarding"
KinesteXAIFramework.createCustomComponentView(
route: "plan-onboarding",
customParams: {
"planOnboardingPrefill": {
"goal": "weight_loss",
"healthIssues": ["back_pain"],
"injuries": [],
"duration": 30,
"lifestyle": "sedentary",
},
},
);
```
_HTML / JavaScript_
```html
const srcURL = "https://ai.kinestex.com/plan-onboarding";
const postData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
planOnboardingPrefill: {
goal: "weight_loss",
healthIssues: ["back_pain"],
injuries: [],
duration: 30,
lifestyle: "sedentary",
},
};
// Reassessment-only flow (skips the entire survey)
const reassessmentPostData = {
userId: "user-123",
company: "YOUR_COMPANY",
key: "YOUR_API_KEY",
planOnboardingPrefill: {
assessmentOnly: true,
},
};
```
_React (TypeScript)_
```tsx
// Use CUSTOM_COMPONENT integration with route "plan-onboarding"
const postData: IPostData = {
key: 'YOUR_API_KEY',
userId: 'user-123',
company: 'YOUR_COMPANY',
customParameters: {
planOnboardingPrefill: {
goal: 'weight_loss',
healthIssues: ['back_pain'],
injuries: [],
duration: 30,
lifestyle: 'sedentary',
},
},
};
```
### URL Parameters
Some parameters can also be passed via URL query string for HTML/JS integrations:
| URL Parameter | Equivalent Config | Example |
|---------------|-------------------|---------|
| style | style | ?style=light |
| delegate | defaultDelegate | ?delegate=GPU |
| debug | showDebugRecording | ?debug=true |
URL parameters serve as defaults and can be overridden by parameters passed in the postMessage data.
### Complete Example
Here's a comprehensive example combining multiple parameter categories:
**Complete Configuration Example**
_Swift (iOS)_
```swift
// Complete configuration example
let kinestex = KinesteXAIKit(
apiKey: "YOUR_API_KEY",
companyName: "MyFitnessApp",
userId: "user_12345"
)
let user = UserDetails(
age: 28,
height: 165,
weight: 60,
gender: .Female,
lifestyle: .Active
)
// Theme & Loading via IStyle class (hex values with #)
let customStyle = IStyle(
style: "dark",
loadingBackgroundColor: "#1A1A2E",
loadingStickmanColor: "#00FF88",
loadingTextColor: "#FFFFFF"
)
kinestex.createWorkoutView(
workout: "Fitness Lite",
user: user,
style: customStyle,
isLoading: $isLoading,
// Other customization via customParams
customParams: [
// Language
"language": "es",
// UI Controls
"isOnboarding": false,
"hideFeelingDialog": true,
// Camera
"landmarkColor": "#00FF88",
"showSilhouette": true,
"shouldShowCameraSelector": true,
// Leaderboard
"showLeaderboard": true,
"username": "FitUser28"
],
onMessageReceived: { message in
// Handle messages
}
)
```
_Kotlin (Android)_
```kotlin
// Complete configuration example
KinesteXSDK.initialize(
context = this,
apiKey = "YOUR_API_KEY",
companyName = "MyFitnessApp",
userId = "user_12345"
)
val userDetails = UserDetails(
age = 28,
height = 165,
weight = 60,
gender = Gender.FEMALE,
lifestyle = Lifestyle.ACTIVE
)
KinesteXSDK.createWorkoutView(
context = this,
workoutName = workoutId,
user = userDetails,
// Theme & Loading via IStyle class (hex values without #)
style = IStyle(
style = "dark",
loadingBackgroundColor = "1A1A2E",
loadingStickmanColor = "e94560",
loadingTextColor = "FFFFFF"
),
isLoading = viewModel.isLoading,
// Other customization via customParams
customParams = mapOf(
// Language
"language" to "es",
// UI Controls
"isOnboarding" to false,
"hideFeelingDialog" to true,
// Camera
"landmarkColor" to "#00FF88",
"showSilhouette" to true,
// Leaderboard
"showLeaderboard" to true,
"username" to "FitUser28"
),
onMessageReceived = { message ->
handleWebViewMessage(message)
},
permissionHandler = this
)
```
_React Native_
```jsx
// Complete configuration example
const postData: IPostData = {
// Required
key: 'YOUR_API_KEY',
userId: 'user_12345',
company: 'MyFitnessApp',
// User Profile (direct support)
age: 28,
height: 165,
weight: 60,
gender: 'Female',
lifestyle: Lifestyle.Active,
// Theme (direct support)
style: {
style: 'dark',
loadingBackgroundColor: '1A1A2E',
loadingTextColor: 'FFFFFF',
},
// Additional parameters via customParameters
customParameters: {
// Language
language: 'es',
// UI Controls
isOnboarding: false,
hideFeelingDialog: true,
// Camera
landmarkColor: '#00FF88',
showSilhouette: true,
shouldShowCameraSelector: true,
// Leaderboard
showLeaderboard: true,
username: 'FitUser28',
},
};
```
_Flutter_
```dart
// Complete configuration example
await KinesteXAIFramework.initialize(
apiKey: "YOUR_API_KEY",
companyName: "MyFitnessApp",
userId: "user_12345",
);
final userDetails = UserDetails(
age: 28,
height: 165,
weight: 60,
gender: Gender.female,
lifestyle: Lifestyle.active,
);
KinesteXAIFramework.createWorkoutView(
workoutName: "Fitness Lite",
user: userDetails,
isShowKinestex: showKinesteX,
isLoading: ValueNotifier(false),
// Theme & Loading via IStyle class
style: IStyle(
style: 'dark',
loadingBackgroundColor: '1A1A2E', // hex without #
loadingStickmanColor: 'e94560',
loadingTextColor: 'FFFFFF',
),
// Other customization via customParams
customParams: {
// Language
"language": "es",
// UI Controls
"isOnboarding": false,
"hideFeelingDialog": true,
// Camera
"landmarkColor": "#00FF88",
"showSilhouette": true,
// Leaderboard
"showLeaderboard": true,
"username": "FitUser28",
},
onMessageReceived: (message) {
handleWebViewMessage(message);
},
);
```
_HTML / JavaScript_
```html
// Complete configuration example
// All parameters passed as flat object
const postData = {
// Required
userId: "user_12345",
company: "MyFitnessApp",
key: "YOUR_API_KEY",
// User Profile
age: 28,
height: 165,
weight: 60,
gender: "Female",
// Theme
style: "dark",
// Language
language: "es",
// UI Controls
isOnboarding: false,
hideFeelingDialog: true,
// Camera
landmarkColor: "#00FF88",
showSilhouette: true,
shouldShowCameraSelector: true,
// Leaderboard
showLeaderboard: true,
username: "FitUser28",
// Loading
loadingBackgroundColor: "#1A1A2E",
loadingTextColor: "#FFFFFF",
};
// Send to iframe
webView.contentWindow.postMessage(postData, srcURL);
```
_React (TypeScript)_
```tsx
// Complete configuration example
const postData: IPostData = {
// Required
key: 'YOUR_API_KEY',
userId: 'user_12345',
company: 'MyFitnessApp',
// User Profile (direct support)
age: 28,
height: 165,
weight: 60,
gender: 'Female',
lifestyle: Lifestyle.Active,
// Theme (direct support)
style: {
style: 'dark',
loadingBackgroundColor: '1A1A2E',
loadingTextColor: 'FFFFFF',
},
// Additional parameters via customParameters
customParameters: {
// Language
language: 'es',
// UI Controls
isOnboarding: false,
hideFeelingDialog: true,
// Camera
landmarkColor: '#00FF88',
showSilhouette: true,
shouldShowCameraSelector: true,
// Leaderboard
showLeaderboard: true,
username: 'FitUser28',
},
};
```
---
## Content API
The KinesteX Content API provides access to workout plans, individual workouts, and exercises. Use it to build custom content browsers, create personalized workout recommendations, or integrate KinesteX content into your app.
**SDK vs REST API:**
- **Swift, Kotlin, Flutter**: Use the built-in SDK convenience methods (recommended)
- **React Native, React TypeScript, HTML/JS**: Use direct REST API calls
**Base URL:** `https://admin.kinestex.com/api/v1/`
**Available Endpoints:**
| Endpoint | Description |
|----------|-------------|
| /workouts | Fetch workout content |
| /plans | Fetch workout plan content |
| /exercises | Fetch exercise content |
**Headers (REST API only):**
| Header | Description |
|--------|-------------|
| x-api-key | Your API key for authentication |
| x-company-name | The name of your company |
### Getting Started
Before using the Content API, ensure your SDK is properly initialized. The API methods are only available after initialization.
**For SDK platforms (Swift, Kotlin, Flutter):** Initialize the SDK with your credentials
**For REST platforms (React Native, React TypeScript, HTML/JS):** Set up your request headers
**Initialize SDK / Setup Headers**
_Swift (iOS)_
```swift
import KinesteXAIKit
// Initialize KinesteXAIKit with your credentials
let kinestex = KinesteXAIKit(
apiKey: "YOUR_API_KEY",
companyName: "YOUR_COMPANY",
userId: "user_123"
)
// Now you can use the Content API methods:
// - kinestex.fetchWorkouts()
// - kinestex.fetchPlans()
// - kinestex.fetchExercises()
// - kinestex.fetchWorkout(id:)
// - kinestex.fetchPlan(id:)
// - kinestex.fetchExercise(id:)
// - kinestex.fetchContent(contentType:, ...)
```
_Kotlin (Android)_
```kotlin
import com.kinestex.kinestexsdkkotlin.KinesteXSDK
// SDK must be initialized before using Content API
// This is typically done in your Application class or Activity
// Access Content API through KinesteXSDK.api
// Available method:
// KinesteXSDK.api.fetchAPIContentData(
// contentType: ContentType,
// id: String? = null,
// title: String? = null,
// category: String? = null,
// bodyParts: List? = null,
// lastDocId: String? = null,
// limit: Int? = null
// ): APIContentResult
```
_Flutter_
```dart
import 'package:kinestex_sdk_flutter/kinestex_sdk.dart';
// Initialize the SDK before using Content API
await KinesteXAIFramework.initialize(
apiKey: "YOUR_API_KEY",
companyName: "YOUR_COMPANY",
userId: "user_123",
);
// Access Content API through:
// KinesteXAIFramework.apiService.fetchContent(...)
```
_React Native_
```jsx
// Set up headers for REST API calls
const API_KEY = 'YOUR_API_KEY';
const COMPANY_NAME = 'YOUR_COMPANY';
const BASE_URL = 'https://admin.kinestex.com/api/v1';
const headers = {
'x-api-key': API_KEY,
'x-company-name': COMPANY_NAME,
};
// Use these headers in all fetch requests
```
_HTML / JavaScript_
```html
// Set up headers for REST API calls
const API_KEY = 'YOUR_API_KEY';
const COMPANY_NAME = 'YOUR_COMPANY';
const BASE_URL = 'https://admin.kinestex.com/api/v1';
const headers = {
'x-api-key': API_KEY,
'x-company-name': COMPANY_NAME,
};
// Use these headers in all fetch requests
```
_React (TypeScript)_
```tsx
// Set up headers for REST API calls
const API_KEY = 'YOUR_API_KEY';
const COMPANY_NAME = 'YOUR_COMPANY';
const BASE_URL = 'https://admin.kinestex.com/api/v1';
const headers: HeadersInit = {
'x-api-key': API_KEY,
'x-company-name': COMPANY_NAME,
};
// TypeScript interfaces for API responses
interface WorkoutModel {
id: string;
title: string;
category: string;
calories: number;
total_minutes: number;
body_parts: string[];
dif_level: string;
description: string;
workout_desc_img: string;
sequence: ExerciseModel[];
}
interface ExerciseModel {
id: string;
title: string;
body_parts: string[];
video_url: string;
thumbnail_url: string;
model_id: string;
}
interface PlanModel {
id: string;
title: string;
img_url: string;
category: Record;
levels: Record;
}
```
**Available SDK Methods** — Swift (iOS)
KinesteXAIKit provides convenient methods that handle all the complexity of API calls for you.
_Convenience Methods (Recommended)_
```swift
// Fetch lists with optional filters
func fetchWorkouts(category: String? = nil, bodyParts: [BodyPart]? = nil, limit: Int? = 10, lastDocId: String? = nil, lang: String = "en") async -> Result
func fetchExercises(bodyParts: [BodyPart]? = nil, limit: Int? = 10, lastDocId: String? = nil, lang: String = "en") async -> Result
func fetchPlans(category: String? = nil, limit: Int? = 10, lastDocId: String? = nil, lang: String = "en") async -> Result
// Fetch single items by ID
func fetchWorkout(id: String, lang: String = "en") async -> Result
func fetchExercise(id: String, lang: String = "en") async -> Result
func fetchPlan(id: String, lang: String = "en") async -> Result
```
_Advanced Method (Full Control)_
```swift
// Use fetchContent for advanced filtering or when you need the raw result type
func fetchContent(
contentType: ContentType, // .workout, .plan, .exercise
id: String? = nil,
title: String? = nil,
lang: String = "en",
category: String? = nil,
bodyParts: [BodyPart]? = nil,
lastDocId: String? = nil,
limit: Int? = nil
) async -> APIContentResult
```
**ContentType Enum** — Kotlin (Android)
Use these values to specify what type of content to fetch.
_Available Content Types_
```kotlin
enum class ContentType {
WORKOUT, // Fetch workouts
PLAN, // Fetch workout plans
EXERCISE // Fetch exercises
}
```
**ContentType Enum** — Flutter
Use these values to specify what type of content to fetch.
_Available Content Types_
```dart
enum ContentType {
workout, // Fetch workouts
plan, // Fetch workout plans
exercise // Fetch exercises
}
```
### Fetching Content Lists
Fetch lists of workouts, plans, or exercises with optional filtering.
**Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| category | String | Filter by category. **Required for plans** in Flutter, Swift, and Kotlin SDKs. Optional for workouts and exercises. |
| bodyParts | [BodyPart] | Filter by targeted body parts (optional) |
| include_kinestex | Bool | Include KinesteX workout library in results (default: true) |
| limit | Int | Number of results to return (default: 10) |
| lang | String | Language code (default: "en") |
| lastDocId | String | For pagination (optional) |
| translation_languages | String | Filter by available translations. Comma-separated language codes (e.g. "es,fr") or repeated query parameters (optional, REST API only) |
**Fetch Workouts**
_Swift (iOS)_
```swift
// Fetch workouts with optional filters
Task {
let result = await kinestex.fetchWorkouts(
category: "Fitness", // or "Rehabilitation"
limit: 10
)
switch result {
case .success(let response):
let workouts = response.workouts
print("Fetched \(workouts.count) workouts")
for workout in workouts {
print("- \(workout.title): \(workout.totalMinutes ?? 0) mins")
}
// Store lastDocId for pagination
let nextPageId = response.lastDocId
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
```
_Kotlin (Android)_
```kotlin
// Fetch workouts using coroutines
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
KinesteXSDK.api.fetchAPIContentData(
contentType = ContentType.WORKOUT,
category = "Fitness", // or "Rehabilitation"
limit = 10
)
}
when (result) {
is APIContentResult.Workouts -> {
val workouts = result.workouts
Log.d("API", "Fetched ${workouts.size} workouts")
workouts.forEach { workout ->
Log.d("API", "- ${workout.title}")
}
// Store lastDocId for pagination
val nextPageId = result.lastDocId
}
is APIContentResult.Error -> {
Log.e("API", "Error: ${result.message}")
}
else -> {
Log.w("API", "Unexpected result type")
}
}
}
```
_Flutter_
```dart
// Fetch workouts
Future fetchWorkouts() async {
final result = await KinesteXAIFramework.apiService.fetchContent(
contentType: ContentType.workout,
category: "Fitness", // or "Rehabilitation"
limit: 10,
);
switch (result) {
case WorkoutsResult(:final response):
final workouts = response.workouts;
print('Fetched ${workouts.length} workouts');
for (final workout in workouts) {
print('- ${workout.title}');
}
// Store lastDocId for pagination
final nextPageId = response.lastDocId;
case ErrorResult(:final message):
print('Error: $message');
default:
print('Unexpected result type');
}
}
```
_React Native_
```jsx
// Fetch workouts using fetch API
const fetchWorkouts = async (
category?: string,
limit: number = 10
): Promise => {
const params = new URLSearchParams({
limit: String(limit),
});
if (category) {
params.append('category', category);
}
const response = await fetch(
`${BASE_URL}/workouts?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data = await response.json();
return data.workouts;
};
// Usage
const workouts = await fetchWorkouts('Fitness', 10);
console.log(`Fetched ${workouts.length} workouts`);
```
_HTML / JavaScript_
```html
// Fetch workouts using fetch API
async function fetchWorkouts(category, limit = 10) {
const params = new URLSearchParams({ limit: String(limit) });
if (category) {
params.append('category', category);
}
const response = await fetch(
`${BASE_URL}/workouts?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data = await response.json();
return data.workouts;
}
// Usage
fetchWorkouts('Fitness', 10)
.then(workouts => console.log(`Fetched ${workouts.length} workouts`))
.catch(error => console.error('Error:', error));
```
_React (TypeScript)_
```tsx
// Fetch workouts with TypeScript
interface WorkoutsResponse {
workouts: WorkoutModel[];
lastDocId?: string;
}
const fetchWorkouts = async (
category?: string,
limit: number = 10
): Promise => {
const params = new URLSearchParams({
limit: String(limit),
});
if (category) {
params.append('category', category);
}
const response = await fetch(
`${BASE_URL}/workouts?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
};
// Usage
const { workouts, lastDocId } = await fetchWorkouts('Fitness', 10);
console.log(`Fetched ${workouts.length} workouts`);
```
**Fetch Plans**
_Swift (iOS)_
```swift
// Fetch workout plans
Task {
let result = await kinestex.fetchPlans(
category: "Strength", // Rehabilitation, Weight Management, Cardio, Strength
limit: 5
)
switch result {
case .success(let response):
let plans = response.plans
print("Fetched \(plans.count) plans")
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
```
_Kotlin (Android)_
```kotlin
// Fetch workout plans
// IMPORTANT: Always provide 'category' when fetching plans.
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
KinesteXSDK.api.fetchAPIContentData(
contentType = ContentType.PLAN,
category = "Strength", // Required: Rehabilitation, Weight Management, Cardio, Strength
limit = 5
)
}
when (result) {
is APIContentResult.Plans -> {
val plans = result.plans
Log.d("API", "Fetched ${plans.size} plans")
}
is APIContentResult.Error -> {
Log.e("API", "Error: ${result.message}")
}
else -> {}
}
}
```
_Flutter_
```dart
// Fetch workout plans
// IMPORTANT: Always provide 'category' when fetching plans.
// If category is null, the SDK may interpret the response as a single PlanResult
// instead of PlansResult (list).
Future fetchPlans() async {
final result = await KinesteXAIFramework.apiService.fetchContent(
contentType: ContentType.plan,
category: "Strength", // Required: Rehabilitation, Weight Management, Cardio, Strength
limit: 5,
);
switch (result) {
case PlansResult(:final response):
final plans = response.plans;
print('Fetched ${plans.length} plans');
case ErrorResult(:final message):
print('Error: $message');
default:
break;
}
}
```
_React Native_
```jsx
// Fetch workout plans
const fetchPlans = async (
category?: string,
limit: number = 5
): Promise => {
const params = new URLSearchParams({ limit: String(limit) });
if (category) {
params.append('category', category);
}
const response = await fetch(
`${BASE_URL}/plans?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data = await response.json();
return data.plans;
};
// Usage - Plan categories: Rehabilitation, Weight Management, Cardio, Strength
const plans = await fetchPlans('Strength', 5);
```
_HTML / JavaScript_
```html
// Fetch workout plans
async function fetchPlans(category, limit = 5) {
const params = new URLSearchParams({ limit: String(limit) });
if (category) {
params.append('category', category);
}
const response = await fetch(
`${BASE_URL}/plans?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data = await response.json();
return data.plans;
}
// Usage - Plan categories: Rehabilitation, Weight Management, Cardio, Strength
fetchPlans('Strength', 5).then(plans => console.log(plans));
```
_React (TypeScript)_
```tsx
// Fetch workout plans
interface PlansResponse {
plans: PlanModel[];
lastDocId?: string;
}
const fetchPlans = async (
category?: string,
limit: number = 5
): Promise => {
const params = new URLSearchParams({ limit: String(limit) });
if (category) {
params.append('category', category);
}
const response = await fetch(
`${BASE_URL}/plans?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
};
// Usage - Plan categories: Rehabilitation, Weight Management, Cardio, Strength
const { plans } = await fetchPlans('Strength', 5);
```
**Fetch Exercises**
_Swift (iOS)_
```swift
// Fetch exercises filtered by body parts
Task {
let result = await kinestex.fetchExercises(
bodyParts: [.abs, .glutes],
limit: 10
)
switch result {
case .success(let response):
let exercises = response.exercises
print("Fetched \(exercises.count) exercises")
for exercise in exercises {
print("- \(exercise.title): \(exercise.bodyParts.joined(separator: ", "))")
}
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
```
_Kotlin (Android)_
```kotlin
// Fetch exercises filtered by body parts
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
KinesteXSDK.api.fetchAPIContentData(
contentType = ContentType.EXERCISE,
bodyParts = listOf(BodyPart.ABS, BodyPart.GLUTES),
limit = 10
)
}
when (result) {
is APIContentResult.Exercises -> {
val exercises = result.exercises
Log.d("API", "Fetched ${exercises.size} exercises")
exercises.forEach { exercise ->
Log.d("API", "- ${exercise.title}")
}
}
is APIContentResult.Error -> {
Log.e("API", "Error: ${result.message}")
}
else -> {}
}
}
```
_Flutter_
```dart
// Fetch exercises filtered by body parts
Future fetchExercises() async {
final result = await KinesteXAIFramework.apiService.fetchContent(
contentType: ContentType.exercise,
bodyParts: [BodyPart.abs, BodyPart.glutes],
limit: 10,
);
switch (result) {
case ExercisesResult(:final response):
final exercises = response.exercises;
print('Fetched ${exercises.length} exercises');
for (final exercise in exercises) {
print('- ${exercise.title}');
}
case ErrorResult(:final message):
print('Error: $message');
default:
break;
}
}
```
_React Native_
```jsx
// Fetch exercises filtered by body parts
const fetchExercises = async (
bodyParts?: string[],
limit: number = 10
): Promise => {
const params = new URLSearchParams({ limit: String(limit) });
if (bodyParts && bodyParts.length > 0) {
params.append('body_parts', bodyParts.join(','));
}
const response = await fetch(
`${BASE_URL}/exercises?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data = await response.json();
return data.exercises;
};
// Usage
const exercises = await fetchExercises(['Abs', 'Glutes'], 10);
```
_HTML / JavaScript_
```html
// Fetch exercises filtered by body parts
async function fetchExercises(bodyParts, limit = 10) {
const params = new URLSearchParams({ limit: String(limit) });
if (bodyParts && bodyParts.length > 0) {
params.append('body_parts', bodyParts.join(','));
}
const response = await fetch(
`${BASE_URL}/exercises?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data = await response.json();
return data.exercises;
}
// Usage
fetchExercises(['Abs', 'Glutes'], 10).then(exercises => console.log(exercises));
```
_React (TypeScript)_
```tsx
// Fetch exercises filtered by body parts
interface ExercisesResponse {
exercises: ExerciseModel[];
lastDocId?: string;
}
const fetchExercises = async (
bodyParts?: string[],
limit: number = 10
): Promise => {
const params = new URLSearchParams({ limit: String(limit) });
if (bodyParts && bodyParts.length > 0) {
params.append('body_parts', bodyParts.join(','));
}
const response = await fetch(
`${BASE_URL}/exercises?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
};
// Usage
const { exercises } = await fetchExercises(['Abs', 'Glutes'], 10);
```
### Fetching Single Items
Fetch a specific workout, plan, or exercise by ID or title.
**By ID:** Use the unique document ID for exact match
**By Title:** Use the content title (case-insensitive, returns first match)
**Fetch by ID**
_Swift (iOS)_
```swift
// Fetch a specific workout by ID
Task {
let result = await kinestex.fetchWorkout(id: "9zE1kzOzpU5d5dAJrPOY")
switch result {
case .success(let workout):
print("Workout: \(workout.title)")
print("Duration: \(workout.totalMinutes ?? 0) minutes")
print("Calories: \(workout.totalCalories ?? 0)")
print("Exercises: \(workout.sequence.count)")
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
// Fetch a specific exercise by ID
Task {
let result = await kinestex.fetchExercise(id: "jz73VFlUyZ9nyd64OjRb")
switch result {
case .success(let exercise):
print("Exercise: \(exercise.title)")
print("Model ID: \(exercise.modelId)")
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
// Fetch a specific plan by ID
Task {
let result = await kinestex.fetchPlan(id: "22B3qRU2r75hVXHgGiGx")
switch result {
case .success(let plan):
print("Plan: \(plan.title)")
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
```
_Kotlin (Android)_
```kotlin
// Fetch a specific workout by ID
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
KinesteXSDK.api.fetchAPIContentData(
contentType = ContentType.WORKOUT,
id = "9zE1kzOzpU5d5dAJrPOY"
)
}
when (result) {
is APIContentResult.Workout -> {
val workout = result.workout
Log.d("API", "Workout: ${workout.title}")
Log.d("API", "Duration: ${workout.totalMinutes} minutes")
}
is APIContentResult.Error -> {
Log.e("API", "Error: ${result.message}")
}
else -> {}
}
}
// Fetch a specific exercise by ID
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
KinesteXSDK.api.fetchAPIContentData(
contentType = ContentType.EXERCISE,
id = "jz73VFlUyZ9nyd64OjRb"
)
}
when (result) {
is APIContentResult.Exercise -> {
val exercise = result.exercise
Log.d("API", "Exercise: ${exercise.title}")
}
is APIContentResult.Error -> {
Log.e("API", "Error: ${result.message}")
}
else -> {}
}
}
```
_Flutter_
```dart
// Fetch a specific workout by ID
Future fetchWorkoutById() async {
final result = await KinesteXAIFramework.apiService.fetchContent(
contentType: ContentType.workout,
id: "9zE1kzOzpU5d5dAJrPOY",
);
switch (result) {
case WorkoutResult(:final workout):
print('Workout: ${workout.title}');
print('Duration: ${workout.totalMinutes} minutes');
print('Exercises: ${workout.sequence.length}');
case ErrorResult(:final message):
print('Error: $message');
default:
break;
}
}
// Fetch a specific exercise by ID
Future fetchExerciseById() async {
final result = await KinesteXAIFramework.apiService.fetchContent(
contentType: ContentType.exercise,
id: "jz73VFlUyZ9nyd64OjRb",
);
switch (result) {
case ExerciseResult(:final exercise):
print('Exercise: ${exercise.title}');
print('Model ID: ${exercise.modelId}');
case ErrorResult(:final message):
print('Error: $message');
default:
break;
}
}
```
_React Native_
```jsx
// Fetch a specific workout by ID
const fetchWorkoutById = async (id: string): Promise => {
const response = await fetch(
`${BASE_URL}/workouts/${id}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
};
// Fetch a specific exercise by ID
const fetchExerciseById = async (id: string): Promise => {
const response = await fetch(
`${BASE_URL}/exercises/${id}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
};
// Fetch a specific plan by ID
const fetchPlanById = async (id: string): Promise => {
const response = await fetch(
`${BASE_URL}/plans/${id}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
};
// Usage
const workout = await fetchWorkoutById('9zE1kzOzpU5d5dAJrPOY');
const exercise = await fetchExerciseById('jz73VFlUyZ9nyd64OjRb');
const plan = await fetchPlanById('22B3qRU2r75hVXHgGiGx');
```
_HTML / JavaScript_
```html
// Fetch a specific workout by ID
async function fetchWorkoutById(id) {
const response = await fetch(
`${BASE_URL}/workouts/${id}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
}
// Fetch a specific exercise by ID
async function fetchExerciseById(id) {
const response = await fetch(
`${BASE_URL}/exercises/${id}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
}
// Fetch a specific plan by ID
async function fetchPlanById(id) {
const response = await fetch(
`${BASE_URL}/plans/${id}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
}
// Usage
fetchWorkoutById('9zE1kzOzpU5d5dAJrPOY').then(workout => console.log(workout));
```
_React (TypeScript)_
```tsx
// Fetch a specific workout by ID
const fetchWorkoutById = async (id: string): Promise => {
const response = await fetch(
`${BASE_URL}/workouts/${id}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
};
// Fetch a specific exercise by ID
const fetchExerciseById = async (id: string): Promise => {
const response = await fetch(
`${BASE_URL}/exercises/${id}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
};
// Fetch a specific plan by ID
const fetchPlanById = async (id: string): Promise => {
const response = await fetch(
`${BASE_URL}/plans/${id}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
};
// Usage
const workout = await fetchWorkoutById('9zE1kzOzpU5d5dAJrPOY');
console.log(`Workout: ${workout.title}`);
```
**Fetch by Title**
_Swift (iOS)_
```swift
// Fetch content by title (returns first match)
Task {
let result = await kinestex.fetchContent(
contentType: .workout,
title: "Fitness Lite"
)
switch result {
case .workout(let workout):
print("Found workout: \(workout.title)")
case .error(let message):
print("Error: \(message)")
default:
print("Unexpected result type")
}
}
```
_Kotlin (Android)_
```kotlin
// Fetch content by title (returns first match)
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
KinesteXSDK.api.fetchAPIContentData(
contentType = ContentType.WORKOUT,
title = "Fitness Lite"
)
}
when (result) {
is APIContentResult.Workout -> {
Log.d("API", "Found workout: ${result.workout.title}")
}
is APIContentResult.Error -> {
Log.e("API", "Error: ${result.message}")
}
else -> {}
}
}
```
_Flutter_
```dart
// Fetch content by title (returns first match)
Future fetchByTitle() async {
final result = await KinesteXAIFramework.apiService.fetchContent(
contentType: ContentType.workout,
title: "Fitness Lite",
);
switch (result) {
case WorkoutResult(:final workout):
print('Found workout: ${workout.title}');
case ErrorResult(:final message):
print('Error: $message');
default:
break;
}
}
```
_React Native_
```jsx
// Fetch content by title (returns first match)
const fetchWorkoutByTitle = async (title: string): Promise => {
const response = await fetch(
`${BASE_URL}/workouts/${encodeURIComponent(title)}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
};
// Usage
const workout = await fetchWorkoutByTitle('Fitness Lite');
```
_HTML / JavaScript_
```html
// Fetch content by title (returns first match)
async function fetchWorkoutByTitle(title) {
const response = await fetch(
`${BASE_URL}/workouts/${encodeURIComponent(title)}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
}
// Usage
fetchWorkoutByTitle('Fitness Lite').then(workout => console.log(workout));
```
_React (TypeScript)_
```tsx
// Fetch content by title (returns first match)
const fetchWorkoutByTitle = async (title: string): Promise => {
const response = await fetch(
`${BASE_URL}/workouts/${encodeURIComponent(title)}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
};
// Usage
const workout = await fetchWorkoutByTitle('Fitness Lite');
```
### Filtering & Parameters
Filter content by category and body parts for targeted results.
**Workout Categories:** Fitness, Rehabilitation
**Plan Categories:** Rehabilitation, Weight Management, Cardio, Strength
> **Important (Flutter, Swift & Kotlin SDKs):** When fetching plans, always provide the \`category\` parameter.
**Include KinesteX Library:**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| include_kinestex | Bool | true | When set to \`true\`, results include workouts, plans, and exercises from the KinesteX workout library. Set to \`false\` to exclude KinesteX library content and only return your own custom content. |
**Filter by Translation Languages (REST API):**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| translation_languages | String | — | Filter content by available translations. Pass a comma-separated list of language codes (e.g. \`es,fr\`) or repeat the parameter for each language (e.g. \`translation_languages=es&translation_languages=fr\`). Only content with translations in **all** specified languages is returned. Optional — omit to return all content regardless of translations. |
**Body Parts (BodyPart enum):**
| SDK Value (Swift) | SDK Value (Kotlin) | SDK Value (Flutter) | REST API Value |
|-------------------|--------------------|--------------------|----------------|
| .abs | ABS | BodyPart.abs | Abs |
| .biceps | BICEPS | BodyPart.biceps | Biceps |
| .calves | CALVES | BodyPart.calves | Calves |
| .chest | CHEST | BodyPart.chest | Chest |
| .externalOblique | EXTERNAL_OBLIQUE | BodyPart.externalOblique | External Oblique |
| .forearms | FOREARMS | BodyPart.forearms | Forearms |
| .glutes | GLUTES | BodyPart.glutes | Glutes |
| .hamstrings | HAMSTRINGS | BodyPart.hamstrings | Hamstrings |
| .lats | LATS | BodyPart.lats | Lats |
| .lowerBack | LOWER_BACK | BodyPart.lowerBack | Lower Back |
| .neck | NECK | BodyPart.neck | Neck |
| .quads | QUADS | BodyPart.quads | Quads |
| .shoulders | SHOULDERS | BodyPart.shoulders | Shoulders |
| .traps | TRAPS | BodyPart.traps | Traps |
| .triceps | TRICEPS | BodyPart.triceps | Triceps |
| .fullBody | FULL_BODY | BodyPart.fullBody | Full Body |
**Filter by Category and Body Parts**
_Swift (iOS)_
```swift
// Combine category and body parts filters
Task {
let result = await kinestex.fetchContent(
contentType: .workout,
category: "Fitness",
bodyParts: [.abs, .glutes, .quads],
limit: 10
)
switch result {
case .workouts(let response):
let workouts = response.workouts
print("Found \(workouts.count) workouts targeting abs, glutes, and quads")
case .error(let message):
print("Error: \(message)")
default:
break
}
}
```
_Kotlin (Android)_
```kotlin
// Combine category and body parts filters
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
KinesteXSDK.api.fetchAPIContentData(
contentType = ContentType.WORKOUT,
category = "Fitness",
bodyParts = listOf(BodyPart.ABS, BodyPart.GLUTES, BodyPart.QUADS),
limit = 10
)
}
when (result) {
is APIContentResult.Workouts -> {
val workouts = result.workouts
Log.d("API", "Found ${workouts.size} workouts")
}
is APIContentResult.Error -> {
Log.e("API", "Error: ${result.message}")
}
else -> {}
}
}
```
_Flutter_
```dart
// Combine category and body parts filters
Future fetchFilteredWorkouts() async {
final result = await KinesteXAIFramework.apiService.fetchContent(
contentType: ContentType.workout,
category: "Fitness",
bodyParts: [BodyPart.abs, BodyPart.glutes, BodyPart.quads],
limit: 10,
);
switch (result) {
case WorkoutsResult(:final response):
print('Found ${response.workouts.length} workouts');
case ErrorResult(:final message):
print('Error: $message');
default:
break;
}
}
```
_React Native_
```jsx
// Combine category and body parts filters
const fetchFilteredWorkouts = async (
category: string,
bodyParts: string[],
limit: number = 10
): Promise => {
const params = new URLSearchParams({
category,
body_parts: bodyParts.join(','),
limit: String(limit),
});
const response = await fetch(
`${BASE_URL}/workouts?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data = await response.json();
return data.workouts;
};
// Usage
const workouts = await fetchFilteredWorkouts(
'Fitness',
['Abs', 'Glutes', 'Quads'],
10
);
```
_HTML / JavaScript_
```html
// Combine category and body parts filters
async function fetchFilteredWorkouts(category, bodyParts, limit = 10) {
const params = new URLSearchParams({
category,
body_parts: bodyParts.join(','),
limit: String(limit),
});
const response = await fetch(
`${BASE_URL}/workouts?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data = await response.json();
return data.workouts;
}
// Usage
fetchFilteredWorkouts('Fitness', ['Abs', 'Glutes', 'Quads'], 10)
.then(workouts => console.log(workouts));
```
_React (TypeScript)_
```tsx
// Combine category and body parts filters
const fetchFilteredWorkouts = async (
category: string,
bodyParts: string[],
limit: number = 10
): Promise => {
const params = new URLSearchParams({
category,
body_parts: bodyParts.join(','),
limit: String(limit),
});
const response = await fetch(
`${BASE_URL}/workouts?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data = await response.json();
return data.workouts;
};
// Usage
const workouts = await fetchFilteredWorkouts(
'Fitness',
['Abs', 'Glutes', 'Quads'],
10
);
```
**Exclude KinesteX Library Content**
_Swift (iOS)_
```swift
// Fetch only your custom workouts (exclude KinesteX library)
Task {
let result = await kinestex.fetchWorkouts(
category: "Fitness",
includeKinestex: false, // Exclude KinesteX library content
limit: 10
)
switch result {
case .success(let response):
let workouts = response.workouts
print("Fetched \(workouts.count) custom workouts")
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
```
_Kotlin (Android)_
```kotlin
// Fetch only your custom workouts (exclude KinesteX library)
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
KinesteXSDK.api.fetchAPIContentData(
contentType = ContentType.WORKOUT,
category = "Fitness",
includeKinestex = false, // Exclude KinesteX library content
limit = 10
)
}
when (result) {
is APIContentResult.Workouts -> {
val workouts = result.workouts
Log.d("API", "Fetched ${workouts.size} custom workouts")
}
is APIContentResult.Error -> {
Log.e("API", "Error: ${result.message}")
}
else -> {}
}
}
```
_Flutter_
```dart
// Fetch only your custom workouts (exclude KinesteX library)
Future fetchCustomWorkouts() async {
final result = await KinesteXAIFramework.apiService.fetchContent(
contentType: ContentType.workout,
category: "Fitness",
includeKinestex: false, // Exclude KinesteX library content
limit: 10,
);
switch (result) {
case WorkoutsResult(:final response):
print('Fetched ${response.workouts.length} custom workouts');
case ErrorResult(:final message):
print('Error: $message');
default:
break;
}
}
```
_React Native_
```jsx
// Fetch only your custom workouts (exclude KinesteX library)
const fetchCustomWorkouts = async (
category: string,
limit: number = 10
): Promise => {
const params = new URLSearchParams({
category,
include_kinestex: 'false', // Exclude KinesteX library content
limit: String(limit),
});
const response = await fetch(
`${BASE_URL}/workouts?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data = await response.json();
return data.workouts;
};
// Usage - returns only your custom content
const customWorkouts = await fetchCustomWorkouts('Fitness', 10);
```
_HTML / JavaScript_
```html
// Fetch only your custom workouts (exclude KinesteX library)
async function fetchCustomWorkouts(category, limit = 10) {
const params = new URLSearchParams({
category,
include_kinestex: 'false', // Exclude KinesteX library content
limit: String(limit),
});
const response = await fetch(
`${BASE_URL}/workouts?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data = await response.json();
return data.workouts;
}
// Usage - returns only your custom content
fetchCustomWorkouts('Fitness', 10)
.then(workouts => console.log(workouts));
```
_React (TypeScript)_
```tsx
// Fetch only your custom workouts (exclude KinesteX library)
const fetchCustomWorkouts = async (
category: string,
limit: number = 10
): Promise => {
const params = new URLSearchParams({
category,
include_kinestex: 'false', // Exclude KinesteX library content
limit: String(limit),
});
const response = await fetch(
`${BASE_URL}/workouts?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data = await response.json();
return data.workouts;
};
// Usage - returns only your custom content
const customWorkouts = await fetchCustomWorkouts('Fitness', 10);
```
### Pagination
For large result sets, use pagination with the `lastDocId` parameter to fetch subsequent pages.
**How it works:**
1. **First request:** Omit `lastDocId` to get the first page
2. **Store the ID:** Save the `lastDocId` from the response
3. **Next request:** Pass the saved ID as `lastDocId` to get the next page
4. **Repeat:** Continue until no more results are returned
**Paginated Fetching**
_Swift (iOS)_
```swift
// Fetch all workouts with pagination
func fetchAllWorkouts() async throws -> [WorkoutModel] {
var allWorkouts: [WorkoutModel] = []
var lastDocId: String? = nil
repeat {
let result = await kinestex.fetchWorkouts(
category: "Fitness",
limit: 10,
lastDocId: lastDocId
)
switch result {
case .success(let response):
allWorkouts.append(contentsOf: response.workouts)
lastDocId = response.lastDocId
// If lastDocId is empty or nil, we've reached the end
if lastDocId?.isEmpty ?? true {
lastDocId = nil
}
print("Fetched page with \(response.workouts.count) workouts")
case .failure(let error):
throw error
}
} while lastDocId != nil
print("Total workouts fetched: \(allWorkouts.count)")
return allWorkouts
}
```
_Kotlin (Android)_
```kotlin
// Fetch all workouts with pagination
suspend fun fetchAllWorkouts(): List {
val allWorkouts = mutableListOf()
var lastDocId: String? = null
do {
val result = KinesteXSDK.api.fetchAPIContentData(
contentType = ContentType.WORKOUT,
category = "Fitness",
limit = 10,
lastDocId = lastDocId
)
when (result) {
is APIContentResult.Workouts -> {
allWorkouts.addAll(result.workouts)
lastDocId = result.lastDocId?.takeIf { it.isNotEmpty() }
Log.d("API", "Fetched page with ${result.workouts.size} workouts")
}
is APIContentResult.Error -> {
throw Exception(result.message)
}
else -> {
lastDocId = null
}
}
} while (lastDocId != null)
Log.d("API", "Total workouts fetched: ${allWorkouts.size}")
return allWorkouts
}
```
_Flutter_
```dart
// Fetch all workouts with pagination
Future> fetchAllWorkouts() async {
final allWorkouts = [];
String? lastDocId;
do {
final result = await KinesteXAIFramework.apiService.fetchContent(
contentType: ContentType.workout,
category: "Fitness",
limit: 10,
lastDocId: lastDocId,
);
switch (result) {
case WorkoutsResult(:final response):
allWorkouts.addAll(response.workouts);
lastDocId = response.lastDocId.isNotEmpty ? response.lastDocId : null;
print('Fetched page with ${response.workouts.length} workouts');
case ErrorResult(:final message):
throw Exception(message);
default:
lastDocId = null;
}
} while (lastDocId != null);
print('Total workouts fetched: ${allWorkouts.length}');
return allWorkouts;
}
```
_React Native_
```jsx
// Fetch all workouts with pagination
const fetchAllWorkouts = async (category: string): Promise => {
const allWorkouts: WorkoutModel[] = [];
let lastDocId: string | undefined = undefined;
do {
const params = new URLSearchParams({
category,
limit: '10',
});
if (lastDocId) {
params.append('lastDocId', lastDocId);
}
const response = await fetch(
`${BASE_URL}/workouts?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data = await response.json();
allWorkouts.push(...data.workouts);
lastDocId = data.lastDocId || undefined;
console.log(`Fetched page with ${data.workouts.length} workouts`);
} while (lastDocId);
console.log(`Total workouts fetched: ${allWorkouts.length}`);
return allWorkouts;
};
```
_HTML / JavaScript_
```html
// Fetch all workouts with pagination
async function fetchAllWorkouts(category) {
const allWorkouts = [];
let lastDocId = null;
do {
const params = new URLSearchParams({
category,
limit: '10',
});
if (lastDocId) {
params.append('lastDocId', lastDocId);
}
const response = await fetch(
`${BASE_URL}/workouts?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data = await response.json();
allWorkouts.push(...data.workouts);
lastDocId = data.lastDocId || null;
console.log(`Fetched page with ${data.workouts.length} workouts`);
} while (lastDocId);
console.log(`Total workouts fetched: ${allWorkouts.length}`);
return allWorkouts;
}
```
_React (TypeScript)_
```tsx
// Fetch all workouts with pagination
const fetchAllWorkouts = async (category: string): Promise => {
const allWorkouts: WorkoutModel[] = [];
let lastDocId: string | undefined = undefined;
do {
const params = new URLSearchParams({
category,
limit: '10',
});
if (lastDocId) {
params.append('lastDocId', lastDocId);
}
const response = await fetch(
`${BASE_URL}/workouts?${params}`,
{ headers }
);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
const data: WorkoutsResponse = await response.json();
allWorkouts.push(...data.workouts);
lastDocId = data.lastDocId || undefined;
console.log(`Fetched page with ${data.workouts.length} workouts`);
} while (lastDocId);
console.log(`Total workouts fetched: ${allWorkouts.length}`);
return allWorkouts;
};
```
### Error Handling
Handle API errors gracefully in your application.
**Response Codes:**
| Status | Description |
|--------|-------------|
| 200/201 | Request successful |
| 400 | Validation error (check parameters) |
| 401 | Unauthorized (invalid API key) |
| 404 | Content not found |
| 500 | Internal server error |
**Error Handling Patterns**
_Swift (iOS)_
```swift
// Comprehensive error handling with Swift SDK
Task {
let result = await kinestex.fetchWorkouts(category: "Fitness", limit: 10)
switch result {
case .success(let response):
// Handle successful response
let workouts = response.workouts
print("Success: Fetched \(workouts.count) workouts")
case .failure(let error):
// Handle different error types
if let urlError = error as? URLError {
switch urlError.code {
case .notConnectedToInternet:
print("No internet connection")
case .timedOut:
print("Request timed out")
default:
print("Network error: \(urlError.localizedDescription)")
}
} else {
print("Error: \(error.localizedDescription)")
}
}
}
// Using fetchContent for advanced error handling
Task {
let result = await kinestex.fetchContent(
contentType: .workout,
id: "invalid_id"
)
switch result {
case .workout(let workout):
print("Found: \(workout.title)")
case .error(let message):
// API returned an error message
print("API Error: \(message)")
case .rawData(let data, let errorMessage):
// Parsing failed, but raw data is available
print("Parse error: \(errorMessage ?? "Unknown")")
print("Raw data: \(data)")
default:
print("Unexpected result type")
}
}
```
_Kotlin (Android)_
```kotlin
// Comprehensive error handling with Kotlin SDK
lifecycleScope.launch {
try {
val result = withContext(Dispatchers.IO) {
KinesteXSDK.api.fetchAPIContentData(
contentType = ContentType.WORKOUT,
category = "Fitness",
limit = 10
)
}
when (result) {
is APIContentResult.Workouts -> {
// Handle successful response
val workouts = result.workouts
Log.d("API", "Success: Fetched ${workouts.size} workouts")
}
is APIContentResult.Error -> {
// API returned an error
Log.e("API", "API Error: ${result.message}")
// Show user-friendly message
Toast.makeText(
this@MainActivity,
"Failed to load workouts: ${result.message}",
Toast.LENGTH_LONG
).show()
}
else -> {
Log.w("API", "Unexpected result type")
}
}
} catch (e: Exception) {
// Handle network or other exceptions
Log.e("API", "Exception: ${e.message}")
when (e) {
is java.net.UnknownHostException -> {
Toast.makeText(this@MainActivity, "No internet connection", Toast.LENGTH_SHORT).show()
}
is java.net.SocketTimeoutException -> {
Toast.makeText(this@MainActivity, "Request timed out", Toast.LENGTH_SHORT).show()
}
else -> {
Toast.makeText(this@MainActivity, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
}
```
_Flutter_
```dart
// Comprehensive error handling with Flutter SDK
Future fetchWithErrorHandling() async {
try {
final result = await KinesteXAIFramework.apiService.fetchContent(
contentType: ContentType.workout,
category: "Fitness",
limit: 10,
);
switch (result) {
case WorkoutsResult(:final response):
// Handle successful response
print('Success: Fetched ${response.workouts.length} workouts');
case ErrorResult(:final message):
// API returned an error
print('API Error: $message');
// Show user-friendly message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to load workouts: $message')),
);
case RawDataResult(:final data, :final errorMessage):
// Parsing failed, but raw data is available
print('Parse error: ${errorMessage ?? "Unknown"}');
print('Raw data keys: ${data.keys}');
default:
print('Unexpected result type');
}
} on SocketException {
// No internet connection
print('No internet connection');
} on TimeoutException {
// Request timed out
print('Request timed out');
} catch (e) {
// Other errors
print('Error: $e');
}
}
```
_React Native_
```jsx
// Comprehensive error handling with fetch API
const fetchWithErrorHandling = async (): Promise => {
try {
const response = await fetch(
`${BASE_URL}/workouts?category=Fitness&limit=10`,
{ headers }
);
if (!response.ok) {
// Handle HTTP errors
switch (response.status) {
case 400:
throw new Error('Invalid request parameters');
case 401:
throw new Error('Invalid API key');
case 404:
throw new Error('Content not found');
case 500:
throw new Error('Server error - please try again later');
default:
throw new Error(`HTTP Error: ${response.status}`);
}
}
const data = await response.json();
// Check for API-level errors
if (data.error) {
throw new Error(data.error);
}
return data.workouts;
} catch (error) {
if (error instanceof TypeError && error.message === 'Network request failed') {
// No internet connection
console.error('No internet connection');
throw new Error('Please check your internet connection');
}
// Re-throw the error
throw error;
}
};
// Usage with error handling
try {
const workouts = await fetchWithErrorHandling();
console.log(`Fetched ${workouts.length} workouts`);
} catch (error) {
Alert.alert('Error', error.message);
}
```
_HTML / JavaScript_
```html
// Comprehensive error handling with fetch API
async function fetchWithErrorHandling() {
try {
const response = await fetch(
`${BASE_URL}/workouts?category=Fitness&limit=10`,
{ headers }
);
if (!response.ok) {
// Handle HTTP errors
switch (response.status) {
case 400:
throw new Error('Invalid request parameters');
case 401:
throw new Error('Invalid API key');
case 404:
throw new Error('Content not found');
case 500:
throw new Error('Server error - please try again later');
default:
throw new Error(`HTTP Error: ${response.status}`);
}
}
const data = await response.json();
// Check for API-level errors
if (data.error) {
throw new Error(data.error);
}
return data.workouts;
} catch (error) {
if (error instanceof TypeError && error.message === 'Failed to fetch') {
// No internet connection or CORS error
console.error('Network error');
throw new Error('Please check your internet connection');
}
// Re-throw the error
throw error;
}
}
// Usage with error handling
fetchWithErrorHandling()
.then(workouts => console.log(`Fetched ${workouts.length} workouts`))
.catch(error => alert(`Error: ${error.message}`));
```
_React (TypeScript)_
```tsx
// Comprehensive error handling with TypeScript
class APIError extends Error {
constructor(
message: string,
public statusCode?: number,
public originalError?: unknown
) {
super(message);
this.name = 'APIError';
}
}
const fetchWithErrorHandling = async (): Promise => {
try {
const response = await fetch(
`${BASE_URL}/workouts?category=Fitness&limit=10`,
{ headers }
);
if (!response.ok) {
// Handle HTTP errors
const errorMessages: Record = {
400: 'Invalid request parameters',
401: 'Invalid API key',
404: 'Content not found',
500: 'Server error - please try again later',
};
throw new APIError(
errorMessages[response.status] || `HTTP Error: ${response.status}`,
response.status
);
}
const data = await response.json();
// Check for API-level errors
if (data.error) {
throw new APIError(data.error);
}
return data.workouts;
} catch (error) {
if (error instanceof APIError) {
throw error;
}
if (error instanceof TypeError) {
throw new APIError('Please check your internet connection', undefined, error);
}
throw new APIError('An unexpected error occurred', undefined, error);
}
};
// Usage with error handling
try {
const workouts = await fetchWithErrorHandling();
console.log(`Fetched ${workouts.length} workouts`);
} catch (error) {
if (error instanceof APIError) {
console.error(`API Error (${error.statusCode}): ${error.message}`);
}
}
```
### Data Models
Reference for the data structures returned by the Content API.
**WorkoutModel:**
| Field | Type | Description |
|-------|------|-------------|
| id | String | Unique identifier |
| title | String | Workout name |
| category | String | Fitness or Rehabilitation |
| calories | Int? | Estimated calories burned |
| totalMinutes | Int? | Total duration in minutes |
| bodyParts | [String] | Targeted body parts |
| difficultyLevel | String? | Difficulty level |
| description | String | Workout description |
| imgURL | String | Workout thumbnail image |
| sequence | [ExerciseModel] | List of exercises |
**ExerciseModel:**
| Field | Type | Description |
|-------|------|-------------|
| id | String | Unique identifier |
| title | String | Exercise name |
| bodyParts | [String] | Targeted body parts |
| videoURL | String | Demo video URL |
| thumbnailURL | String | Thumbnail image URL |
| modelId | String | Motion tracking model ID (use in Camera Component) |
| description | String | Exercise description |
| steps | [String] | Step-by-step instructions |
| commonMistakes | String | Common mistakes to avoid |
| tips | String | Tips for proper form |
**PlanModel:**
| Field | Type | Description |
|-------|------|-------------|
| id | String | Unique identifier |
| title | String | Plan name |
| imgURL | String | Plan thumbnail image |
| category | PlanModelCategory | Category with description and levels |
| levels | [String: PlanLevel] | Dictionary of levels (1, 2, 3, etc.) |
| createdBy | String | Creator identifier |
**PlanLevel:**
| Field | Type | Description |
|-------|------|-------------|
| title | String | Level title |
| description | String | Level description |
| days | [String: PlanDay] | Dictionary of days |
**PlanDay:**
| Field | Type | Description |
|-------|------|-------------|
| title | String | Day title |
| description | String | Day description |
| workouts | [WorkoutSummary]? | List of workouts for this day |
**Working with Models**
_Swift (iOS)_
```swift
// Accessing workout model properties
Task {
let result = await kinestex.fetchWorkout(id: "9zE1kzOzpU5d5dAJrPOY")
switch result {
case .success(let workout):
// Basic properties
print("Title: \(workout.title)")
print("Category: \(workout.category ?? "N/A")")
print("Duration: \(workout.totalMinutes ?? 0) minutes")
print("Calories: \(workout.totalCalories ?? 0)")
print("Difficulty: \(workout.difficultyLevel ?? "N/A")")
// Body parts
print("Targets: \(workout.bodyParts.joined(separator: ", "))")
// Exercise sequence
print("\nExercises (\(workout.sequence.count)):")
for (index, exercise) in workout.sequence.enumerated() {
print("\(index + 1). \(exercise.title)")
print(" Model ID: \(exercise.modelId)") // Use for Camera Component
print(" Reps: \(exercise.workoutReps ?? exercise.averageReps ?? 0)")
}
// Access raw JSON if needed
if let rawJSON = workout.rawJSON {
print("\nRaw JSON available: \(rawJSON.keys.count) keys")
}
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
// Working with plan structure
Task {
let result = await kinestex.fetchPlan(id: "22B3qRU2r75hVXHgGiGx")
switch result {
case .success(let plan):
print("Plan: \(plan.title)")
print("Category: \(plan.category.description)")
// Iterate through levels
for (levelKey, level) in plan.levels {
print("\nLevel \(levelKey): \(level.title)")
// Iterate through days
for (dayKey, day) in level.days {
print(" Day \(dayKey): \(day.title)")
// List workouts for this day
if let workouts = day.workouts {
for workout in workouts {
print(" - \(workout.title) (\(workout.totalMinutes) min)")
}
}
}
}
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
```
_Kotlin (Android)_
```kotlin
// Accessing workout model properties
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
KinesteXSDK.api.fetchAPIContentData(
contentType = ContentType.WORKOUT,
id = "9zE1kzOzpU5d5dAJrPOY"
)
}
when (result) {
is APIContentResult.Workout -> {
val workout = result.workout
// Basic properties
Log.d("API", "Title: ${workout.title}")
Log.d("API", "Category: ${workout.category}")
Log.d("API", "Duration: ${workout.totalMinutes} minutes")
Log.d("API", "Calories: ${workout.calories}")
// Body parts
Log.d("API", "Targets: ${workout.bodyParts.joinToString(", ")}")
// Exercise sequence
Log.d("API", "Exercises (${workout.sequence.size}):")
workout.sequence.forEachIndexed { index, exercise ->
Log.d("API", "${index + 1}. ${exercise.title}")
Log.d("API", " Model ID: ${exercise.modelId}") // Use for Camera Component
}
// Pretty print as JSON
val gson = GsonBuilder().setPrettyPrinting().create()
val prettyJson = gson.toJson(workout)
Log.d("API", "JSON:\n$prettyJson")
}
is APIContentResult.Error -> {
Log.e("API", "Error: ${result.message}")
}
else -> {}
}
}
```
_Flutter_
```dart
// Accessing workout model properties
Future workWithModels() async {
final result = await KinesteXAIFramework.apiService.fetchContent(
contentType: ContentType.workout,
id: "9zE1kzOzpU5d5dAJrPOY",
);
switch (result) {
case WorkoutResult(:final workout):
// Basic properties
print('Title: ${workout.title}');
print('Category: ${workout.category}');
print('Duration: ${workout.totalMinutes} minutes');
print('Calories: ${workout.totalCalories}');
print('Difficulty: ${workout.difficultyLevel}');
// Body parts
print('Targets: ${workout.bodyParts.join(", ")}');
// Exercise sequence
print('\nExercises (${workout.sequence.length}):');
for (var i = 0; i < workout.sequence.length; i++) {
final exercise = workout.sequence[i];
print('${i + 1}. ${exercise.title}');
print(' Model ID: ${exercise.modelId}'); // Use for Camera Component
}
// Access raw JSON if needed
if (workout.rawJSON != null) {
print('\nRaw JSON available: ${workout.rawJSON!.keys.length} keys');
}
case ErrorResult(:final message):
print('Error: $message');
default:
break;
}
}
// Working with plan structure
Future workWithPlan() async {
final result = await KinesteXAIFramework.apiService.fetchContent(
contentType: ContentType.plan,
id: "22B3qRU2r75hVXHgGiGx",
);
switch (result) {
case PlanResult(:final plan):
print('Plan: ${plan.title}');
print('Category: ${plan.category.description}');
// Iterate through levels
plan.levels.forEach((levelKey, level) {
print('\nLevel $levelKey: ${level.title}');
// Iterate through days
level.days.forEach((dayKey, day) {
print(' Day $dayKey: ${day.title}');
// List workouts for this day
day.workouts?.forEach((workout) {
print(' - ${workout.title} (${workout.totalMinutes} min)');
});
});
});
case ErrorResult(:final message):
print('Error: $message');
default:
break;
}
}
```
_React Native_
```jsx
// TypeScript interfaces for Content API models
interface WorkoutModel {
id: string;
title: string;
category: string;
calories: number;
total_minutes: number;
body_parts: string[];
dif_level: string;
description: string;
workout_desc_img: string;
sequence: ExerciseModel[];
}
interface ExerciseModel {
id: string;
title: string;
body_parts: string[];
video_url: string;
male_video_url: string;
thumbnail_url: string;
male_thumbnail_url: string;
model_id: string;
description: string;
steps: string[];
common_mistakes: string;
tips: string;
workout_reps?: number;
workout_countdown?: number;
average_reps?: number;
average_countdown?: number;
rest_duration?: number;
}
interface PlanModel {
id: string;
title: string;
img_url: string;
category: {
description: string;
levels: Record;
};
levels: Record;
created_by: string;
}
interface PlanLevel {
title: string;
description: string;
days: Record;
}
interface PlanDay {
title: string;
description: string;
workouts?: WorkoutSummary[];
}
interface WorkoutSummary {
id: string;
img_url: string;
title: string;
calories?: number;
total_minutes: number;
}
// Working with fetched data
const displayWorkout = (workout: WorkoutModel) => {
console.log(`Title: ${workout.title}`);
console.log(`Duration: ${workout.total_minutes} minutes`);
console.log(`Calories: ${workout.calories}`);
console.log(`Targets: ${workout.body_parts.join(', ')}`);
console.log(`\nExercises (${workout.sequence.length}):`);
workout.sequence.forEach((exercise, index) => {
console.log(`${index + 1}. ${exercise.title}`);
console.log(` Model ID: ${exercise.model_id}`); // Use for Camera Component
});
};
```
_HTML / JavaScript_
```html
// Working with fetched workout data
function displayWorkout(workout) {
console.log(`Title: ${workout.title}`);
console.log(`Duration: ${workout.total_minutes} minutes`);
console.log(`Calories: ${workout.calories}`);
console.log(`Targets: ${workout.body_parts.join(', ')}`);
console.log(`\nExercises (${workout.sequence.length}):`);
workout.sequence.forEach((exercise, index) => {
console.log(`${index + 1}. ${exercise.title}`);
console.log(` Model ID: ${exercise.model_id}`); // Use for Camera Component
});
}
// Working with plan structure
function displayPlan(plan) {
console.log(`Plan: ${plan.title}`);
console.log(`Category: ${plan.category.description}`);
// Iterate through levels
Object.entries(plan.levels).forEach(([levelKey, level]) => {
console.log(`\nLevel ${levelKey}: ${level.title}`);
// Iterate through days
Object.entries(level.days).forEach(([dayKey, day]) => {
console.log(` Day ${dayKey}: ${day.title}`);
// List workouts for this day
if (day.workouts) {
day.workouts.forEach(workout => {
console.log(` - ${workout.title} (${workout.total_minutes} min)`);
});
}
});
});
}
```
_React (TypeScript)_
```tsx
// Full TypeScript interfaces for Content API models
interface WorkoutModel {
id: string;
title: string;
category: string;
calories: number;
total_minutes: number;
body_parts: string[];
dif_level: string;
description: string;
workout_desc_img: string;
sequence: ExerciseModel[];
}
interface ExerciseModel {
id: string;
title: string;
body_parts: string[];
video_url: string;
male_video_url: string;
thumbnail_url: string;
male_thumbnail_url: string;
model_id: string;
description: string;
steps: string[];
common_mistakes: string;
tips: string;
workout_reps?: number;
workout_countdown?: number;
average_reps?: number;
average_countdown?: number;
rest_duration?: number;
}
interface PlanModel {
id: string;
title: string;
img_url: string;
category: PlanCategory;
levels: Record;
created_by: string;
}
interface PlanCategory {
description: string;
levels: Record;
}
interface PlanLevel {
title: string;
description: string;
days: Record;
}
interface PlanDay {
title: string;
description: string;
workouts?: WorkoutSummary[];
}
interface WorkoutSummary {
id: string;
img_url: string;
title: string;
calories?: number;
total_minutes: number;
}
// Helper function to display workout
const displayWorkout = (workout: WorkoutModel): void => {
console.log(`Title: ${workout.title}`);
console.log(`Duration: ${workout.total_minutes} minutes`);
console.log(`Calories: ${workout.calories}`);
console.log(`Targets: ${workout.body_parts.join(', ')}`);
console.log(`\nExercises (${workout.sequence.length}):`);
workout.sequence.forEach((exercise, index) => {
console.log(`${index + 1}. ${exercise.title}`);
console.log(` Model ID: ${exercise.model_id}`); // Use for Camera Component
});
};
```
---
## Support
**Contact:**
• support@kinestex.com - Technical support
• hello@kinestex.com - Sales & demos
**Repositories:**
• [Swift/iOS](https://github.com/KinesteX/KinesteX-Swift-Demo)
• [Kotlin/Android](https://github.com/KinesteX/KinesteX-SDK-Kotlin)
• [React Native](https://github.com/KinesteX/KinesteX-SDK-ReactNative)
• [Flutter](https://github.com/KinesteX/KinesteX-SDK-Flutter)
• [HTML/JS](https://github.com/KinesteX/KinesteX-SDK-HTML-JS)
---