DEV Community

Cover image for Introducing GroupTrack: Simplified Location-Based Services for Flutter
Ishita
Ishita

Posted on

Introducing GroupTrack: Simplified Location-Based Services for Flutter

Hello community👋.

We all know that working with location-based services can be challenging😩—from managing location accuracy to ensuring efficient background updates, it’s a time-consuming⌛ process.

Developers often face issues like::

  • Managing accuracy
  • Handling background services
  • Optimising battery usage
  • Real-time background updates

To make this easier for developers, our CanopasTeam has built GroupTrack, an open-source Flutter project aimed at simplifying location-based services and background operations. GroupTrack tackles the common problems developers face.

Let's delve into the specific approaches used in the project.

1️⃣ Managing accuracy

The project utilises the geolocator package, a popular Flutter plugin for accessing location services. This package allows the app to specify the desired accuracy when requesting location data.

Position position = await Geolocator.getCurrentPosition(
  desiredAccuracy: LocationAccuracy.high,
  distanceFilter: 10, // Update every 10 meters
);
Enter fullscreen mode Exit fullscreen mode
  • Desired Accuracy Levels: The desiredAccuracy parameter can be set to levels like highmedium, or low, depending on the needs of the app at that moment.
  • Impact on Battery Life: Higher accuracy requires more power. By adjusting the accuracy level, the app conserves battery when high precision isn't necessary.
  • Reduces Unnecessary Updates: By only receiving updates when the user has moved a certain distance, the app minimizes redundant processing and this helps in reducing CPU usage and conserves battery life.

iOS Implementation:
For iOS, we use native code in the AppDelegate file to manage location services.

var locationManager: CLLocationManager?

override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
    setUpLocation()
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

private func setUpLocation() {
    locationManager = CLLocationManager()
    locationManager?.delegate = self
    locationManager?.distanceFilter = 10
    locationManager?.desiredAccuracy = kCLLocationAccuracyBest
}
Enter fullscreen mode Exit fullscreen mode

2️⃣ Handling background services

To handle location services in the background, we configure both Android and iOS separately.

Android
Add the following service configuration to your AndroidManifest.xml file:

<service  
    android:name="id.flutter.flutter_background_service.BackgroundService"
    android:enabled="true"
    android:exported="true"
    android:foregroundServiceType="location" />
Enter fullscreen mode Exit fullscreen mode

iOS
Enable Background Mode in the Signing & Capabilities section.

Add background modes

add this in app delegate

private func setUpLocation() {
    locationManager?.allowsBackgroundLocationUpdates = true
    locationManager?.pausesLocationUpdatesAutomatically = false
    locationManager?.startMonitoringSignificantLocationChanges()
}
Enter fullscreen mode Exit fullscreen mode

Flutter
The project utilises the flutter_background_service package for managing background service.

void main() async {
WidgetsFlutterBinding.ensureInitialized();
  startService();
  runApp(
    UncontrolledProviderScope(container: container, child: const App()),
  );
}
Enter fullscreen mode Exit fullscreen mode
final bgService = FlutterBackgroundService();
void startService() async {
  await bgService.configure(
    androidConfiguration: AndroidConfiguration(
      onStart: onStart,
      autoStart: false,
      isForegroundMode: true,
    ),
    iosConfiguration: IosConfiguration(
      autoStart: false,
      onForeground: onStart,
      onBackground: onIosBackground,
    ),
  );

  final isLocationPermission = await Permission.location.isGranted;
  if (isLocationPermission) {
    bgService.startService();
  }
}
Enter fullscreen mode Exit fullscreen mode
StreamSubscription<Position>? positionSubscription;
Position? _position;

@pragma('vm:entry-point')
Future<void> onStart(ServiceInstance service) async {
  WidgetsFlutterBinding.ensureInitialized();
  final isLocationPermission = await Permission.location.isGranted;
  if (!isLocationPermission) return;

  if (service is AndroidServiceInstance) {
    service.setForegroundNotificationInfo(
      title: "Your space Location",
      content: "Location is being tracked",
    );
    service.setAsForegroundService();
  }

  _startLocationUpdates()

  service.on('stopService').listen((event) {
    positionSubscription?.cancel();
service.stopSelf();
  });
}

