betracky_background_location 2.1.1 copy "betracky_background_location: ^2.1.1" to clipboard
betracky_background_location: ^2.1.1 copied to clipboard

BeTracky Background Location Service — background location tracking with offline storage and upload to your own server.

example/lib/main.dart

import 'dart:async';

import 'package:betracky_background_location/betracky_background_location.dart';
import 'package:flutter/material.dart';
import 'package:flutter_background_service/flutter_background_service.dart';
import 'package:geolocator/geolocator.dart';
import 'package:permission_handler/permission_handler.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'BeTracky Feature Test',
      theme: ThemeData(colorSchemeSeed: Colors.teal, useMaterial3: true),
      home: const FeatureTestPage(),
    );
  }
}

class FeatureTestPage extends StatefulWidget {
  const FeatureTestPage({super.key});
  @override
  State<FeatureTestPage> createState() => _FeatureTestPageState();
}

class _FeatureTestPageState extends State<FeatureTestPage> {
  // Service state
  bool _isRunning = false;
  bool _isPaused = false;

  // F2: stream event log
  final List<String> _log = [];

  // F2: latest values from streams
  LocationDataModel? _lastLoc;
  int _pendingUploads = 0;
  int _storedCount = 0;

  // F7: last geofence event
  String _geofenceStatus = '—';

  final List<StreamSubscription> _subs = [];

  @override
  void initState() {
    super.initState();
    // Reconnect to running service if app was reopened after a kill
    BeTrackyBackgroundLocation.getStatus().then((status) {
      if (mounted) {
        setState(() {
          _isRunning = status['isRunning'] as bool? ?? false;
          _isPaused = !(status['isTracking'] as bool? ?? true);
          _pendingUploads = status['pendingUploads'] as int? ?? 0;
        });
      }
    });
    final svc = FlutterBackgroundService();

    _subs.addAll([
      // F2: live location
      svc.on('update').listen((data) {
        if (data == null) return;
        setState(() {
          _lastLoc = LocationDataModel.fromJson(data);
          _addLog('📍 update: ${_lastLoc!.latitude.toStringAsFixed(5)}, '
              '${_lastLoc!.longitude.toStringAsFixed(5)} '
              '| alt=${_lastLoc!.altitude?.toStringAsFixed(1)} '
              '| spd=${_lastLoc!.speed?.toStringAsFixed(2)}');
        });
      }),

      // F2: upload status
      svc.on('uploadStatus').listen((data) {
        if (data == null) return;
        setState(() {
          _pendingUploads = (data['pending'] as int?) ?? 0;
          final ok = data['success'] as bool? ?? false;
          _addLog('${ok ? '✅' : '❌'} uploadStatus: '
              'uploaded=${data['uploaded']} pending=$_pendingUploads '
              '${data['error'] ?? ''}');
        });
      }),

      // F2: service status
      svc.on('serviceStatus').listen((data) {
        if (data == null) return;
        setState(() => _addLog(
            '⚙️  serviceStatus: running=${data['running']} '
            'tracking=${data['tracking']}'));
      }),

      // F2: location count (offline mode)
      svc.on('locationCount').listen((data) {
        if (data == null) return;
        setState(() {
          _storedCount = (data['stored'] as int?) ?? 0;
          _addLog('💾 locationCount: stored=$_storedCount');
        });
      }),

      // F7: geofence events
      svc.on('geofenceEvent').listen((data) {
        if (data == null) return;
        setState(() {
          _geofenceStatus =
              '${data['event'].toString().toUpperCase()} "${data['id']}" '
              '(${(data['distance'] as double?)?.toStringAsFixed(0) ?? '?'}m away)';
          _addLog('🔵 geofence: $_geofenceStatus');
        });
      }),
    ]);
  }

  void _addLog(String msg) {
    _log.insert(0, msg);
    if (_log.length > 30) _log.removeLast();
  }

  @override
  void dispose() {
    for (final s in _subs) {
      s.cancel();
    }
    super.dispose();
  }

  // ── Actions ──────────────────────────────────────────────────────────────

  Future<void> _requestPermissions() async {
    await [
      Permission.location,
      Permission.locationAlways,
      Permission.notification,
    ].request();
    await Geolocator.requestPermission();
  }

  Future<void> _start() async {
    await _requestPermissions();

    // Register a geofence at the current position (F7)
    final pos = await Geolocator.getCurrentPosition();
    // Place fence 20 m north so we can observe entry/exit by moving
    await BeTrackyBackgroundLocation.addGeofence(Geofence(
      id: 'test_zone',
      latitude: pos.latitude + 0.00018, // ~20 m north
      longitude: pos.longitude,
      radiusMeters: 30,
    ));

    await BeTrackyBackgroundLocation.startService(
      distanceFilter: 0,
      accuracy: LocationAccuracy.high,
      startOnBoot: false,
      foregroundService: true,
      timeInterval: 5,      // at most one update per 5 s
      uploadFullData: true, // include altitude, speed, heading
      offlineEnabled: true, // store locally so we can verify DB count
      maxRetries: 3,
    );

    setState(() {
      _isRunning = true;
      _isPaused = false;
    });
    _addLog('▶️  service started');
  }

  Future<void> _stop() async {
    await BeTrackyBackgroundLocation.stopService();
    setState(() {
      _isRunning = false;
      _isPaused = false;
    });
    _addLog('⏹  service stopped');
  }

