A quick guide: obtaining current location on Google Maps using Riverpod in Flutter
Tags: flutter, riverpod, beginners, googlecloud
Published: 2021-12-13 06:55:14

## Goal: Get the current location and set a marker


![Google Maps on Flutter](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kbz971g8zdog7rezu7j3.jpg)

Hi everyone! This article will look at how we get the current location of the user and set a marker as being a specific location on Flutter. This app has several features as shown below.

1. Show Google Map by google_maps_flutter
2. Obtain the current location
3. Set the marker and shift for it after pushing the bottom right button

## Get API key

At first, let's obtain your API key for Google Maps.

## Fix AndroidManifest.xml

- android/app/src/main/AndroidManifest.xml

```bash
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.googlemap.testgooglemap">
	 // Add this
	 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
   <application
        android:label="testgooglemap"
        android:icon="@mipmap/ic_launcher">
				// Add this
				<meta-data
          android:name="com.google.android.geo.API_KEY"
          android:value="<YOUR API KEY>" />
		...
    </application>
</manifest>
```

## Modify AppDelegate.swift

- ios/Runner/AppDelegate.swift

```swift
import UIKit
import Flutter
import GoogleMaps

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GMSServices.provideAPIKey("<YOUR API KEY>")
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}
```

## Add a key in Info.plist

To show a dialog to allow the user gives the current location to app, put a pair of key and description into `Info.plist` .

- ios/Runner/Info.plist

```swift
<dict>
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>Your location is required for this app</string>
		...
</dict>
```

## Upgrade minSdkVersion in build.gradle

You have to make minSdkVersion going up to 20 from 16 for working out well this app on google_maps_flutter package. In my case, I also had to fix compileSdkVersion to 31.

