Creating Real-Time Location Tracking Apps with Flutter and Mapbox

Real-time location tracking is a popular feature in many mobile applications, from navigation and ride-sharing to delivery services and social networking. Combining Flutter, a cross-platform UI toolkit, with Mapbox, a powerful mapping platform, allows developers to create sophisticated real-time location tracking apps with ease.

What is Real-Time Location Tracking?

Real-time location tracking involves continuously monitoring the geographic location of a device and displaying it on a map in real-time. This feature relies on GPS or other location services to provide accurate and up-to-date positional information.

Why Use Flutter and Mapbox?

  • Flutter: Enables cross-platform development with a single codebase, ensuring faster development and consistent UI/UX across Android and iOS.
  • Mapbox: Offers highly customizable maps, real-time data visualization, and geocoding services, making it perfect for location-based applications.

Setting Up Your Development Environment

Before you start building your real-time location tracking app, ensure you have Flutter and the necessary plugins configured.

Step 1: Install Flutter

Follow the official Flutter documentation to install Flutter on your development machine. You can find the instructions here: Flutter Installation Guide

Step 2: Create a New Flutter Project

Open your terminal and run the following command to create a new Flutter project:

flutter create real_time_location_app
cd real_time_location_app

Step 3: Add Required Dependencies

Add the following dependencies to your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  mapbox_gl: ^0.16.0  # Or the latest version
  geolocator: ^10.1.2  # Or the latest version
  permission_handler: ^11.1.0  # Or the latest version
  http: ^1.1.0 # Or the latest version

dev_dependencies:
  flutter_test:
    sdk: flutter

Then, run flutter pub get to install the dependencies.

Integrating Mapbox with Flutter

Step 1: Set Up Your Mapbox Account

If you don’t have one already, create a Mapbox account at Mapbox. Once you have an account, obtain your Access Token from your Mapbox dashboard. You will need this token to use Mapbox services in your Flutter app.

Step 2: Configure the Mapbox Plugin

In your android/app/src/main/AndroidManifest.xml, add the following meta-data tag inside the <application> tag. Replace YOUR_MAPBOX_ACCESS_TOKEN with your actual Mapbox Access Token.

<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"
    package=\"com.example.real_time_location_app\">

    <application
        android:name=\"${applicationName}\"
        android:icon=\"@mipmap/ic_launcher\"
        android:label=\"real_time_location_app\">

        <meta-data
            android:name=\"com.mapbox.token\"
            android:value=\"YOUR_MAPBOX_ACCESS_TOKEN\" />

        <activity
            android:name=\".MainActivity\"
            android:configChanges=\"orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode\"
            android:exported=\"true\"
            android:hardwareAccelerated=\"true\"
            android:launchMode=\"singleTop\"
            android:theme=\"@style/LaunchTheme\"
            android:windowSoftInputMode=\"adjustResize\">

            <meta-data
                android:name=\"io.flutter.embedding.android.NormalTheme\"
                android:resource=\"@style/NormalTheme\" />

            <intent-filter>
                <action android:name=\"android.intent.action.MAIN\" />
                <category android:name=\"android.intent.category.LAUNCHER\" />
            </intent-filter>
        </activity>

        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name=\"flutterEmbedding\"
            android:value=\"2\" />
    </application>
</manifest>

For iOS, add the following to your ios/Runner/Info.plist:

<key>io.flutter.embedded_views_preview</key>
<string>YES</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to your location when open.</string>

Step 3: Request Location Permissions

Before accessing the device’s location, request the necessary permissions using the permission_handler plugin. Create a function to handle permission requests:

import 'package:permission_handler/permission_handler.dart';

Future<void> requestLocationPermission() async {
  final status = await Permission.location.request();
  if (status.isGranted) {
    print('Location permission granted');
  } else if (status.isDenied) {
    print('Location permission denied');
  } else if (status.isPermanentlyDenied) {
    print('Location permission permanently denied');
    openAppSettings(); // Opens app settings for manual permission grant
  }
}

Building the Real-Time Location Tracking Feature

Step 1: Initialize the Map

Create a stateful widget to hold the map and location-related data.

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:mapbox_gl/mapbox_gl.dart';

class MapScreen extends StatefulWidget {
  @override
  _MapScreenState createState() => _MapScreenState();
}

class _MapScreenState extends State<MapScreen> {
  MapboxMapController? mapController;
  LatLng currentLocation = LatLng(0.0, 0.0); // Default location
  StreamSubscription<Position>? positionStream;

