location_tracking 2.1.2 copy "location_tracking: ^2.1.2" to clipboard
location_tracking: ^2.1.2 copied to clipboard

A Flutter plugin for receiving location updates even when the app is terminated. Works on both iOS and Android with support for background execution.

example/lib/main.dart

import 'dart:async';
import 'dart:isolate';
import 'dart:ui';

import 'package:location_tracking/background_locator.dart';
import 'package:location_tracking/location_dto.dart';
import 'package:location_tracking/settings/android_settings.dart';
import 'package:location_tracking/settings/ios_settings.dart';
import 'package:location_tracking/settings/locator_settings.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart' as permission_handler;
import 'file_manager.dart';
import 'location_callback_handler.dart';
import 'location_service_repository.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  ReceivePort port = ReceivePort();

  String logStr = '';
  bool? isRunning;
  LocationDto? lastLocation;
  bool isInsideGeofence = false;

  @override
  void initState() {
    super.initState();

    if (IsolateNameServer.lookupPortByName(
            LocationServiceRepository.isolateName) !=
        null) {
      IsolateNameServer.removePortNameMapping(
          LocationServiceRepository.isolateName);
    }

    IsolateNameServer.registerPortWithName(
        port.sendPort, LocationServiceRepository.isolateName);

    port.listen(
      (dynamic data) async {
        await updateUI(data);
      },
    );
    initPlatformState();
  }

  @override
  void dispose() {
    super.dispose();
    IsolateNameServer.removePortNameMapping(
        LocationServiceRepository.isolateName);
  }

  Future<void> updateUI(dynamic data) async {
    final log = await FileManager.readLogFile();

    LocationDto? locationDto;
    bool? geofenceStatus;

    if (data != null && data is Map<String, dynamic>) {
      locationDto = LocationDto.fromJson(data['location']);
      geofenceStatus = data['isInsideGeofence'];
    }

    if (locationDto != null) {
      await _updateNotificationText(locationDto, geofenceStatus);
    }

    setState(() {
      if (locationDto != null) {
        lastLocation = locationDto;
      }
      if (geofenceStatus != null) {
        isInsideGeofence = geofenceStatus;
      }
      logStr = log;
    });
  }

  Future<void> _updateNotificationText(
      LocationDto data, bool? insideGeofence) async {
    String geofenceStatus =
        insideGeofence == true ? "Inside geofence" : "Outside geofence";
    await BackgroundLocator.updateNotificationText(
        title: "New location received",
        msg: "${DateTime.now()}",
        bigMsg: "${data.latitude}, ${data.longitude}\n$geofenceStatus");
  }

  Future<void> initPlatformState() async {
    print('Initializing...');
    await BackgroundLocator.initialize();
    print('Initialization done');
    final isRunning = await BackgroundLocator.isServiceRunning();
    setState(() {
      isRunning = isRunning;
    });
    print('Running ${isRunning.toString()}');
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
        brightness: Brightness.dark,
        scaffoldBackgroundColor: const Color(0xFF1E1E1E),
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Geofence Tracker'),
          elevation: 0,
          centerTitle: true,
        ),
        body: SafeArea(
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                _buildStatusCard(),
                const SizedBox(height: 16),
                _buildControlButtons(),
                const SizedBox(height: 16),
                Expanded(child: _buildLogCard()),
              ],
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildStatusCard() {
    return Card(
      elevation: 8,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
      child: Container(
        decoration: BoxDecoration(
          gradient: const LinearGradient(
            colors: [Colors.blueAccent, Colors.lightBlue],
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
          ),
          borderRadius: BorderRadius.circular(16),
        ),
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            children: [
              Text(
                isRunning == true ? 'Active' : 'Inactive',
                style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white),
              ),
              const SizedBox(height: 8),
              Text(
                "Geofence: ${isInsideGeofence ? 'Inside' : 'Outside'}",
                style: const TextStyle(fontSize: 16, color: Colors.white70),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildControlButtons() {
    return Row(
      children: [
        Expanded(
          child: ElevatedButton.icon(
            icon: const Icon(Icons.play_arrow),
            label: const Text('Start'),
            onPressed: _onStart,
            style: ElevatedButton.styleFrom(
              padding: const EdgeInsets.symmetric(vertical: 16),
              shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
              backgroundColor: Colors.green,
              shadowColor: Colors.greenAccent,
              elevation: 5,
            ),
          ),
        ),
        const SizedBox(width: 16),
        Expanded(
          child: ElevatedButton.icon(
            icon: const Icon(Icons.stop),
            label: const Text('Stop'),
            onPressed: onStop,
            style: ElevatedButton.styleFrom(
              padding: const EdgeInsets.symmetric(vertical: 16),
              backgroundColor: Colors.red,
              shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
              shadowColor: Colors.redAccent,
              elevation: 5,
            ),
          ),
        ),
      ],
    );
  }

  Widget _buildLogCard() {
    return Card(
      elevation: 8,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                const Text(
                  'Log',
                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                ),
                IconButton(
                  icon: const Icon(Icons.clear),
                  onPressed: () {
                    FileManager.clearLogFile();
                    setState(() {
                      logStr = '';
                    });
                  },
                ),
              ],
            ),
          ),
          Expanded(
            child: SingleChildScrollView(
              padding: const EdgeInsets.all(16),
              child: Text(logStr),
            ),
          ),
        ],
      ),
    );
  }

  void onStop() async {
    await BackgroundLocator.unRegisterLocationUpdate();
    final isRunning = await BackgroundLocator.isServiceRunning();
    print('Locator stopped: $isRunning');
    setState(() {
      isRunning = isRunning;
    });
  }

  void _onStart() async {
    if (await _checkLocationPermission()) {
      print('Permissions granted, starting locator...');
      await _startLocator();
      final isRunning = await BackgroundLocator.isServiceRunning();
      print('Locator started: $isRunning');
      setState(() {
        isRunning = isRunning;
        lastLocation = null;
      });
      print('Locator started: $isRunning');
    } else {
      print('Permissions not granted');
      // show error
    }
  }

  Future<bool> _checkLocationPermission() async {
    final access = await permission_handler.Permission.location.status;
    print('Current permission status: $access');
    switch (access) {
      case permission_handler.PermissionStatus.limited:
      case permission_handler.PermissionStatus.denied:
      case permission_handler.PermissionStatus.restricted:
        final permission = await permission_handler.Permission.location.request();
        return permission == permission_handler.PermissionStatus.granted;
      case permission_handler.PermissionStatus.granted:
        return true;
      default:
        return false;
    }
  }

  Future<void> _startLocator() async {
    Map<String, dynamic> data = {'countInit': 1};
    return await BackgroundLocator.registerLocationUpdate(
        LocationCallbackHandler.callback,
        initCallback: LocationCallbackHandler.initCallback,
        initDataCallback: data,
        disposeCallback: LocationCallbackHandler.disposeCallback,
        iosSettings: const IOSSettings(
            accuracy: LocationAccuracy.NAVIGATION,
            distanceFilter: 0,
            stopWithTerminate: true),
        autoStop: false,
        androidSettings: const AndroidSettings(
            accuracy: LocationAccuracy.NAVIGATION,
            interval: 5,
            distanceFilter: 0,
            client: LocationClient.google,
            androidNotificationSettings: AndroidNotificationSettings(
                notificationChannelName: 'Location tracking',
                notificationTitle: 'Start Location Tracking',
                notificationMsg: 'Track location in background',
                notificationBigMsg:
                    'Background location is on to keep the app up-to-date with your location. This is required for main features to work properly when the app is not running.',
                notificationIconColor: Colors.grey,
                notificationTapCallback:
                    LocationCallbackHandler.notificationCallback)));
  }
}
0
likes
150
points
228
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for receiving location updates even when the app is terminated. Works on both iOS and Android with support for background execution.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, permission_handler

More

Packages that depend on location_tracking

Packages that implement location_tracking