Skip to content
CARP Docs
Ask AISystemLightDark
On this page

Migrate to 2.0

Update an app from movesense_plus 1.x, carp_movesense_flutter or mdsflutter to movesense_plus 2.0.

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

movesense_plus 2.0 is the plugin that was published as carp_movesense_flutter 0.1.x, renamed and extended with a typed device API. It replaces the 1.x releases of movesense_plus, which wrapped the mdsflutter plugin. Version 2.0 bundles the Movesense mobile library (MDS) for Android and iOS, so an app no longer adds the library itself.

What to change depends on the package that the app uses now:

PackageWhat changes
movesense_plus 1.xThe native setup and most of the API. See Migrate from 1.x.
carp_movesense_flutter 0.1.xThe dependency, the imports and a few class names. See Migrate from carp_movesense_flutter.
mdsflutterThe dependency, the import and the native setup. See Migrate from mdsflutter.

Version 2.0 needs Dart 3.12 or later, which comes with Flutter 3.44 or later. It also needs Android minSdk 24 or later and iOS 15.0 or later. To configure Android and iOS, see Installation.

Migrate from 1.x

Raise the version constraint:

flutter pub add "movesense_plus:^2.0.0"

The update also removes mdsflutter, which 1.x brought along. If your code imports package:mdsflutter/Mds.dart, change that import as well. See Migrate from mdsflutter.

Remove the MDS setup

Version 1.x needed MDS in the app itself: the mdslib library on Android and the Movesense pod on iOS. Version 2.0 bundles both, so remove them.

On Android, delete the mdslib-<version>-release.aar file, usually in android/libs/. Then remove the flatDir repository that points to its folder:

android/build.gradle.kts
allprojects {
    repositories {
        google()
        mavenCentral()
        flatDir { 
            dirs("$rootDir/libs") 
        } 
    }
}

In a Groovy android/build.gradle, the block reads flatDir { dirs "$rootDir/libs" }.

On iOS, remove the pod 'Movesense' line from ios/Podfile. The 1.x setup also replaced the use_frameworks! line of the Flutter template with static linkage and added use_modular_headers!. If you made these changes only for Movesense, undo them:

ios/Podfile
target 'Runner' do
  use_modular_headers! 
  use_frameworks! :linkage => :static
  use_frameworks! 

  pod 'Movesense', :git => 'https://bitbucket.org/movesense/movesense-mobile-lib.git'

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
  # ... the rest of the target
end

The plugin builds with the template's use_frameworks! and with Swift Package Manager.

Update the code

The API of version 2.0 is new. These are the most common changes:

1.x2.0
ScanningMovesense().scan(), Movesense().devicesMovesense().startScan(), Movesense().scanResults
MovesenseDevice constructorOptional address, serial, name and deviceTypeRequired address, optional name and movesense; the properties are read-only
connect() and disconnect()Return voidReturn Future<void>; follow statusEvents for the outcome
canConnect()Returns true when address is setRemoved, because address is required
Sensor streamshr, ecg, imu, temperature and getStateEvents() are broadcast streams, empty when not connectedSingle-subscription streams that emit a StateError when not connected
Connection statusDeviceConnectionStatusMovesenseDeviceStatus
Battery levelDeviceBatteryLevelMovesenseBatteryLevel
System state componentsSystemStateComponentMovesenseSystemState
Device typesMovesenseDeviceType.MD, HR_PLUS, HR2, FLASHMovesenseDeviceType.md, hrPlus, hr2, flash
MovesenseHRaverage as int; rr is the first R-R interval of a notificationaverage as double, plus the full rrData list; rr is the last interval, or null when there is none
MovesenseTemperaturemeasurement as intmeasurement as double, plus a celsius getter
MovesenseStateExtends MovesenseData, with an int timestampA standalone class with component and value, and an int? timestamp that is null for getState() reads
Device informationfromMovesenseData(String), fromMap(), toMap() and toJsonString(), and fromMap() and toMap() on MovesenseAddressInfofromMovesenseData(Map), fromJson() and toJson(), and fromJson() and toJson() on MovesenseAddressInfo
Data model constructorsPositionalNamed, except the sample classes of MovesenseIMU, which keep positional (x, y, z) constructors with double instead of num values

Scanning changes like this:

final movesense = Movesense();
movesense.devices.listen((device) => print('Found ${device.name} [${device.address}]'));
movesense.scan();
movesense.scanResults.listen( 
  (device) => print('Found ${device.name} [${device.address}]'),
  onError: (Object error) => print('Scan error: $error'),
);
await movesense.startScan();

In 1.x, the scan stopped by itself after 60 seconds on iOS. In 2.0, it runs until you call stopScan(), or on iOS until you connect to a sensor.

Connecting and streaming change like this:

