📍

GoogleMapに位置情報を保存する

2023/09/05に公開

Overview

Flutterで Google Mapのパッケージを使用したときに、ピンを立てて位置情報を端末に保存する方法を調べていたときに、作ったロジックが何か役に立ちそうだなと思い記録を残す目的で記事にすることにしました。

FlutterでGoogleMapの設定をするときはこちらの記事を参考にしてください。codelabのチュートリアルを参考にしました。
https://zenn.dev/joo_hashi/articles/40fe8d9053d75c

summary

地図を使うのと位置情報を保存するには、以下のパッケージが必要なので追加する。
https://pub.dev/packages/google_maps_flutter
https://pub.dev/packages/shared_preferences
https://pub.dev/packages/geolocator

こちらが今回使用したソースコード
この設定通りにやれば地図にピンを立てて位置情報を保存できます。

import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// [表示している地図にピンを立てて、そのピンを保存する]
class MapSample extends StatefulWidget {
  const MapSample({super.key});

  
  State<MapSample> createState() => MapSampleState();
}

class MapSampleState extends State<MapSample> {
  // この_controllerは、GoogleMapのonMapCreatedで使用する.
  Completer<GoogleMapController> _controller = Completer();
  // この_setは、GoogleMapのmarkersで使用する.
  Set<Marker> _markers = {};

  
  void initState() {
    super.initState();
    // このページが呼ばれた時に、_loadMarkers()を実行する.
    _loadMarkers();
  }
  // このメソッドは、SharedPreferencesから保存されたマーカーを読み込む.
  _loadMarkers() async {
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    // SharedPreferencesから、保存されたマーカーを読み込む.
    if (prefs.getString('markers') != null) {
      // 保存されたマーカーをJSON形式からList<dynamic>に変換する.
      final List<dynamic> markersJson = jsonDecode(prefs.getString('markers')!);
      // List<dynamic>から、Set<Marker>に変換する.
      setState(() {
        // _markersに、Set<Marker>を代入する.
        _markers = markersJson.map((e) {
          // Set<Marker>に変換する.
          return Marker(
            markerId: MarkerId(e['id']),// MarkerIdは、String型でないといけない.
            position: LatLng(e['lat'], e['lng']),// LatLngは、double型でないといけない.
            icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueRed),// マーカーの色を赤色にする.
            onTap: () => _showRemoveDialog(e['id']),// マーカーをタップした時に、_showRemoveDialog()を実行する.
          );
        }).toSet();// Set<Marker>に変換する.
      });
    }
  }
  // このメソッドは、GoogleMapをタップした時に実行される.
  _showRemoveDialog(String markerId) {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        // ダイアログを表示する.
        return AlertDialog(
          title: const Text("地図のピンを削除する"),
          content: const Text("保存した位置情報を削除しても良いですか?"),
          actions: <Widget>[
            TextButton(
              child: const Text("いいえ"),
              onPressed: () {
                Navigator.of(context).pop();
              },
            ),
            TextButton(
              child: const Text("はい"),
              onPressed: () {
                setState(() {
                  // _markersから、markerIdと一致するマーカーを削除する.
                  _markers.removeWhere((m) => m.markerId.value == markerId);
                });
                // SharedPreferencesに、削除したマーカーを保存する.
                _saveMarkers();
                Navigator.of(context).pop();
              },
            ),
          ],
        );
      },
    );
  }
  // このメソッドは、SharedPreferencesにマーカーを保存する.
  Future<void> _saveMarkers() async {
    // SharedPreferencesを取得する.
    final SharedPreferences prefs = await SharedPreferences.getInstance();
    // _markersをJSON形式に変換する.
    final String markersList = jsonEncode(_markers.map((m) => {"id": m.markerId.value, "lat": m.position.latitude, "lng": m.position.longitude}).toList());
    // SharedPreferencesに、JSON形式に変換したマーカーを保存する.
    await prefs.setString('markers', markersList);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      // UIにGoogleMapを表示する.
      body: GoogleMap(
        mapType: MapType.normal,// 通常の地図を表示する.
        // 初期表示する位置情報を設定する.
        initialCameraPosition: const CameraPosition(target: LatLng(37.42796133580664, -122.085749655962), zoom: 14.4746),
        // GoogleMapのonMapCreatedで、_controllerにGoogleMapControllerを代入する.
        onMapCreated: (GoogleMapController controller) {
          // _controllerにGoogleMapControllerを代入する.
          _controller.complete(controller);
        },
        markers: _markers,// GoogleMapのmarkersに、_markersを代入する.
        onTap: _handleTap,// GoogleMapをタップした時に、_handleTap()を実行する.
      ),
    );
  }
  // このメソッドは、GoogleMapをタップした時に実行される.
  _handleTap(LatLng point) {
    // タップした位置情報を、_markersに追加する.
    final String markerIdVal = point.toString();
    setState(() {
      // _markersに、タップした位置情報を追加する.
      final marker = Marker(
        markerId: MarkerId(markerIdVal),// MarkerIdは、String型でないといけない.
        position: point,// LatLngは、double型でないといけない.
        // マーカーの色を赤色にする.
        icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueRed),
        onTap: () => _showRemoveDialog(markerIdVal),// マーカーをタップした時に、_showRemoveDialog()を実行する.
      );
      _markers.add(marker);// _markersに、タップした位置情報を追加する.
    });
    _saveMarkers();// SharedPreferencesに、_markersを保存する.
  }
}

あとは、main.dartでimportして実行する。

import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_map_tutorial/ui/map_example.dart';

void main() async {
  // runAppを呼び出す前にバインディングを初期化する.
  WidgetsFlutterBinding.ensureInitialized();
  // initStateの中には、書けないので、main関数の中で実行する.
  LocationPermission permission = await Geolocator.checkPermission();
  if (permission == LocationPermission.denied) {
    await Geolocator.requestPermission();
  }
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Google Maps Demo',
      home: MapSample(),
    );
  }
}

アプリを実行する
何もない状態

ピンを立てる

ピンを増やす。アプリを再起動してピンが残っていたら、端末にデータが保存できてます。

ピンをタップするとダイアログが出てきて削除できる。

画面から消えたら成功!

thoughts

GoogleMapの位置情報を自分の端末に保存する機能を作ってみましたが、iOSでは赤いピンが出てくるけど、Androidでは出てこなかったです🤔
次は、この課題を解決しないといけないですね。短い記事になりましたが誰かのお役に立てると嬉しいです。

Discussion