Bỏ qua, tới nội dung chính
Fleetwork
Fleetwork

Liên hệ

Chúng tôi rất mong nhận được liên hệ từ bạn. Vui lòng điền vào biểu mẫu bên dưới.

SDK

Hướng dẫn tích hợp Flutter SDK

Hướng dẫn tích hợp GPS Tracking cho ứng dụng Flutter mobile (iOS & Android).

Package: vietmap_tracking_plugin

Plugin xây dựng trên native platform channel (MethodChannel + EventChannel), không dùng thư viện location bên thứ ba. Hỗ trợ tracking nền thời gian thực, cảnh báo tốc độ, cache offline tự đồng bộ, phát hiện GPS giả và tối ưu pin.

Liên hệ Vietmap để lấy API key trước khi tích hợp: maps.info@vietmap.vn


Tính năng

Tính năngMô tả
Background GPSForeground service (Android) / background location mode (iOS).
Xác thực API keyinitializeTracking xác thực phía server — throw INVALID_API_KEY nếu key không hợp lệ.
MetadatasetMetadata đính kèm key-value tuỳ ý vào mỗi bản ghi GPS.
Package CodessetPackages gắn danh sách mã kiện hàng vào mỗi bản ghi GPS.
Cảnh báo tốc độGiám sát tốc độ thời gian thực qua Alert API.
Offline CacheCache SQLite, tự upload khi có mạng trở lại.
Speed FallbackTự tính tốc độ khi hệ điều hành không trả về (Xiaomi/MIUI); gắn cờ speedSource.
Fake GPS DetectionPhát hiện ở native với 4 policy; SDK tự bắn notification kể cả khi app bị kill.
Smart BatteryOpt-in — điều chỉnh tần suất tracking theo mức pin và chuyển động.
Tracking PresetsNavigation / Fitness / General / BatterySaver — bản theo thời gian và theo khoảng cách.
Tracking InterruptedPhát hiện khi tracking nền bị hệ điều hành dừng và khi khôi phục.
Location UtilitiesTính khoảng cách Haversine, đổi đơn vị tốc độ, kiểm tra geofence.

Yêu cầu hệ thống

Minimum
Flutter3.3.0+
Dart3.8.0+
AndroidAPI 21+ (Android 5.0)
iOS15.0+

Native SDK đi kèm

Nền tảngSDKPhiên bản
iOSVietmapTrackingSDK (CocoaPods)1.5.1
Androidcom.github.vietmap-company:vietmap-tracking-sdk-android1.5.2

1. Cài đặt

pubspec.yaml

yaml
dependencies:
  vietmap_tracking_plugin: ^1.1.3
Shell
flutter pub get

2. Cấu hình Android

2.1. Quyền — android/app/src/main/AndroidManifest.xml

XML
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<!-- Bắt buộc để hiện notification trên Android 13+ -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Bắt buộc để đặt lại notification sau khi khởi động lại máy -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

Android 10+ (API 29+): ACCESS_BACKGROUND_LOCATION phải được xin sau khi quyền foreground đã được cấp. Plugin tự xử lý luồng 2 bước này.

2.2. Receiver cho notification — trong khối <application>

Bắt buộc để flutter_local_notifications xin được quyền runtime:

XML
<receiver android:exported="false"
    android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
<receiver android:exported="false"
    android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
        <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
        <action android:name="android.intent.action.QUICKBOOT_POWERON" />
        <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
    </intent-filter>
</receiver>
<receiver android:exported="false"
    android:name="com.dexterous.flutterlocalnotifications.ActionBroadcastReceiver" />

2.3. Maven repository — android/build.gradle

groovy
allprojects {
    repositories {
        maven { url 'https://jitpack.io' }
    }
}

3. Cấu hình iOS

3.1. Mô tả quyền vị trí — ios/Runner/Info.plist

XML
<key>NSLocationWhenInUseUsageDescription</key>
<string>Ứng dụng cần GPS để theo dõi vị trí khi đang chạy.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Ứng dụng cần GPS để tiếp tục theo dõi khi chạy nền.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Ứng dụng cần GPS khi chạy nền.</string>

3.2. Background Modes — ios/Runner/Info.plist

