🗺️

【Flutter】Riverpodを使用したGoogleMap上での現在位置表示の実装

2023/08/19に公開

はじめに

この記事ではRiverpodによる状態管理で、GoogleMapに自分の現在位置を表示する方法をまとめていきたいと思います。
ここではRiverpodに関連した部分だけに着目して話していくため、APIKeyの発行手順やGoogleMap導入の際に必要になってくるInfo.plistやAndroidManifest.xmlの設定は以下のサイトなどを参考に、事前に設定しておいて下さい。
https://qiita.com/my_programming/items/26b9ac6f0d2b3d1bd766#2-2地図をアプリで表示するためのコーディング
https://cyublog.com/articles/flutter-ja/jp-flutter-google-maps-flutter/

使用したパッケージ

  • google_maps_flutter: ^2.2.8
  • flutter_riverpod: ^2.3.6
  • geolocator: ^9.0.2

位置情報を返す方法

まずGeolocatorでは次のように、getCurrentPosition()を呼ぶことで位置情報を返すことができます。

return Geolocator.getCurrentPosition();

これを使用して、以下のlocation_provider.dartでは位置情報のパーミッションの許可を促した後に、許可されていたらGeolocatorで位置情報を返すコードの全文です。

location_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:geolocator/geolocator.dart';

final locationProvider = FutureProvider((ref) async {
  bool serviceEnabled;
  LocationPermission permission;

  // 位置情報サービスが有効かどうかのテスト
  serviceEnabled = await Geolocator.isLocationServiceEnabled();
  if (!serviceEnabled) {
    return null;
  }

  //位置情報サービスのパーミッションチェック
  permission = await Geolocator.checkPermission();
  if (permission == LocationPermission.denied) {
    //位置情報にアクセスするための許可を促す
    permission = await Geolocator.requestPermission();
    if (permission == LocationPermission.denied) {
      return null;
    }
  }

  //拒否されている場合ここでエラーになる
  if (permission == LocationPermission.deniedForever) {
    return null;
  }

  //許可された場合、位置情報を返す
  return Geolocator.getCurrentPosition();
});

マップ生成時にカメラを現在地に移動させる方法

Page側では、まず位置データを取得してその地点へカメラを移動させるメソッドを用意します。
ref を引数として受け取り、その ref を使ってプロバイダーにアクセスできます。

latitude又はlongitudeがnullだった場合returnしているのは、現在地の取得に失敗した際でもCameraUpdateをしてエラーになってしまわないようにするために必須です。

Future<void> moveCamera(WidgetRef ref) async {
      position = await ref.refresh(locationProvider.future);
      final mapController = await mapControllerCompleter.future;
      final latitude = position?.latitude;
      final longitude = position?.longitude;
      if (latitude == null || longitude == null) {
        return;
      }
      await mapController.moveCamera(
        CameraUpdate.newCameraPosition(
          CameraPosition(
            target: LatLng(latitude, longitude),
            zoom: 13,
          ),
        ),
      );
    }

そして、Widgetが初めてビルドされた後に、上記で作成した非同期関数 moveCamera を呼び出すようにしています。
これにより、Widgetが描画された後に一度だけ実行されることが保証されます。

WidgetsBinding.instance.addPostFrameCallback((_) async {
      await moveCamera(ref);
    });

最後に

ここまでのコードの全文です。

map_page.dart
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

import '../provider/location_provider.dart';

class MapPage extends ConsumerWidget {
  const MapPage({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final mapControllerCompleter = Completer<GoogleMapController>();
    Position? position;//null許容で宣言

    Future<void> onMapCreated(GoogleMapController controller) async {
      mapControllerCompleter.complete(controller);
    }

    // 位置データを取得し、カメラを移動させるメソッド
    Future<void> moveCamera(WidgetRef ref) async {
      position = await ref.refresh(locationProvider.future);
      final mapController = await mapControllerCompleter.future;
      final latitude = position?.latitude;
      final longitude = position?.longitude;
      if (latitude == null || longitude == null) {
        return;
      }
      await mapController.moveCamera(
        CameraUpdate.newCameraPosition(
          CameraPosition(
            target: LatLng(latitude, longitude),
            zoom: 13,
          ),
        ),
      );
    }

    // Widgetが初めてビルドされた後にこのメソッドを呼び出す
    WidgetsBinding.instance?.addPostFrameCallback((_) async {
      await moveCamera(ref);
    });

    return GoogleMap(
      onMapCreated: onMapCreated,
      initialCameraPosition: CameraPosition(
        target: LatLng(
	 //positionがnullなら初期座標を東京付近に設定する
          position?.latitude ?? 36,
          position?.longitude ?? 140,
        ),
        zoom: 16,
      ),
      myLocationEnabled: true,
    );
  }
}

参考にさせていただいたサイト等
https://zenn.dev/namioto/articles/3abb0ccf8d8fb6
https://www.flutter-note.fun/build-callback/
https://qiita.com/KKoichi51/items/a7926410a5e2b7e1162c
https://qiita.com/vsuine/items/480ebec85901ae4f1c93

最後までご覧頂き、ありがとうございました。
不足事項等あれば教えていただけると幸いです。

お世話になっているコミュニティ

https://flutteruniv.com/
https://galirage.com/

Discussion