What is Geolocation?

Geolocation refers to the identification of the geographic location of a user or computing device via a variety of data collection mechanisms. Typically, most geolocation services use network routing addresses or internal GPS devices to determine this location. Geolocation is a device-specific API. This means that browsers or devices must support geolocation in order to use it through web applications.

The two packages for figuring out a user's current physical location are Geolocation and Geocoding.Platform-specific location services and geocoding/reverse-geocoding tools are easily accessible using these packages.











Table of contents


  1. How to Set Up Environments 

  • For Android

  • For IOS

  1. Handle Location Permission and Services

  2. Get User Latitude and Longitude


1. Setting up the project Environments


First, We must add two packages in you Pubspec.yaml required dependencies:


dependencies:

 geolocator: ^10.1.0

 geocoding: ^2.1.1


Or using this command, you also add this :

$ flutter pub add geolocator

$ flutter pub add geocoding    


Then, we have to add permission for Android and iOS



For Android


  • First we have to add the following codes in your gradle.properties file,To do so open the AndroidManifest.xml file (located under android/app/src/main) and add one of the following two lines as direct

    children of the <manifest> tag (when you configure both permissions the ACCESS_FINE_LOCATION will be used by the geolocator plugin)


android.useAndroidX=true

android.enableJetifier=true


  • Then make sure your compileSdkVersion in your android/app/build.gradle file:


android {

 compileSdkVersion 31


 ...

}


  • Then we have to add ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permission in your android/app/src/main/AndroidManifest.xml  file:


<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />


<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />


  • Starting from Android 10 you need to add the ACCESS_BACKGROUND_LOCATION permission (next to the ACCESS_COARSE_LOCATION or the ACCESS_FINE_LOCATION permission) if you want to continue receiving updates even when your App is running in the background (note that the geolocator plugin doesn't support receiving and processing location updates while running in the background):


<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />


For iOS


  • We need to add following lines in your ios/Runner/Info.plist file for access to your location. 


<key>NSLocationWhenInUseUsageDescription</key>

<string>This app needs access to location when open.</string>

<key>NSLocationAlwaysUsageDescription</key>

<string>This app needs access to location when in the background.</string>


  • If you would like to receive updates when your App is in the background, you'll also need to add the Background Modes capability to your XCode project (Project > Signing and Capabilities > "+ Capability" button) and select Location Updates. Be careful with this, you will need to explain in detail to Apple why your App needs this when submitting your App to the AppStore. If Apple isn't satisfied with the explanation your App will be rejected.

When using the requestTemporaryFullAccuracy({purposeKey: "YourPurposeKey"}) method, a dictionary should be added to the Info.plist file.

<key>NSLocationTemporaryUsageDescriptionDictionary</key>

<dict>

  <key>YourPurposeKey</key>

  <string>The example App requires temporary access to the device&apos;s precise location.</string>

</dict>



2. Handle location services and permissions


In order to get the user’s location, we must have the user's permission. So we will create a method to check and request the location services and permission.





import 'package:flutter/material.dart';
import 'package:flutter_location_practice/home_page.dart';
import 'package:geocoding/geocoding.dart' as gc;
import 'package:geolocator/geolocator.dart' as gl;
import 'package:permission_handler/permission_handler.dart' as ph;

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  String? _currentIsoCode;

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

    WidgetsBinding.instance.addObserver(this);

    _isAndroidPermissionGranted();
    _requestPermissions();
    registerNotification();

    _getCurrentPosition();
    if (Platform.isAndroid) {
      _checkLocationPermissionAndRestartApp();
    }
  }

@override
void dispose() {
  // Remove the observer added in initState
  WidgetsBinding.instance.removeObserver(this);
  // Call the parent class's dispose method
  super.dispose();
}

This dispose method is part of the State lifecycle in Flutter. It's used to clean up resources when the State object is removed, such as removing observers. In this case, it removes the observer added in the initState method to listen for app lifecycle changes.


Future _forceRequestLocationPermission() async {
  // Check the location permission status
  final status = await ph.Permission.location.status;

  // If the permission is denied, request it forcefully
  if (status.isDenied) {
    final status = await ph.Permission.location.request();

    // If permission is permanently denied, open app settings
    if (status.isPermanentlyDenied) {
      ph.openAppSettings();
    } else if (status.isGranted) {
      // If permission is granted, get the current position
      await gl.Geolocator.getCurrentPosition(
          desiredAccuracy: gl.LocationAccuracy.high)
          .then((gl.Position position) {
        _getAddressFromLatLng(position);
      }).catchError((e) {
        debugPrint(e);
      });
    }
  }
}
 



This dispose method is part of the State lifecycle in Flutter. It's used to clean up resources when the State object is removed, such as removing observers. In this case, it removes the observer added in the initState method to listen for app lifecycle changes.



Future _forceRequestLocationPermission() async {

  // Check the location permission status

  final status = await ph.Permission.location.status;


  // If the permission is denied, request it forcefully

  if (status.isDenied) {

    final status = await
      ph.Permission.location.request();


    // If permission is permanently denied, open app
      settings

    if (status.isPermanentlyDenied) {

      ph.openAppSettings();

    } else if (status.isGranted) {

      // If permission is granted, get the
      current position

      await
      gl.Geolocator.getCurrentPosition(

          desiredAccuracy:
      gl.LocationAccuracy.high)

          .then((gl.Position
      position) {

        _getAddressFromLatLng(position);

      }).catchError((e) {

        debugPrint(e);

      });

    }

  }

}
  