XML
<key>UIBackgroundModes</key>
<array>
    <string>location</string>
    <string>background-processing</string>
    <string>background-fetch</string>
</array>

3.3. Background Task Identifiers (iOS 15+)

XML
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
    <string>com.vietmaptrackingsdk.location-sync</string>
    <string>com.vietmaptrackingsdk.background-location</string>
</array>

3.4. Notification khi app đang mở — ios/Runner/AppDelegate.swift

Mặc định iOS ẩn banner notification khi app đang ở foreground. Cần 2 thay đổi:

  1. Gán UNUserNotificationCenter.current().delegate = self trước GeneratedPluginRegistrant.register(with: self).
  2. Override userNotificationCenter(_:willPresent:) và gọi thẳng completionHandler([.banner, .sound, .badge]) — không gọi super.

3.5. CocoaPods

Ruby
# ios/Podfile
platform :ios, '15.0'
Shell
cd ios && pod install

Lần cài đầu tiên: chạy pod install --repo-update để VietmapTrackingSDK lấy đúng spec mới nhất.


4. Khởi tạo SDK

Dart
import 'package:flutter/services.dart';
import 'package:vietmap_tracking_plugin/vietmap_tracking_plugin.dart';

final controller = VietmapTrackingController.instance;

Future<void> initSdk() async {
  try {
    // Xác thực API key phía server — throw INVALID_API_KEY nếu bị từ chối
    await controller.initializeTracking(
      'YOUR_API_KEY',
      baseURL: 'https://live.fleetwork.vn/api/v1', // tuỳ chọn
    );
    // Đăng ký lifecycle observer để SDK tự chuyển background/foreground
    controller.registerLifecycleObserver();
  } on PlatformException catch (e) {
    if (e.code == 'INVALID_API_KEY') {
      print('API key không hợp lệ: ${e.message}');
    }
  }
}

Gọi initializeTracking() trước tất cả method khác. baseURL là tuỳ chọn — SDK dùng URL production mặc định.


5. Quyền truy cập

Dart
// Bước 1 — quyền vị trí foreground
final result = await controller.requestLocationPermissions();
if (!result.granted) return;

// Bước 2 — quyền background (Android 10+ hiện popup riêng)
await controller.requestAlwaysLocationPermissions();

// Bước 3 — quyền notification (Android 13+ / iOS)
// Android
await Permission.notification.request();
// iOS
await FlutterLocalNotificationsPlugin()
    .resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
    ?.requestPermissions(alert: true, sound: true, badge: false);

Kiểm tra trạng thái quyền (không hiện popup):

Dart
final result = await controller.hasLocationPermissions();
if (!result.granted) {
  // Dẫn user đến màn hình cấp quyền
}

6. Bắt đầu / Dừng theo dõi

userId là bắt buộc — bản ghi GPS được gắn theo giá trị này ở phía server. Thiếu userId thì bản ghi không xác định được chủ sở hữu.

Dart
await controller.startTracking(
  LocationTrackingConfig(
    userId: 'user-123',            // bắt buộc
    intervalMs: 30000,             // 30 giây / lần
    accuracy: LocationAccuracy.high,
    backgroundMode: true,
    notificationTitle: 'Đang theo dõi hành trình',
    notificationMessage: 'Chạm để mở ứng dụng',
  ),
);

// Hoặc dùng preset dựng sẵn:
await controller.startTracking(
  TrackingPresets.general().copyWith(userId: 'user-123'),
);

// Hoặc để native SDK tự quyết định tần suất (timer 10 giây / sàn 25 mét):
await controller.startTracking(
  LocationTrackingConfig.sdkDefault(userId: 'user-123'),
);

// Dừng tracking
await controller.stopTracking();
controller.unregisterLifecycleObserver();

Tracking giữ nguyên tần suất bạn truyền vào. Smart Battery là opt-in — truyền enableSmartBattery: true nếu muốn SDK tự điều chỉnh interval theo mức pin và chuyển động.


7. Chế độ tracking & Presets

LocationTrackingConfig hỗ trợ 2 chiến lược loại trừ lẫn nhau. Chỉ truyền một trong intervalMs hoặc distanceFilter, cái còn lại để null.

