🔥

FlutterでGoogleマップのapiキーをenv化してWeb, Android, iOSに対応させる。

2024/11/26に公開
1

env化しようと思ったが意外と情報がなかったのでメモ。

env化するメリット

  • githubなどにあげた時にapiキーがあがらないので不正使用されにくい。
  • 環境に合わせてキーを使い分けできる。
  • 他の人がgithubなどからcloneして使う時、一ファイルだけapiキーを書き足せばいいので簡単。

env化のデメリット

  • Flutter&Google Mapだと1プラットフォームずつ設定しなければならいのでちょっと面倒くさい。

やってみる

env.json作成

プロジェクトのルートディレクトリにenv.jsonを作る。リリース用とデバック用のキーを分けたかったら、env/release-env.jsonとenv/debug-env.jsonを作る。

env.json登録

IntelIJ(Android Studio)のConfigurationをいじる

  1. 上のmain.dartと書いてあるところを押す。

    上のmain.dartと書いてあるところを押す。

  2. Edit Configurations...を押す。

  3. 図の通り変更する。

VSCodeなど使っている人

実行時に以下のコマンドを入力する。

flutter run --dart-define-from-file=env.json

envの中身を書く

APIキー追加

env.json
{
  "GMAP_ANDROID_KEY": "(Android用キー)",
  "GMAP_IOS_KEY": "(iOS用キー)",
  "GMAP_WEB_KEY": "(Web用キー)"
}

gitignore

.gitignore
env/*
env.json
!ex-*.json # サンプル用の空欄のex-env.jsonなどはignoreしない。

組み込み

dartからはどうやって読める?

String gmapWebKey = String.fromEnvironment("GMAP_WEB_KEY")

これで取得できます。

Android実装

android/app/build.gradle
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}

+ def dartDefines= []
+ if (project.hasProperty('dart-defines')) {
+     dartDefines = project.property('dart-defines').split(',').collectEntries{ entry ->
+         def pair = new String(entry.decodeBase64(), 'UTF-8').split('=')
+         [(pair.first()): pair.last()]
+     }
+ }
android/app/build.gradle
android {
// (省略)
    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId "com.amegamo.last_two_hours"
        // You can update the following values to match your application needs.
        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
        minSdkVersion 24
        targetSdkVersion 34
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
+        resValue "string", "GMAP_KEY", "${dartDefines.GMAP_ANDROID_KEY}"
    }
android/app/src/main/AndroidManifest.xml
    <application
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:enableOnBackInvokedCallback="true"
        android:usesCleartextTraffic="true">
-        <meta-data
-            android:name="com.google.android.geo.API_KEY"
-            android:value="(キー)" />
+        <meta-data
+            android:name="com.google.android.geo.API_KEY"
+            android:value="@string/GMAP_KEY" />

iOS実装

ios/Runner/Info.plist
+    <key>DartDefines</key>
+    <string>$(DART_DEFINES)</string>
 </dict>
 </plist>
ios/Runner/AppDelegate.swift
-    GMSServices.provideAPIKey("(キー)")
+    let dartDefinesString = Bundle.main.infoDictionary!["DartDefines"] as! String
+    var dartDefines = [String:String]()
+    for definedValue in dartDefinesString.components(separatedBy: ",") {
+      let decoded = String(data: Data(base64Encoded: definedValue)!, encoding: .utf8)!
+      let values = decoded.components(separatedBy: "=")
+      dartDefines[values[0]] = values[1]
+    }
+    GMSServices.provideAPIKey(dartDefines["GMAP_IOS_KEY"]!)
     return super.application(application, didFinishLaunchingWithOptions: launchOptions)
   }
 }

Web実装

dart公式のjsパッケージ使用。(標準のdart:jsよりpackage:jsの方がwasm対応)

pubspec.yaml
+  js: ^0.6.7
web/index.html
 <body>
+<script>
+    function setGmapApiKey(key) {
+       const sc=document.createElement("script");
+       sc.src=`https://maps.googleapis.com/maps/api/js?key=${key}&v=quarterly`;
+       document.head.appendChild(sc);
+    }
+</script>

https://zenn.dev/tocot/articles/3361161cf1021f
これにそって、2ファイル作る。
cuiでやってもguiでやってもいい。

touch lib/js_or_empty.dart
touch lib/web_import.except.dart
js_or_empty.dart
export 'web_import_except.dart' if (dart.library.js) 'package:js/js.dart';
web_import_except.dart
class JS {
  final String? name;
  const JS([this.name]);
}

そして、main.dartでapiキーをセットする。

main.dart
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
+import 'js_or_empty.dart';

+("setGmapApiKey")
+external void _setGmapApiKey(String key);

void main() {
+  if (kIsWeb) {
+    const gmapWebKey = String.fromEnvironment("GMAP_WEB_KEY");
+    _setGmapApiKey(gmapWebKey);
+  }
   runApp(
     const MaterialApp(
       home: Scaffold(
         body: GoogleMap(
           initialCameraPosition: CameraPosition(
             target: LatLng(35.6680456583835, 139.6953623525739),
           ),
         ),
       ),
     ),
   );
  }
}

完成!

完成!イェーイ!

Discussion

tOcOttOcOt

ご指摘ありがとうございます。env.jsonの間違いでしたので、訂正させていただきます。
ちなみに、
https://github.com/zenn-dev/zenn-community/issues/226
によれば、GithubのPull Requestを使ってくれと書いてありますね。時間が空いた時にGithub連携もしようと思います。