Docs / AI Trainer Chat

AI Trainer Integration

Limited previewRolling out gradually

Overview

The AI Personal Trainer is a guided chat experience that walks the user through a personalized session: profile setup → readiness assessment → workout → check-in → scheduling the next session.


Your app only needs to mount the trainer view, optionally pre-fill demographic data, and listen for the events below — most importantly trainer_schedule_next_workout, which fires when the user picks a date/time for their next session and is the trigger for your reminder logic.

Requirements

Camera permission, an internet connection, and a recent SDK that supports the Trainer Chat integration:


PlatformMinimum SDK
Swift (iOS)KinesteXAIKit1.1.3 — iOS 13+, Swift 5.5+, SwiftUI
Kotlin (Android)KinesteX-SDK-Kotlin2.0.6
React Nativekinestex-sdk-react-native1.2.9
Flutterkinestex_sdk_flutter1.4.7
React TS (Web)kinestex-sdk-react-ts0.0.3
HTML & JSiframe at https://ai.kinestex.com (HTTPS host page required)

If you haven't installed the SDK yet, follow the installation guide first — only trainer-specific concerns are covered below.

Launching the Trainer

Mount the trainer with the AI_TRAINER_CHAT integration option (or, on Swift / Kotlin / Flutter, call createTrainerChatView). The trainer flow is rendered entirely inside the SDK.

1import SwiftUI
2import KinesteXAIKit
3
4struct TrainerScreen: View {
5    @State private var isLoading = false
6    @State private var showKinesteX = true
7
8    // KinesteXAIKit is configured per-instance — no global initialize call.
9    private let kit = KinesteXAIKit(
10        apiKey: "<YOUR_API_KEY>",
11        companyName: "<YOUR_COMPANY>",
12        userId: "<USER_ID>"
13    )
14
15    var body: some View {
16        if showKinesteX {
17            kit.createTrainerChatView(
18                user: nil,                       // or your UserDetails — see below
19                style: IStyle(style: "dark"),
20                isLoading: $isLoading,
21                onMessageReceived: handleMessage
22            )
23        }
24    }
25
26    private func handleMessage(_ message: KinestexMessage) { /* see below */ }
27}

Pre-filling user data

The trainer asks profile questions on first use. You can skip the basic demographics by passing them when you mount the SDK — anything you omit, the trainer will simply ask for.


FieldUnit / Allowed values
ageyears
heightcm (UI lets users switch to imperial after)
weightkg
gender"male" or "female"

Don't pass empty strings or zero — omit the field instead. On Swift, Kotlin, and Flutter the UserDetails initializer requires all fields, so either pass the complete value or skip user: entirely (nil on Swift, omit on Kotlin/Flutter).

1// UserDetails on Swift requires ALL five fields — pass the full value when
2// you know everything, otherwise pass `user: nil` and let the trainer ask.
3let user = UserDetails(
4    age: 32,
5    height: 178,         // cm
6    weight: 76,          // kg
7    gender: .Male,
8    lifestyle: .Active   // not used in the trainer UI, but required by the initializer
9)
10
11kit.createTrainerChatView(
12    user: user,
13    style: IStyle(style: "dark"),
14    isLoading: $isLoading,
15    onMessageReceived: handleMessage
16)

Handling messages

The trainer emits the standard SDK events plus two trainer-specific ones:


  • workout_exit_request — the user exited a workout that was launched from the trainer.
  • trainer_schedule_next_workout — the user picked a date/time for their next session (see below).

Add the new cases to your existing handler.

1private func handleMessage(_ message: KinestexMessage) {
2    switch message {
3    case .exit_kinestex:
4        // User exited the trainer screen via the back button.
5        showKinesteX = false
6
7    case .error_occurred(let data):
8        print("KinesteX error:", data)
9
10    // `trainer_schedule_next_workout` is not yet a typed case — it
11    // arrives as .custom_type, so check the `type` field on the payload.
12    case .custom_type(let data):
13        guard let type = data["type"] as? String else { return }
14        switch type {
15        case "workout_exit_request":
16            // User exited a workout that was launched from the trainer.
17            break
18        case "trainer_schedule_next_workout":
19            // payload: { type, scheduledFor: "YYYY-MM-DDTHH:MM" }
20            if let scheduledFor = data["scheduledFor"] as? String {
21                scheduleNextWorkoutReminder(scheduledFor: scheduledFor)
22            }
23        default:
24            break
25        }
26
27    default:
28        break
29    }
30}

Scheduling the next workout

When the AI generates the user's next plan, the SDK fires:


1{
2  "type": "trainer_schedule_next_workout",
3  "scheduledFor": "2026-04-29T14:30"
4}

scheduledFor is `YYYY-MM-DDTHH:MM` with no timezone — interpret it as the device's local time (new Date(scheduledFor) in JS and DateTime.parse(scheduledFor) in Dart both do this correctly). The event is not sent if the user skips scheduling.


The SDK does not schedule the reminder for you — it just tells you when. The host app delivers the reminder. Two options:


  • Local notification (recommended for native). No backend, works offline, fires even if the app is force-quit. Example below.
  • Server-side push. POST { userId, scheduledFor, timezone } to your backend and queue a push (APNs / FCM / web-push / OneSignal). Use this when the user has multiple devices, you want delivery analytics, or the platform has no reliable local-notification path (e.g. plain web).

Whichever you pick, use a stable identifier per user (e.g. "trainer-next-workout") so a re-schedule replaces the previous reminder instead of stacking, skip past timestamps, and request notification permission early.

Local-notification example
1import UserNotifications
2
3private let trainerReminderId = "kinestex.trainer.next_workout" // stable per user
4
5func scheduleNextWorkoutReminder(scheduledFor: String) {
6    // Parse "YYYY-MM-DDTHH:MM" as local time. Don't use ISO8601DateFormatter —
7    // it expects seconds and/or a timezone designator.
8    let formatter = DateFormatter()
9    formatter.dateFormat = "yyyy-MM-dd'T'HH:mm"
10    formatter.timeZone = .current
11    formatter.locale = Locale(identifier: "en_US_POSIX")
12
13    guard let date = formatter.date(from: scheduledFor),
14          date.timeIntervalSinceNow > 5 else { return }
15
16    let center = UNUserNotificationCenter.current()
17    center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
18        guard granted else { return }
19
20        // Replace the previous reminder so re-schedules don't stack.
21        center.removePendingNotificationRequests(withIdentifiers: [trainerReminderId])
22
23        let content = UNMutableNotificationContent()
24        content.title = "Time for your workout"
25        content.body  = "Your AI Trainer has your next session ready."
26        content.sound = .default
27
28        let components = Calendar.current.dateComponents(
29            [.year, .month, .day, .hour, .minute], from: date
30        )
31        let trigger = UNCalendarNotificationTrigger(
32            dateMatching: components, repeats: false
33        )
34        center.add(UNNotificationRequest(
35            identifier: trainerReminderId,
36            content: content,
37            trigger: trigger
38        ))
39    }
40}

Quick checklist

  • KinesteXAIKit ≥ 1.1.3 installed via Swift Package Manager.

  • KinesteXAIKit(apiKey:companyName:userId:) instantiated where you mount the trainer.

  • Mount via kit.createTrainerChatView(...).

  • Pass a fully-populated UserDetails (age, height in cm, weight in kg, gender, lifestyle) when known; otherwise user: nil.

  • Handle trainer_schedule_next_workout (arrives as .custom_type) and schedule a reminder via UNUserNotificationCenter (or push).

  • Use a stable identifier (e.g. "kinestex.trainer.next_workout") so re-schedules replace the previous reminder.

  • NSCameraUsageDescription set in Info.plist and notification permission requested early.

Want early access for your team?

AI Trainer Chat is in limited rollout. Reach out and we'll enable it for your account.

Contact Us