Chế độintervalMsdistanceFilterHành vi
Interval (theo thời gian)> 0nullCập nhật mỗi N mili-giây, bất kể có di chuyển hay không.
Distance (theo khoảng cách)null> 0Chỉ cập nhật sau khi thiết bị di chuyển M mét.
SDK defaultnullnullDùng tần suất mặc định của native SDK — timer 10 giây, sàn khoảng cách 25 mét. Dùng LocationTrackingConfig.sdkDefault().

SDK nâng distanceFilter lên sàn 25 mét. Nếu truyền cả hai giá trị thì timer thắng và distance bị bỏ qua (logic giống nhau trên Android và iOS).

Preset dựng sẵn

Theo thời gian:

Dart
TrackingPresets.navigation()    //  5 giây  — theo dõi xe thời gian thực / dẫn đường
TrackingPresets.fitness()       // 10 giây  — hoạt động ngoài trời
TrackingPresets.general()       // 30 giây  — cân bằng cho đội xe (mặc định)
TrackingPresets.batterySaver()  //  5 phút  — xe đỗ hoặc pin yếu

Theo khoảng cách:

Dart
TrackingPresets.navigationDistance()    // mỗi  25 m — sàn khoảng cách của SDK
TrackingPresets.fitnessDistance()       // mỗi  50 m
TrackingPresets.generalDistance()       // mỗi  70 m
TrackingPresets.batterySaverDistance()  // mỗi 120 m

Luôn gắn userId qua copyWith:

Dart
await controller.startTracking(
  TrackingPresets.general().copyWith(userId: 'user-123'),
);

8. Kiểm tra trạng thái & cập nhật cấu hình

Dart
final isActive = await controller.isTrackingActive(); // bool

final status = await controller.getTrackingStatus();
print(status.isTracking);          // bool
print(status.trackingDuration);    // int (ms kể từ startTracking)
print(status.lastLocationUpdate);  // int? (unix ms)

// Đổi config mà không cần restart tracking
await controller.updateTrackingConfig(
  LocationTrackingConfig(
    userId: 'user-123',
    intervalMs: 60000,
    accuracy: LocationAccuracy.medium,
    backgroundMode: true,
  ),
);

Lưu ý iOS với updateTrackingConfig: iOS thực hiện stop → start ở bên trong. Hệ quả: có khoảng trống vị trí ngắn, trackingDuration bị reset, và stream trạng thái bắn false → true.


9. Smart Battery Optimization

Smart Battery là opt-in. Truyền enableSmartBattery: true vào startTracking(); stopTracking() sẽ tắt lại.

Dart
// Đặt profile ưu tiên khi xe đang di chuyển (mặc định: general)
controller.setSmartBatteryPreferredProfile(SmartBatteryProfile.navigation);

await controller.startTracking(config, enableSmartBattery: true);

// Feed GPS vào Smart Battery để phát hiện vào cua và đứng yên
controller.onLocationUpdate.listen((LocationData loc) {
  controller.feedLocationToSmartBattery(loc.speed, heading: loc.bearing);
});

// Lắng nghe khi profile thay đổi
controller.onSmartBatteryProfileChanged.listen((profile) {
  print('Battery profile: $profile');
});

feedLocationToSmartBattery cấp tốc độ và hướng cho bộ điều phối profile — nhờ đó SDK phát hiện được xe đứng yên (≥ 60 giây) và vào cua (heading lệch > 15°) để tự chuyển profile.

Có thể bật/tắt giữa phiên:

Dart
await controller.enableSmartBatteryOptimization();   // áp preset của profile hiện tại
controller.disableSmartBatteryOptimization();        // chỉ teardown phía Dart
await controller.updateTrackingConfig(myConfig);     // …nên khôi phục lại config sau khi tắt
ProfileIntervalKhi nào dùng
navigation5 giâyDẫn đường, cần độ chính xác cao
general30 giâyTheo dõi đội xe tiêu chuẩn
batterySaver5 phútPin yếu / xe đang đỗ

Vì sao là opt-in: ngay khi bật, SDK áp preset native của profile đang active, ghi đè interval mà startTracking() vừa nhận — một config yêu cầu SDK default 10 giây sẽ bị đẩy về 30 giây của general chỉ vài khoảnh khắc sau khi start. Việc giãn upload khi xe đứng yên (1 heartbeat mỗi 5 phút) là cơ chế native riêng, luôn bật và không phụ thuộc flag này.

SDK tự chuyển profile theo điều kiện:

  • Pin < 15% và không sạc → batterySaver
  • Xe đứng yên ≥ 60 giây → general
  • Xe vào cua (heading lệch > 15°) → navigation
  • Còn lại → preferredMovingProfile (mặc định: general)

Config tuỳ chỉnh kết hợp Smart Battery

Phần này chỉ áp dụng khi Smart Battery được bật — không bật thì config giữ nguyên như bạn truyền vào.

Khi đã bật, Smart Battery ghi đè interval native ở mọi lần chuyển profile (pin yếu, đứng yên, vào cua). Với preset chuẩn thì đây là hành vi mong muốn. Với LocationTrackingConfig tuỳ chỉnh mang giá trị không thuộc preset, hãy đăng ký callback customGeneralConfigOverride trước khi gọi startTracking() để config của bạn được áp lại thay vì preset native.

Dart
// Đăng ký trước startTracking — yêu cầu Smart Battery áp lại config của bạn
// thay vì chuyển sang preset native ở mỗi lần đổi profile.
SmartBatteryManager.instance.customGeneralConfigOverride = () async {
  await controller.updateTrackingConfig(myCustomConfig);
};

await controller.startTracking(myCustomConfig, enableSmartBattery: true);

Xoá override khi dừng để nó không tồn tại sang phiên sau:

Dart
await controller.stopTracking();
SmartBatteryManager.instance.customGeneralConfigOverride = null;

Lưu ý: Callback được gọi ở mọi lần chuyển profile — kể cả batterySaver và navigation — nên Smart Battery vẫn phát hiện và báo cáo thay đổi profile qua onSmartBatteryProfileChanged, nhưng interval tracking thực tế luôn theo config của bạn.


10. Package Codes

setPackages gắn danh sách mã kiện hàng vào mỗi bản ghi GPS dưới dạng trường top-level packages — nằm cạnh metadata, không nằm trong metadata:

JSON
{
  "time": 1777876800000,
  "lat": 10.762219,
  "lng": 106.656956,
  "packages": ["#10001", "#10002"],
  "metadata": {}
}
Dart
await controller.setPackages(['#10001', '#10002']);

// Danh sách rỗng = xoá trường — payload bỏ hẳn field, không gửi []
await controller.setPackages([]);

Trường này là tuỳ chọn, có thể set trước hoặc trong khi tracking. Danh sách được chốt theo từng GPS point tại thời điểm ghi nhận, nên một point cache offline vẫn giữ mã kiện lúc nó được capture, thay vì lấy giá trị đang set khi upload.


11. Offline Cache & Sync

SDK tự động lưu local khi mất mạng và gửi lại khi có kết nối.

Dart
// Tắt auto upload để tự kiểm soát
await controller.setAutoUpload(false);

final pending   = await controller.getCachedLocationsCount(); // số điểm chờ gửi
final sizeBytes = await controller.getDatabaseSizeBytes();    // kích thước cache

// Cấu hình giới hạn cache
await controller.configureCacheLimits(
  maxRecords: 5000,
  maxDbSizeBytes: 50 * 1024 * 1024, // 50 MB
  batchSize: 50,
);

// Gửi cache ngay / xoá toàn bộ cache (không hoàn tác)
await controller.uploadCachedLocationsManually();
await controller.clearCachedLocations();

12. Fake GPS Detection

Native SDK tự phát hiện vị trí giả lập. Kể cả khi app bị kill, SDK vẫn bắn notification qua kênh VietmapTracking trên Android (không phụ thuộc flutter_local_notifications).

12.1. Bật phát hiện

Fake GPS detection mặc định tắt (allowMockLocation: true). Để bật:

Dart
await controller.startTracking(
  TrackingPresets.general().copyWith(
    userId: 'user-123',
    allowMockLocation: false, // bật kiểm tra fake GPS ở native
  ),
);
await controller.setFakeGpsPolicy(FakeGpsPolicy.warn);

12.2. Chọn policy

