Flutterで新規アプリ作る時にやることまとめ
はじめに
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
とファイル名が固定されるので都合がいい。
<key>CFBundleName</key>
- <string>アプリの名前</string>
+ <string>$(PRODUCT_NAME)</string>
...
+ <key>CFBundleDisplayName</key>
+ <string>$(APP_NAME)</string>
輸出コンプライアンス
「いいえ」と答える場合はInfo.plistにあらかじめ追記しておくと手動でいいえする必要がないので楽。
+ <key>ITSAppUsesNonExemptEncryption</key>
+ <false/>
Bundle IdentifierのSuffix
RunnerのBuild SettingsからProduct Bundle Identifierを検索して以下のようにAPP_SUFFIX
を追記。
- 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
)を次のように変更する。
- PROVISIONING_PROFILE_SPECIFIER = "";
+ PROVISIONING_PROFILE_SPECIFIER = "$(APP_PROVISIONING_PROFILE_SPECIFIER)";
Build SettingsからDevelopment Team(DEVELOPMENT_TEAM
)を適切なチームに変更する。
+ DEVELOPMENT_TEAM = XXXXXXXXXX;
Build SettingsからCode Signing Identity(CODE_SIGN_IDENTITY
)を全て利用するProvisioning Profileに関連したApple Distributionへ変更する。
Apple Distribution
でも問題なくビルドが通る場合もあるが、あくまでApple Distribution
は証明書を自動で選択するものなので、たまたまうまく行っているに過ぎない。
+ 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
で渡す。
<key>com.apple.developer.associated-domains</key>
<array>
<string>${APP_ASSOCIATED_DOMAIN}</string>
</array>
App Groupsなどでも同様。
アプリの標準言語を日本語にする
- developmentRegion = en;
+ developmentRegion = ja;
knownRegions = (
- en,
+ ja,
Base,
);
サポートするiOSバージョン
RunnerのInfoのiOS Deployment Target
をAppleが算出しているシェア率を参考にして変更する。
ios/Podfile
の最上部にコメントアウトしてあるplatformも同じ値に変更。
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0' ←これ
最下部のpost_installの中身を次のようにIPHONEOS_DEPLOYMENT_TARGET
を指定する。
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 Bitcode
をYES
にする。
iOSの参考文献
- Flutter 1.17 — no more Flavors, no more iOS Schemas. Command argument that changes everything | by Denis Beketsky | ITNEXT
- dart-define で Flutter アプリの Firebase 開発環境と本番環境を使い分ける iOS 編 - Qiita
- ビルド環境による App Groups を変更する設定方法 - Qiita
- iOS10のサポートを切るときにやったこと - Qiita
- Creating an iOS Bitcode enabled app · flutter/flutter Wiki
- Xcode でアプリの標準言語を日本語にする方法 - Qiita
- iOS Installation | FlutterFire
- flutter 1.19 parse sh script
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
signingConfigs {
release {
storeFile file("upload-keystore.jks")
storePassword "android"
keyAlias "upload"
keyPassword "android"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
アプリ名
labelを次のように修正。
<application
android:label="@string/app_name">
dart-define
からの入力をパースしてセットする。
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
を配置する。
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の参考文献
- Flutter 1.17 — no more Flavors, no more iOS Schemas. Command argument that changes everything | by Denis Beketsky | ITNEXT
- dart-define で Flutter アプリの Firebase 開発環境と本番環境を使い分ける Android 編 - Qiita
- ビルド環境による App Groups を変更する設定方法 - Qiita
- android - How can I create a keystore? - Stack Overflow
- Authenticating Your Client | Android 用 Google API | Google Developers
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
{
"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
に含まれる。
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
コードカバレッジ計測の参考文献
- Flutter test coverage will not report untested files · Issue #27997 · flutter/flutter
- How can I generate test coverage of untested files on my flutter tests? - Stack Overflow
- priezz/dart_full_coverage: Helper for full tests coverage checkup for you Dart/Flutter project
Codecov
プロジェクトルートにcodecov.ymlを配置すれば、カバレッジには含めないファイルを指定できるなど、いろいろ設定できる。
例:codecov.yml
localizationsDelegates
GlobalWidgetsLocalizations
などを指定するならpubspec.yaml
に追記しておく。
return MaterialApp(
localizationsDelegates: const [
GlobalCupertinoLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
);
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のアダプティブアイコン周りで不具合があるため微妙。
- Adaptive Icon Foreground needs to have padding otherwise ends up stretching · Issue #96 · fluttercommunity/flutter_launcher_icons
- Adaptive Icon Scaling by knyghtryda · Pull Request #181 · fluttercommunity/flutter_launcher_icons
どうせ頻繁に変えるものでもないので、Android Studioで生成したり適当なジェネレータを利用した方が良い。
Discussion