Skip to content
CARP Docs
Ask AISystemLightDark
On this page

Installation

Add movesense_plus to a Flutter app and configure Android and iOS for Bluetooth.

Copy MarkdownOpen inOpen in GitHubView as MarkdownOpen in Scira AIOpen in ChatGPTOpen in ClaudeOpen in Cursor

Setting up movesense_plus takes four steps: add the dependency, configure Android, configure iOS and request the Bluetooth permissions at runtime.

Add the dependency

flutter pub add movesense_plus

The plugin requires Dart 3.12 or later and Flutter 3.44 or later. It bundles the Movesense mobile library (MDS) for both platforms: the mdslib AAR for Android and MovesenseMDS.xcframework for iOS. There is no Movesense SDK to download.

Upgrading from 1.x

Version 2.0 bundles the Movesense library and has a new API. Remove the library that you added for 1.x: the pod 'Movesense' line in ios/Podfile and the mdslib AAR in android/libs/ with its flatDir repository. See Migrate from 1.x.

Configure Android

The plugin and RxAndroidBle, the Bluetooth library that MDS uses, add these permissions to your app's manifest:

PermissionAndroid versionsPurpose
BLUETOOTH_SCAN12 or laterFind the sensor. The neverForLocation flag tells Android that the app does not derive location from scans.
BLUETOOTH_CONNECT12 or laterConnect to the sensor.
BLUETOOTH, BLUETOOTH_ADMIN11 and lowerScan for and connect to the sensor before Android 12.
ACCESS_FINE_LOCATION11 and lowerGet scan results before Android 12.
ACCESS_COARSE_LOCATIONAllDeclared by RxAndroidBle. The plugin does not need it.

You do not add these permissions for the plugin, but your app must request some of them at runtime. See Request permissions at runtime.

If your app declares ACCESS_FINE_LOCATION itself, the manifest merger copies the plugin's android:maxSdkVersion="30" onto that entry. The app then has no precise location on Android 12 or later. To keep precise location for your app's own features, add tools:remove to the entry and declare the tools namespace:

android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
        tools:remove="android:maxSdkVersion" />
    <!-- ... the rest of the manifest -->
</manifest>

The merger also adds the neverForLocation flag to a BLUETOOTH_SCAN entry of your own. If your app derives location from Bluetooth scans, add tools:remove="android:usesPermissionFlags" to that entry.

The plugin needs minSdk 24 or later. If android/app/build.gradle.kts sets a lower value, set minSdk to 24 or later.

The bundled Movesense library contains native code for arm64-v8a, armeabi-v7a and x86, but not for x86_64. Flutter builds Android apps for x86_64 as well, and that build fails at launch, when the plugin starts MDS. To leave x86 and x86_64 out of the APK and the app bundle, exclude their native libraries, as the plugin's example app does:

android/app/build.gradle.kts
android {
    // ... namespace, compileSdk, defaultConfig and buildTypes

    packaging { 
        jniLibs { 
            excludes.addAll(listOf("lib/x86/**", "lib/x86_64/**")) 
        } 
    } 
}

Restricting abiFilters in defaultConfig is not enough: Flutter adds x86_64 to the filters itself. An x86_64 Android emulator cannot run the app either way, so test on a physical phone, which Bluetooth needs anyway.

Configure iOS

Add the Bluetooth usage description to ios/Runner/Info.plist, inside the top-level <dict>:

ios/Runner/Info.plist
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to connect to Movesense sensors.</string>

iOS shows the text in its Bluetooth permission dialog, and it terminates an app that uses Bluetooth without the key. The plugin starts Core Bluetooth when the app starts, so iOS asks for Bluetooth access at the first launch, not at the first scan.

To keep data from the sensor coming while the app is in the background, also add the bluetooth-central mode:

ios/Runner/Info.plist
<key>UIBackgroundModes</key>
<array>
  <string>bluetooth-central</string>
</array>

If your app uses the sensor only in the foreground, leave the mode out.

In Xcode, the mode is Uses Bluetooth LE accessories under Signing & Capabilities → Background Modes. If UIBackgroundModes already exists, add bluetooth-central to its array.

The plugin needs iOS 15.0 or later. If your app targets an earlier version, select Runner → General in Xcode and set Minimum Deployments to 15.0 or later. The plugin builds with both CocoaPods and Swift Package Manager.

MovesenseMDS.xcframework has no build for the simulator on Apple silicon Macs, and the simulator has no Bluetooth, so run the app on an iPhone.

Request permissions at runtime

