Terra Docs
Dashboard
  • Docs
  • API Reference
  • Changelog
  • Get started ⚡️ Choose your integration
  • Health & Fitness API
    • Overview
    • Set up your Integrations
    • Connect a User
    • Receive data updates
    • Request Historical data
    • Write data
    • Debugging FAQ
    • Mobile-Only Sources: Apple, Samsung, Google Fit
      • iOS (Swift)
      • Android (Kotlin)
      • React Native
      • Flutter
  • Streaming API
    • Getting Started
    • Wearable -> Your app
      • iOS (Swift)
      • Android
    • Your app -> Terra
      • iOS (Swift)
      • Android
    • Terra -> Your Backend
  • Teams API
  • Biomarkers API - Upcoming
Powered by GitBook
On this page
  • Setup
  • iOS Setup - Apple Health
  • Background Delivery setup (iOS)
  • Android - Health Connect
  • Initialize the SDK
  • Connect to Apple Health/Samsung Health
  • Validate the Connection
  • Disconnecting a user
  • Historical Data retrieval
  • Writing Planned Workouts (Apple-Only)
  • Overview
  • Usage

Was this helpful?

  1. Health & Fitness API
  2. Mobile-Only Sources: Apple, Samsung, Google Fit

Flutter

PreviousReact NativeNextGetting Started

Last updated 2 months ago

Was this helpful?

Setup

Install the terra-react package using npm (flutter pub add terra_flutter_bridge), and follow the respective guides below based on the platforms you want to access

iOS Setup - Apple Health

Using XCode, open the .xcodeproj for the app in the /ios directory of your project

Background Delivery setup (iOS)

RunsetUpBackgroundDelivery in your AppDelegate's didFinishLaunchingWithOptions function

This will ensure you get updates for the user's Apple Health data automatically sent to your destination.

Data will

  • still be sent even if your app is exited/killed completely

  • only be sent if the device is unlocked

#import "AppDelegate.h"

#import <React/RCTBundleURLProvider.h>
#import <TerraiOS/TerraiOS-Swift.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  self.moduleName = @"AwesomeProject";
  // You can add your custom initial props in the dictionary below.
  // They will be passed down to the ViewController used by React Native.
  self.initialProps = @{};
  [Terra setUpBackgroundDelivery];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

Android - Health Connect

Follow the instructions below within the /android directory of your project

Initialize the SDK

First, initialize the TerraManager class at the top level of the app.

Terra initialization

Terra should be initialized every time your app is opened.

This is a necessary prerequisite for other SDK functions to run as expected

import 'package:flutter/material.dart';
import 'package:terra_flutter_bridge/terra_flutter_bridge.dart';

void main() async {
  // Ensure that Flutter bindings are initialized
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize Terra once when the app starts
  await _initializeTerra();

  // Launch your app thereafter
  runApp(Container());  // Empty container or basic widget if needed
}

Future<void> _initializeTerra() async {
  try {
    String devID = 'YOUR_DEV_ID';
    String referenceID = 'YOUR_REFERENCE_ID';

    SuccessMessage? result = await initTerra(devID, referenceID);

    if (result?.success == true) {
      print('Terra initialized successfully');
    } else {
      print('Initialization failed: ${result?.error}');
    }
  } catch (error) {
    print('Error during Terra initialization: $error');
  }
}

Terra is thereafter ready to be used with other functions

Connect to Apple Health/Samsung Health

Once Terra is initialized, you can create a connection.

Run the initConnection function with Connections.APPLE or Connections.SAMSUNG to have the Apple Health or Health Connect permission screen pop up.

initConnection only needs to be run a single time.

Apple Health & Health Connect prohibit the permission popup to appear more than once for any given permission, so calling initConnection more than once will result in no action at all

The only case where it would re-appear is if:

  • you call initConnection with an expanded set of customPermissions

  • the app is deleted & reinstalled.

try {
  // Example token received from your backend
  String token = 'example_token';
  
  // Define connection type and custom permissions
  Connection connection = Connection.APPLE;
  List<CustomPermission> customPermissions = [];  // Leave empty to default to all permissions
  bool schedulerOn = true;  // Enables background delivery

  // Initialize the connection
  SuccessMessage? successMessage = await initConnection(
    connection,       // The type of connection (e.g., Apple Health)
    token,            // Token received from backend
    schedulerOn,      // Enables background delivery
    customPermissions // Empty list for custom permissions
  );

  if (successMessage?.success == true) {
    print('Connection successful!');
  } else {
    print('Connection failed: ${successMessage?.error}');
  }
} catch (error) {
  print('Connection failed with error: $error');
}