  Future<void> _pause() async {
    await BeTrackyBackgroundLocation.pauseTracking(); // F3
    setState(() => _isPaused = true);
    _addLog('⏸  tracking paused (F3)');
  }

  Future<void> _resume() async {
    await BeTrackyBackgroundLocation.resumeTracking(); // F3
    setState(() => _isPaused = false);
    _addLog('▶️  tracking resumed (F3)');
  }

  Future<void> _refreshCount() async {
    // F8: query API
    final count = await BeTrackyBackgroundLocation.getStoredLocationCount();
    final status = await BeTrackyBackgroundLocation.getStatus(); // F2
    setState(() {
      _storedCount = count;
      _pendingUploads = (status['pendingUploads'] as int?) ?? 0;
      _addLog('🔍 getStoredLocationCount=$count | getStatus=${status['isTracking']}');
    });
  }

  Future<void> _clearStored() async {
    await BeTrackyBackgroundLocation.clearStoredLocations(); // F8
    setState(() {
      _storedCount = 0;
      _addLog('🗑  clearStoredLocations() called (F8)');
    });
  }

  // ── UI ────────────────────────────────────────────────────────────────────

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('BeTracky v2.1.0 — Feature Test'),
        backgroundColor: Theme.of(context).colorScheme.primaryContainer,
      ),
      body: Column(
        children: [
          // Status bar
          Container(
            color: _isRunning
                ? (_isPaused ? Colors.orange.shade100 : Colors.green.shade100)
                : Colors.grey.shade100,
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            child: Row(
              children: [
                Icon(
                  _isRunning
                      ? (_isPaused ? Icons.pause_circle : Icons.check_circle)
                      : Icons.stop_circle,
                  color: _isRunning
                      ? (_isPaused ? Colors.orange : Colors.green)
                      : Colors.grey,
                ),
                const SizedBox(width: 8),
                Text(
                  _isRunning
                      ? (_isPaused ? 'PAUSED' : 'TRACKING')
                      : 'STOPPED',
                  style: const TextStyle(fontWeight: FontWeight.bold),
                ),
                const Spacer(),
                Text('💾 $_storedCount  |  ⏳ $_pendingUploads pending'),
              ],
            ),
          ),

          // Location display (F5: shows full fields)
          if (_lastLoc != null)
            Container(
              color: Colors.teal.shade50,
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
              child: Row(
                children: [
                  Expanded(
                    child: Text(
                      'lat=${_lastLoc!.latitude.toStringAsFixed(5)} '
                      'lng=${_lastLoc!.longitude.toStringAsFixed(5)}\n'
                      'alt=${_lastLoc!.altitude?.toStringAsFixed(1)}m  '
                      'spd=${_lastLoc!.speed?.toStringAsFixed(2)}m/s  '
                      'hdg=${_lastLoc!.heading?.toStringAsFixed(0)}°',
                      style: const TextStyle(fontSize: 12, fontFamily: 'monospace'),
                    ),
                  ),
                ],
              ),
            ),

          // Geofence status (F7)
          Container(
            color: Colors.blue.shade50,
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
            child: Row(
              children: [
                const Icon(Icons.radio_button_checked, size: 16, color: Colors.blue),
                const SizedBox(width: 6),
                Text('Geofence: $_geofenceStatus',
                    style: const TextStyle(fontSize: 12)),
              ],
            ),
          ),

          // Action buttons
          Padding(
            padding: const EdgeInsets.all(8),
            child: Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                ElevatedButton.icon(
                  onPressed: _isRunning ? _stop : _start,
                  icon: Icon(_isRunning ? Icons.stop : Icons.play_arrow),
                  label: Text(_isRunning ? 'Stop' : 'Start'),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: _isRunning ? Colors.red : Colors.green,
                    foregroundColor: Colors.white,
                  ),
                ),
                if (_isRunning) ...[
                  ElevatedButton.icon(
                    onPressed: _isPaused ? _resume : _pause,
                    icon: Icon(_isPaused ? Icons.play_arrow : Icons.pause),
                    label: Text(_isPaused ? 'Resume' : 'Pause'), // F3
                  ),
                ],
                OutlinedButton.icon(
                  onPressed: _refreshCount,
                  icon: const Icon(Icons.refresh, size: 16),
                  label: const Text('Refresh'), // F8
                ),
                OutlinedButton.icon(
                  onPressed: _clearStored,
                  icon: const Icon(Icons.delete_outline, size: 16),
                  label: const Text('Clear DB'), // F8
                ),
              ],
            ),
          ),

          const Divider(height: 1),

          // Event log (F2: all stream events shown live)
          Expanded(
            child: ListView.builder(
              padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
              itemCount: _log.length,
              itemBuilder: (_, i) => Text(
                _log[i],
                style: const TextStyle(fontSize: 11, fontFamily: 'monospace'),
              ),
            ),
          ),
        ],
      ),
    );
  }
}
1
likes
150
points
44
downloads

Documentation

API reference

Publisher

verified publisherdineshwayaman.com

Weekly Downloads

BeTracky Background Location Service — background location tracking with offline storage and upload to your own server.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#location #background-service #gps #tracking #geolocation

License

MIT (license)

Dependencies

flutter, flutter_background_service, geolocator, http, path, shared_preferences, sqflite, workmanager

More

Packages that depend on betracky_background_location