Flutter で Google Map に現在位置を表示する

思ったよりも情報が少なく、少し古い情報が多いのでまとめます。

## はじめにお断り

この記事では Google Map の導入部分には触れていません。
[google_maps_flutter](https://pub.dev/packages/google_maps_flutter) の Sample Usage のコードをベースに現在位置を表示させる部分を書いています。
※ Google Map の導入記事はたくさんよいものがあるので、ググってみてね :yum:

## 前提 

```yaml
environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  # https://pub.dev/packages/google_maps_flutter
  # `Flutter upgrade` しないとエラーになることがある
  google_maps_flutter: ^0.5.28+1

  # https://pub.dev/packages/location
  location: ^3.0.2
```



## 実装

### インストール

[location](https://pub.dev/packages/location) を使って現在地を表示するので、まずはパッケージをインストールしましょう。

```yaml:pubspec.yaml
dependencies:
  location: ^3.0.2
```

`pub get` も忘れずに。

```shell
$ flutter pub get
```



#### iOS の場合

パーミッションの設定が必要です。キーと説明文を追加しましょう。
（ Android メインでやってるので、違ってたら教えて下さい）

```xml
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your location is required for this app</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Your location is required for this app</string>
```



### 使用方法

流れとしては次のようになります。

- `initState` で現在位置の取得と監視をスタート
- `dispose` で監視を停止
- `build` で現在位置の緯度と経度を利用して表示位置を決定（現在位置取得まではプログレス）

```dart
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:location/location.dart';

class MapPage extends StatefulWidget {
  @override
  _MapPageState createState() => _MapPageState();
}

class _MapPageState extends State<MapPage> {
  Completer<GoogleMapController> _controller = Completer();
  Location _locationService = Location();

  // 現在位置
  LocationData? _yourLocation;

  // 現在位置の監視状況
  StreamSubscription? _locationChangedListen;

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

    // 現在位置の取得
    _getLocation();

    // 現在位置の変化を監視
    _locationChangedListen =
        _locationService.onLocationChanged.listen((LocationData result) async {
          setState(() {
            _yourLocation = result;
          });
        });
  }

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

    // 監視を終了
    _locationChangedListen?.cancel();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _makeGoogleMap(),
    );
  }

  Widget _makeGoogleMap() {
    if (_yourLocation == null) {
      // 現在位置が取れるまではローディング中
      return Center(
        child: CircularProgressIndicator(),
      );
    } else {
      // Google Map ウィジェットを返す
      return GoogleMap(

        // 初期表示される位置情報を現在位置から設定
        initialCameraPosition: CameraPosition(
          target: LatLng(_yourLocation.latitude, _yourLocation.longitude),
          zoom: 18.0,
        ),
        onMapCreated: (GoogleMapController controller) {
          _controller.complete(controller);
        },

        // 現在位置にアイコン（青い円形のやつ）を置く
        myLocationEnabled: true,
      );
    }
  }

  void _getLocation() async {
    _yourLocation = await _locationService.getLocation();
  }
}
```

![現在位置の表示](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135956/1b956d43-6da0-070f-f8c9-7f92dc4739e7.png)


表示されました！



#### 🔰 現在位置が外国な件

エミュレーターはそのまま利用していると現在位置が外国の場合があります。
その場合は、次の手順で OK です！

- エミュレーターの右側に表示されているナビウインドウから `・・・` を選択
- 新たに表示されるウィンドウから `Location` を選択
- マップが表示されるので検索なりなんなりして現在位置にしたい場所をクリック
- `SAVE POINT` をクリックして位置情報を保存
- `Saved points` に表示される位置情報の一覧から現在位置にしたいものを選択して `SET LOCATION` をクリック

![エミュレーターの現在位置設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135956/61666e78-a85b-b28b-a560-faac7f0237c9.png)



## まとめ

できあがったものを見ると簡単なのですが、実装しているときは色々とつまづきました。

リスナーの解放なんかは現在位置でググってみても少なかったので気をつけないといけませんね。
（画面切り替えてもリッスンしててビビったｗ）



マップを利用する時に現在位置の取得は必ずといっていいほど行うはずなので、この記事がどなたかの役に立てれば幸いです :yum: