Flutter+Google Maps APIで現在地表示

# はじめに
FlutterでGoogle Maps APIを用いて現在地を表示するのに結構苦労したのでメモ代わりに残しておこうと思います。現在地取得、現在地表示についてを見たい方は[ここ](https://qiita.com/butachin/items/795e8f2d7fdfe1f14330#%E7%8F%BE%E5%9C%A8%E5%9C%B0%E8%A1%A8%E7%A4%BA)を押してください。

# APIキーの取得
APIキー取得までは[Google公式Flutter用Google Mapsプラグインを一通り使ってみた](https://qiita.com/aoinakanishi/items/25f2526fc7c1fe8350c6)を参考にしてください。

# Google Mapsを表示
## pubspec.yamlにgoogle_maps_flutterを追加する
```pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  google_maps_flutter: ^0.5.13 // 追加


dev_dependencies:
  flutter_test:
    sdk: flutter
```
[google_maps_flutter](https://pub.dev/packages/google_maps_flutter)を追加し、flutter pub getする。
## Android用の設定
android/app/src/main/AndroidManifest.xmlに以下を追記する

```android/app/src/main/AndroidManifest.xml
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="flutter_maps"
        android:icon="@mipmap/ic_launcher">
        <meta-data android:name="com.google.android.geo.API_KEY" // 追加
            android:value="取得したAPIキー"/>                       // 追加
        <activity
            android:name=".MainActivity"
　　　　　　　...
```
## iOS用の設定
ios/Runner/Info.plistを開いて以下を追記します

```ios/Runner/Info.plist
<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationPortraitUpsideDown</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
</array>
	<key>UIViewControllerBasedStatusBarAppearance</key>
	<false/>
<key>io.flutter.embedded_views_preview</key> // 追加
<string>YES</string>                         // 追加
```
ios/Runner/AppDelegate.mに以下を追記します

```ios/Runner/AppDelegate.m
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"
// Add the GoogleMaps import.
#import "GoogleMaps/GoogleMaps.h" // 追加

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GMSServices provideAPIKey:@"取得したAPIキー"];                      // 追加
  [GeneratedPluginRegistrant registerWithRegistry:self];
  // Override point for customization after application launch.
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
```

## main.dart, google_maps.dartのコード(コピペでOK)
```lib/main.dart
import 'package:flutter/material.dart';
import 'google_maps.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Maps',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: GoogleMaps(),
    );
  }
}
```
```lib/google_maps.dart
import 'dart:async';

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

class GoogleMaps extends StatefulWidget {
  @override
  _GoogleMapsState createState() => _GoogleMapsState();
}

class _GoogleMapsState extends State<GoogleMaps> {
  Completer<GoogleMapController> _controller = Completer();

  void _onMapCreated(GoogleMapController controller) {
    _controller.complete(controller);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("Flutter Maps"),
        ),
        body: GoogleMap(
          onMapCreated: _onMapCreated,
          initialCameraPosition: CameraPosition(  // 最初のカメラ位置
            target: LatLng(34.643208, 134.997586),
            zoom: 17.0,
          ),
        ),
      ),
    );
  }
}
```
## スクリーンショット
### Android
<img width="365" alt="スクリーンショット 2019-05-28 14 53 17" src="https://user-images.githubusercontent.com/33411216/58454104-68fc9680-8158-11e9-8fa6-945c9cae16bd.png">
※実機の関係でこの記事ではスクリーンショットはAndroidだけとします。

# 現在地表示
## pubspec.yamlにlocationを追加します
```pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  google_maps_flutter: ^0.5.13
  location: ^2.3.5 // 追加
```
[location](https://pub.dev/packages/location)を追加し、flutter pub getする。
## Android用の設定
android/build.gradleを開いて以下に変更します

```android/build.gradle
buildscript {
    repositories {
        google()
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.3.0' // 変更
        classpath 'com.google.gms:google-services:4.2.0' // 追加
    }
}
```
android/gradle.propertiesに以下を追記します

```android/gradle.properties
android.enableJetifier=true   // 追加
android.useAndroidX=true      // 追加
org.gradle.jvmargs=-Xmx1536M
```
android/app/src/main/AndroidManifest.xmlに以下を追記します

```android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.butachin.flutter_maps">

    <uses-permission android:name="android.permission.INTERNET"/>              // 追加
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> // 追加

    <!-- io.flutter.app.FlutterApplication is an android.app.Application that
         calls FlutterMain.startInitialization(this); in its onCreate method.
         In most cases you can leave this as-is, but you if you want to provide
         additional functionality it is fine to subclass or reimplement
         FlutterApplication and put your custom class here. -->
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="flutter_maps"
```
android.permission.ACCESS_FINE_LOCATIONを追加することによって位置情報の権限を付与することができる。android.permission.INTERNETは追加しなくても良いと思う。

## iOS用の設定
ios/Runner/Info.plistを開いて以下を追記します

```ios/Runner/Info.plist
	</array>
	<key>UIViewControllerBasedStatusBarAppearance</key>
	<false/>
	<key>io.flutter.embedded_views_preview</key>
    <string>YES</string>
    <key>NSLocationAlwaysUsageDescription</key>             // 追加
    <string>Your location is required for this app</string> // 追加
    <key>NSLocationWhenInUseUsageDescription</key>          // 追加
    <string>Your location is required for this app</string> // 追加
</dict>
</plist>
```
## google_maps.dartの変更, 追加
```lib/google_maps.dart
class _GoogleMapsState extends State<GoogleMaps> {
  LocationData currentLocation;                                              // 追加
  // StreamSubscription<LocationData> locationSubscription;

  Location _locationService = new Location();                                 // 追加
  String error;                                                               // 追加

  @override                                                                   // 追加
  void initState() {                                                          // 追加
    super.initState();                                                        // 追加

    initPlatformState();                                                      // 追加
    _locationService.onLocationChanged().listen((LocationData result) async { // 追加
      setState(() {                                                           // 追加
        currentLocation = result;                                             // 追加
      });                                                                     // 追加
    });                                                                       // 追加
  }                                                                           // 追加

  Completer<GoogleMapController> _controller = Completer();

  void _onMapCreated(GoogleMapController controller) {
  ...
  initialCameraPosition: CameraPosition(
            target: LatLng(currentLocation.latitude, currentLocation.longitude), // 緯度経度を取得したものに変更
            zoom: 17.0,
　...
  void initPlatformState() async {
    LocationData myLocation;
    try {
      myLocation = await _locationService.getLocation();
      error = "";
    }on PlatformException catch(e){
      if(e.code == 'PERMISSION_DENITED')
        error = 'Permission denited';
      else if(e.code == 'PERMISSION_DENITED_NEVER_ASK')
        error = 'Permission denited - please ask the user to enable it from the app settings';
      myLocation = null;
    }
    setState(() {
      currentLocation = myLocation;
    });
  }
}
```
initPlatformState()は全てコピペで追加してください。
## スクリーンショット
### Android
<img width="362" alt="スクリーンショット 2019-05-28 14 52 54" src="https://user-images.githubusercontent.com/33411216/58454285-f17b3700-8158-11e9-8fcb-9e63644e67d5.png">

このままのコードだと、一瞬ですがこの赤い画面が表示されると思います。これは現在地を取得できていないよというエラーなので取得するまで待つようにしたいと思います。

※もしこの画面がずっと出ていたら、AndroidならAndroidmanifest.xmlのandroid.permission.ACCESS_FINE_LOCATIONが、iOSならNSLocationWhenInUseUsageDescription, NSLocationAlwaysUsageDescriptionがうまく反映されていないかもしれないです。再起動したりpubspec.lockを消してpub getし直したりしてみると直るかも。ちなみに私はどうしても権限が付与できなくて、GitHubにあげたコードをCloneしてきたら動きました。

## google_maps.dartに追加
```lib/google_maps.dart
@override
  Widget build(BuildContext context) {
    if (currentLocation == null) {          // 追加
      return Center(                        // 追加
        child: CircularProgressIndicator(), // 追加
      );                                    // 追加
    } else {                                // 追加
      return MaterialApp(
```
上記を追加することにより、赤い画面を出すことなく現在地を表示することができます。
# まとめ
今回はFlutterの現在地を取得する記事があまりなかったため調べて実装したことをメモ程度に書きました。Flutterを始めて、Google Maps APIを触ってみようかなくらいの人の助けになれば嬉しいです。
# ソースコード
https://github.com/butachin/flutter_maps
# 参考
[Google公式Flutter用Google Mapsプラグインを一通り使ってみた](https://qiita.com/aoinakanishi/items/25f2526fc7c1fe8350c6)