The plugin requests no permissions itself. On Android, your app must request these permissions before the first scan:

Android versionPermissionsReason
12 or laterPermission.bluetoothScan, Permission.bluetoothConnectScanning needs BLUETOOTH_SCAN, and connecting needs BLUETOOTH_CONNECT. Android asks for both in one nearby devices dialog.
11 and lowerPermission.locationWhenInUseScans return no results without location access.

RxAndroidBle declares ACCESS_COARSE_LOCATION for every version, so on Android 12 or later a location request shows an approximate location dialog. The plugin does not need location on those versions, so the function below requests it only on Android 11 and lower.

On iOS, the system asks for Bluetooth access at the first launch, so your app requests nothing.

The function below uses permission_handler and device_info_plus. Add both plugins:

flutter pub add permission_handler device_info_plus

permission_handler requires compileSdk 37 or later. With a lower value, such as Flutter 3.47's default of 36, the Android build fails at the AAR metadata check. Set compileSdk in android/app/build.gradle.kts:

android/app/build.gradle.kts
android {
    // ... namespace
    compileSdk = flutter.compileSdkVersion 
    compileSdk = 37
    // ... the rest of the android block
}

This function requests the permissions that the Android version needs:

lib/bluetooth_permissions.dart
import 'dart:io';

import 'package:device_info_plus/device_info_plus.dart';
import 'package:permission_handler/permission_handler.dart';

Future<bool> requestBluetoothPermissions() async {
  if (!Platform.isAndroid) return true; // iOS shows its own prompt

  final android = await DeviceInfoPlugin().androidInfo;
  final permissions = android.version.sdkInt >= 31
      ? [Permission.bluetoothScan, Permission.bluetoothConnect]
      : [Permission.locationWhenInUse];

  final statuses = await permissions.request();
  return statuses.values.every((status) => status.isGranted);
}

API level 31 is Android 12. The function returns true on iOS and when the user allows every permission. Call it before startScan().

Without the permissions, a scan reports an error on Android 12 or later and finds no sensors on Android 11 and lower. On Android 11 and lower, a scan may also find nothing while location is turned off in the phone's settings.

The plugin's example app requests only the scan and connect permissions. To run it on Android 11 and lower, allow location for it in the system settings.

Troubleshooting

The Android build fails at the AAR metadata check

Set compileSdk to 37 or later, as Request permissions at runtime shows. permission_handler requires it, and Gradle stops with:

Execution failed for task ':app:checkDebugAarMetadata'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.CheckAarMetadataWorkAction
   > An issue was found when checking AAR metadata:

       1.  Dependency ':permission_handler_android' requires libraries and applications that
           depend on it to compile against version 37 or later of the
           Android APIs.

           :app is currently compiled against android-36.
The app does not run on an x86_64 Android emulator

Run the app on a physical phone. The bundled Movesense library has no native code for x86_64, so the app cannot start on an x86_64 emulator. See Configure Android.

The scan finds no sensor on Android 11 and lower

Request the location permission, and turn on location in the phone's settings. On Android 11 and lower, scans return results only with location access, and may return none while location is off. See Request permissions at runtime.

The iOS build reports a conflicting MovesenseMDS target

Remove movesense_plus from an app that uses carp_movesense_package 3.1.0. carp_movesense_package 3.1.0 depends on carp_movesense_flutter 0.1.x, the earlier name of this plugin, which bundles the same Movesense library. With Swift Package Manager, flutter build ios then fails with:

xcodebuild: error: Could not resolve package dependencies:
  multiple packages ('carp_movesense_flutter-0.1.1', 'movesense_plus') declare targets with a conflicting name: 'MovesenseMDS’; target names need to be unique across the package graph

To find sensors in such an app, see Find the sensor.

The simulator build fails with an undefined symbol

Run the app on an iPhone. MovesenseMDS.xcframework has no build for the simulator on Apple silicon Macs, so linking fails:

Error (Xcode): Undefined symbol: _OBJC_CLASS_$_MDSWrapper
Error (Xcode): Linker command failed with exit code 1 (use -v to see invocation)
The app closes at launch on iOS

Add NSBluetoothAlwaysUsageDescription to ios/Runner/Info.plist, as Configure iOS shows. The plugin starts Core Bluetooth when the app starts, and iOS terminates an app that uses Bluetooth without the key.

Next step

Quickstart

Build an app that prints the heart rate from a sensor.

Quick links

Page · DocumentationCARP DocumentationPage · DocumentationIntroductionPage · DocumentationQuickstartPage · DocumentationTerminologyPage · DocumentationArchitecture