device.statusEvents.listen((status) {
  if (status == DeviceConnectionStatus.connected) { 
  if (status == MovesenseDeviceStatus.connected) { 
    device.hr.listen((hr) => print('Heart rate: ${hr.average} bpm'));
  }
});
device.connect();
await device.connect();

Read a sensor stream only after the status is connected: in version 2.0, a stream read before the sensor is connected emits a StateError.

Version 2.0 also fixes the inertial measurement unit (IMU) data. In 1.x, MovesenseIMU read the gyroscope and magnetometer samples from the accelerometer array, so both lists held accelerometer values.

IMU data from version 2.0 therefore differs from 1.x data. Do not use the gyroscope and magnetometer values of IMU data that the app recorded with 1.x.

Migrate from carp_movesense_flutter

pub.dev lists carp_movesense_flutter as discontinued and replaced by movesense_plus. Replace the dependency:

flutter pub remove carp_movesense_flutter
flutter pub add movesense_plus

Not with carp_movesense_package 3.1.0

carp_movesense_package 3.1.0 depends on carp_movesense_flutter 0.1.x, which bundles MDS as a target named MovesenseMDS, as movesense_plus does. With Swift Package Manager, an iOS build of an app with these two plugins fails: "target names need to be unique across the package graph". Do not migrate an app that uses carp_movesense_package 3.1.0.

Change the imports and rename the client:

import 'package:carp_movesense_flutter/carp_movesense_flutter.dart';
import 'package:movesense_plus/movesense_plus.dart';

final client = CarpMovesenseFlutter();
final client = Movesense();

In 0.1.x, MovesenseDevice was the plain scan result class. That class is now MovesenseScanResult. The scanResults stream of Movesense now emits the typed MovesenseDevice, which still has name and address but no const constructor.

Update a custom platform

If the app has its own platform implementation, change its imports:

import 'package:carp_movesense_flutter/carp_movesense_flutter_platform_interface.dart';
import 'package:carp_movesense_flutter/carp_movesense_flutter_pigeon.dart';
import 'package:movesense_plus/movesense_plus_platform_interface.dart';
import 'package:movesense_plus/movesense_plus_pigeon.dart';

Then rename these classes:

Name in 0.1.xName in 2.0
CarpMovesenseFlutterPlatformMovesensePlatform
PigeonCarpMovesenseFlutterPigeonMovesense
CarpMovesenseHostGateway, PigeonCarpMovesenseHostGatewayMovesenseHostGateway, PigeonMovesenseHostGateway
MethodChannelCarpMovesenseFlutterRemoved; use PigeonMovesense

The scanResults getter of the platform now emits MovesenseScanResult. For a fake platform that works with version 2.0, see Testing without a sensor.

Migrate from mdsflutter

Replace the dependency:

flutter pub remove mdsflutter
flutter pub add movesense_plus

Change the import. Mds and MdsAsync have the same methods and signatures as in mdsflutter 2.3.1, so the calls compile unchanged:

import 'package:mdsflutter/Mds.dart';
import 'package:movesense_plus/movesense_plus.dart';

mdsflutter needs the same MDS setup as 1.x. To remove it, see Remove the MDS setup.

These behaviors differ from mdsflutter:

  • Scan duration: on iOS, mdsflutter stops a scan after 60 seconds. movesense_plus sets no time limit: a scan runs until you call Mds.stopScan(), or on iOS until you connect to a sensor.
  • Scan errors: Mds.startScan() and MdsAsync.startScan() take no error callback, so a scan error, such as Bluetooth is not powered on. on iOS, becomes an unhandled exception. To handle scan errors, scan with a Movesense client and listen to scanResults with onError. See Scan for sensors.
  • Connected sensors: on iOS, an mdsflutter scan also reports sensors that are already connected to the iPhone. A movesense_plus scan reports only sensors that advertise.
  • Connection callbacks: on Android, Mds.connect() now calls onBleConnected; mdsflutter calls it only on iOS.
  • Connecting again: on Android, you can call Mds.connect() again after a sensor disconnects by itself. mdsflutter reports Already connected to <address> to onConnectionError instead.
  • Subscriptions: MdsAsync.subscribe() subscribes when you listen to the stream, not when you call it.
  • Error type: MdsError now implements Exception, so an on Exception clause catches it too; in mdsflutter, it is a plain class.

To scan for a set time, stop the scan in your code:

Mds.startScan((name, address) => print('Found $name [$address]'));
await Future.delayed(const Duration(seconds: 10));
Mds.stopScan();

To handle failed requests, catch MdsError:

try {
  final info = await MdsAsync.get(Mds.createRequestUri(serial, '/Info'), '{}');
  print('Firmware: ${info['sw']}');
} on MdsError catch (error) {
  print('Request failed with status ${error.status}: ${error.error}');
}

To replace the Mds calls with the typed device API, see Connecting a device.

Quick links

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