This method _forceRequestLocationPermission is designed to handle situations where location permission is denied. It forcefully requests location permission and, if granted, proceeds to get the current device position.








Future _handleLocationPermission() async {

  bool serviceEnabled;

  gl.LocationPermission permission;


  // Get shared preferences

  final SharedPreferences prefs = await
    SharedPreferences.getInstance();


  try {

    // Check if location service is enabled

    serviceEnabled = await
    gl.Geolocator.isLocationServiceEnabled();


    // Log the service status

    logger.e(serviceEnabled);


    if (!serviceEnabled) {

      // If location service is not enabled,
    request permission and show a message

      permission = await
    gl.Geolocator.requestPermission();

      locationController.getIsLocationServiceEnabled(false);

      await Fluttertoast.showToast(

          msg: "You need
    to enable location services");

      await 3.delay();

      logger.e("service not enabled");

      return false;

    }


    // If location service is enabled, update the
    controller

    locationController.getIsLocationServiceEnabled(serviceEnabled);


    // Check location permission

    permission = await
    gl.Geolocator.checkPermission();

    logger.d(permission);


    if (permission == gl.LocationPermission.denied)
    {

      // If location permission is denied,
    forcefully request it

      final permission1 = await
    gl.Geolocator.requestPermission();

      if (permission1 ==
    gl.LocationPermission.denied) {

        await
    _forceRequestLocationPermission();

        return true;

      }

    } else if (permission ==
    gl.LocationPermission.deniedForever) {

      // If location permission is
    permanently denied, forcefully request it

      await
    _forceRequestLocationPermission();

      return true;

    }


    // If all checks pass, set a flag in shared
    preferences and return true

    prefs.setBool('locationPermissionGranted',
    true);

    return true;

  } catch (e) {

    // Handle exceptions and set the flag to false in
    case of an error

    prefs.setBool('locationPermissionGranted',
    false);

    logger.e("$e");

    return false;

  }

}



The _handleLocationPermission method is responsible for checking and handling location permissions. It checks whether location services are enabled and requests permission if necessary. It also handles scenarios where permission is denied or permanently denied, providing appropriate feedback and taking necessary actions.




Future _getCurrentPosition() async {
  logger.d("get current location");
  try {
    // Check and handle location permission
    final hasPermission = await _handleLocationPermission();
    logger.d(hasPermission);

    if (!hasPermission) return;

    logger.f("message");

    // Get the current device position
    final position = await gl.Geolocator.getCurrentPosition(
        desiredAccuracy: gl.LocationAccuracy.lowest);

    logger.f("message");

    await _getAddressFromLatLng(position);
  } catch (e) {
    // Handle exceptions related to getting the current position
    logger.f("message  $e");
  }
}

The _getCurrentPosition method is responsible for getting the current device position. It first checks and handles location permissions using _handleLocationPermission. If permission is granted, it proceeds to get the current position using Geolocator.







 

Future _getAddressFromLatLng(gl.Position position) async
    {

  try {

    // Get the address from latitude and
    longitude

    final placeMarks = await
    gc.placemarkFromCoordinates(

        position.latitude,
    position.longitude);


    gc.Placemark place = placeMarks[0];

    _currentIsoCode = place.isoCountryCode?.toString()
    ?? "";

    await SharedRefrence().saveIsocode(key: "IsoCode",
    data: _currentIsoCode);


    // Update loading status in the controller

    locationController.getIsLoading(false);

  } catch (e) {

    // Handle exceptions related to getting address
    from coordinates

    locationController.getIsLoading(false);

    logger.e(e);

  }

}

The _getAddressFromLatLng method takes a Position object (latitude and longitude) and uses Geocoding to get the address information. It extracts the ISO country code and saves it using SharedRefrence. The loading status is updated in the controller.






  @override

  Widget build(BuildContext context) {

    return const MaterialApp(

      title: 'Flutter Location Demo',

      debugShowCheckedModeBanner:
    false,

      home: HomePage(

latitude: position.latitude,

          longitude:
    position.longitude,

          position:
    '$position',


),

    );

  }

}




3. Get user’s latitude and longitude


Now it's time to add the geolocation functionality. To query the current location of the device, simply make a call to the getCurrentPosition() method. For example:






 
import 'package:flutter/material.dart';


class HomePage extends StatelessWidget {

  final double latitude;

  final double longitude;

  final String position;


  const HomePage({Key? key, required this.latitude, required
    this.longitude, required this.position}) : super(key: key);


  @override

  Widget build(BuildContext context) {

    return Scaffold(

      appBar: AppBar(

        title: Text('Flutter
    Location Demo'),

      ),

      body: Center(

        child: Column(

          mainAxisAlignment:
    MainAxisAlignment.center,

          children:
    [

            Text(

              'Latitude:
    $latitude',

              style:
    TextStyle(fontSize: 18),

            ),

            Text(

              'Longitude:
    $longitude',

              style:
    TextStyle(fontSize: 18),

            ),

            Text(

              'Position:
    $position',

              style:
    TextStyle(fontSize: 18),

            ),

          ],

        ),

      ),

    );

  }

}