Example PRO

import { Button, List, Navigation, NavigationStack, Script, Section, Text, useState } from "scripting"

// Wrap a unit operation as a "safe" call. On incompatible dimensions or an invalid unit
// string, HealthKit used to raise an Objective-C exception that Swift cannot catch, crashing
// the app outright; it now throws a JS error you can handle with try/catch.
function attempt(fn: () => string): string {
  try {
    return fn()
  } catch (e) {
    return `threw → ${String(e)}`
  }
}

function Example() {
  const dismiss = Navigation.useDismiss()
  const [rows, setRows] = useState<{ label: string; value: string }[] | null>(null)

  const runDemo = () => {
    const out: { label: string; value: string }[] = []

    // 1) Build units: basic / prefixed / compound / parsed from a string. All local, no Health authorization needed.
    const kcal = HealthUnit.kilocalorie()
    const kJ = HealthUnit.jouleUnit(HealthMetricPrefix.kilo)
    const mgPerML = HealthUnit.gramUnit(HealthMetricPrefix.milli)
      .divided(HealthUnit.literUnit(HealthMetricPrefix.milli))
    const parsed = HealthUnit.fromString("g/mL")
    out.push({ label: "Units", value: `${kcal.unitString}, ${kJ.unitString}, ${mgPerML.unitString}, ${parsed.unitString}` })

    // 2) Create an energy sample locally and read it back in another compatible unit — a normal conversion.
    const sample = HealthQuantitySample.create({
      type: "activeEnergyBurned",
      startDate: new Date(),
      endDate: new Date(),
      value: 150,
      unit: kcal,
    })
    if (sample != null) {
      out.push({ label: "150 kcal in kJ", value: `${sample.quantityValue(kJ).toFixed(1)} kJ` })
    }

    // 3) Safety: read the energy sample in an incompatible unit (meter) — throws a catchable error instead of crashing.
    if (sample != null) {
      out.push({
        label: "Read energy as meter",
        value: attempt(() => `unexpected: ${sample.quantityValue(HealthUnit.meter())}`),
      })
    }

    // 4) Safety: parse an invalid unit string — throws a catchable error instead of crashing.
    out.push({
      label: "fromString(\"not-a-unit\")",
      value: attempt(() => `unexpected: ${HealthUnit.fromString("not-a-unit").unitString}`),
    })

    // 5) Safety: create a step-count sample with a mismatched unit (kilogram) — throws a catchable error instead of crashing.
    out.push({
      label: "Create stepCount with kg",
      value: attempt(() => {
        const bad = HealthQuantitySample.create({
          type: "stepCount",
          startDate: new Date(),
          endDate: new Date(),
          value: 100,
          unit: HealthUnit.gramUnit(HealthMetricPrefix.kilo),
        })
        return bad == null ? "rejected (null)" : "unexpected: created"
      }),
    })

    setRows(out)
  }

  return <NavigationStack>
    <List
      navigationTitle={"HealthUnit"}
      navigationBarTitleDisplayMode={"inline"}
      toolbar={{
        cancellationAction: <Button title={"Done"} action={dismiss} />,
      }}
    >
      <Text font={"footnote"} foregroundStyle={"secondaryLabel"}>
        Build units, convert between compatible units, and see how incompatible units or invalid unit strings now throw a catchable error instead of crashing the app. Runs locally — no Health authorization required.
      </Text>

      <Button title={"Run"} action={runDemo} />

      {rows != null && <Section header={<Text>Results</Text>}>
        {rows.map((row, i) => <Text key={String(i)} font={"footnote"}>
          {`${row.label}: ${row.value}`}
        </Text>)}
      </Section>}
    </List>
  </NavigationStack>
}

async function run() {
  await Navigation.present({
    element: <Example />
  })

  Script.exit()
}

run()