PolicyHành vi
skipChỉ bắn event — không notification, không dừng tracking. Mặc định.
warnSDK bắn notification native (debounce 30 giây mỗi cửa sổ).
stopTrackingTự dừng tracking ngay lần phát hiện đầu tiên.
logToServerVẫn upload bản ghi kèm is_fake=1 + header X-Fake-GPS: true.
Dart
await controller.setFakeGpsPolicy(FakeGpsPolicy.warn);

controller.onFakeGpsDetected.listen((FakeGpsEvent ev) {
  print('Fake GPS: lat=${ev.lat}, lng=${ev.lng}, reason=${ev.reason}');
});

12.3. Tuỳ chỉnh nội dung notification

Dart
await controller.setFakeGpsNotificationConfig(
  title: 'Phát hiện GPS giả',
  message: 'Vui lòng tắt ứng dụng giả lập vị trí để dữ liệu được ghi nhận chính xác.',
);

Gọi sau initializeTracking. SDK lưu cấu hình này suốt vòng đời tiến trình (kể cả khi app chạy nền hoặc bị kill trên Android).

12.4. Cơ chế phát hiện theo nền tảng

Nền tảngCơ chế phát hiện
iOS 15+CLLocationSourceInformation
Android 12+Location.isMock()
Android 18–30Location.isFromMockProvider()

Trên iOS, icon notification luôn là icon app. Trường reason trong FakeGpsEvent nhận giá trị "simulatedBySoftware" hoặc "producedByAccessory".


13. Speed Fallback

Một số thiết bị — điển hình là Xiaomi/MIUI với chế độ tiết kiệm pin gắt — trả về GPS fix không có tốc độ hợp lệ (hoặc báo speed = 0 trong khi xe rõ ràng đang chạy). Mặc định SDK phát hiện tình huống này và tự tính tốc độ từ khoảng cách Haversine tới fix trước đó chia cho thời gian trôi qua, để giá trị gửi lên server phản ánh chuyển động thật thay vì 0.

Cơ chế fallback thận trọng và tự hiệu chỉnh:

  • Chỉ kích hoạt khi tốc độ từ hệ điều hành bị thiếu hoặc thấp bất hợp lý so với quãng đường thực tế; tốc độ phần cứng hợp lệ luôn được tin dùng.
  • Có kiểm chứng theo độ chính xác của fix và khoảng thời gian, đồng thời qua bộ chặn outlier theo cửa sổ trượt để một cú nhảy GPS không tạo ra spike.
  • Fix thực sự đứng yên vẫn giữ 0.

Mỗi bản ghi upload mang nguồn gốc tốc độ trong metadata.speedSource:

speedSourceÝ nghĩa
gpsTốc độ phần cứng từ chip (dùng nguyên giá trị).
derivedHệ điều hành không trả tốc độ → tính từ vị trí/thời gian.
correctedHệ điều hành trả tốc độ ~0 trong khi đang di chuyển → thay bằng giá trị tính.
unknownKhông có tốc độ và fallback không kiểm chứng được (gửi 0).

Mặc định bật. Tắt đi (giữ nguyên tốc độ thô từ hệ điều hành) qua LocationTrackingConfig:

Dart
await controller.startTracking(
  TrackingPresets.general().copyWith(
    userId: 'user-123',
    enableSpeedFallback: false, // mặc định true
  ),
);

14. Cảnh báo tốc độ

Dart
// Gọi sau initializeTracking
await controller.configureAlertAPI('your-alert-api-key', 'your-alert-api-id');

await controller.turnOnAlert();
await controller.turnOffAlert();

15. Tracking Interrupted

Bắn event khi tracking nền ngừng sinh GPS trong lúc phiên vẫn đang active, và bắn lại khi khôi phục. Hệ điều hành không tự resume — dùng event này để nhắc người dùng dừng và bắt đầu tracking lại.

Dart
await controller.setTrackingInterruptedNotificationEnabled(true);
await controller.setTrackingInterruptedNotificationConfig(
  title: 'Theo dõi bị gián đoạn',
  message: 'Vui lòng mở ứng dụng và bật lại theo dõi hành trình.',
);

controller.onTrackingInterrupted.listen((TrackingInterruptedEvent ev) {
  print('${ev.reason} — recovered: ${ev.recovered}');
});