  @override
  void initState() {
    super.initState();
    requestLocationPermission(); // Request location permission on initialization
    _getCurrentLocation(); // Get initial location
    _startLocationTracking(); // Start real-time location tracking
  }

  @override
  void dispose() {
    positionStream?.cancel(); // Cancel the stream when the widget is disposed
    mapController?.dispose();
    super.dispose();
  }

  void _onMapCreated(MapboxMapController controller) {
    mapController = controller;
  }

  Future<void> _getCurrentLocation() async {
    try {
      Position position = await Geolocator.getCurrentPosition(
          desiredAccuracy: LocationAccuracy.high);
      setState(() {
        currentLocation = LatLng(position.latitude, position.longitude);
      });
      _updateCameraPosition(currentLocation); // Update the camera position
    } catch (e) {
      print('Error getting current location: $e');
    }
  }

  void _updateCameraPosition(LatLng location) {
    mapController?.animateCamera(
      CameraUpdate.newLatLng(location),
    );
  }

  void _startLocationTracking() {
    const LocationSettings locationSettings = LocationSettings(
      accuracy: LocationAccuracy.high,
      distanceFilter: 10, // Update location every 10 meters
    );

    positionStream = Geolocator.getPositionStream(locationSettings: locationSettings).listen(
        (Position? position) {
      if (position != null) {
        setState(() {
          currentLocation = LatLng(position.latitude, position.longitude);
        });
        _updateCameraPosition(currentLocation); // Update the camera position
        _updateUserLocationSymbol(currentLocation); // Update the symbol on the map
      }
    });
  }

  Future<void> _updateUserLocationSymbol(LatLng location) async {
    final point = location;

    // Check if symbol already exists, if so update, otherwise add
    if (mapController!.getSymbol("userLocation") != null){
      mapController!.updateSymbol(
        "userLocation",
        SymbolOptions(
          geometry: point,
          iconImage: "assetImage",  // Replace with your asset icon name or other icon options
          iconSize: 1.0,
        )
      );
    }else{
      mapController!.addSymbol(
        SymbolOptions(
          geometry: point,
          iconImage: "assetImage", // Replace with your asset icon name or other icon options
          iconSize: 1.0,
          symbolId: "userLocation"
        ),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Real-Time Location Tracking'),
      ),
      body: MapboxMap(
        accessToken: 'YOUR_MAPBOX_ACCESS_TOKEN',  // Replace with your Mapbox access token
        onMapCreated: _onMapCreated,
        initialCameraPosition: CameraPosition(
          target: currentLocation,
          zoom: 14.0,
        ),
      ),
    );
  }
}

Replace 'YOUR_MAPBOX_ACCESS_TOKEN' with your actual Mapbox access token.

Step 2: Fetch Current Location

Use the geolocator plugin to get the device’s current location.

Step 3: Real-Time Location Updates

Implement a stream using Geolocator.getPositionStream to continuously monitor the device’s location. Update the map’s camera position and add a symbol to represent the user’s location.

Step 4: Add UI Enhancements

Customize the map appearance by adding markers, polylines, or other UI elements. Use Mapbox Studio to design custom map styles that match your app’s branding.

Advanced Features

Consider adding advanced features to enhance your location tracking app:

  • Geofencing: Define virtual boundaries and trigger events when a device enters or exits a geofence.
  • Directions API: Calculate routes and provide turn-by-turn navigation instructions.
  • Clustering: Aggregate nearby markers to improve map readability when displaying many locations.
  • Offline Maps: Allow users to download map regions for offline use.

Troubleshooting and Common Issues

  • Permission Issues: Always ensure that you handle location permissions gracefully. Handle cases where permissions are denied and guide the user on how to enable them in settings.
  • Accuracy Problems: Location accuracy can vary significantly based on GPS signal strength and device hardware. Use LocationAccuracy.high when initializing the location stream for best results.
  • Battery Consumption: Real-time location tracking can drain the battery quickly. Implement strategies to optimize battery usage, such as reducing the frequency of location updates or using geofencing instead of continuous tracking where appropriate.

Conclusion

By leveraging the power of Flutter and Mapbox, you can create feature-rich real-time location tracking applications that offer a seamless and engaging user experience. The ability to build cross-platform apps quickly and easily, combined with Mapbox’s customizable mapping capabilities, makes this combination a great choice for developers. Following the guide above allows creation of sophisticated, location-aware mobile apps.