Get Started
The Flutter Tracking SDK has three parts: a Dart dependency and wrapper, native Android setup, and native iOS setup. All three are required - the Dart layer has nothing to call into until the native side is wired up.
Prerequisites and requirements
- Access Key: an API key issued during onboarding - see Contact us to request access.
- Tenant, username and password: the Tracking SDK sends tracking logs for a tracked object to a specific tenant and requires authentication to do so - see Contact us to request access.
- Flutter: a recent stable Flutter SDK.
- Completed native prerequisites for Android and iOS.
Exchange your credentials for a token
curl -X POST 'https://api-gw.sovereignsolutions.com/gateway/authen/oauth/token?api-key=$API_KEY' \
-d 'grant_type=password' \
-d 'username=$USERNAME' \
-d 'password=$PASSWORD'
{
"access_token": "<JWT>",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "<token>"
}
Keep the access_token. Every other endpoint needs it.
Full details: Authentication reference
1. Add Dart dependencies
In pubspec.yaml:
dependencies:
flutter:
sdk: flutter
permission_handler: ^11.3.1
shared_preferences: ^2.3.3
flutter pub get
2. Add the MethodChannel wrapper
Create lib/tracking_sdk.dart:
import 'package:flutter/services.dart';
class TrackingSdk {
static const MethodChannel _channel = MethodChannel('TrackingSdk');
static Future<String> initAndStartTracking({
required String baseUrl,
required String apiKey,
required String username,
required String clientId,
}) async {
final result = await _channel.invokeMethod<dynamic>(
'initAndStartTracking',
{
'baseUrl': baseUrl,
'apiKey': apiKey,
'username': username,
'clientId': clientId,
},
);
return _messageFromResult(result, fallback: 'Tracking started');
}
static Future<String> stopTracking() async {
final result = await _channel.invokeMethod<dynamic>('stopTracking');
return _messageFromResult(result, fallback: 'Tracking stopped');
}
static Future<bool> getTrackingState() async {
final result = await _channel.invokeMethod<dynamic>('getTrackingState');
if (result is bool) return result;
if (result is Map) return result['enabled'] == true;
return false;
}
static Future<Map<String, dynamic>> getSavedTrackingStatus() async {
final result = await _channel.invokeMethod<Map<dynamic, dynamic>>(
'getSavedTrackingStatus',
);
return Map<String, dynamic>.from(result ?? {});
}
static Future<Map<String, dynamic>> sync() async {
final result = await _channel.invokeMethod<Map<dynamic, dynamic>>('sync');
return Map<String, dynamic>.from(result ?? {});
}
static Future<Map<String, dynamic>> refreshAccessToken() async {
final result = await _channel.invokeMethod<Map<dynamic, dynamic>>(
'refreshAccessToken',
);
return Map<String, dynamic>.from(result ?? {});
}
static Future<Map<String, dynamic>> destroy() async {
final result = await _channel.invokeMethod<Map<dynamic, dynamic>>('destroy');
return Map<String, dynamic>.from(result ?? {});
}
static String _messageFromResult(dynamic result, {required String fallback}) {
if (result == null) return fallback;
if (result is String) return result;
if (result is Map) {
final message = result['message'];
if (message != null && message.toString().trim().isNotEmpty) {
return message.toString();
}
}
return fallback;
}
}
3. Native Android setup
Add the native SDK dependency, permissions and MethodChannel handler exactly as documented on the
Android Tracking SDK page, then register the
channel in MainActivity.kt:
class MainActivity : FlutterActivity() {
companion object {
private const val CHANNEL = "TrackingSdk"
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"initAndStartTracking" -> { /* forward to SsBackgroundGeolocation.initSDK + startTracking */ }
"stopTracking" -> { /* forward to SsBackgroundGeolocation.stopTracking */ }
"getTrackingState" -> { /* forward to SsBackgroundGeolocation.getState */ }
"getSavedTrackingStatus" -> { /* read from SharedPreferences */ }
else -> result.notImplemented()
}
}
}
}
See Android Usage for the full SsBackgroundGeolocation
call signatures each case should forward to.
4. Native iOS setup
Add the SDK pod, Info.plist permissions and Background Modes exactly as documented on the
iOS Tracking SDK page. Because Flutter iOS projects
using multi-window support register through SceneDelegate.swift rather than AppDelegate.swift,
register the same channel name there:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
private let channelName = "TrackingSdk"
func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
registerTrackingSdkChannel()
}
private func registerTrackingSdkChannel() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
guard let controller = self.window?.rootViewController as? FlutterViewController else {
return
}
let channel = FlutterMethodChannel(name: self.channelName,
binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler { call, result in
switch call.method {
case "initAndStartTracking":
// forward to SSBackgroundGeolocation.shared.initSDK + startTracking
break
case "stopTracking":
// forward to SSBackgroundGeolocation.shared.stopTracking
break
case "getTrackingState":
// forward to SSBackgroundGeolocation.shared.getState
break
case "sync", "refreshAccessToken", "destroy":
// forward to the matching SSBackgroundGeolocation.shared method
break
default:
result(FlutterMethodNotImplemented)
}
}
}
}
}
See iOS Usage for the full SSBackgroundGeolocation call
signatures each case should forward to.
Changes to MainActivity.kt or SceneDelegate.swift need a full rebuild - uninstall the app and
run again. Hot reload does not pick up native channel registration changes.
Next steps
- Usage - call the SDK from Dart
- Sample apps - reference source code on GitHub
Looking for the map rendering SDK instead? See the Flutter Map SDK.