@panter/react-native-motiontag
v0.1.1
Published
React Native Turbo Module for the MotionTag tracking SDK
Readme
@panter/react-native-motiontag
Turbo Module wrapping the MotionTag tracking SDK for React Native (new architecture only).
The JS surface mirrors the official Flutter SDK so payloads are interchangeable. Bridges the iOS SDK v6.5.x and the Android SDK v7.2.x — the platform asymmetry is hidden behind a shared TS contract.
Contents
- Install in your app
- Setup with Expo (recommended)
- Setup with bare React Native
- Public API
- Pre-RN events
- Native dependency versions
- Running the example app
- Repo layout
- Developing the package
- Releasing
Install in your app
yarn add @panter/react-native-motiontag
# or: npm install @panter/react-native-motiontagThen follow either the Expo path (recommended) or the bare React Native path below — pick the one that matches your project.
Public API
import MotionTag from '@panter/react-native-motiontag'
await MotionTag.setUserToken(jwt)
await MotionTag.start()
const active = await MotionTag.isTrackingActive()
await MotionTag.stop()
const subscription = MotionTag.addListener(event => {
if (event.type === 'transmissionError' && event.errorCode === 401) {
// re-auth flow
}
})
subscription.remove()addListener supports multiple subscribers; each addListener returns its
own EventSubscription. The MotionTagEvent discriminated union covers
started, stopped, location, transmissionSuccess, transmissionError,
authorization (iOS), powerSaveModeChanged (Android),
batteryOptimizationsChanged (Android), and a fall-through log channel
that carries the diagnostic string format the underlying SDKs emit.
The platform-only methods (isPowerSaveModeEnabled,
isBatteryOptimizationsEnabled on Android; getWifiOnlyDataTransfer /
setWifiOnlyDataTransfer / clearData on Android) resolve to safe defaults
(false) or reject ('UNSUPPORTED') on iOS, matching the Flutter SDK's
behaviour.
Setup with Expo (recommended)
The package ships an Expo config plugin that wires up everything for you on
expo prebuild: AppDelegate bootstrap (iOS), MainApplication bootstrap
(Android), Info.plist permission + background-mode keys, foreground-service
notification factory, the Azure DevOps Maven repo, and the extra Android
permissions (POST_NOTIFICATIONS, FOREGROUND_SERVICE).
Add the plugin to app.json:
{
"expo": {
"newArchEnabled": true,
"plugins": [
[
"@panter/react-native-motiontag",
{
"iosPermissions": {
"locationAlwaysAndWhenInUse": "We use your location to track your trips.",
"locationWhenInUse": "We use your location to track your trips.",
"motion": "We use motion data to detect transport modes."
},
"androidNotification": {
"channelId": "motiontag_tracking",
"channelName": "Tracking",
"title": "MyApp",
"text": "Tracking is active"
}
}
]
]
}
}Then run npx expo prebuild --clean followed by npx expo run:ios /
npx expo run:android. Expo Go is not supported — the library has native
code, you need a development client build.
Setup with bare React Native
The MotionTag SDKs need to be initialised before React Native starts.
Turbo modules are instantiated lazily on first JS access, so they cannot run
this themselves — the host app must call a small bootstrap from its
AppDelegate (iOS) and Application.onCreate (Android).
iOS — AppDelegate.swift
import RNMotionTag
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
MotionTagBootstrap.bootstrap(launchOptions: launchOptions)
// … rest of RN bootstrap …
}
func application(
_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void
) {
MotionTagBootstrap.processBackgroundSessionEvents(
identifier: identifier,
completionHandler: completionHandler
)
}The host's Info.plist must declare:
NSLocationAlwaysAndWhenInUseUsageDescription,NSLocationWhenInUseUsageDescription,NSMotionUsageDescription— user-facing copy stays host-owned.UIBackgroundModescontaininglocation,fetch,processing.BGTaskSchedulerPermittedIdentifierscontainingcom.motiontag.sdk.backgroundrefreshandcom.motiontag.sdk.backgroundtask(required from SDK 6.5.0).FirebaseAppDelegateProxyEnabled = falseif the app uses Firebase, so its swizzling doesn't interfere with MotionTag's background URLSession.
Tested with both CocoaPods' default static-library linkage and
use_frameworks! :linkage => :static (commonly enabled by Firebase,
MapBox, and other Swift-only iOS SDKs) — no host-side workaround needed
in either mode.
Android — Application.onCreate
import de.motiontag.reactnative.MotionTagBootstrap
override fun onCreate() {
super.onCreate()
loadReactNative(this)
MotionTagBootstrap.init(this, createNotification())
}The host owns the foreground-service Notification (channel id, title,
text, icon) — the package does not impose copy or branding. Required
SDK permissions (ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION,
ACCESS_BACKGROUND_LOCATION, ACTIVITY_RECOGNITION,
FOREGROUND_SERVICE_LOCATION) are merged into the host manifest by Gradle.
Pre-RN events
Events that fire between native init (in MotionTagBootstrap.init /
MotionTagBootstrap.bootstrap) and the JS subscription being installed are
dropped — only the diagnostic log line goes to logcat / Console. In
practice this is rare and only affects authorization-status changes; the
3-second polling reconciler in useTrackingChecks re-establishes state.
Native dependency versions
| Platform | Version | Source |
| --- | --- | --- |
| iOS | MotionTagSDK ~> 6.5.0 | CocoaPods trunk (transitive from this pod) |
| Android | de.motiontag:tracker:7.2.5 | pkgs.dev.azure.com/motiontag/releases (Maven repo declared in this package) |
The two SDKs are intentionally out of sync — aligning them is tracked as a follow-up.
Running the example app
The example/ folder is a working Expo dev-client app that exercises the
package end-to-end: paste a JWT, request permissions, start tracking, watch
events stream in.
Requirements: Node 18+, Xcode for iOS, Android Studio for Android. Expo Go won't work — the package has native code, so you need a development client build.
git clone https://github.com/panter/react-native-motiontag
cd react-native-motiontag
yarn install # installs root devDeps + runs `bob build`
cd example
yarn install # installs Expo deps + links the parent
npx expo prebuild --clean # runs the config plugin, generates ios/ + android/
npx expo run:ios # build + boot iOS simulator
# or
npx expo run:android # needs an Android emulator running firstAfter the first run, the dev loop is:
cd example
npx expo start --dev-client # Metro only; reuses the installed appPress r to reload the JS bundle. Edits to App.tsx, src/, or the
library's src/index.ts hot-reload via Fast Refresh. Native (Swift/Kotlin)
edits require another expo run:ios / expo run:android.
For real motion-tracking validation use a physical device
(npx expo run:ios --device) — simulators can't generate motion-sensor
data and only fake locations via Xcode's debug-location menu / the Android
emulator's location controls.
Repo layout
react-native-motiontag/
├── src/ # TS source (the JS API surface)
├── android/ # Android Turbo Module (Kotlin, AAR)
├── ios/ # iOS Turbo Module (Swift + ObjC++)
├── plugin/ # Expo config plugin (plain JS, no build)
├── app.plugin.js # Plugin entrypoint loaded by `expo prebuild`
├── react-native-motiontag.podspec
├── package.json # Published npm package
└── example/ # Standalone Expo demo app consuming the packageThe library and the example are independent npm packages. The example declares the parent via a relative path:
{ "dependencies": { "@panter/react-native-motiontag": "file:.." } }When you yarn install in example/, that resolves as a symlink to the
parent — edit library source and changes show up in the example without a
re-install.
Developing the package
- Build the library:
yarn installat the root runsreact-native-builder-bobvia thepreparescript. Output lands inlib/{commonjs,module,typescript}. These are produced for npm consumers; the example usessrc/directly via thereact-nativefield inpackage.json. - Type-check the source:
npx tsc -p tsconfig.build.json --noEmit. - Dedup peer deps:
react/react-nativeare installed both at the root (devDeps, forbob build+ tsc) and inexample/(runtime). Metro is configured inexample/metro.config.jsto block the root copies and redirect every'react-native'/'react'import to the example's copy — a single physical instance at runtime. .npmrcat root setslegacy-peer-deps=trueso npm doesn't try to auto-install our declared peers when someone runsnpm installat the library root.- Iterate on the Expo config plugin by editing files in
plugin/and re-runningnpx expo prebuild --cleaninexample/. The injected blocks are wrapped in@generatedmarkers and de-duplicated on re-run.
Releasing
Releases are fully automated via release-please
and npm Trusted Publishing — no manual npm publish, no tokens, no
hand-edited changelog.
The flow
- Land commits on
mainusing Conventional Commits:feat: …— minor bump (0.1.0→0.2.0)fix: …— patch bump (0.1.0→0.1.1)feat!: …orBREAKING CHANGE:in body — major bumpchore:,docs:,refactor:,test:,ci:— no bump, but appear in the changelog under their respective sections
- The Release Please workflow opens (or updates) a PR titled
chore(main): release X.Y.Z. The PR contains the version bump inpackage.json, an updatedCHANGELOG.mdassembled from the commit subjects since the last release, and an updated.release-please-manifest.json. - Merge that PR when you're ready to release. Release-please then creates
the git tag (
vX.Y.Z) and a matching GitHub Release with the changelog body. - The
publishjob in the same workflow runsyarn prepare(bob build) andnpm publishwith npm provenance attached via OIDC.
One-time npm setup
On npmjs.com → @panter/react-native-motiontag → Settings →
Trusted Publisher → Add:
- Publisher: GitHub Actions
- Organization:
panter - Repository:
react-native-motiontag - Workflow filename:
release-please.yml - Environment: (leave blank)
No NPM_TOKEN secret is required.
Hotfix / manual override
If you need to publish a version that release-please can't produce (e.g. a
hotfix from a non-main branch), bump package.json + push manually:
yarn prepare
npm publish --access publicYou'll need a local npm auth token for the manual path — Trusted Publishing only covers the GitHub Actions workflow.