reason do SDK quản lý, là một trong các hằng TrackingInterruptedReason: locationUnavailable, providerDisabled, paused, authDowngraded, authDenied, locationServicesOff, permissionRevoked, staleNoUpdates.

Tắt notification không làm dừng stream — event vẫn tiếp tục được bắn.


16. Event Streams

Dart
controller.onLocationUpdate            // Stream<LocationData>
controller.onTrackingStatusChanged     // Stream<TrackingStatus>
controller.onFakeGpsDetected           // Stream<FakeGpsEvent>
controller.onTrackingInterrupted       // Stream<TrackingInterruptedEvent>
controller.onSmartBatteryProfileChanged // Stream<SmartBatteryProfile>
Dart
controller.onLocationUpdate.listen((LocationData loc) {
  print('${loc.latitude}, ${loc.longitude} @ ${loc.speed} m/s');
});

controller.onTrackingStatusChanged.listen((TrackingStatus s) {
  print('Tracking: ${s.isTracking}');
});

controller.onFakeGpsDetected.listen((FakeGpsEvent ev) {
  print('Fake GPS tại ${ev.lat}, ${ev.lng}');
});

17. Lịch sử hành trình

Dart
final List<GpsLocation> history = await controller.getTrackingHistory(
  userId: 'user-123',
  fromTimestamp: DateTime(2026, 5, 1).millisecondsSinceEpoch,  // tuỳ chọn
  toTimestamp: DateTime(2026, 5, 12).millisecondsSinceEpoch,   // tuỳ chọn
  pageNumber: 1,
  pageSize: 50,
  sortDescending: true,
);

18. Data Models

LocationTrackingConfig

  • userIdString?
    Bắt buộc. Bản ghi GPS được gắn theo giá trị này.
  • vehicleIdString?
    ID phương tiện (tuỳ chọn).
  • intervalMsint?
    Chế độ timer: cập nhật mỗi N ms.
  • distanceFilterdouble?
    Chế độ khoảng cách: cập nhật sau mỗi M mét.
  • accuracyLocationAccuracy
    high / medium / low.
  • backgroundModebool
    Tiếp tục tracking khi app ở nền.
  • notificationTitleString?
    Android: tiêu đề notification foreground service.
  • notificationMessageString?
    Android: nội dung notification foreground service.
  • allowMockLocationbool
    false để bật phát hiện fake GPS ở native.
  • enableSpeedFallbackbool
    false để giữ nguyên tốc độ thô từ hệ điều hành.

LocationData

  • latitudedouble
    Vĩ độ.
  • longitudedouble
    Kinh độ.
  • altitudedouble
    Độ cao so với mực nước biển.
  • accuracydouble
    Độ chính xác ngang.
  • speeddouble
    Tốc độ tức thời.
  • bearingdouble
    Hướng đầu xe (0 = Bắc).
  • timestampint
    Thời điểm capture.
  • dateTimeDateTime
    Getter từ timestamp.

Lưu ý: speed đơn vị là m/s (mét/giây). Đổi sang km/h: speed * 3.6 hoặc LocationUtils.metersPerSecondToKmh(speed).

TrackingStatus

  • isTrackingbool
    Tracking đang chạy.
  • lastLocationUpdateint?
    Unix ms lần GPS gần nhất.
  • trackingDurationint
    ms kể từ startTracking().
  • lastUpdateTimeDateTime?
    Getter từ lastLocationUpdate.
  • durationDuration
    Getter từ trackingDuration.

FakeGpsEvent

  • lat / lngdouble
    Toạ độ bị phát hiện là giả lập.
  • timestampdouble
    Unix timestamp theo giây.
  • isFirstDetectionbool
    Lần phát hiện đầu tiên trong cửa sổ debounce 30 giây.
  • reasonString
    Chỉ iOS: "simulatedBySoftware" | "producedByAccessory".

TrackingInterruptedEvent

  • reasonString
    Do SDK quản lý — xem TrackingInterruptedReason.
  • recoveredbool
    true khi vị trí đã hoạt động trở lại.
  • isInBackgroundbool
    App đang ở nền khi event bắn.
  • secondsSinceLastFixint
    -1 nếu không xác định.