void _startLocationUpdates() {
  positionSubscription = Geolocator.getPositionStream(
    locationSettings: const LocationSettings(
      accuracy: LocationAccuracy.high,
      distanceFilter: LOCATION_UPDATE_DISTANCE,
    ),
  ).listen((position) {
    _position = position;
  });
}
Enter fullscreen mode Exit fullscreen mode
@pragma('vm:entry-point')
bool onIosBackground(ServiceInstance service) {
  onStart(service);
  return true;
}
Enter fullscreen mode Exit fullscreen mode

Description:
The app starts a background service that tracks the user’s location. It maintains this service in the background using flutter_background_service for Android and appropriate iOS configurations.

3️⃣ Optimising battery usage
We prioritize battery efficiency while conducting background updates in our app.

To achieve this, we request battery optimization permission from users. This allows the app to operate smoothly in the background without being terminated by the system's battery-saving measures.

For Android, obtaining this permission is crucial for uninterrupted background operations.
So you have to keep in mind that some of the devices do not allow direct battery restriction after allowing permission they want to enable manually battery restriction for apps from system settings.

On iOS, we utilize background fetch and location updates to further enhance battery optimization.

4️⃣ Real-time background updates

For real-time location updates, we handle iOS separately to manage background update and battery optimisation

iOS

private func setUpLocation() {
    locationManager?.allowsBackgroundLocationUpdates = true
    locationManager?.pausesLocationUpdatesAutomatically = false
    locationManager?.startMonitoringSignificantLocationChanges()
}

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let currentLocation = locations.last else { return }
    let now = Date().timeIntervalSince1970
    let lastUpdate = previousLocation?.timestamp.timeIntervalSince1970 ?? 0
    let timeDifference = now - lastUpdate
    if timeDifference < 60 { return }
    previousLocation = currentLocation

    let locationData: [String: Any] = [
        "latitude": currentLocation.coordinate.latitude,
        "longitude": currentLocation.coordinate.longitude,
        "timestamp": currentLocation.timestamp.timeIntervalSince1970 * 1000
    ]

    if let controller = window?.rootViewController as? FlutterViewController {
        let methodChannel = FlutterMethodChannel(name: "com.app_name/location", binaryMessenger: controller.binaryMessenger)
        methodChannel.invokeMethod("onLocationUpdate", arguments: locationData)
    }
}

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    if status == .authorizedAlways || status == .authorizedWhenInUse {
        locationManager?.startMonitoringSignificantLocationChanges()
    } else {
        locationManager?.stopUpdatingLocation()
    }
}
Enter fullscreen mode Exit fullscreen mode

Flutter side:

const platform = MethodChannel('com.app_name/location');
Enter fullscreen mode Exit fullscreen mode
platform.setMethodCallHandler(_handleLocationUpdates);
Enter fullscreen mode Exit fullscreen mode
Future<void> _handleLocationUpdates(MethodCall call) async {
  if (call.method == 'onLocationUpdate') {
    final Map<String, dynamic> locationData = Map<String, dynamic>.from(call.arguments);

    final double latitude = locationData['latitude'];
    final double longitude = locationData['longitude'];
    final DateTime timestamp = DateTime.fromMillisecondsSinceEpoch(locationData['timestamp'].toInt());

    // perform action required after getting location updates
  }
}
Enter fullscreen mode Exit fullscreen mode

For real-time updates in Android, there's no need for native implementation; the geolocator will handle location updates by position that we declare before.

Conclusion
Group Track simplifies location-based services for Flutter developers, making it easy to integrate real-time tracking with secure, efficient, and reliable performance. By addressing common pain points like accuracy, background services, battery optimization, and real-time updates, Group Track streamlines the process, helping developers build better apps faster.

Here is the github repository, for more information check out this - https://github.com/canopas/group-track-flutter

Top comments (0)