spike_flutter_sdk 1.0.16
spike_flutter_sdk: ^1.0.16 copied to clipboard
Reads HealthKit data from iOS HealthKit, and syncs it with Spike backend.
Spike Flutter SDK allows you to read the Apple HealthKit and Android Health Connect data. Then, it allows you to send the data to your backend chosen, and get the data returned which is processed by the backend. Also, you can enable background delivery in case iOS device is being used. And finally, it is possible to enable event tracking to see if everything works as intended or just provide your own event tracking library.
Features #
- Read the Apple HealthKit data.
- Send this data to backend.
- Register periodic background tasks to send and read the data.
- Track various events of the package by utilizing shared preferences.
Getting started #
iOS Setup Guide #
At first in your app's entitlements select HealthKit and in your app's info.plist file add permissions:
<key>NSHealthShareUsageDescription</key>
<string>WHY_YOU_NEED_TO_SHARE_DATA</string>
<key>NSHealthUpdateUsageDescription</key>
<string>WHY_YOU_NEED_TO_USE_DATA</string>
If you plan to use WorkoutRoute Series please provide additionally CoreLocation permissions:
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>WHY_YOU_NEED_TO_ALWAYS_SHARE_LOCATION</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>WHY_YOU_NEED_TO_SHARE_LOCATION</string>
Android Setup Guide #
IMPORTANT! In order to properly set-up your app, you must read carefully the whole README about Android set-up, and do the steps as they are written here. Android set-up proved the hardest thing here. Later, there are plans to simplify the set-up by delegating some functionalities to the native Bridge level (such as asking for permissions), but for now it is processed through the native Spike Android SDK.
- Create config.xml in the following path: android/app/src/main/res/values. Content of this file must be the following:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="health_permissions">
<item>androidx.health.permission.Nutrition.READ</item>
<item>androidx.health.permission.ActiveCaloriesBurned.READ</item>
<item>androidx.health.permission.TotalCaloriesBurned.READ</item>
<item>androidx.health.permission.Steps.READ</item>
<item>androidx.health.permission.Distance.READ</item>
<item>androidx.health.permission.ElevationGained.READ</item>
<item>androidx.health.permission.RestingHeartRate.READ</item>
<item>androidx.health.permission.HeartRateVariabilityRmssd.READ</item>
<item>androidx.health.permission.FloorsClimbed.READ</item>
<item>androidx.health.permission.BasalMetabolicRate.READ</item>
<item>androidx.health.permission.SleepSession.READ</item>
<item>androidx.health.permission.HeartRate.READ</item>
<item>androidx.health.permission.ExerciseSession.READ</item>
<item>androidx.health.permission.Speed.READ</item>
<item>androidx.health.permission.Power.READ</item>
<item>androidx.health.permission.OxygenSaturation.READ</item>
<item>androidx.health.permission.BloodGlucose.READ</item>
<item>androidx.health.permission.RespiratoryRate.READ</item>
<item>androidx.health.permission.Weight.READ</item>
<item>androidx.health.permission.Height.READ</item>
<item>androidx.health.permission.BodyFat.READ</item>
<item>androidx.health.permission.LeanBodyMass.READ</item>
<item>androidx.health.permission.BodyWaterMass.READ</item>
<item>androidx.health.permission.BodyTemperature.READ</item>
<item>androidx.health.permission.BloodPressure.READ</item>
<item>androidx.health.permission.BoneMass.READ</item>
</array>
</resources>
- Now comes the
AndroidManifest.xml
modification part. Add the following query in the
<queries>
<package android:name="com.google.android.apps.healthdata" />
</queries>
- Then reference the permissions to your AndroidManifest.xml file in the activity that will call for health permissions. This meta-data reference should be added under the
<meta-data
android:name="health_permissions"
android:resource="@array/health_permissions" />
- As well as that you have to add intent filter to your activity definition so that you can request the permissions at runtime. This intent filter reference should be added under the
<intent-filter>
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
</intent-filter>
- In case permissions handling is not working, this might be related to launch mode being
singleTop
. This might be not needed, but some apps faced problems when requesting permissions. If you face them, then you should try removing the following line:
android:launchMode="singleTop"
- In case app is not building, it might be related to label replacement issue. In this case, you should add the following line to the
tools:replace="android:label"
- Afterwards, add the following line into the android/app/build.gradle under dependencies:
implementation 'androidx.appcompat:appcompat:1.3.1'
- Since we are using closed source native Android SDK, separate repository is needed. Thus, add the following dependency into your android/builde.gradle file (it must be added both in repositories and allprojects node of repositories):
maven {
url 'https://gitlab.com/api/v4/projects/43396247/packages/maven'
}
- Finally, in local.properties of your project for Flutter, add these lines:
flutter.minSdkVersion=29
flutter.compileSdkVersion=34
- In case your default Flutter project does not take these values, do the necessary changes in the android/app/build.gradle:
def flutterCompileSdkVersion = localProperties.getProperty('flutter.compileSdkVersion')
if (flutterCompileSdkVersion == null) {
flutterCompileSdkVersion = '34'
}
- And then, add or change compileSdkVersion under android node like this:
compileSdkVersion flutterCompileSdkVersion.toInteger()
- In case you face an issue not described here, feel free to contact us, and give us as much information as possible for us to provide more information about how to resolve your specific issue. The problems sometimes are caused by just different environments, and there is no way to test all of them when there are so many of them.
Usage #
The examples below demonstrate how to use this package in various of cases.
Initialization #
First, you must get the correct credentials in order for this package to be usable. In case you want to test web hooks, but do not have or want to set-up your own environment, you can try the following free service: https://webhook.site
const authToken = '{YOUR_AUTH_TOKEN}';
const appId = '{YOUR_APP_ID}';
const customerEndUserId = '{YOUR_CUSTOMER_END_USER_ID}';
const postbackURL = 'https://example.com/';
Creating connection (Without logger) #
Using the credentials received, you can create the connection. This is done in the following way.
final connection = await SpikeSDK.createConnection(
appId: appId,
authToken: authToken,
customerEndUserId: customerEndUserId,
);
Creating connection (With logger) #
Using the credentials received, you can create the connection. Also, you can add a logger that logs the most important events in the connection. First, you would need to implement the SpikeLogger abstract class. Example of how to do so is below.
- Implementation of the SpikeLogger example.
import 'package:spike_flutter_sdk/spike_flutter_sdk.dart';
class PrintLogger extends SpikeLogger {
const PrintLogger();
// You specify if you want to receive debug messages through this logger.
@override
bool get isDebugEnabled => true;
// You specify if you want to receive error messages through this logger.
@override
bool get isErrorEnabled => true;
// You specify if you want to receive info messages through this logger.
@override
bool get isInfoEnabled => true;
@override
void debug(SpikeConnection connection, String message) =>
_log(connection, message, 'debug');
@override
void error(SpikeConnection connection, String message) =>
_log(connection, message, 'error');
@override
void info(SpikeConnection connection, String message) =>
_log(connection, message, 'info');
/*
* Of course, in the production apps using 'print' is an antipattern in Flutter,
* but this is intended to give you an idea how to use logger, and also to have
* a compilable version of the logger to get you started as fast as possible.
*/
void _log(SpikeConnection connection, String message, String level) =>
print('[$level] [${connection.connectionId}]: $message');
}
- Example how to provide the logger to the newly created connection. This is pretty simple, you just specify what logger you want to use.
final connection = await SpikeSDK.createConnection(
appId: appId,
authToken: authToken,
customerEndUserId: customerEndUserId,
logger: const PrintLogger(),
);
Creating WebHook connection (without logger and with logger) #
- Creating Web Hook connection is also super easy to create.
final webHookConnection = await SpikeSDK.createWebhookConnection(
appId: appId,
authToken: authToken,
customerEndUserId: customerEndUserId,
postbackURL: postbackURL,
);
- Also, as in the simple connection creation, you can provide logger to web hook connections, too. Below, is an example of how to use it.
final webHookConnection = await SpikeSDK.createWebhookConnection(
appId: appId,
authToken: authToken,
customerEndUserId: customerEndUserId,
postbackURL: postbackURL,
logger: const PrintLogger(),
);
Requesting permissions #
In order to use the connection properly, you must first ask for the permissions needed for your purposes. Let us assume you want to get heart data. For this, you must to ask for heart data permissions. See the example below about how easy it is to do so.
connection.ensurePermissionsAreGranted(types: [SpikeDataType.heart]);
Just one line of code, and you are requesting the permissions. In the future, there are plans to get rid of requesting permissions, and instead do it automatically from the SDK part when the data is requested.
Using the connection to get the data (without specified date range) #
Using both simple and Web Hook connections are very easy to use. See the examples below how to use both simple and WebHook connections without date range specified.
final stepsData = await connection.extractData(SpikeDataType.steps);
if (stepsData.data.isNotEmpty) {
print("Steps (Count): ${stepsData.data.length}");
print("Steps[0]: ${stepsData.data[0].value}");
}
final heartJobDetails = await webHookConnection.extractAndPostData(SpikeDataType.heart);
print('submittedAt: ${heartJobDetails.submittedAt}');
print('notificationId: ${heartJobDetails.notificationId}');
Using the connection to get the data (with specified date range) #
Also, you are allowed to specify the date range wanted. Flutter does not use extractDataInRange or something like that, since Dart gives us a way to use optional parameters, and this feature if Dart is being used here. See the examples below:
final now = DateTime.now()
final stepsData = await connection.extractData(
SpikeDataType.steps,
from: now.subtract(const Duration(days: 1)),
to: now,
);
if (stepsData.data.isNotEmpty) {
print("Steps (Count): ${stepsData.data.length}");
print("Steps[0]: ${stepsData.data[0].value}");
}
final heartJobDetails = await webHookConnection.extractAndPostData(
SpikeDataType.heart,
from: now.subtract(const Duration(days: 1)),
to: now,
);
print('submittedAt: ${heartJobDetails.submittedAt}');
print('notificationId: ${heartJobDetails.notificationId}');
Enabling background delivery (WebHook connection and iOS only) #
Enabling the background delivery should be straightforward as shown below.
await webHookConnection.enableBackgroundDelivery(
types: [SpikeDataType.summary, SpikeDataType.heart],
);
Creating listener for the background delivery and using it (WebHook connection and iOS only) #
- In case you want to listen what happens with background delivery, you can implement SpikeWebhookConnectionListener, and provide your implementation as listener for the background delivery. Below, there is an example of how to implement SpikeWebhookConnectionListener.
import 'package:spike_flutter_sdk/spike_flutter_sdk.dart';
class PrintBackgroundListener extends SpikeWebhookConnectionListener {
const PrintBackgroundListener();
@override
void onBackgroundLog(String log) => print(log);
}
- To listen for the events for the background delivery, you should not just enable it, but also set the listener. Example below shows how to both enable background delivery and set listener to it:
await webHookConnection.enableBackgroundDelivery(
types: [SpikeDataType.summary, SpikeDataType.heart],
);
webHookConnection.setListener(const PrintBackgroundListener());
Checking the background delivery types (WebHook connection and iOS only) #
Sometimes you may want to check for which types the connection are registered for the background delivery. This is very simple.
final types = await webHookConnection.getBackgroundDeliveryTypes();