[google_maps_flutter | Flutter Package](https://pub.dev/packages/google_maps_flutter)

```dart
android {
		compileSdkVersion 31 // up to 31 from 30
		...
    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId "com.googlemap.testgooglemap"
        minSdkVersion 20 // up to 20 from 16
        targetSdkVersion 30
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }
```

## pubspec.yaml

```yaml
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  google_maps_flutter: ^2.1.1
  geolocator: ^8.0.0
  riverpod: ^1.0.0
  flutter_hooks: ^0.18.0
  hooks_riverpod: ^1.0.0
  http: ^0.13.4
  freezed_annotation: ^1.0.0
  json_annotation: ^4.3.0
  flutter_secure_storage: ^5.0.1
  geocoding: ^2.0.1

dev_dependencies:
  flutter_test:
    sdk: flutter
  freezed: ^1.0.0
  build_runner: ^2.1.5
  json_serializable: ^6.0.1
```

## main.dart

Let's modify your code in main.dart. Some errors would be shown on your text editor when pasting it, but don't have to be careful about that for now. We'll fix it up by adding other code.

- lib/main.dart

```dart
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '/models/controllers/location/location_controller.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'dart:async';

void main() {
  runApp(ProviderScope(
    child: MyApp(),
  ));
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const Homepage(),
    );
  }
}

class Homepage extends HookConsumerWidget {
  const Homepage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final locationState = ref.watch(locationNotifierProvider);
    final locationNotifier = ref.watch(locationNotifierProvider.notifier);

    useEffect(() {
      Future.microtask(() async {
        ref.watch(locationNotifierProvider.notifier).getCurrentLocation();
      });
      return;
    }, const []);

    return Scaffold(
        body: locationState.isBusy
            ? const Center(child: CircularProgressIndicator())
            : GoogleMap(
                mapType: MapType.normal,
                myLocationButtonEnabled: true,
                myLocationEnabled: true,
                zoomControlsEnabled: false,
                initialCameraPosition: CameraPosition(
                  target: locationState.currentLocation,
                  zoom: 14.4746,
                ),
                markers: locationState.markers,
                onMapCreated: locationNotifier.onMapCreated,
              ),
        floatingActionButton: FloatingActionButton(
            onPressed: () async {
              locationNotifier.getNewLocation();
            },
            child: const Icon(Icons.location_searching)));
  }
}
```

Let's take a look at that. We can handle the state on Flutter by Riverpod. In this sample app, `locationState` for reading state and `locationNotifier` for changing state are defined.

```dart
@override
Widget build(BuildContext context, WidgetRef ref) {
  final locationState = ref.watch(locationNotifierProvider);
  final locationNotifier = ref.watch(locationNotifierProvider.notifier);
```

`useEffect` is useful to initialize something on every build. In here's sample, we call the `getCurrentLocatoin()` function defined in location_controller.dart for state management.

```dart
useEffect(() {
  Future.microtask(() async {
    locationNotifier.getCurrentLocation();
  });
  return;
}, const []);
```

When user presses the bottom right button, `FloatingActionButton()` call `getNewLocation()` function that shifts the position of camera and sets a pin.

```dart
floatingActionButton: FloatingActionButton(
    onPressed: () async {
      locationNotifier.getNewLocation();
    },
    child: const Icon(Icons.location_searching)));
```

## LocationController

As you can see, LocationController extends StateNotifier class.

- lib/models/controllers/location/location_controller.dart

```dart
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:riverpod/riverpod.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter/material.dart';
import '/models/repositories/location/location_repository.dart';
import '/models/controllers/location/location_state.dart';
import 'dart:async';

final locationNotifierProvider =
    StateNotifierProvider<LocationController, LocationState>(
  (ref) => LocationController(),
);

class LocationController extends StateNotifier<LocationState> {
  LocationController() : super(const LocationState());

  final repository = LocationRepository();
  final Completer<GoogleMapController> _mapController = Completer();

  void onMapCreated(GoogleMapController controller) {
    _mapController.complete(controller);
  }

  Future<void> getCurrentLocation() async {
    state = state.copyWith(isBusy: true);
    try {
      final data = await repository.getCurrentPosition();
      state = state.copyWith(
          isBusy: false,
          currentLocation: LatLng(data.latitude, data.longitude));
    } on Exception catch (e, s) {
      debugPrint('login error: $e - stack: $s');
      state = state.copyWith(isBusy: false, errorMessage: e.toString());
    }
  }

  Future<void> getNewLocation() async {
    await _setNewLocation();
    await _setMaker();
  }

  Future<void> _setNewLocation() async {
    state = state.copyWith(newLocation: const LatLng(35.658034, 139.701636));
  }

  Future<void> _setMaker() async {
    // Set markers
    final Set<Marker> _markers = {};
    _markers.add(Marker(
        markerId: MarkerId(state.newLocation.toString()),
        position: state.newLocation,
        infoWindow:
            const InfoWindow(title: 'Remember Here', snippet: 'good place'),
        icon: BitmapDescriptor.defaultMarker));
    state = state.copyWith(markers: _markers);

    // Shift camera position
    CameraPosition _kLake =
        CameraPosition(target: state.newLocation, zoom: 14.4746);
    final GoogleMapController controller = await _mapController.future;
    controller.animateCamera(CameraUpdate.newCameraPosition(_kLake));
  }
}
```

LocationController defines three functions to call from main.dart.

```dart
void onMapCreated(GoogleMapController controller) {}
Future<void> getCurrentLocation() async {}
Future<void> getNewLocation() async {}
```

## Freezed for controller

This sample app uses Freezed package, making the data immutable easily. This is used for LocationController.

- lib/models/controllers/location/location_state.dart

```dart
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

part 'location_state.freezed.dart';

@freezed
class LocationState with _$LocationState {
  const factory LocationState({
    @Default(false) bool isBusy,
    @Default(LatLng(35.658034, 139.701636)) LatLng currentLocation,
    @Default(LatLng(35.658034, 139.701636)) LatLng newLocation,
    @Default({}) Set<Marker> markers,
    String? errorMessage,
  }) = _LocationState;
}
```

Run the following command with build_runner.

```bash
$ flutter pub run build_runner build --delete-conflicting-outputs
```

## Repository class for the error handling

We'll also set up `getCurrentPosition()` in LocationRepository class, the function to handle errors on the permission regarding the location.

- lib/models/repositories/location/location_repository.dart

```dart
import 'package:geolocator/geolocator.dart';

class LocationRepository {
  Future<Position> getCurrentPosition() async {
    bool serviceEnabled;
    LocationPermission permission;

    serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      return Future.error('Location services are disabled.');
    }

    permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        return Future.error('Location permissions are denied');
      }
    }

    if (permission == LocationPermission.deniedForever) {
      return Future.error(
          'Location permissions are permanently denied, we cannot request permissions.');
    }

    return await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high);
  }
}
```

## References

- [Flutterのgoogle_maps_flutterとgeolocatorで位置情報を取得して現在地をGoogle Mapsに表示する | ZUMA Lab](https://zuma-lab.com/posts/flutter-google-maps-with-location)
- [Google Flutter コラム－ 第2 回　GoogleMapとmapboxの地図表示を比較してみた - 日本システム開発株式会社(NSK)](https://www.nskint.co.jp/2021/06/30/google_flutter_column_2/)
- [Flutter get current location address from latitude & Longitude - geolocator](https://protocoderspoint.com/flutter-get-current-location-address-from-latitude-longitude/)
- [[Flutter]GoogleMapの任意の場所にマーカーを置く｜HI｜note](https://note.com/_hi/n/n18da3b870f4b)