mathems32
mathems32

Reputation: 355

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 : )

Upvotes: 1

Views: 2722

Answers (5)

Srinivas Nahak
Srinivas Nahak

Reputation: 1866

In addition to @MauritsvanBeusekom 's answer people who are using Genymotion emulators should try to turn on the GPS and change the default values of latitude/longitude or even just rotate the navigation compass.

In my case although the GPS was on due to some technical glitch I was unable to retrieve the location with accuracy best and just by altering the latitude and longitude, I solved my issue. enter image description here

Upvotes: 0

Bombicode
Bombicode

Reputation: 31

geolocator 8.2.1 I do not know but why the API 28 Pie emulator works, but example the "Set Location" button - for me does not work on emulator API version 29 30 31 --> loop D/FlutterGeolocator(26734): Attaching Geolocator to activity D/FlutterGeolocator(26808): Attaching Geolocator to activity. I will work in the API emulator 28 Pie. maybe this will help someone.

Upvotes: 0

Maurits van Beusekom
Maurits van Beusekom

Reputation: 5989

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.

Upvotes: 3

mathems32
mathems32

Reputation: 355

I have found a solution and a reasonable explanation for why current-position data will not be retrieved by geolocator when operating with

  • android's emulator

:

The reason is because the android emulator is not a real device and lacks the level of functionality found within real android devices; android emulator cannot accept geolocator's current-position function.

Android emulator does accept geolocator's lastKnownLocation function though, and a location set within the emulator's location settings will be noticed and confirmed by geolocator via its lastKnownLocation function.

I trust that this finding helps everyone working with geolocator relying on the android emulator : )

Dart code example:

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 lastKnownPosition() async {
    await locationServicesStatus();
    await checkLocationPermissions();
    try {
      Position? position = await Geolocator.getLastKnownPosition();
      print(position);
    } catch (e) {
      print(e);
    }
  }

  void locationHereIs() async {
    await locationServicesStatus();
    await checkLocationPermissions();
    try {
      Position position = await Geolocator.getCurrentPosition(
              desiredAccuracy: LocationAccuracy.low)
          .timeout(Duration(seconds: 28));
      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().lastKnownPosition();
    super.initState();
  }

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

Console output with lastKnownLocation:

✓  Built build/app/outputs/flutter-apk/app-debug.apk.
Installing build/app/outputs/flutter-apk/app.apk...
Debug service listening on ws://127.0.0.1:52586/aA04hHZ7dIg=/ws
Syncing files to device sdk gphone64 x86 64...
I/flutter (11353): Currently, the emulator's Location Services Status = true.
D/CompatibilityChangeReporter(11353): Compat change id reported: 78294732; UID 10149; state: ENABLED
I/flutter (11353): Current Location Permission Status = LocationPermission.whileInUse.
I/flutter (11353): Latitude: 37.333333333333336, Longitude: -121.89206891099967

Conclusion: there has been a suspicion that the geolocator package is flawed without explanation. The above shows and explains that geolocator works fine with android emulator and should remain a favourite of android developers.

Upvotes: 0

Farzad
Farzad

Reputation: 212

Use this code for fix it :

Future<Position> _getGeoLocationPosition() async {
    bool serviceEnabled;
    LocationPermission permission;
    // Test if location services are enabled.
    serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      await Geolocator.openLocationSettings();
      return Future.error('Location services are disabled.');
    }
    permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        print('Location permissions are denied');
        return Future.error('Location permissions are denied');
      }
    }
    if (permission == LocationPermission.deniedForever) {
      // Permissions are denied forever, handle appropriately.
      print('Location permissions are permanently denied, we cannot request permissions.');
      return Future.error(
          'Location permissions are permanently denied, we cannot request permissions.');
    }
    // When we reach here, permissions are granted and we can
    // continue accessing the position of the device.
    return await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
  }

Result :

Position position = await _getGeoLocationPosition();

Upvotes: 0

Related Questions