まえおき
2019年10月、alwaysDRINK が突如として福岡にサービスインされたのですが、
当時のサービスは毎日使うにはしんどいUIだったので、自分が毎日使ってもしんどくない程度のAndroidアプリを勝手に作りました。
これは適当にAndroidのネイティブで実装していて、
- ViewModelで保持している「選択されたお店」を変更時に地図の中心をanimateCameraで動かす(データバインディング)
- 縮尺によって、マーカーをまとめる(android-maps-utils の Marker Clustering)
のように、シンプルな技術要素で成り立っていました。
「この程度なら Flutterでサクッと書けるんじゃね?」と思ってやってみたら見事に撃沈したというお話をします。
GoogleMapの表示/マーカーの表示:「余裕!」
2019年12月現在、Google Mapを使うなら、公式のプラグイン https://pub.dev/packages/google_maps_flutter 一択です。
単純に地図を表示する+マーカーを表示する程度であれば、
- Marker(マーカーの位置・色など)
- InfoWindow(マーカーをタップしたときに表示するウインドウ)
あたりを利用して、
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);
},
),
こんなかんじのコードを書けば、
このくらいの動きのものはサクッと作れます。
"選択されたお店"を変更時に地図の中心をanimateCameraで動かす:「・・・余裕!」
これは以前Qiita記事で書いたので、読んでみてください。
FlutterでState管理されている緯度経度に向かってGoogleMapController#animateCamera する
わかってしまえば簡単なんだけど、ぐぐっても期待通りの動作をしてくれる方法があまり出てきません。
シンプルに、GoogleMapを含むウィジェットの build() で GoogleMap#animateCamera
すればOKです。
initCameraPosition や onMapCreated で選択されたお店の位置を指定しても動きません。理由は↑の記事で書いたとおり、これらのプロパティはOSネイティブのMapViewが作られた最初の1回だけ実行されるもので、FlutterのState更新契機では呼ばれないからです。
@override
Widget build(BuildContext context) {
_googleMapController.future.then((GoogleMapController googleMap) {
if (selectedShop != null) {
googleMap.animateCamera(CameraUpdate.newLatLng(selectedShop.location));
}
});
"選択されたお店" のマーカーの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
なんですが、「どーーーーーしても使いたい」っていう私のような人は世の中に他にもいたようで、
[WIP] Add methods to programmatically show/hide marker's infowindow - #2181
ズバリなPull Requestが出ていました。
このPull Requestを取り込むと、とりあえずshowInfoWindow/hideIndoWindowなどができるようになるので、以下のようにhideInfoWindowしてshowInfoWindowするコードを書けば
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);
}
}
}
こんなかんじになります。
(早くPull Request #2181マージされないかなぁ〜...)
(2020.03.02 追記: マージされました!)
縮尺によって、マーカーをまとめる:「・・・できない!」
マーカークラスタリングの機能も、2019年12月現在まだ未実装されていません。
これもissueがあります。[Google Maps] Support marker clustering - #26863
Pull Requestも一応あるにはありますが、iOSのコードがパット見で存在していないので、おそらくこのまま使うことはできないでしょう。
[google_maps_flutter] Support marker clustering - #1480
(蛇足) Flutter for Web, Flutter for DesktopでGoogle Map:「・・できない!」
「スマホで使えるものがデスクトップアプリでも使えたらステキ♪」とか思っても、Google MapはWebやDesktopには未対応です。
- GitHubのissue:[web] How to integrate Map in flutter web - #38327
- StackOverflow:Which map solution / package to use with Flutter Web?
まとめ
FlutterでのGoogle Map利用は、趣味のスマホアプリであれば(少し妥協と努力をすれば)実用できるレベルになってきました。
とはいえ、マーカークラスタリングなど、本格的にGoogle Mapを利用したいケースではまだまだ足りません。
Flutterの今後に期待です・・・!
おまけ: Dart Advent Calendar 2019もよろしく!
大人気のFlutter Advent Calendar #1 #2 の裏で、かなり過疎っている Dart Advent Calendar 2019 があります
2019/12/01現在、まだまだ参加枠あるので、Dartのこと好きな人も嫌いな人も、日頃の思いやノウハウをぜひ発信してください