skip to Main Content

I have a Flutter application that allows an administrator to create a route on a map by saving the latitude and longitude. The admin can then save this route so that users can use it to find the admin-set fishing points of interest. However, I encountered an issue where the application stopped tracking the route when the admin put their phone in their pocket or locked the screen. I’ve been told that I can solve this problem by using background services.

Here is my code for tracking:

class TrackRouteScreen extends StatefulWidget {
  final String id;

  TrackRouteScreen({required this.id});

  @override
  State<TrackRouteScreen> createState() => _TrackRouteScreenState();
}

class _TrackRouteScreenState extends State<TrackRouteScreen> {
  bool isTracking = false;
  final CollectionReference routesCollection = FirebaseFirestore.instance.collection('Fish     POI');
  late MapController mapController;
  LatLng? currentPosition;
  List<LatLng> polylineCoordinates = [];
  List<Marker> markers = [];
  StreamSubscription<Position>? positionStreamSubscription;

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

  Future<void> initialize() async {
    mapController = MapController();
    currentPosition = await getCurrentLocation();
    setState(() {});
  }

  // Get current location of the user
  Future<LatLng> getCurrentLocation() async {
    bool serviceEnabled;
    LocationPermission permission;

    serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      throw Exception('Location services are disabled');
    }

    permission = await Geolocator.checkPermission();

    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission==LocationPermission.denied||permission == LocationPermission.deniedForever) {
        throw Exception('Location permissions are denied');
      }
    }

    if (permission == LocationPermission.deniedForever) {
      throw Exception('Location permissions are permanently denied');
    }

    Position position = await Geolocator.getCurrentPosition(
      desiredAccuracy: LocationAccuracy.high
    );

    return LatLng(position.latitude, position.longitude);
  }

  // Start tracking
  void startTracking() {
    setState(() {
      isTracking = true;
      polylineCoordinates.clear();
    });

    positionStreamSubscription = Geolocator.getPositionStream(
      locationSettings: LocationSettings()).listen((Position position) {
        updateLocation(position);
      }
    );
  }

  // Update location and polyline
  void updateLocation(Position position) {
    setState(() {    
      currentPosition = LatLng(position.latitude, position.longitude);
      polylineCoordinates.add(currentPosition!);
    });
  }

  // Stop tracking
  void stopTracking() {
    setState(() {
      isTracking = false;
    });
    positionStreamSubscription!.cancel();
    saveTrackedRoute(polylineCoordinates);
  }

  // Save tracked route to Firestore DB
  Future<void> saveTrackedRoute(List<LatLng> trackedRoute) async {
    try {
      final DocumentReference routeDocRef = routesCollection.doc(widget.id);

      await routeDocRef.update({
        'route': trackedRoute .map((latLng) => GeoPoint(latLng.latitude, latLng.longitude))
          .toList(),
      });

      Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => AddImagesScreen(id: widget.id)),
      );
    } catch (error) {
      print('Error saving tracked route: $error');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(body: body());
  }

  Widget body() {
    return Container(
      width: MediaQuery.of(context).size.width,
      height: MediaQuery.of(context).size.height,
      child: Stack(
        children: [
          FlutterMap(
            mapController: mapController,
            options: MapOptions(center: currentPosition, zoom: 15),
            children: [
              TileLayer(
                urlTemplate: mapboxUrlTemplate,
                additionalOptions: {
                  'accessToken': mapboxAccessToken,
                  'id': 'mapbox.mapbox-streets-v8',
                },
              ),
              PolylineLayer(
                polylines: [
                  Polyline(
                    points: polylineCoordinates,
                    strokeWidth: 5,
                    color: Colors.blue,
                  ),
                ],
              ),
              CurrentLocationLayer(
                followOnLocationUpdate: FollowOnLocationUpdate.always,
                style: LocationMarkerStyle(
                  marker: DefaultLocationMarker(),
                ),
              ),
            ],
          ),
          Padding(
            padding: EdgeInsets.symmetric(horizontal: 15, vertical: 20),
            child: Align(
              alignment: Alignment.bottomCenter,
              child: GradientElevatedButton(
                onTap: isTracking ? stopTracking : startTracking,
                width: MediaQuery.of(context).size.width,
                beginningColor: isTracking ? Colors.red : Colors.green,
                endingColor: isTracking ? Colors.red : Colors.green,
                text: isTracking ? 'Stop' : 'Start',
              ),
            ),
          ),
          Positioned(
            top: 40.0,
            right: 15.0,
            child: FloatingActionButton(
              onPressed: () {
                if (isTracking) {
                  showDialog(
                    context: context,
                    builder: (BuildContext context) {
                      return AlertDialog(
                        title: Text('Quit Tracking'),
                        content: Text('Are you sure you want to quit tracking?'),
                        actions: <Widget>[
                          TextButton(
                            child: Text('Cancel'),
                            onPressed: () {
                              Navigator.of(context).pop();
                            },
                          ),
                          TextButton(
                            child: Text('Quit'),
                            onPressed: () {
                              Navigator.of(context).pop();
                              Navigator.of(context).pop();
                              stopTracking();
                            },
                          ),
                        ],
                      );
                    },
                  );
                } else {
                  Navigator.pop(context);
                }
              },
              child: Icon(Icons.close),
              backgroundColor: Colors.grey[300],
              foregroundColor: Colors.black,
            ),
          ),
        ],
      ),
    );
  }
}