customPermissions

🚧 Webviews & HealthKit permissions screen

HealthKit implements the permissions popup as a webview

If your app is also based on a webview, you will need to interrupt your webview, call initConnection, then upon completion re-open your webview

token

Validate the Connection

static Future<UserId?> getUserId(Connection connection) async
  • if the connection exists, all is good! 🎉 keep vibing along

Disconnecting a user

In order to disconnect an Apple Health user, you may use the same endpoint as for Web API-based integrations, called from your backend

Historical Data retrieval

You may set toWebhook to false if you wish for the callback function to return the data payload on the client side.

try {
  // Example connection type and dates
  Connection connection = Connection.APPLE;  // Assuming Connection is an enum with APPLE
  DateTime startDate = DateTime.now().subtract(Duration(days: 1));  // Replace with actual start date
  DateTime endDate = DateTime.now();                                // Replace with actual end date
  
  // Optionally, send data to webhook or not
  bool toWebhook = false;  // Set to false to receive the data in the response

  // Call the getDaily function
  DataMessage? dataMessage = await getDaily(connection, startDate, endDate, toWebhook: toWebhook);

  // Handle the response based on the structure of DataMessage
  if (dataMessage?.success == true) {
    if (dataMessage?.data != null) {
      print('Daily data: ${dataMessage?.data}');
    } else {
      print('Failed to unpack data');
    }
  } else {
    print('Request failed with error: ${dataMessage?.error}');
  }

} catch (error) {
  // Handle failure (network error, etc.)
  print('Failed to get daily data: $error');
}

Check out the Flutter SDK reference for details about all the functions in the SDK

Writing Planned Workouts (Apple-Only)

Planned Workouts are only available for APPLE, and on iOS 17 and above

Overview

This will typically look like the following example:

Planned workouts consist of a series of steps. Each step has:

  • Completion criteria (called duration for all intents and purposes). For example:

    • distance (keep going until you cover 500m),

    • energy expenditure (keep going until you burn 200 kcal)

    • heart rate threshold (keep going until your heart rate reaches above 150 bpm)

    • etc..

  • A target to be maintained

    • heart rate (maintain heart rate between certain bounds)

    • speed (maintain speed between certain bounds)

    • grade (inclination above a certain threshold)

    • etc...

  • An associated intensity label

    • Warmup

    • Active

    • Cooldown

    • etc..

  • An exercise type

    • Running

    • Cycling

    • Deadlifting

    • etc...

  • A series of sub-steps

These modalities allow any workout to be described in a series of defined steps for each of its segments.

Once written, a planned workout will be available in the user's Workout app on their Apple Watch. They will then be able to follow the workout and receive guidance throughout each step.

Usage

import 'package:terra_flutter/terra_flutter.dart';

void sendPlannedWorkout() async {
  // Create a planned workout payload
  TerraPlannedWorkout plannedWorkout = TerraPlannedWorkout(
    metadata: TerraPlannedWorkoutMetaData(
      id: "unique-workout-id",
      name: "Morning Run",
      type: TerraActivityType.RUNNING,
      planned_date: DateTime.now().toIso8601String(),
      estimated_duration_seconds: 3600, // Estimated 1 hour workout
    ),
    steps: [
      PlannedWorkoutStep(
        order: 1,
        type: PlannedWorkoutStepType.STEP,
        name: "Warm-up",
        durations: [PlannedWorkoutStepDuration.Time(
          TimePlannedWorkoutStepDuration(seconds: 600)
        )], // 10-minute warm-up
        targets: [PlannedWorkoutStepTarget.HeartRate(
          HRPlannedWorkoutStepTarget(hr_bpm_low: 120, hr_bpm_high: 140)
        )]
      ),
      PlannedWorkoutStep(
        order: 2,
        type: PlannedWorkoutStepType.STEP,
        name: "Run",
        durations: [PlannedWorkoutStepDuration.Time(
          TimePlannedWorkoutStepDuration(seconds: 3000)
        )], // 50-minute run
        targets: [PlannedWorkoutStepTarget.Speed(
          SpeedPlannedWorkoutStepTarget(speed_meters_per_second: 3.0)
        )]
      )
    ],
  );

  // Send the workout to Apple Health
  SuccessMessage? response = await TerraFlutter.postPlannedWorkout(
    Connection.APPLE, 
    plannedWorkout
  );

  // Check if the operation was successful
  if (response?.success == true) {
    print("Planned workout successfully posted.");
  } else {
    print("Failed to post the planned workout: ${response?.error}");
  }
}