PermissionResult

  • grantedbool
    Đủ quyền.
  • statusString
    'granted' / 'denied' / 'notGranted'.
  • fineLocationbool
    Quyền GPS chính xác.
  • coarseLocationbool
    Quyền GPS xấp xỉ.
  • backgroundLocationbool
    Quyền background.

SmartBatteryProfile

Dart
enum SmartBatteryProfile {
  navigation,   // 5 giây — chính xác cao
  general,      // 30 giây — cân bằng mặc định
  batterySaver, // 5 phút — tiết kiệm pin tối đa
}

19. Utility Functions

Dart
// Khoảng cách Haversine (mét)
LocationUtils.calculateDistance(lat1, lng1, lat2, lng2);
LocationUtils.distanceBetween(location1, location2);

// Định dạng toạ độ
LocationUtils.formatCoordinates(21.0285, 105.8542);
// → "21.028500° N, 105.854200° E"

// Đổi đơn vị tốc độ
LocationUtils.metersPerSecondToKmh(25.0);  // → 90.0
LocationUtils.kmhToMetersPerSecond(90.0);  // → 25.0

// Kiểm tra geofence
LocationUtils.isWithinRadius(location, targetLat, targetLng, radiusMetres);

20. Full API Reference

Khởi tạo

MethodReturnMô tả
initializeTracking(apiKey, {baseURL})Future<void>Xác thực key phía server. Throw INVALID_API_KEY nếu thất bại.
setMetadata(Map)Future<void>Đính kèm key-value tuỳ ý vào mỗi bản ghi GPS.
setPackages(List<String>)Future<void>Gắn mã kiện hàng vào trường top-level packages. List rỗng → bỏ field.
configureAlertAPI(apiKey, apiID)Future<void>Cấu hình thông tin đăng nhập cảnh báo tốc độ.
setFakeGpsNotificationConfig({title, message})Future<void>Tuỳ chỉnh notification GPS giả native của SDK.
registerLifecycleObserver()voidBật chuyển đổi SDK background/foreground tự động.
unregisterLifecycleObserver()voidGỡ lifecycle observer (gọi khi dừng tracking).

Điều khiển tracking

MethodReturnMô tả
startTracking(config, {enableSmartBattery = false})Future<bool>Bắt đầu GPS tracking. userId trong config là bắt buộc. enableSmartBattery: true để Smart Battery tự điều chỉnh interval.
stopTracking()Future<bool>Dừng tracking; SDK có thể flush các bản ghi còn chờ.
isTrackingActive()Future<bool>Tracking đang chạy?
getCurrentLocation()Future<LocationData>Lấy vị trí hiện tại theo yêu cầu — dùng được cả trước startTracking(). Throw nếu không lấy được (thiếu quyền, tắt GPS, timeout).
getTrackingStatus()Future<TrackingStatus>Snapshot trạng thái hiện tại.
updateTrackingConfig(config)Future<bool>Áp config mới cho phiên đang chạy (xem lưu ý iOS ở mục 8).
getTrackingHealthStatus()Future<Map>Snapshot chẩn đoán: mạng, cache, SDK flags.

Quyền

MethodReturnMô tả
requestLocationPermissions()Future<PermissionResult>Xin quyền foreground.
requestAlwaysLocationPermissions()Future<String>Xin quyền background.
hasLocationPermissions()Future<PermissionResult>Kiểm tra quyền, không popup.

Fake GPS

MethodReturnMô tả
setFakeGpsPolicy(policy)Future<void>Đặt chính sách xử lý GPS giả.
setFakeGpsNotificationConfig({title, message})Future<void>Nội dung notification GPS giả.
onFakeGpsDetectedStream<FakeGpsEvent>Stream sự kiện GPS giả.

Tracking Interrupted

MethodReturnMô tả
setTrackingInterruptedNotificationEnabled(enabled)Future<void>Bật/tắt notification gián đoạn.
setTrackingInterruptedNotificationConfig({title, message})Future<void>Nội dung notification gián đoạn.
onTrackingInterruptedStream<TrackingInterruptedEvent>Stream sự kiện gián đoạn / khôi phục.

Cảnh báo tốc độ

MethodReturnMô tả
configureAlertAPI(apiKey, apiID)Future<void>Cấu hình Alert API.
turnOnAlert()Future<void>Bật cảnh báo tốc độ.
turnOffAlert()Future<void>Tắt cảnh báo tốc độ.

