Flutter iOS - cannot get current location for initialCameraPosition before loading Google Maps widget

1,423

The problem you are rendering your map before you have your initial position which I believe you realize this a FutureBuilder would work but might be over kill. All you really need is a Boolean.

GoogleMapController mapController;
  LocationData currentLocation;
  Location location;
  bool _serviceEnabled;
  PermissionStatus _permissionGranted;
  CameraPosition _center =
      CameraPosition(target: SOURCE_LOCATION, zoom: CAMERA_ZOOM);
  bool isLoading = false;

  @override
  void initState() {
    super.initState();
    location = new Location();

    _getLocation();
  }

  _getLocation() async {
    setState(() {
      isLoading = true;
    });
    initialize();
    currentLocation = await location.getLocation();
    if (currentLocation == null) {
      return;
    }
    _center = CameraPosition(
        target: LatLng(currentLocation.latitude, currentLocation.longitude),
        zoom: CAMERA_ZOOM);

    setState(() {
      isLoading = false;
    });

    print("CurrentLocation: $currentLocation");
  }

  Future<void> initialize() async {
    _serviceEnabled = await location.serviceEnabled();
    if (!_serviceEnabled) {
      _serviceEnabled = await location.requestService();
      if (!_serviceEnabled) {
        return;
      }
    }

    _permissionGranted = await location.hasPermission();
    if (_permissionGranted == PermissionStatus.DENIED) {
      _permissionGranted = await location.requestPermission();
      if (_permissionGranted != PermissionStatus.GRANTED) {
        return;
      }
    }
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.orangeAccent,
        title: Text('Map'),
        centerTitle: true,
      ),
      body: isLoading
          ? CircularProgressIndicator()
          : Container(
              child: GoogleMap(
                onMapCreated: _onMapCreated,
                initialCameraPosition: _center,
                myLocationEnabled: true,
              ),
            ),
    );
  }
Share:
1,423
Vuco
Author by

Vuco

Updated on December 19, 2022

