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ăng | Mô tả |
|---|---|
| Background GPS | Foreground service (Android) / background location mode (iOS). |
| Xác thực API key | initializeTracking xác thực phía server — throw INVALID_API_KEY nếu key không hợp lệ. |
| Metadata | setMetadata đính kèm key-value tuỳ ý vào mỗi bản ghi GPS. |
| Package Codes | setPackages 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 Cache | Cache SQLite, tự upload khi có mạng trở lại. |
| Speed Fallback | Tự tính tốc độ khi hệ điều hành không trả về (Xiaomi/MIUI); gắn cờ speedSource. |
| Fake GPS Detection | Phát hiện ở native với 4 policy; SDK tự bắn notification kể cả khi app bị kill. |
| Smart Battery | Opt-in — điều chỉnh tần suất tracking theo mức pin và chuyển động. |
| Tracking Presets | Navigation / Fitness / General / BatterySaver — bản theo thời gian và theo khoảng cách. |
| Tracking Interrupted | Phát hiện khi tracking nền bị hệ điều hành dừng và khi khôi phục. |
| Location Utilities | Tính khoảng cách Haversine, đổi đơn vị tốc độ, kiểm tra geofence. |
Yêu cầu hệ thống
| Minimum | |
|---|---|
| Flutter | 3.3.0+ |
| Dart | 3.8.0+ |
| Android | API 21+ (Android 5.0) |
| iOS | 15.0+ |
Native SDK đi kèm
| Nền tảng | SDK | Phiên bản |
|---|---|---|
| iOS | VietmapTrackingSDK (CocoaPods) | 1.5.1 |
| Android | com.github.vietmap-company:vietmap-tracking-sdk-android | 1.5.2 |
1. Cài đặt
pubspec.yaml
dependencies:
vietmap_tracking_plugin: ^1.1.3flutter pub get2. Cấu hình Android
2.1. Quyền — android/app/src/main/AndroidManifest.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_LOCATIONphả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:
<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
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
<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
<key>UIBackgroundModes</key>
<array>
<string>location</string>
<string>background-processing</string>
<string>background-fetch</string>
</array>3.3. Background Task Identifiers (iOS 15+)
<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:
- Gán
UNUserNotificationCenter.current().delegate = selftrướcGeneratedPluginRegistrant.register(with: self). - Override
userNotificationCenter(_:willPresent:)và gọi thẳngcompletionHandler([.banner, .sound, .badge])— không gọisuper.
3.5. CocoaPods
# ios/Podfile
platform :ios, '15.0'cd ios && pod installLần cài đầu tiên: chạy
pod install --repo-updateđểVietmapTrackingSDKlấy đúng spec mới nhất.
4. Khởi tạo SDK
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.baseURLlà tuỳ chọn — SDK dùng URL production mặc định.
5. Quyền truy cập
// 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):
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
userIdlà bắt buộc — bản ghi GPS được gắn theo giá trị này ở phía server. ThiếuuserIdthì bản ghi không xác định được chủ sở hữu.
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: truenế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ế độ | intervalMs | distanceFilter | Hành vi |
|---|---|---|---|
| Interval (theo thời gian) | > 0 | null | Cậ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 | > 0 | Chỉ cập nhật sau khi thiết bị di chuyển M mét. |
| SDK default | null | null | Dù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
distanceFilterlê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:
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ếuTheo khoảng cách:
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 mLuôn gắn userId qua copyWith:
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
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ệnstop → startở bên trong. Hệ quả: có khoảng trống vị trí ngắn,trackingDurationbị reset, và stream trạng thái bắnfalse → true.
9. Smart Battery Optimization
Smart Battery là opt-in. Truyền enableSmartBattery: true vào startTracking(); stopTracking() sẽ tắt lại.
// Đặ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');
});
feedLocationToSmartBatterycấ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:
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| Profile | Interval | Khi nào dùng |
|---|---|---|
navigation | 5 giây | Dẫn đường, cần độ chính xác cao |
general | 30 giây | Theo dõi đội xe tiêu chuẩn |
batterySaver | 5 phút | Pin 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ủageneralchỉ 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.
// Đă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:
await controller.stopTracking();
SmartBatteryManager.instance.customGeneralConfigOverride = null;Lưu ý: Callback được gọi ở mọi lần chuyển profile — kể cả
batterySavervànavigation— nên Smart Battery vẫn phát hiện và báo cáo thay đổi profile quaonSmartBatteryProfileChanged, 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:
{
"time": 1777876800000,
"lat": 10.762219,
"lng": 106.656956,
"packages": ["#10001", "#10002"],
"metadata": {}
}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.
// 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:
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
| Policy | Hành vi |
|---|---|
skip | Chỉ bắn event — không notification, không dừng tracking. Mặc định. |
warn | SDK bắn notification native (debounce 30 giây mỗi cửa sổ). |
stopTracking | Tự dừng tracking ngay lần phát hiện đầu tiên. |
logToServer | Vẫn upload bản ghi kèm is_fake=1 + header X-Fake-GPS: true. |
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
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ảng | Cơ chế phát hiện |
|---|---|
| iOS 15+ | CLLocationSourceInformation |
| Android 12+ | Location.isMock() |
| Android 18–30 | Location.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 |
|---|---|
gps | Tốc độ phần cứng từ chip (dùng nguyên giá trị). |
derived | Hệ điều hành không trả tốc độ → tính từ vị trí/thời gian. |
corrected | Hệ điều hành trả tốc độ ~0 trong khi đang di chuyển → thay bằng giá trị tính. |
unknown | Khô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:
await controller.startTracking(
TrackingPresets.general().copyWith(
userId: 'user-123',
enableSpeedFallback: false, // mặc định true
),
);14. Cảnh báo tốc độ
// 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.
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
controller.onLocationUpdate // Stream<LocationData>
controller.onTrackingStatusChanged // Stream<TrackingStatus>
controller.onFakeGpsDetected // Stream<FakeGpsEvent>
controller.onTrackingInterrupted // Stream<TrackingInterruptedEvent>
controller.onSmartBatteryProfileChanged // Stream<SmartBatteryProfile>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
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.accuracyLocationAccuracyhigh/medium/low.backgroundModeboolTiếp tục tracking khi app ở nền.notificationTitleString?Android: tiêu đề notification foreground service.notificationMessageString?Android: nội dung notification foreground service.allowMockLocationboolfalseđể bật phát hiện fake GPS ở native.enableSpeedFallbackboolfalseđể giữ nguyên tốc độ thô từ hệ điều hành.
LocationData
latitudedoubleVĩ độ.longitudedoubleKinh độ.altitudedoubleĐộ cao so với mực nước biển.accuracydoubleĐộ chính xác ngang.speeddoubleTốc độ tức thời.bearingdoubleHướng đầu xe (0 = Bắc).timestampintThời điểm capture.dateTimeDateTimeGetter từtimestamp.
Lưu ý:
speedđơn vị là m/s (mét/giây). Đổi sang km/h:speed * 3.6hoặcLocationUtils.metersPerSecondToKmh(speed).
TrackingStatus
isTrackingboolTracking đang chạy.lastLocationUpdateint?Unix ms lần GPS gần nhất.trackingDurationintms kể từstartTracking().lastUpdateTimeDateTime?Getter từlastLocationUpdate.durationDurationGetter từtrackingDuration.
FakeGpsEvent
lat/lngdoubleToạ độ bị phát hiện là giả lập.timestampdoubleUnix timestamp theo giây.isFirstDetectionboolLần phát hiện đầu tiên trong cửa sổ debounce 30 giây.reasonStringChỉ iOS:"simulatedBySoftware"|"producedByAccessory".
TrackingInterruptedEvent
reasonStringDo SDK quản lý — xemTrackingInterruptedReason.recoveredbooltruekhi vị trí đã hoạt động trở lại.isInBackgroundboolApp đang ở nền khi event bắn.secondsSinceLastFixint-1nếu không xác định.
PermissionResult
grantedboolĐủ quyền.statusString'granted'/'denied'/'notGranted'.fineLocationboolQuyền GPS chính xác.coarseLocationboolQuyền GPS xấp xỉ.backgroundLocationboolQuyền background.
SmartBatteryProfile
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
// 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
| Method | Return | Mô 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() | void | Bật chuyển đổi SDK background/foreground tự động. |
unregisterLifecycleObserver() | void | Gỡ lifecycle observer (gọi khi dừng tracking). |
Điều khiển tracking
| Method | Return | Mô 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
| Method | Return | Mô 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
| Method | Return | Mô 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ả. |
onFakeGpsDetected | Stream<FakeGpsEvent> | Stream sự kiện GPS giả. |
Tracking Interrupted
| Method | Return | Mô 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. |
onTrackingInterrupted | Stream<TrackingInterruptedEvent> | Stream sự kiện gián đoạn / khôi phục. |
Cảnh báo tốc độ
| Method | Return | Mô 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
| Method | Return | Mô tả |
|---|---|---|
setSmartBatteryPreferredProfile(profile) | void | Đặt profile ưu tiên khi đang di chuyển. |
feedLocationToSmartBattery(speed, {heading}) | void | Cấ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() | void | Tắt Smart Battery (teardown phía Dart). |
onSmartBatteryProfileChanged | Stream<SmartBatteryProfile> | Stream thay đổi profile. |
Cache & Đồng bộ
| Method | Return | Mô 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ử
| Method | Return | Mô 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
┌──────────────────────────────────────────────────┐
│ 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_LOCATIONtrongAndroidManifest.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_NOTIFICATIONScó 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 trongallprojects { repositories { ... } }.
iOS
Vị trí ngừng cập nhật khi chạy nền
UIBackgroundModesphải chứalocation.- Người dùng phải cấp quyền “Always”.
backgroundMode: truephải được set trongLocationTrackingConfig.
Banner notification không hiện khi app đang mở
- Kiểm tra
UNUserNotificationCenter.current().delegate = selfđược gán trướcGeneratedPluginRegistrant.register. - Override
willPresentphải gọicompletionHandler([.banner, .sound, .badge])— không gọisuper.
pod install thất bại
- Chạy
pod repo updaterồipod install. Podfilephải khai báoplatform :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:
post_install do |installer|
Dir.glob(File.join(installer.sandbox.root, '**', '._*')).each { |f| FileUtils.rm_f(f) }
end