Search code examples
androidflutterasync-awaitgeolocationflutter-dependencies

Flutter's Geolocator package returns nulls for latitude and longitude values. Why? [Android]


This is my first StackOverflow question and I am learning Dart and Flutter as a newbie.

I have been introduced to the geolocator package and I have been using the latest version which is version 8.2.1. I am also working with Android Studio and its Android Emulator.

The challenge has been to gain latitude and longitude values, and these would come from position data acquired by geolocator.

After some tests, the results have been that the location services are enabled, permission for the location data to be noticed is granted, but despite this, the position data is not being retrieved by geolocator.

I spent some time in the emulator's settings and managed to set a location in San Jose, California, which I hoped that geolocator would then find and work with, but doing so made no difference.

The console's response which seems important is "Future Not Completed":

✓ Built build/app/outputs/flutter-apk/app-debug.apk. Installing build/app/outputs/flutter-apk/app.apk... D/FlutterGeolocator( 6027): Attaching Geolocator to activity D/FlutterGeolocator( 6027): Creating service. D/FlutterGeolocator( 6027): Binding to location service. D/FlutterGeolocator( 6027): Geolocator foreground service connected D/FlutterGeolocator( 6027): Initializing Geolocator services Debug service listening on ws://127.0.0.1:64162/f6U62iu6OXc=/ws Syncing files to device sdk gphone64 x86 64... I/flutter ( 6027): Currently, the emulator's Location Services Status = true. D/CompatibilityChangeReporter( 6027): Compat change id reported: 78294732; UID 10149; state: ENABLED I/flutter ( 6027): Current Location Permission Status = LocationPermission.whileInUse. I/flutter ( 6027): TimeoutException after 0:00:05.000000: Future not completed

My Code:

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

void main() {
  runApp(ScreenView());
}

class ScreenView extends StatefulWidget {
  double? latitude;
  double? longitude;

  ScreenView({this.latitude, this.longitude});

  void locationHereIs() async {
    await locationServicesStatus();
    await checkLocationPermissions();
    try {
      Position position = await Geolocator.getCurrentPosition(
              desiredAccuracy: LocationAccuracy.low)
          .timeout(Duration(seconds: 5));
      print(position);
    } catch (e) {
      print(e);
    }
  }

  Future<void> checkLocationPermissions() async {
    LocationPermission permission = await Geolocator.requestPermission();
    print('Current Location Permission Status = $permission.');
  }

  void checkLocationSettings() async {
    await Geolocator.openLocationSettings();
  }

  Future<void> locationServicesStatus() async {
    bool isLocationServiceEnabled = await Geolocator.isLocationServiceEnabled();
    print(
        'Currently, the emulator\'s Location Services Status = $isLocationServiceEnabled.');
  }

  @override
  State<ScreenView> createState() => _ScreenViewState();
}

class _ScreenViewState extends State<ScreenView> {
  @override
  void initState() {
    ScreenView().locationHereIs();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

If you can help me understand what's going wrong so that I can fix things and get results, that'd be perfect. In your response, please assume that I have used complileSdkVersion 32, miniSdkVersion 23 and targetSdkVersion 32.

With thanks : )


Solution

  • The problem here is related to the specified desired accuracy. In case of mathems32 it is set to LocationAccuracy.low. On Android this translates to the PRIORITY_LOW_POWER setting which means Android will only use the cellular network to triangulate the device's location. This will work on real devices (as long as they have a cellular connection) but doesn't on the emulator unfortunately.

    There is no direct relationship to the getLastKnownPosition. The getLastKnowPosition maps to the getLastLocation on the Android FusedLocationProviderClient API which according to the documentation:

    Returns the most recent historical location currently available. Will return null if no historical location is available. The historical location may be of an arbitrary age, so clients should check how old the location is to see if it suits their purposes.

    The getCurrentPosition will always try to return the actual location (or null if the actual location cannot be determined), while the getLastKnownPosition will (very quickly) return the last known position which is cached by the device (or null if no location is cached). The later might be very old and not representative at all, therefore you should always check its timestamp and determine if the position is recent enough to use for your needs.

    If you'd like to test the location on the emulator set the desired accuracy to LocationAccuracy.high or higher. Below is a table showing the translation between the accuracies used by the Geolocator and how they map to the Android priorities:

    Geolocator Android
    LocationAccuracy.lowest PRIORITY_PASSIVE
    LocationAccuracy.low PRIORITY_LOW_POWER
    LocationAccuracy.medium PRIORITY_BALANCED_POWER_ACCURACY
    LocationAccuracy.high PRIORITY_HIGH_ACCURACY
    LocationAccuracy.best PRIORITY_HIGH_ACCURACY
    LocationAccuracy.bestForNavigation PRIORITY_HIGH_ACCURACY

    UPDATE (see also issue 1082 on GitHub):

    The conclusion that the Android emulator doesn't support the getCurrentPosition method is incorrect. I have run the code you attached, chancing the desiredAccuracy too LocationAccuracy.high, on the Android emulator (Android Pixel 5 - API 30) and it correctly reports the configured location. Here is the output:

    W/GooglePlayServicesUtil( 2212): Google Play Store is missing.
    I/flutter ( 2212): Currently, the emulator's Location Services Status = true.
    I/flutter ( 2212): Current Location Permission Status = LocationPermission.whileInUse.
    W/GooglePlayServicesUtil( 2212): Google Play Store is missing.
    I/flutter ( 2212): Latitude: 52.56731333333333, Longitude: 5.641091666666666
    

    Here is how I used it:

    1. Started the Android emulator.
    2. Once started I configured a position using the emulator settings (see attached image, and make sure to hit the "Set Location" button).
    3. Now start running the example App.
    4. Check the console for debug information.

    Configure location on Android Emulator

    I did notice however that sometimes the Android emulator doesn't want to play nice and I'd have to set the location a few times before the emulator picks it up. I have experienced this only with a fresh emulator, once I have the initial location it keeps on working just fine. What also might help is to setup a route in the Android emulator settings and repeat the playback.

    Also make sure the android/app/src/main/AndroidManifest.xml contains the correct permissions (ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION) as shown in this sample snippet:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.issue_1082">
    
       <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
       <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     
       <application
            android:label="issue_1082"
            android:name="${applicationName}"
            android:icon="@mipmap/ic_launcher">
            <activity
                android:name=".MainActivity"
                android:exported="true"
                android:launchMode="singleTop"
                android:theme="@style/LaunchTheme"
                android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
                android:hardwareAccelerated="true"
                android:windowSoftInputMode="adjustResize">
                <!-- Specifies an Android theme to apply to this Activity as soon as
                     the Android process has started. This theme is visible to the user
                     while the Flutter UI initializes. After that, this theme continues
                     to determine the Window background behind the Flutter UI. -->
                <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>
    

    P.S. for what it is worth, I am the author and maintainer of the Geolocator package.