Comments

  • Vuco
    Vuco over 1 year

    I'm trying to implement a simple map functionality in my Flutter iOS aplication. I followed guides starting from Flutter location package readme, through Medium "Implement Real-time Location Updates on Google Maps in your Flutter Apps" article, ending on many stack questions regarding similar issues.

    The problem is that I cannot get current location before loading the maps, therefore getting its LatLng in initialCameraPosition results in NPE. What I can do is loading the maps with hardcoded initialCameraPosition, current location loads correctly then and I can move camera to it either by dragging or by clicking the floating location button in the right-bottom corner.

    Here is my code sample:

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:google_maps_flutter/google_maps_flutter.dart';
    import 'package:location/location.dart';
    
    const double CAMERA_ZOOM = 16;
    const LatLng SOURCE_LOCATION = LatLng(37.3317, -122.0325086);
    
    class MapPage extends StatefulWidget {
      @override
      _MapPageState createState() => _MapPageState();
    }
    
    class _MapPageState extends State<MapPage> {
      GoogleMapController mapController;
      LocationData currentLocation;
      Location location;
      bool _serviceEnabled;
      PermissionStatus _permissionGranted;
    
      @override
      void initState() {
        super.initState();
        location = new Location();
    
        location.onLocationChanged.listen((event) {
          currentLocation = event;
        });
    
        setInitialLocation();
      }
    
      void setInitialLocation() async {
        _serviceEnabled = await location.serviceEnabled();
        if (!_serviceEnabled) {
          _serviceEnabled = await location.requestService();
          if (!_serviceEnabled) {
            return;
          }
        }
    
        _permissionGranted = await location.hasPermission();
        if (_permissionGranted == PermissionStatus.denied) {
          _permissionGranted = await location.requestPermission();
          if (_permissionGranted != PermissionStatus.granted) {
            return;
          }
        }
    
        currentLocation =  await location.getLocation();
      }
    
      void _onMapCreated(GoogleMapController controller) {
        mapController = controller;
      }
    
    
      CameraPosition initialCameraPosition() {
        LatLng target;
        if (currentLocation != null)
          target = LatLng(currentLocation.latitude, currentLocation.longitude);
        else
          target = SOURCE_LOCATION;
        return CameraPosition(target: target, zoom: CAMERA_ZOOM);
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            backgroundColor: Colors.orangeAccent,
            title: Text('Map'),
            centerTitle: true,
          ),
          body:
          GoogleMap(
          onMapCreated: _onMapCreated,
          initialCameraPosition: initialCameraPosition(),
          myLocationEnabled: true,
          ),
        );
      }
    }
    

    I understand that getting current location is an asynchronous process, so I also tried to use FutureBuilder in the body section:

          FutureBuilder<LocationData>(
            future: currentLocation,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                LocationData currentLocation = snapshot.data;
                return GoogleMap(
                  onMapCreated: _onMapCreated,
                  initialCameraPosition: CameraPosition(
                      target: LatLng(
                          currentLocation.latitude, currentLocation.longitude)),
                  myLocationEnabled: true,
                );
              } else if (snapshot.hasError) {
                return (Text("${snapshot.error}"));
              }
              return Center(
                child: CircularProgressIndicator(
                  backgroundColor: Colors.orangeAccent,
                ),
              );
            },
          ),
    

    which worked like a charm when I had to retrieve data from an http GET request, but this time it results only in an endless CircularProgressIndicator.

    I tried many other solutions from stack questions, including returning empty Container when currentLocation is null, creating conditions for FutureBuilder snapshot.connectionState, again with no success.

    Funny thing is that when I dug deeper into the Location.getCurrentLocation() method I found:

    /// Gets the current location of the user.
      ///
      /// Throws an error if the app has no permission to access location.
      /// Returns a [LocationData] object.
      Future<LocationData> getLocation() async {
        return LocationPlatform.instance.getLocation();
      }
    

    which then goes to:

    /// Gets the current location of the user.
      ///
      /// Throws an error if the app has no permission to access location.
      /// Returns a [LocationData] object.
      Future<LocationData> getLocation() {
        throw UnimplementedError();
      }
    

    which puts me in doubt if this plugin even works at all.

    Here is my flutter doctor -v result:

    [✓] Flutter (Channel master, v1.18.0-6.0.pre.82, on Mac OS X 10.15.4 19E287, locale pl-PL)
        • Flutter version 1.18.0-6.0.pre.82 at /Users/Vuco/flutter
        • Framework revision f35b673f2b (26 hours ago), 2020-04-19 02:45:01 +0530
        • Engine revision a5e0b2f2f2
        • Dart version 2.9.0 (build 2.9.0-1.0.dev 5b19445d9c)
    
    
    [✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
        • Android SDK at /Users/Vuco/Library/Android/sdk
        • Platform android-29, build-tools 29.0.3
        • Java binary at: /Users/Vuco/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/192.6308749/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
        • Java version OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 11.4.1)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • Xcode 11.4.1, Build version 11E503a
        • CocoaPods version 1.9.1
    
    [✓] Android Studio (version 3.6)
        • Android Studio at /Users/Vuco/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/192.6308749/Android Studio.app/Contents
        • Flutter plugin version 45.0.1
        • Dart plugin version 192.7761
        • Java version OpenJDK Runtime Environment (build 1.8.0_212-release-1586-b4-5784211)
    
    [!] IntelliJ IDEA Ultimate Edition (version 2020.1)
        • IntelliJ at /Users/Vuco/Applications/JetBrains Toolbox/IntelliJ IDEA Ultimate.app
        ✗ Flutter plugin not installed; this adds Flutter specific functionality.
        ✗ Dart plugin not installed; this adds Dart specific functionality.
        • For information about installing plugins, see
          https://flutter.dev/intellij-setup/#installing-the-plugins
    
    [✓] VS Code (version 1.44.0)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.9.1
    
    [✓] Connected device (1 available)
        • iPhone 8 • F60856B8-2C6E-401D-A0F9-06FFC7E09876 • ios • com.apple.CoreSimulator.SimRuntime.iOS-13-4 (simulator)
    
    ! Doctor found issues in 1 category.
    

    and the dependencies I use:

    google_maps_flutter: ^0.5.25+3
    location: ^3.0.2
    
  • Vuco
    Vuco about 4 years
    Going with what You suggested solved my issue, thanks a lot!
  • wcyankees424
    wcyankees424 about 4 years
    No problem glad to help
  • Rageh Azzazy
    Rageh Azzazy about 3 years
    getLocation() function just stays forever and never returns i followed all steps above,, permissions are granted, and it just does not continue and does not return null what's going on here ?
  • Vuco
    Vuco about 3 years
    @RagehElAzzazy I haven't been working in Flutter for some time, but is there a chance you forgot to generate and add Google Maps API key to your project? Also version of google_maps_flutter I've been using back then required adding permission messages to Info.plist (for iOS), but that could change in current version as I don't see that instructions anywhere now. Have you followed Medium article that I linked in the post?
  • Alauddin Afif Cassandra
    Alauddin Afif Cassandra almost 3 years
    same with @RagehElAzzazy. still not work on iOS.