# 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
    customParams = mutableMapOf("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',
  },
};

<KinestexSDK
  ref={kinestexSDKRef}
  data={postData}
  integrationOption={IntegrationOption.CHALLENGE}
  handleMessage={(type, data) => {
    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<bool>(false),
  onMessageReceived: (message) {
    if (message is ExitKinestex) {
      setState(() => showKinesteX.value = 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',
  },
};

<KinesteXSDK
  data={postData}
  integrationOption={IntegrationOption.CHALLENGE}
  handleMessage={(type, data) => {
    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<String, Any>()

    // 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,
                customParams = 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 from 'kinestex-sdk-react-native';
import { IntegrationOption, IPostData } from 'kinestex-sdk-react-native/src/types';

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 (
    <View style={styles.container}>
      {showKinesteX ? (
        <KinestexSDK
          data={postData}
          integrationOption={IntegrationOption.CHALLENGE}
          handleMessage={(type) => {
            if (type === 'exit_kinestex') {
              setShowKinesteX(false);
            }
          }}
        />
      ) : (
        <Button
          title={`Start ${challengeExercise} Challenge (${challengeDuration}s)`}
          onPress={() => setShowKinesteX(true)}
        />
      )}
    </View>
  );
};

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<void> 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<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void dispose() {
    disposeKinesteXAIFramework();
    super.dispose();
  }

  Future<void> 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<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  ValueNotifier<bool> showKinesteX = ValueNotifier<bool>(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: <Widget>[
            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<bool>(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
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>KinesteX: Challenge</title>
  </head>
  <body>
    <button id="startKinesteX">Start KinesteX</button>
    <div id="webViewContainer" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; display: none;">
      <iframe id="webView" frameborder="0" allow="camera; autoplay; accelerometer; gyroscope; magnetometer" sandbox="allow-same-origin allow-scripts" style="width: 100%; height: 100%"></iframe>
    </div>
    <script>
      const webView = document.getElementById("webView");
      const webViewContainer = document.getElementById("webViewContainer");

      const postData = {
        userId: "YOUR_USER_ID",
        company: "YOUR_COMPANY",
        key: "YOUR_API_KEY",
        style: "dark",
        exercise: "Squats",
        showLeaderboard: true,
        countdown: 100,
      };

      const srcURL = "https://ai.kinestex.com/challenge";

      function sendMessage() {
        if (webView.contentWindow) {
          webView.contentWindow.postMessage(postData, srcURL);
        } else {
          setTimeout(() => {
            try {
              webView.contentWindow.postMessage(postData, srcURL);
            } catch {
              webView.contentWindow.postMessage(postData, srcURL);
            }
          }, 100);
        }
      }

      document.getElementById("startKinesteX").addEventListener("click", function () {
        webViewContainer.style.display = "block";
        webView.src = srcURL;
        webView.onload = sendMessage;
      });

      window.addEventListener("message", function (event) {
        if (event.origin !== "https://ai.kinestex.com") return;

        const message = JSON.parse(event.data);
        switch (message.type) {
          case "kinestex_launched":
            console.log("KinesteX is launched");
            break;
          case "kinestex_loaded":
            sendMessage();
            break;
          case "exit_kinestex":
            webViewContainer.style.display = "none";
            console.log("User exited the KinesteX view");
            break;
          // Handle other message types...
        }
      });
    </script>
  </body>
</html>
```

_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 (
    <div style={{ height: '100vh', width: '100vw' }}>
      {showKinesteX ? (
        <KinesteXSDK
          data={postData}
          integrationOption={IntegrationOption.CHALLENGE}
          handleMessage={(type) => {
            if (type === 'exit_kinestex') {
              setShowKinesteX(false);
            }
          }}
        />
      ) : (
        <button onClick={() => setShowKinesteX(true)}>
          Start {challengeExercise} Challenge ({challengeDuration}s)
        </button>
      )}
    </div>
  );
};

export default ChallengeIntegration;
```

---
Source: https://www.kinestex.com/docs/integration/challenge-view · Index: https://www.kinestex.com/llms.txt
