Field Track 360

Developer guide

Integrate the SDK

Native SDKs for Android and iOS, with bridges for React Native and Flutter. Pick your platform - the setup genuinely differs, so these are not one page.

Install

RN 0.76+ with the New Architecture. Bare CLI is the primary path.

npm install @devstree/react-native-trackit

On install, a postinstall script fetches the pinned iOS XCFrameworks and verifies a SHA-256 per framework. A checksum mismatch fails loudly - the iOS SDK's 1.0.1 tag has been force-published in place more than once, so a tag-only pin is not safe on its own.

Platform floors - not adjustable from the bridge

  • React Native 0.76+, New Architecture enabled
  • iOS 17.0
  • Android minSdk 26, compileSdk 37, JDK 17

These are the maximum of the SDK's own constraints.

Android setup

JitPack credentials, SDK floors and a Maps key - all host steps a library cannot do for you.

RN templates use dependencyResolutionManagement with FAIL_ON_PROJECT_REPOS, so a library cannot declare a repository for you. This is a required host step.

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven {
            url 'https://jitpack.io'
            credentials { username = providers.gradleProperty('authToken').getOrElse('') }
        }
    }
}

The authToken goes in your user-level ~/.gradle/gradle.properties, never the project's - the project file is committed by default and the token grants read access to private packages.

Then set minSdkVersion 26 and compileSdkVersion 37, and add a Google Maps API key to AndroidManifest.xml - both map components render through Google Maps.

iOS setup

One unavoidable native line, because Apple requires background-task registration before JS starts.

TrackIt.ready() reaches BGTaskScheduler.register, which Apple requires before didFinishLaunching returns - and React Native starts JS only after that. So one native line is unavoidable:

import TrackIt

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: ...) -> Bool {
    TrackItLaunch.ready()
    // ... your existing RN setup ...
}

Calling TrackIt.ready(config) from JS later is safe and re-applies capture parameters, but fields affecting background-task registration are fixed at launch on iOS.

Add the usual Info.plist keys, the verbatim background-task identifiers, and a Podfile post_install gate on IPHONEOS_DEPLOYMENT_TARGET - s.platforms alone only hard-fails a clean install, and dyld then cannot load a framework whose MinimumOSVersion is 17.

Usage

One unified API, with platform-specific namespaces for the rest.

TrackIt.* is what both platforms do; TrackIt.ios.* and TrackIt.android.* hold the platform-specific rest, and calling the wrong one rejects with unsupportedOnPlatform.

import TrackIt, { onTrackItEvent, onLiveTrack, TrackMapView } from '@devstree/react-native-trackit';

const res = await TrackIt.ready({ trackingMode: 'adaptive', intervalMs: 1000 });
if (!res.ok) console.warn(res.code, res.message);

await TrackIt.start('my-trip');
const track = await TrackIt.buildTrack();
await TrackIt.stop();

How failures cross the bridge

Domain failures resolve as TrackItResult - { ok: false, code, message }. Promises reject only for bridge faults: bad arguments, a call before init, or unsupportedOnPlatform. Never swallow the ready() result; everything downstream is gated on it.

Subscriptions return an unsubscribe function

onTrackItEvent, onLiveTrack, onPoints, onStateChange and onProviderStateChange each return a function - call it on unmount.

Known limitations

Stated, not discovered.

  • getCurrentLocation({ feedIngestor: true }) is iOS-only and rejects on Android.
  • getCurrentLocation failures all report fixTimeout on iOS. Timeout, missing authorization and a concurrent call share one code; only message distinguishes them. Do not treat it as "retry later".
  • Geofence dwell is iOS-only. geofenceAdded and geofenceRemoved are Android-only. Setting dwellAfterMs or notifyOnEntry/Exit false on Android is refused with invalidConfig.
  • Geofence crossings delivered to a relaunched process never reach a live JS subscriber - read them from TrackIt.geofences.getEvents().
  • A JS-implemented road-snap provider is not supported. OSRM is configurable via setOsrmSnapProvider.
  • The two sync network gates are not unified: iOS requiresNetworkConnectivity and Android requiresUnmeteredNetwork are different policies.
  • Android needs a Google Maps API key and JitPack credentials in the host settings.gradle.
  • Android release builds need a licence token that does not exist yet.

Expo

An Expo prebuild host cannot do the manual steps above, because android/ and ios/ are generated and overwritten. A config plugin is the only path that survives a prebuild, and it ships in a later release. Until then use the bare CLI path, or an Expo development build once the plugin is published.

Why we verify licences on the device, not on our servers

A tracking app that stops because a licence server was unreachable is worse than a licence that survives a few hours too long.

Every Field Track 360 licence is verified on the device, with no network call. The token carries its own signature, the SDK carries the public key, and the check happens locally in milliseconds.

The trade we made

We also check periodically to see whether a licence has been revoked. That check can fail - no signal, a flat DNS, our own outage. When it does, the SDK keeps working on its cached verdict.

That is deliberate, and it costs us something: a revoked licence can survive until the device next reaches the network. We think that is the right side to fail on. A field team's tracking going dark mid-shift because a server blinked is a far worse outcome than a licence lasting a few hours longer than it should.

What it means for you

Your app starts tracking on a plane, on first launch, in a tunnel, after a reboot. There is no licence server in the path of your users doing their jobs.

Application keys: what one key actually covers

One key, one app identifier, both platforms, forever. Here is why it is bound that way and what to do when the id changes.

An application key licenses one app identifier - your Android applicationId or your iOS CFBundleIdentifier - on both platforms.

Why is it bound to the identifier? Because the identifier is cryptographically signed into the token itself. That is what makes a key impossible to copy into a second app: change the identifier and the signature no longer matches. It also means the binding cannot be edited afterwards, by us or by anyone else.

Debug and staging builds

Variants are included, as long as they extend your identifier with a dot. com.acme.app.dev and com.acme.app.staging are covered by a key for com.acme.app. com.acme.appX is a different application and is not - the dot is what separates a variant from a namesake.

When the identifier changes

It happens: a rebrand, a store migration, a typo caught late. Generate a corrected key from your account and the old one is revoked automatically. There is a limit per plan, because a revoked key still verifies offline, and support can help beyond it.

Try it before you buy

A 30-day trial licence for one application, issued instantly. Development builds are licence-waived, so you can evaluate the whole SDK first.

Get a trial key

Verification API

The SDK handles licensing for you. This is documented for tooling.

POST https://fieldtrack360-sdk.devstree.in/api/v1/verify