Add Android background geolocation with notification error handling
Add flutter_background_geolocation for background location tracking on Android. Service automatically sends coordinates to server when app is in background. Error messages are shown as system notifications using flutter_local_notifications.
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg;
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import '../config/api.dart';
|
||||
|
||||
class BackgroundGeoService {
|
||||
static final BackgroundGeoService _instance = BackgroundGeoService._init();
|
||||
|
||||
factory BackgroundGeoService() => _instance;
|
||||
|
||||
BackgroundGeoService._init();
|
||||
|
||||
final http.Client _client = http.Client();
|
||||
final FlutterLocalNotificationsPlugin _notifications =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
String? _token;
|
||||
String? _geoId;
|
||||
bool _isReady = false;
|
||||
bool _isStarted = false;
|
||||
bool _notificationsInitialized = false;
|
||||
|
||||
bool get isReady => _isReady;
|
||||
bool get isStarted => _isStarted;
|
||||
String? get token => _token;
|
||||
String? get geoId => _geoId;
|
||||
|
||||
Future<void> initNotifications() async {
|
||||
if (_notificationsInitialized) return;
|
||||
|
||||
const androidSettings = AndroidInitializationSettings(
|
||||
'@mipmap/ic_launcher',
|
||||
);
|
||||
const iosSettings = DarwinInitializationSettings(
|
||||
requestAlertPermission: true,
|
||||
requestBadgePermission: true,
|
||||
requestSoundPermission: true,
|
||||
);
|
||||
const initSettings = InitializationSettings(
|
||||
android: androidSettings,
|
||||
iOS: iosSettings,
|
||||
);
|
||||
|
||||
await _notifications.initialize(
|
||||
initSettings,
|
||||
onDidReceiveNotificationResponse: _onNotificationResponse,
|
||||
);
|
||||
|
||||
_notificationsInitialized = true;
|
||||
}
|
||||
|
||||
void configure({required String token, required String geoId}) {
|
||||
_token = token;
|
||||
_geoId = geoId;
|
||||
}
|
||||
|
||||
Future<void> init() async {
|
||||
if (_isReady) return;
|
||||
|
||||
await initNotifications();
|
||||
|
||||
bg.BackgroundGeolocation.onLocation(_onLocation, _onLocationError);
|
||||
bg.BackgroundGeolocation.onMotionChange(_onMotionChange);
|
||||
bg.BackgroundGeolocation.onProviderChange(_onProviderChange);
|
||||
bg.BackgroundGeolocation.onHttp(_onHttp);
|
||||
|
||||
await bg.BackgroundGeolocation.ready(bg.Config(
|
||||
desiredAccuracy: bg.Config.DESIRED_ACCURACY_HIGH,
|
||||
distanceFilter: 10.0,
|
||||
stopOnTerminate: false,
|
||||
startOnBoot: true,
|
||||
debug: false,
|
||||
logLevel: bg.Config.LOG_LEVEL_OFF,
|
||||
autoSync: false,
|
||||
));
|
||||
|
||||
_isReady = true;
|
||||
}
|
||||
|
||||
Future<void> start() async {
|
||||
if (!_isReady) return;
|
||||
if (_isStarted) return;
|
||||
if (_token == null || _geoId == null || _geoId!.isEmpty) return;
|
||||
|
||||
await bg.BackgroundGeolocation.start();
|
||||
_isStarted = true;
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
if (!_isStarted) return;
|
||||
await bg.BackgroundGeolocation.stop();
|
||||
_isStarted = false;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
bg.BackgroundGeolocation.removeListeners();
|
||||
_isReady = false;
|
||||
_isStarted = false;
|
||||
_token = null;
|
||||
_geoId = null;
|
||||
}
|
||||
|
||||
void _onLocation(bg.Location location) {
|
||||
if (_token == null || _geoId == null || _geoId!.isEmpty) return;
|
||||
if (location.sample == true) return;
|
||||
|
||||
_sendPosition(location.coords.latitude, location.coords.longitude);
|
||||
}
|
||||
|
||||
void _onLocationError(bg.LocationError error) {
|
||||
_showErrorNotification('Ошибка геолокации: $error');
|
||||
}
|
||||
|
||||
void _onMotionChange(bg.Location location) {
|
||||
kDebugMode? print('[BackgroundGeo] Motion changed: isMoving=${location.isMoving}'):{};
|
||||
}
|
||||
|
||||
void _onProviderChange(bg.ProviderChangeEvent event) {
|
||||
kDebugMode?print('[BackgroundGeo] Provider changed: $event'):{};
|
||||
}
|
||||
|
||||
void _onHttp(bg.HttpEvent event) {
|
||||
kDebugMode? print('[BackgroundGeo] HTTP: status=${event.status}, success=${event.success}'):{};
|
||||
}
|
||||
|
||||
Future<void> _sendPosition(double latitude, double longitude) async {
|
||||
if (_token == null || _geoId == null || _geoId!.isEmpty) return;
|
||||
|
||||
try {
|
||||
final response = await _client.put(
|
||||
Uri.parse('${ApiConfig.geoUrl}?id=$_geoId'),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer $_token',
|
||||
},
|
||||
body: jsonEncode({
|
||||
'x': longitude,
|
||||
'y': latitude,
|
||||
}),
|
||||
);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
_showErrorNotification(
|
||||
'Не удалось отправить позицию. Код ответа: ${response.statusCode}',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
_showErrorNotification('Ошибка отправки позиции: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showErrorNotification(String message) async {
|
||||
if (!_notificationsInitialized) return;
|
||||
|
||||
const androidDetails = AndroidNotificationDetails(
|
||||
'background_geo_errors',
|
||||
'Background Geo Errors',
|
||||
channelDescription: 'Уведомления об ошибках геолокации в фоне',
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
ticker: 'geo_error',
|
||||
);
|
||||
const iosDetails = DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
sound: 'default',
|
||||
);
|
||||
const details = NotificationDetails(
|
||||
android: androidDetails,
|
||||
iOS: iosDetails,
|
||||
);
|
||||
|
||||
await _notifications.show(
|
||||
0,
|
||||
'Ошибка геолокации',
|
||||
message,
|
||||
details,
|
||||
payload: 'geo_error',
|
||||
);
|
||||
}
|
||||
|
||||
void _onNotificationResponse(NotificationResponse response) {
|
||||
print('[Notification] ${response.payload}');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user