As a 17-year-old girl who is new to app development, I find this concept confusing. What does it mean to track in the background? How can I implement it? Additionally, I’m worried about users who may not have a data connection. In such cases, where can the data be stored until a connection is available again?

Can someone give me a solid understanding of this, modify the code and explain it in a simple way? And which package will suit this?

2

Answers


  1. If i, understood your purpose correctly, you can try this package https://pub.dev/packages/flutter_background_service

    Login or Signup to reply.
  2. It’s impressive that you’re pursuing app development at such a young age! I’ll do my best to explain background tracking and assist you in simplifying the code. Not that I am a professional in this area, but it seems like people are ignoring you.

    Background tracking refers to your application’s ability to continue tracking the user’s location even when the app is not actively operating or the screen is locked. This is essential for situations where the administrator wants to track routes without keeping the app open all the time, such as with your fishing app.

    To implement background monitoring, use the background_location_tracker Flutter package. This package enables you to monitor the location of the user in the background and execute code when location updates are received.

    To implement location monitoring in the background with background_location_tracker:

      dependencies:
      flutter:
        sdk: flutter
      background_location_tracker: ^1.4.1  // Or the newest version
    

    Also, compileSdkVersion and targetSdkVersion should be at least 29. Modify android/app/build.gradle as follows:

    android {
      ...
      compileSdkVersion 29
      ...
    
      defaultConfig {
        ...
        targetSdkVersion 29
        ...
      }
      ...
    }
    
    • Run flutter pub get in your terminal to install the package.

    In your main.dart file, you should initialize the package. This is how your main.dart file could look like:

    @pragma('vm:entry-point')
    void backgroundCallback() {
      BackgroundLocationTrackerManager.handleBackgroundUpdated(
        (data) async {},
      );
    }
    
    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Firebase.initializeApp();
    
      await BackgroundLocationTrackerManager.initialize(
        backgroundCallback,
        config: BackgroundLocationTrackerConfig(
          loggingEnabled: true,
          androidConfig: AndroidConfig(
            trackingInterval: Duration(seconds: 3),
          ),
          iOSConfig: IOSConfig(
            activityType: ActivityType.NAVIGATION,
            restartAfterKill: true,
          ),
        ),
      );
      runApp(RodRoute());
    }
    
    class RodRoute extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          home: SplashScreen(),
        );
      }
    }
    

    After initializing the package, let’s modify your TrackRouteScreen. First, import the necessary packages:

    import 'dart:async';
    import 'package:background_location_tracker/background_location_tracker.dart';
    import 'package:cloud_firestore/cloud_firestore.dart';
    import 'package:flutter_map/flutter_map.dart';
    import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
    import 'package:geolocator/geolocator.dart';
    import 'package:latlong2/latlong.dart';
    import 'package:flutter/material.dart';
    

    Update your _TrackRouteScreenState class with the required modifications for background location tracking. Add necessary member variables:

    bool isTracking = false;
    final CollectionReference routesCollection = FirebaseFirestore.instance.collection('Fishing POI');
    late MapController mapController;
    LatLng? currentPosition;
    List<LatLng> polylineCoordinates = [];
    List<Marker> markers = [];
    StreamSubscription<Position>? positionStreamSubscription;
    

    Add the functions you need:

    @override
      void initState() {
        initialize();
        super.initState();
      }
    
      Future<void> initialize() async {
        mapController = MapController();
        currentPosition = await getCurrentLocation();
    
        setState(() {});
      }
    
      // Get current location of the user
      Future<LatLng> getCurrentLocation() async {
        bool serviceEnabled;
        LocationPermission permission;
    
        serviceEnabled = await Geolocator.isLocationServiceEnabled();
        if (!serviceEnabled) {
          throw Exception('Location services disabled');
        }
    
        permission = await Geolocator.checkPermission();
    
        if (permission == LocationPermission.denied) {
          permission = await Geolocator.requestPermission();
          if (permission == LocationPermission.denied || permission == LocationPermission.deniedForever) {
            throw Exception('Location permissions denied');
          }
        }
    
        Position position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high, forceAndroidLocationManager: true);
        return LatLng(position.latitude, position.longitude);
      }
    
      // Start tracking
      void startTracking() async {
        setState(() {
          isTracking = true;
          polylineCoordinates.clear();
        });
    
        await BackgroundLocationTrackerManager.startTracking(
          config: AndroidConfig(trackingInterval: Duration(seconds: 3)),
        );
    
        positionStreamSubscription = Geolocator.getPositionStream(locationSettings: LocationSettings()).listen((Position position) {
          updateLocation(position);
        });
      }
    
      // Update location and polyline
      void updateLocation(Position position) async {
        setState(() {
          currentPosition = LatLng(position.latitude, position.longitude);
          polylineCoordinates.add(currentPosition!);
        });
      }
    
      // Stop tracking
      void stopTracking() {
        setState(() {
          isTracking = false;
        });
    
        positionStreamSubscription!.cancel();
        saveTrackedRoute(polylineCoordinates);
      }
    
      // Save the tracked route to firestore db
      Future<void> saveTrackedRoute(List<LatLng> trackedRoute) async {
        try {
          final DocumentReference routeDocRef = routesCollection.doc(widget.id);
          List<GeoPoint> geoPoints = trackedRoute.map((latLng) => GeoPoint(latLng.latitude, latLng.longitude)).toList();
    
          await routeDocRef.update({
            'trackedRoute': FieldValue.arrayUnion(geoPoints),
          });
        } catch (error) {
          print(error);
        }
      }
    

    Make sure to call the startTracking function when the ‘Start’ button is pressed, and make sure to call the stopTracking function when the ‘Stop’ button is pressed.

    Your app will now monitor the user’s location even when the screen is locked or the app is in the background.

    Concerning your concern regarding users without a data connection, you can store tracked data locally using a database or file until a data connection becomes available. Use a package such as sqflite to store the data in a local SQLite database or shared_preferences to store it as key-value pairs on the device. When an available data connection is present, you can then submit the data to your Firestore database. But this is it’s own topic, which you can ask in another question.

    I hope this explanation and the code modifications make it simpler for you to comprehend and implement background tracking in your app.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search