Smart Battery

MethodReturnMô tả
setSmartBatteryPreferredProfile(profile)voidĐặt profile ưu tiên khi đang di chuyển.
feedLocationToSmartBattery(speed, {heading})voidCấp tốc độ/hướng để phát hiện đứng yên & vào cua.
enableSmartBatteryOptimization()Future<void>Bật Smart Battery giữa phiên.
disableSmartBatteryOptimization()voidTắt Smart Battery (teardown phía Dart).
onSmartBatteryProfileChangedStream<SmartBatteryProfile>Stream thay đổi profile.

Cache & Đồng bộ

MethodReturnMô tả
setAutoUpload(enabled)Future<bool>Bật/tắt auto upload.
isNetworkConnected()Future<bool>Kiểm tra kết nối mạng.
getCachedLocationsCount()Future<int>Số records đang chờ gửi.
getDatabaseSizeBytes()Future<int>Kích thước cache (bytes).
uploadCachedLocationsManually()Future<bool>Flush cache ngay.
clearCachedLocations()Future<bool>Xoá toàn bộ cache.
configureCacheLimits(...)Future<bool>Cấu hình giới hạn cache.

Lịch sử

MethodReturnMô tả
getTrackingHistory({userId, fromTimestamp, toTimestamp, pageNumber, pageSize, sortDescending})Future<List<GpsLocation>>Lấy lịch sử GPS theo bộ lọc.

21. Kiến trúc

Text
┌──────────────────────────────────────────────────┐
│                   Flutter App                    │
│          VietmapTrackingController               │
│          SmartBatteryManager                     │
├──────────────────────────────────────────────────┤
│           Platform Interface (Dart)              │
│    MethodChannel: vietmap_tracking_plugin        │
│    EventChannel:  /location_updates              │
│    EventChannel:  /tracking_status               │
│    EventChannel:  /fake_gps                      │
├──────────────────────┬───────────────────────────┤
│   iOS Native Bridge  │  Android Native Bridge    │
│   Swift              │  Kotlin                   │
├──────────────────────┼───────────────────────────┤
│  VietmapTrackingSDK  │  vietmap-tracking-sdk     │
│  1.5.1 (CocoaPods)   │  1.5.2 (JitPack)          │
└──────────────────────┴───────────────────────────┘

22. Xử lý sự cố

Android

Tracking nền bị dừng bất thường

  • Khai báo đủ FOREGROUND_SERVICE + FOREGROUND_SERVICE_LOCATION trong AndroidManifest.xml.
  • Loại app khỏi danh sách tối ưu pin trong cài đặt máy.
  • Trên Xiaomi / Huawei / Samsung, phải bật thủ công quyền chạy nền trong cài đặt hệ thống.

Không thấy notification GPS giả

  • Đảm bảo POST_NOTIFICATIONS có trong manifest và đã được cấp runtime (Android 13+).
  • Trên MIUI: Cài đặt → Ứng dụng → [App] → Thông báo → bật “Floating notifications”.

Build Gradle lỗi — không tìm thấy SDK

  • Kiểm tra maven { url 'https://jitpack.io' } nằm trong allprojects { repositories { ... } }.

iOS

Vị trí ngừng cập nhật khi chạy nền

  • UIBackgroundModes phải chứa location.
  • Người dùng phải cấp quyền “Always”.
  • backgroundMode: true phải được set trong LocationTrackingConfig.

Banner notification không hiện khi app đang mở

  • Kiểm tra UNUserNotificationCenter.current().delegate = self được gán trước GeneratedPluginRegistrant.register.
  • Override willPresent phải gọi completionHandler([.banner, .sound, .badge]) — không gọi super.

pod install thất bại

  • Chạy pod repo update rồi pod install.
  • Podfile phải khai báo platform :ios, '15.0' trở lên.

FileSystemException: Failed to decode data using encoding 'utf-8'

  • Xảy ra khi project nằm trên phân vùng không phải APFS (ví dụ ExFAT). Thêm vào ios/Podfile:
Ruby
post_install do |installer|
  Dir.glob(File.join(installer.sandbox.root, '**', '._*')).each { |f| FileUtils.rm_f(f) }
end