Skip to content

[Bug]: Location Tracking Lasts 10 minutes #1596

@vdiaza

Description

@vdiaza

Required Reading

  • Confirmed

Plugin Version

5.0.0-beta

Flutter Doctor

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.38.3, on macOS 15.5 24F74 darwin-arm64, locale es-CL)
[✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.4)
[✓] Chrome - develop for the web
[✓] Connected device (2 available)
[✓] Network resources

• No issues found!

Mobile operating-system(s)

  • iOS
  • Android

Device Manufacturer(s) and Model(s)

Samsung S25

Device operating-systems(s)

Android 15

What happened?

App stops sending tracking information after 10 minutes.

It should be continue sending info until stopped.

Having debug=true it continue beeping but it does not send the request.

Plugin Code and/or Config

class BackgroundLocationProService {
  BackgroundLocationProService._();

  static final BackgroundLocationProService instance =
      BackgroundLocationProService._();

  bool _configured = false;
  String? _url;
  Map<String, dynamic> _data = {};

  // Check if Background Geolocation Pro is enabled for this app
  bool get isEnabled {
    final enabled = dotenv.env['ENABLE_BG_LOCATION_PRO']?.toLowerCase();
    return enabled == 'true';
  }

  Future<Map<String, dynamic>> configure(Map<String, dynamic> settings) async {
    // Return early if plugin is not enabled (no license for this app)
    if (!isEnabled) {
      AppGlobal.printLog(
          '[BG Geolocation] Plugin disabled for this app (ENABLE_BG_LOCATION_PRO=false)');
      return {'enabled': false, 'disabled_reason': 'no_license'};
    }

    _url = settings['url'] as String?;
    _data = (settings['data'] as Map<String, dynamic>?) ?? {};

    // Build params that the plugin will send with each location
    final Map<String, dynamic> params = Map<String, dynamic>.from(_data);

    // For trip monitoring: need updates even when stationary
    // Server timeout: 2 minutes (120s) without updates = "connection lost"
    final int intervalMs = settings['interval'] ?? 30000; // 30 seconds default

    final config = Config(
      // Some options (like reset) remain on the root config
      reset: settings['reset'] ?? true,

      notification: Notification(
        title: Constants.appName!,
        text: "Modo Activo",
        channelName: "Location Tracking",
        priority: NotificationPriority.high,
        sticky: true, // CRITICAL: Prevent user from dismissing notification
      ),

      // Geolocation-related options
      geolocation: GeoConfig(
        desiredAccuracy: DesiredAccuracy.navigation;,
        // CRITICAL: Set distanceFilter to 0 to enable time-based updates
        // With distanceFilter: 0, locationUpdateInterval takes effect
        distanceFilter: 0,
        // Time-based location updates (works when distanceFilter: 0)
        // This gets you updates even when stationary (default 60s)
        locationUpdateInterval: 30000, // in milliseconds
        fastestLocationUpdateInterval: 3000,
        locationTimeout: 60,
      ),

      // App / lifecycle options
      app: AppConfig(
        // Headless Mode: Continue tracking even when app is terminated
        enableHeadless: true,
        stopOnTerminate: false,
        startOnBoot: true,
        // Heartbeat as backup (min 60s on Android)
        // iOS only with preventSuspend: true
        heartbeatInterval: 60.0,
        preventSuspend: true,
      ),

      // HTTP Configuration - plugin will auto-POST locations
      http: HttpConfig(
        url: _url,
        params: {"data": params},
        headers:
            {
              'Content-Type': 'application/json',
            },
        autoSync: true,
        batchSync: true,
        maxBatchSize: -1,
      ),

      // Logging / debug options
      logger: LoggerConfig(
        debug: true,
        // If you were previously passing a numeric logLevel, you may
        // want to adjust this mapping to LogLevel.* accordingly.
        logLevel: LogLevel.verbose,
      ),
    );

    final state = await BackgroundGeolocation.ready(config);

    if (!_configured) {
      _configured = true;

      // Set up event listeners for debugging/logging

      // onLocation: Fires for EVERY location recorded while moving
      // Plugin auto-POSTs these to the configured URL
      BackgroundGeolocation.onLocation((Location location) {
        AppGlobal.printLog(
            '[BG Geolocation] Location: ${location.coords.latitude}, ${location.coords.longitude}, sample: ${location.sample}');
      });

      // onHeartbeat: Fires periodically (every heartbeatInterval seconds)
      // even when stationary. Plugin auto-POSTs the location.
      BackgroundGeolocation.onHeartbeat((HeartbeatEvent event) {
        AppGlobal.printLog(
            '[BG Geolocation] Heartbeat: location will be posted to server');
      });

      // CRITICAL: Monitor when tracking stops (helps debug service termination)
      BackgroundGeolocation.onEnabledChange((bool enabled) {
        AppGlobal.printLog(
            '[BG Geolocation] ⚠️ Tracking state changed: ${enabled ? "STARTED" : "STOPPED"}');
      });

      // Monitor connectivity issues that could stop location sync
      BackgroundGeolocation.onConnectivityChange((ConnectivityChangeEvent event) {
        AppGlobal.printLog(
            '[BG Geolocation] Connectivity: ${event.connected ? "CONNECTED" : "DISCONNECTED"}');
      });

      // Monitor GPS/location provider changes
      BackgroundGeolocation.onProviderChange((ProviderChangeEvent event) {
        AppGlobal.printLog(
            '[BG Geolocation] Provider - GPS enabled: ${event.enabled}, Status: ${event.status}');
      });
    }

    final map = state.toMap();
    return map != null ? Map<String, dynamic>.from(map) : <String, dynamic>{};
  }

  Future<Map<String, dynamic>> start() async {
    await BackgroundGeolocation.start();
    final state = await BackgroundGeolocation.state;
    final map = state.toMap();
    return map != null ? Map<String, dynamic>.from(map) : <String, dynamic>{};
  }

  Future<Map<String, dynamic>> stop() async {
    await BackgroundGeolocation.stop();
    final state = await BackgroundGeolocation.state;
    final map = state.toMap();
    return map != null ? Map<String, dynamic>.from(map) : <String, dynamic>{};
  }
}

Relevant log output

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions