FlutterでGoogle Mapを使ってできること、できないこと（2019年版）

## まえおき

2019年10月、[alwaysDRINK](https://always.fan/lp/drink/) が突如として福岡にサービスインされたのですが、
当時のサービスは毎日使うにはしんどいUIだったので、自分が毎日使ってもしんどくない程度のAndroidアプリを勝手に作りました。

![image](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F74571%2Fc9483d82-08f4-5d63-0e22-79b984bda514.gif?ixlib=rb-1.2.2&auto=compress%2Cformat&gif-q=60&w=1400&fit=max&s=eca28d19b0b0fbeab67a6d82e19704c8)

これは適当にAndroidのネイティブで実装していて、

* ViewModelで保持している「選択されたお店」を変更時に地図の中心をanimateCameraで動かす（データバインディング）
* 縮尺によって、マーカーをまとめる（[android-maps-utils](https://github.com/googlemaps/android-maps-utils) の Marker Clustering）
    * https://qiita.com/YusukeIwaki/items/d4979fc698d7f0998e30

のように、シンプルな技術要素で成り立っていました。

「この程度なら Flutterでサクッと書けるんじゃね？」と思ってやってみたら**見事に撃沈**したというお話をします。

## GoogleMapの表示/マーカーの表示：「余裕！」

2019年12月現在、Google Mapを使うなら、公式のプラグイン https://pub.dev/packages/google_maps_flutter 一択です。

単純に地図を表示する＋マーカーを表示する程度であれば、

* Marker（マーカーの位置・色など）
* InfoWindow（マーカーをタップしたときに表示するウインドウ）

あたりを利用して、

```dart
  static const CameraPosition _initCameraPosition = CameraPosition(target: LatLng(34.6870728, 135.0490244), zoom: 5.0);
  final Completer<GoogleMapController> _googleMapController = Completer<GoogleMapController>();

      /// 中略

          GoogleMap(
            initialCameraPosition: _initCameraPosition,
            markers: shops.map((Shop shop) {
              return Marker(
                  markerId: MarkerId(shop.uuid),
                  position: shop.location,
                  icon: shop.uuid == selectedShop?.uuid
                      ? BitmapDescriptor.defaultMarker
                      : BitmapDescriptor.defaultMarkerWithHue(180),
                  infoWindow: InfoWindow(title: shop.markerTitle()),
                  onTap: () {
                    _updateSelectedShop(shop);
                  });
            }).toSet(),
            myLocationButtonEnabled: false,
            onMapCreated: (GoogleMapController googleMap) {
              _googleMapController.complete(googleMap);
            },
          ),
```

こんなかんじのコードを書けば、

![qiita.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/6a6384a0-9128-916d-1b1c-444090af1387.gif)

このくらいの動きのものはサクッと作れます。

## "選択されたお店"を変更時に地図の中心をanimateCameraで動かす：「・・・余裕！」

これは以前Qiita記事で書いたので、読んでみてください。
[FlutterでState管理されている緯度経度に向かってGoogleMapController#animateCamera する](https://qiita.com/YusukeIwaki/items/28d81ec14b0e5ab9e148)

わかってしまえば簡単なんだけど、ぐぐっても期待通りの動作をしてくれる方法があまり出てきません。
シンプルに、GoogleMapを含むウィジェットの build() で `GoogleMap#animateCamera` すればOKです。

initCameraPosition や onMapCreated で選択されたお店の位置を指定しても動きません。理由は↑の記事で書いたとおり、これらのプロパティはOSネイティブのMapViewが作られた最初の１回だけ実行されるもので、FlutterのState更新契機では呼ばれないからです。

```dart
  @override
  Widget build(BuildContext context) {
    _googleMapController.future.then((GoogleMapController googleMap) {
      if (selectedShop != null) {
        googleMap.animateCamera(CameraUpdate.newLatLng(selectedShop.location));
      }
    });
```

![qiita.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/0f09c148-cc7d-8086-1e53-8ebe871196d0.gif)


## "選択されたお店" のマーカーのInfoWindowを自動で開く：「・・・・・一応できる」

Androidネイティブであれば `Marker` クラスに showInfoWindow, hideInfoWindow, isInfoWindowShown などのメソッドが生えているため、選択されたお店のInfoWindowを自動で開くように実装することは簡単です。

~~しかし、FlutterのGoogle Mapでは、MarkerにInfoWindowをshow/hideするメソッドはありません。実は、**2019年12月現在まだshowInfoWindow/hideInfoWindow の実装がされていない**のです。~~
(2020.03.02 追記: **できるようになりました！**)

issueもあります。[Programmatically show a Marker InfoWindow on Google Map - #33481](https://github.com/flutter/flutter/issues/33481)

なんですが、「どーーーーーしても使いたい」っていう私のような人は世の中に他にもいたようで、
[[WIP] Add methods to programmatically show/hide marker's infowindow - #2181](https://github.com/flutter/plugins/pull/2181)
ズバリなPull Requestが出ていました。

このPull Requestを取り込むと、とりあえずshowInfoWindow/hideIndoWindowなどができるようになるので、以下のようにhideInfoWindowしてshowInfoWindowするコードを書けば

```dart
  void _updateSelectedShop(Shop newShop) {
    _hideInfoWindowForSelectedShop();
    setState(() {
      selectedShop = newShop;
    });
    _showInfoWindowForSelectedShop();
  }

  Future<void> _showInfoWindowForSelectedShop() async {
    if (selectedShop != null && _googleMapController.isCompleted) {
      final GoogleMapController googleMap = await _googleMapController.future;

      final MarkerId selectedShopMarker = MarkerId(selectedShop.uuid);
      final bool isSelectedShopMarkerShown = await googleMap.isMarkerInfoWindowShown(selectedShopMarker);
      if (!isSelectedShopMarkerShown) {
        await googleMap.showMarkerInfoWindow(selectedShopMarker);
      }
    }
  }

  Future<void> _hideInfoWindowForSelectedShop() async {
    if (selectedShop != null && _googleMapController.isCompleted) {
      final GoogleMapController googleMap = await _googleMapController.future;

      final MarkerId selectedShopMarker = MarkerId(selectedShop.uuid);
      final bool isSelectedShopMarkerShown = await googleMap.isMarkerInfoWindowShown(selectedShopMarker);
      if (isSelectedShopMarkerShown) {
        await googleMap.hideMarkerInfoWindow(selectedShopMarker);
      }
    }
  }
```

こんなかんじになります。

![qiita.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/43bcfe4a-0031-aaea-b668-82b1a8fc9327.gif)

~~（早くPull Request [#2181](https://github.com/flutter/plugins/pull/2181)マージされないかなぁ〜...）~~

(2020.03.02 追記: **マージされました！**)

## 縮尺によって、マーカーをまとめる：「・・・できない！」

マーカークラスタリングの機能も、2019年12月現在まだ未実装されていません。
これもissueがあります。[[Google Maps] Support marker clustering - #26863](https://github.com/flutter/flutter/issues/26863)

Pull Requestも一応あるにはありますが、iOSのコードがパット見で存在していないので、おそらくこのまま使うことはできないでしょう。
[[google_maps_flutter] Support marker clustering - #1480](https://github.com/flutter/plugins/pull/1480)

## (蛇足) Flutter for Web, Flutter for DesktopでGoogle Map：「・・できない！」

「スマホで使えるものがデスクトップアプリでも使えたらステキ♪」とか思っても、Google MapはWebやDesktopには未対応です。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74571/7fa09ec2-99bd-2951-969f-399ef83bb278.png)


* GitHubのissue：[[web] How to integrate Map in flutter web - #38327](https://github.com/flutter/flutter/issues/38327)
* StackOverflow：[Which map solution / package to use with Flutter Web?](https://stackoverflow.com/questions/57155881/which-map-solution-package-to-use-with-flutter-web/57488553#57488553)

## まとめ

FlutterでのGoogle Map利用は、趣味のスマホアプリであれば（少し妥協と努力をすれば）実用できるレベルになってきました。

とはいえ、マーカークラスタリングなど、本格的にGoogle Mapを利用したいケースではまだまだ足りません。
Flutterの今後に期待です・・・！

### おまけ： Dart Advent Calendar 2019もよろしく！

大人気のFlutter Advent Calendar [#1](https://qiita.com/advent-calendar/2019/flutter) [#2](https://qiita.com/advent-calendar/2019/flutter-2) の裏で、かなり過疎っている :sweat_smile:  [Dart Advent Calendar 2019](https://qiita.com/advent-calendar/2019/dartlang) があります
2019/12/01現在、まだまだ参加枠あるので、Dartのこと好きな人も嫌いな人も、日頃の思いやノウハウをぜひ発信してください :thumbsup: