🛫

Flutterで新規アプリ作る時にやることまとめ

2021/05/07に公開

はじめに

Flutterで新規にアプリを作る時にテンプレ的に大体同じような事をやっているが、いつも忘れるので備忘録としてまとめ。
Flutterは絶賛開発中なので、本稿の内容は当時これでうまくいっていたぐらいに留めていただけると幸いです。
また、本稿は個人的なメモの意味合いが強いため、割愛している説明が多々あります。

本稿の変更を適用したサンプルリポジトリはこちら

記事もテンプレ自体も随時更新予定

旧バージョンの記事はこちら:Flutterで新規アプリ作る時にやることまとめ - Qiita

この記事でできること要約

  • 大体アプリ作るときにやるであろう手順の備忘録
  • dart-defineを用いて開発環境、ステージング環境、本番環境のフレーバーを切り替え、別々のアプリとして独立させる方法
  • フレーバーに応じて利用するFirebaseプロジェクトを切り替える方法
  • コードカバレッジ計測のTips

環境

Flutter 2.5.2 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 3595343e20 (10 days ago) • 2021-09-30 12:58:18 -0700
Engine • revision 6ac856380f
Tools • Dart 2.14.3

Flutter Create

プロジェクトを作る。

flutter create --org com.example your_project_name

orgはパッケージ名やAndroidのディレクトリ構造などに影響するため、後から変更するのは面倒なので注意。

Firebaseプロジェクト作る

Firebase コンソールから作成する。
アプリを実行してインストールを確認という疎通確認のステップはとりあえず無視。

[deprecated] iOSとAndroidアプリをそれぞれ追加し、アプリIDは後ほど使うのでメモしておく。

FirestoreやApp Distributionを利用するなら予めFirebaseコンソールから有効にしておくことを忘れずに。

開発環境や本番環境などのフレーバー毎にそれぞれプロジェクトを作成しておくこと。

デフォルトのGCPリソース ロケーション

東京はasia-northeast1

[deprecated] Firebaseの構成ファイル

構成ファイルはブラウザからポチッとダウンロードしてもいいが、firebase コマンドでダウンロードもできるので、スクリプトを書いておくと楽。
(このスクリプトでは後述するFlavor対応のために適切なディレクトリとファイル名で配置するようにしている)

iOS

CFBundleNameとCFBundleDisplayName

Info.plistのCFBundleNameとCFBundleDisplayNameを変更する。
PRODUCT_NAMEにしておくとexportする際にRunner.ipaとファイル名が固定されるので都合がいい。

ios/Runner/Info.plist
  <key>CFBundleName</key>
- <string>アプリの名前</string>
+ <string>$(PRODUCT_NAME)</string>
...
+ <key>CFBundleDisplayName</key>
+ <string>$(APP_NAME)</string>

輸出コンプライアンス

「いいえ」と答える場合はInfo.plistにあらかじめ追記しておくと手動でいいえする必要がないので楽。

ios/Runner/Info.plist
+ <key>ITSAppUsesNonExemptEncryption</key>
+ <false/>

スクショ

Bundle IdentifierのSuffix

RunnerのBuild SettingsからProduct Bundle Identifierを検索して以下のようにAPP_SUFFIXを追記。

ios/Runner.xcodeproj/project.pbxproj
- PRODUCT_BUNDLE_IDENTIFIER = com.example.app;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.example.app$(APP_SUFFIX)";

dart-defineからの変数を読み込む下準備

次にRunnerのSchemeをEditして、BuildのPre-actionsに次のRunScriptを追加する。

function entry_decode() { echo "${*}" | base64 --decode; }

IFS=',' read -r -a define_items <<<"$DART_DEFINES"

for index in "${!define_items[@]}"
do
    define_items[$index]=$(entry_decode "${define_items[$index]}");
done

printf "%s\n" "${define_items[@]}" | grep '^APP_' >>${SRCROOT}/Flutter/Generated.xcconfig

Provide build settings fromはRunnerを選択する。

Provisioning Profile

マニュアルでProvisioning Profileを管理したいのでSigningのAutomatically manage signingのチェックを外す。

スクショ

Build SettingsからProvisioning Profile(PROVISIONING_PROFILE_SPECIFIER)を次のように変更する。

ios/Runner.xcodeproj/project.pbxproj
- PROVISIONING_PROFILE_SPECIFIER = "";
+ PROVISIONING_PROFILE_SPECIFIER = "$(APP_PROVISIONING_PROFILE_SPECIFIER)";

Build SettingsからDevelopment Team(DEVELOPMENT_TEAM)を適切なチームに変更する。

ios/Runner.xcodeproj/project.pbxproj
+ DEVELOPMENT_TEAM = XXXXXXXXXX;

Build SettingsからCode Signing Identity(CODE_SIGN_IDENTITY)を全て利用するProvisioning Profileに関連したApple Distributionへ変更する。

Apple Distributionでも問題なくビルドが通る場合もあるが、あくまでApple Distributionは証明書を自動で選択するものなので、たまたまうまく行っているに過ぎない。

ios/Runner.xcodeproj/project.pbxproj
+ CODE_SIGN_IDENTITY = "Apple Distribution: Kohei Kanagu (4XBP3H82S7)";

[deprecated] GoogleService-Info.plist

FirebaseのSDKはios/Runner/GoogleService-Info.plistを自動で読み込むのでフレーバーに応じて差し替えてあげる必要がある。

まず適当な内容でGoogleService-Info.plistを作って、XcodeのProject NavigatorでRunnerディレクトリ下にドラッグアンドドロップしてGoogleService-Info.plistへの参照を作っておく。

echo '
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
</dict>
</plist>
' > ios/Runner/GoogleService-Info.plist

次に、Build PhasesのNew Run Script PhaseでGoogleService-Info.plist を Flavor 毎に切り替える RunScript dart-define を使った場合をコピペ。

作ったRun ScriptのOutput Filesには以下を追加し、Copy Bundle Resourcesよりも上に配置する。

$SRCROOT/Runner/GoogleService-Info.plist

最後に、ios/Runner/GoogleService-Info.plistをgitignoreに追加する。

echo "ios/Runner/GoogleService-Info.plist" >> .gitignore

Runner.entitlements

フレーバー毎にAssociated Domainsを切り替えるため、com.apple.developer.associated-domainsの値は変数にしておき、後述のdart-defineで渡す。

./ios/Runner/Runner.entitlements
<key>com.apple.developer.associated-domains</key>
<array>
    <string>${APP_ASSOCIATED_DOMAIN}</string>
</array>

App Groupsなどでも同様。

アプリの標準言語を日本語にする

ios/Runner.xcodeproj/project.pbxproj
- developmentRegion = en;
+ developmentRegion = ja;
  knownRegions = (
-     en,
+     ja,
      Base,
  );

サポートするiOSバージョン

RunnerのInfoのiOS Deployment TargetAppleが算出しているシェア率を参考にして変更する。

ios/Podfileの最上部にコメントアウトしてあるplatformも同じ値に変更。

ios/Podfile
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0' ←これ

最下部のpost_installの中身を次のようにIPHONEOS_DEPLOYMENT_TARGETを指定する。

ios/Podfile
post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)
    target.build_configurations.each do |config|
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.3'
    end
  end
end

なお、Flutterプロジェクト作りたての時点ではios/Podfileが存在しないため、例えば次のように一旦ビルドすると生成される。

flutter build ios --config-only

Bitcodeのサポート

Build SettingsからEnable BitcodeYESにする。

iOSの参考文献

Android

署名

keystoreはAndroidStudioで生成するか、次のように作成する。

keytool -genkey -v -keystore upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload

後述のandroid/app/build.gradleで指定することになるので、android/app/upload-keystore.jksに置いておく。

SHAは次のように求めてFirebaseに登録しておく。

keytool -list -v -alias upload --keystore upload-keystore.jks

build.gradle

signingConfigsにkeystoreを追記し、releaseビルド時に利用する。
keystoreのパスワードは必要に応じて隠そう。
参考:今更ながら Android の keystore と 署名(signingConfigs) の管理・運用について考えてみた - Qiita

android/app/build.gradle
signingConfigs {
    release {
        storeFile file("upload-keystore.jks")
        storePassword "android"
        keyAlias "upload"
        keyPassword "android"
    }
}

buildTypes {
    release {
        signingConfig signingConfigs.release
    }
}

アプリ名

labelを次のように修正。

android/app/src/main/AndroidManifest.xml
<application
    android:label="@string/app_name">

dart-defineからの入力をパースしてセットする。

android/app/build.gradle
def dartEnvironmentVariables = [
        APP_NAME: 'デフォルト',
        APP_SUFFIX: null,
        APP_ENV: 'default'
]

if (project.hasProperty('dart-defines')) {
    dartEnvironmentVariables = dartEnvironmentVariables + project.property('dart-defines')
            .split(',')
            .collectEntries { entry ->
                def pair = new String(entry.decodeBase64(), 'UTF-8').split('=')
                [(pair.first()): pair.last()]
            }
}
android {
...
    defaultConfig {
        applicationIdSuffix dartEnvironmentVariables.APP_SUFFIX
        resValue "string", "app_name", dartEnvironmentVariables.APP_NAME
    }
...
}

[deprecated] google-services.json

FirebaseのSDKはandroid/app/google-services.jsonを自動で読み込むのでフレーバーに応じて差し替えてあげる必要がある。

以下のタスクを追加して、フレーバーに応じたgoogle-services.jsonを配置する。

android/app/build.gradle
task copyGoogleServicesJson(type: Copy) {
    from "google-services-${dartEnvironmentVariables.APP_ENV}.json"
    into './'
    rename "(.+)-${dartEnvironmentVariables.APP_ENV}.json", '$1.json'
}

tasks.whenTaskAdded { task ->
    if (task.name == 'processDebugGoogleServices') {
        task.dependsOn copyGoogleServicesJson
    }
    if (task.name == 'processReleaseGoogleServices') {
        task.dependsOn copyGoogleServicesJson
    }
}

最後にandroid/app/google-services.jsonはFlavorを変える毎に変更されるのでgitignoreに追加しておく。

echo "android/app/google-services.json" >> .gitignore

サポートするAndroidバージョン

Flutter 2.0.6時点ではminSdkVersionの初期値は16だが、いくらなんでも低すぎるので上げる。
少なくともgoogle_mobile_adsなどは19以上である必要がある。

シェア5%を切ったら切るぐらいか。

Androidの参考文献

dart-define

前述してきたフレーバー毎に変えるAPP_*のような変数は、dart-defineでiOS、Androidそれぞれビルドする際に適用する。

flutter build

以下のようにしてdart-defineで値を渡す
APP_NAME="devアプリ"のようにすると"まで渡されてしまうので注意)
APP_ASSOCIATED_DOMAINが不要の場合は--dart-define APP_ASSOCIATED_DOMAIN=""ではなく削除する)

flutter build ios \
    --dart-define APP_NAME=devアプリ \
    --dart-define APP_SUFFIX=.dev \
    --dart-define APP_ENV=dev \
    --dart-define APP_ASSOCIATED_DOMAIN=applinks:com.example.page.link

Flutter側で値を使いたいならこのようにする。(finalではなくconstにすること)

const appName = String.fromEnvironment('APP_NAME', defaultValue: 'unknownName');
const appSuffix = String.fromEnvironment('APP_SUFFIX', defaultValue: 'unknownSuffix');
const appEnv = String.fromEnvironment('APP_ENV', defaultValue: 'unknownEnv');

Visual Studio Code

launch.json の例

{
  "name": "dev",
  "request": "launch",
  "program": "lib/main.dart",
  "type": "dart",
  "args": [
    "--dart-define=APP_NAME=devアプリ",
    "--dart-define=APP_SUFFIX=.dev",
    "--dart-define=APP_ENV=dev",
    "--dart-define=APP_ASSOCIATED_DOMAIN=applinks:com.example.page.link"
  ]
}

FlutterFire

FlutterFire Overview | FlutterFireを参考にして導入する。
iOS/Androidそれぞれのプラットフォーム別のセットアップも忘れずに。

Crashlytics

iOSでCrashlyticsを導入する際、${PODS_ROOT}/FirebaseCrashlytics/runのScriptは最後に配置する。
スクショ

参考:Firebase Crashlytics を使ってみる

dSYMの手動アップロード

Bitcodeを有効にしていた場合は、App StoreからdSYMをダウンロードして手動でアップロードする必要がある。
dSYMをアップロードするスクリプト

参考:Firebase Crashlytics SDK を使用して難読化解除されたクラッシュ レポートを取得する

FirestoreのImprove iOS Build Times

iOSのFirestore SDKはC++で50万行あるらしいので、普通に参照するとビルドに時間がかかる。
そこでプリコンパイルされたバージョンを参照すると大幅にビルド時間を短縮できるのでおすすめ。
参考:FlutterFire Overview | FlutterFire

コードカバレッジ計測

コードカバレッジ計測する場合、以下のように--coverageを付与すれば./coverage/lcov.infoが出力される。

flutter test --coverage

ただし、これはテストの対象になっているコードの結果のみが出力されるため、テストコードを全く書いていないとlcov.infoには何も出力されない。
つまり全部のdartファイルをimportするだけのテストを用意しておけば、全てのコードがlcov.infoに含まれる。

test/coverage_test.dart のようなもの
import 'package:example_app/hoge1.dart';
import 'package:example_app/hoge2.dart';
import 'package:example_app/hoge3.dart';

void main() {}

手動で追記していくのは面倒なのでヘルプスクリプトで生成する。

curl https://raw.githubusercontent.com/KoheiKanagu/dart_full_coverage/master/dart-coverage-helper | sh

gitignoreもしておく。

echo "coverage/lcov.info" >> .gitignore

コードカバレッジ計測の参考文献

Codecov

プロジェクトルートにcodecov.ymlを配置すれば、カバレッジには含めないファイルを指定できるなど、いろいろ設定できる。
例:codecov.yml

localizationsDelegates

GlobalWidgetsLocalizationsなどを指定するならpubspec.yamlに追記しておく。

main.dart
return MaterialApp(
    localizationsDelegates: const [
    GlobalCupertinoLocalizations.delegate,
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    ],
);
pubspec.yaml
dependencies:
  flutter_localizations:
    sdk: flutter
  intl: any

fvm

Flutterのバージョンはfvmで管理する。
gitignoreへの追加と.vscode/settings.jsonの修正も忘れずに。

Configuration | Flutter Version Management

echo ".fvm/flutter_sdk" >> .gitignore

アプリのアイコン

flutter_launcher_icons | Dart Packageを利用する方法もあるが、バージョン0.9.0時点ではAndroidのアダプティブアイコン周りで不具合があるため微妙。

どうせ頻繁に変えるものでもないので、Android Studioで生成したり適当なジェネレータを利用した方が良い。

GitHubで編集を提案

Discussion