To do this, run the as below:

customPermissions is used to customize the permissions list shown in the Apple Health/Health Connect popup when calling . When empty, it defaults to all available permissions

To ensure a connection is still valid on the client side, use the method. This function is synchronous and returns the user_id right away or null if none exists.

Always use this method right after to see if the connection still exists,

if you expect the connection to exist, but returns null, to re-establish it. No permission screen will be shown again and the user flow will be unaffected

if it is expected for the connection to no longer exist, you may allow the user to re-connect Apple Health if they so choose (you'd then call , but no permission screen would be shown)

You can request for historical data using one of the

Writing planned workouts allows you to create a workout plan for the user (such as those )

In order to write a workout for the user to follow on their Apple Watch, use the function as below, with the

commonly used with Zwift
initializing Terra
call initConnection
as before
instance of terra you created above
postPlannedWorkout
Example workout from
https://whatsonzwift.com/workouts/baby-on-board

SDK Installation

Requirements

  • Android 28 and above

Inside the dependencies section of your build.gradle file, add the following line of code

implementation 'co.tryterra:terra-android:{VERSION_NUMBER}'

Sync your project with Gradle by clicking on the "Sync Project with Gradle Files" button in the toolbar. This will download the TerraAndroid SDK and make it available to your project.

Health Connect Setup

In the Health Connect app:

Give all permissions between the app you wish to read from (e.g. Samsung Health/Google Fit & Health Connect.

In your AndroidManifest.xml

Include the following tags under the Activity you wish to link the user to when they click the privacy policy link in the Health Connect permission screen:

<intent-filter>
    <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
<intent-filter>
  <action android:name="android.intent.action.VIEW_PERMISSION_USAGE"/>
  <category android:name="android.intent.category.HEALTH_PERMISSIONS"/>
</intent-filter>

Apply for Health Connect access

For each permission which you are not using, please add the following lines to your AndroidManifest.xml

<uses-permission android:name="android.permission.health.READ_HEART_RATE" tools:node="remove"/>

with android.permission.health.XXX for each permission you aren't using

installed on users' devices

You can find the latest version number on .

Before going LIVE (release), you will need to apply for permissions to access the Health Connect API with Google. Use this .

Health Connect
Maven Central
application form

Summary:

  1. Add https://github.com/tryterra/TerraiOS as a package dependency

  2. Add Capabilities:

    1. Healthkit > Healthkit Background Delivery

    2. Background Modes > Background processing

    3. Background modes > Background fetch

  3. In your info.plist, add:

    1. Privacy - Health Share Usage Description: custom text shown in the Apple Health permission screen

    2. Permitted background task scheduler: co.tryterra.data.post.request

If you do not need to access the , there is no reason to use Terra mobile SDKs!

If you need to access Terra's Health & Fitness API, it is only secure to do so from your server backend and send the desired data to your mobile frontend.

Never expose your API key on the client side unless you are simply testing

token is a single-use token created to ensure the authentication endpoint for creating a (and connecting the SDK to Terra's servers) does not get abused.

In order to generate it, place the call below on your server, and provide it to your client side using your own logic.

Testing & developing

During the development phase, it it acceptable to place this call on the client side, exposing your API key in your mobile app.

For a production implementation, DO NOT expose your API key this way, and make sure to only make this call from your backend

Generates an authentication token for the Terra mobile SDKs

post

Creates a token to be used with initConnection() functions in the Terra mobile SDKs in order to create a user record for Apple Health or Samsung Health (or equivalent)

Header parameters
dev-idstringRequired

your developer ID

Example: testingTerra
x-api-keystringRequired

your API key

Example: OtHJok60oQmT8zhnUWc4SWBJI7ztPTs88C0gOsJJ
Responses
200
200
application/json
404
404
application/json
post
POST /v2/auth/generateAuthToken HTTP/1.1
Host: api.tryterra.co
dev-id: text
x-api-key: text
Accept: */*
{
    "status": "success",
    "token": "250c68b9c21b78e40e7a3285a2d538d3bc24aabd3b4c76a782fb0a571ca4501d",
    "expires_in": 180
}
Terra manager initialization function
initConnection
getUserId
getUserId
initConnection
data retrieval functions.
mobile-only integrations