Zenn
Open14

【Flutter】初期設定が不要になるテンプレートを作りたい

Nugget DysonNugget Dyson

アプリ開発、やりたいけどめんどくさい問題

こんなアプリ欲しいぃぃぃぃ!このアイデアよくね???
という思いは束の間。10s laterには他のこと考えています。これが界隈との差か...

僕はだらしないので、めんどくささ、やる気、気分が味方になってくれずなかなか手がつきません。

今回はこの3つの要因のうち「めんどくささ」にフォーカスしたソリューションになります。
やる気、気分は解決しないのでそこは自分でどうにかしてください。僕も頑張ります。

この記事で私は発信デビューです。温かい目で見てもらえたら幸いでございます。

そんなこんなで今回は面倒な作業を全てテンプレート化しようと思います。
最初からアプリ開発に集中できる環境を整えましょうってことですね!

例えば以下のような作業。flavorに関わる内容が多いっす

  • fvm
  • DBアクセス設定
  • スプラッシュ
  • アプリアイコン
  • アプリ名
  • モデルのテンプレート
  • Firebaseの基本的な部分(authなど)
  • フッター
  • router
  • android, iOS両方のビルド可能な状態
Nugget DysonNugget Dyson

まずはvscodeのコマンドパレットからflutter new project

プロジェクト名は「flutter_app_template」にしました。

一旦この時点でgitにpushしとこう。

Nugget DysonNugget Dyson

さて、まずはfvmの設定ですね。

fvm releases

でflutterのバージョン一覧を見てみましょう。

最新は3.27.3みたいです。とりあえず最新を使います。

fvm use 3.27.3

gitコミットしときましょうね

Nugget DysonNugget Dyson

ではこの段階でiOSビルドして問題ないか確認しておきます。
launch.jsonがないとvscodeのGUIからデバッグできません。
create a launch.json fileすればおkです

ビルドしたらプロテインを飲みながら待ちましょう。

iOSビルドできましたね。

androidも見とくかあ。
ヤニ吸ってる間に済ませましょう。

なんて言ってる間に終わって草。ありがとう。

iOS,androidともに問題ないですね。
fvm設定しただけなのでビルドできて当然ですが、ちょくちょくチェックポイント作っておくのは個人的に大事だと思います。
もしこれ以降にビルドできない問題が発生しても「ここまでは問題なかった」と切り分けできるので、今回に限らず普段から意識しておくのはいいことだと思われまする。

Nugget DysonNugget Dyson

Flavor設定やってくぞ(dart define from file)

方針

「dart define from file」でflavorを設定します。

Flavor① 「from file」のfileを作成します。

今回は開発環境(dev)と本番環境(prod)としてFlavorを設定しようと思います。
以下のように環境ごとに設定値を定義するファイルを作成しました。
ディレクトリ構成は後々整理するとして

  • lib/flavor/dev.json
  • lib/flavor/prod.json

に配置しています。

dev.json
{
    "FLAVOR": "dev",
    "APP_NAME": "開発環境",
    "APP_ID": "com.nuggetdyson.template.dev"
}
prod.json
{
    "FLAVOR": "prod",
    "APP_NAME": "本番環境",
    "APP_ID": "com.nuggetdyson.template"
}

今は一旦飛ばしますが、DBアクセスの設定値なども環境ごとにこのファイルに記述していきます。

Flavor② iOS ①の変数をXcodeに流せるようにする

flutter 3.19 から標準でdart define from fileが利用できなくなりました。
そのためにこちらの作業が必要となってきます。

  1. スクリプトファイルを作成
  2. XcodeのBuild Pre-actionsに1のスクリプトを実行するように設定

以下のファイルを作成します。

ios/scripts/extract_dart_defines.sh
#!/bin/sh

# 参考:https://zenn.dev/altiveinc/articles/separating-environments-in-flutter#ios%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AB%E5%BF%85%E8%A6%81%E3%81%AA%E5%AF%BE%E5%BF%9C

# Dart defineを書き出すファイルパスを指定します。
# ここでは `Dart-Defines.xcconfig` というファイル名で作成することにします。
OUTPUT_FILE="${SRCROOT}/Flutter/Dart-Defines.xcconfig"
# Dart defineの中身を変更した時に古いプロパティが残らないように、初めにファイルを空にしています。
: > $OUTPUT_FILE

# この関数でDart defineをデコードします。
function decode_url() { echo "${*}" | base64 --decode; }

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

for index in "${!define_items[@]}"
do
    item=$(decode_url "${define_items[$index]}")
    # Dartの定義にはFlutter側で自動定義された項目も含まれます。
    # しかし、それらの定義を書き出してしまうとエラーによりビルドができなくなるので、
    # flutterやFLUTTERで始まる項目は出力しないようにしています。
    lowercase_item=$(echo "$item" | tr '[:upper:]' '[:lower:]')
    if [[ $lowercase_item != flutter* ]]; then
        echo "$item" >> "$OUTPUT_FILE"
    fi
done

次にXcodeでのbuild pre actionの設定ですね
画面上部のメニューからProduct>Scheme>Edit Schemeから設定します。
「+」ボタンで新しくスクリプトを登録できます。

Provide build settings from: Runner

`${SRCROOT}/scripts/extract_dart_defines.sh`

参考記事m(._.)m

https://zenn.dev/altiveinc/articles/separating-environments-in-flutter

iOSビルドを行って確認してみましょう。
その前にlaunch.jsonからflaverの情報を流すような設定が必要です。

.vscode/launch.json
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "dev",
            "request": "launch",
            "type": "dart",
+           "args": [
+                "--dart-define-from-file=lib/flavor/dev.json",
+            ],
        },
        {
            "name": "prod",
            "request": "launch",
            "type": "dart",            
+            "args": [
+                "--dart-define-from-file=lib/flavor/prod.json",
+           ],
        },
    ]
}

これでビルドしてみましょう!
ios/Flutter/Dart-Defines.xcconfigが生成されています。

中身をのぞいてみると、jsonで設定した変数が定義されているのがわかりますね。(devでビルドしてます)

自動で動的に生成されるファイルなのでgitignoreも忘れずにしておきましょう

ios/.gitignore
+ # Flavor defines
+ Flutter/Dart-Defines.xcconfig

次はこれらの値をXcode上で参照してアプリ名やbundle idを動的に設定できるようにしていきます。

Nugget DysonNugget Dyson

Flavor③ iOS ①の変数を参照し設定する(Xcode)

どこで設定するねん>ios/Runner/Info.plist
Xcodeで表示してみましょう

ふむふむ。どうやら$(FLUTTER_BUILD_NAME)のように動的な設定値は$(XXX)で指定してやればええんすね。

既存のこやつらはどこに元にあるんや?

$(PRODUCT_BUNDLE_IDENTIFIER)
targets>Runner>Build Setting

$(FLUTTER_BUILD_NAME)
$(FLUTTER_BUILD_NUMBER)

ios/Flutter/Generated.xcconfig

$(EXECUTABLE_NAME)
$EXECUTABLE_PREFIX
$PRODUCT_NAME
$EXECUTABLE_SUFFIX
の連結らしいです。よくわからないのでそのままで。。
https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-SW71

Nugget DysonNugget Dyson

とりあえずやってみよう!
今回は

  • アプリ名
  • bundle id
    を指定します。

まずは先ほど自動生成できるようになったDart-Defines.xcconfigを参照できるようにします。

ios/Flutter/Debug.xcconfig
#include "Generated.xcconfig"
+#include "Dart-Defines.xcconfig"
ios/Flutter/Release.xcconfig
#include "Generated.xcconfig"
+#include "Dart-Defines.xcconfig"

BundleDisplayNameBundleNameは直で記述されていたのでinfo.plistの値をそれぞれ $(APP_NAME) に変更しました。

Product Bundle IdentifierはBuild Settingsに大元の記述があるのでそちらを$(APP_ID)に変更しました

これでビルドしてみましゅ!

....できたぁぁ!
アプリの識別IDが変わったから別アプリとして起動します。
アプリ名も開発・本番で変わってますね!後々アイコンも変えていきます

Nugget DysonNugget Dyson

Flavor④ Android ①の変数を参照し設定する

androidは

  • android/app/build.gradle
  • android/app/src/main/AndroidManifest.xml
    を編集します
android/app/build.gradle
+ def dartDefines = [:];
+ if (project.hasProperty('dart-defines')) {
+     // カンマ区切りかつBase64でエンコードされている dart-defines をデコードして変数に格納します。
+     dartDefines = dartDefines + project.property('dart-defines')
+         .split(',')
+         .collectEntries { entry ->
+             def pair = new String(entry.decodeBase64(), 'UTF-8').split('=')
+             pair.length == 2 ? [(pair.first()): pair.last()] : [:]
+         }
+     println(dartDefines)
+ }

defaultConfig {                
        // You can update the following values to match your application needs.
        // For more information, see: https://flutter.dev/to/review-gradle-config.
        minSdk = flutter.minSdkVersion
        targetSdk = flutter.targetSdkVersion
        versionCode = flutter.versionCode
        versionName = flutter.versionName
-       applicationId = "com.example.flutter_app_template"
+       applicationId "${dartDefines.APP_ID}"
+       resValue "string", "app_name", "${dartDefines.APP_NAME}"
}
android/app/src/main/AndroidManifest.xml
 <application
-       android:label="flutter_app_template"
+       android:label="@string/app_name"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">

これでそれぞれビルド!
開発、本番OKですね!

参考記事

https://zenn.dev/altiveinc/articles/separating-environments-in-flutter#androidアプリに必要な対応

Nugget DysonNugget Dyson

環境ごとにアイコンを設定する

使用するパッケージはこちら
https://pub.dev/packages/flutter_launcher_icons

準備

assets/app_icons配下に

  • icon_dev.png
  • icon_prod.png

を作成します。

次にこちらを参考にyamlファイルを作成します。プロジェクト直下でOKです。
https://github.com/fluttercommunity/flutter_launcher_icons/tree/master/example/flavors

flutter_launcher_icons-dev.yaml
flutter_launcher_icons:
  image_path: "assets/app_icons/icon_dev.png"
  android: "launcher_icon"
  ios: true
  remove_alpha_ios: true
flutter_launcher_icons-prod.yaml
flutter_launcher_icons:
  image_path: "assets/app_icons/icon_prod.png"
  android: "launcher_icon"
  ios: true
  remove_alpha_ios: true

あとは以下のコマンドをターミナルから叩きましょう。android,iOS用に画像が生成されます。
素晴らしい🎉

dart run flutter_launcher_icons


iOS

デフォルトではAppIconというアセットが指定されているので、AppIcon-$(FLAVOR)で先ほど生成されたアセットを指定してあげるといいですね。
設定場所はTarget>Runner>Build Settings>Primary App Icon Set Nameになります。

編集を加えてビルドすると...🫨

Android

Androidはandroid/app/src/main/res配下存在するアセットがアイコンとして設定されます。
flutter_launcher_iconにより生成したアセットはandroid/app/${FLAVOR}/resに入っていますので、ビルド時にmain/resにコピーするコードをandroid/app/build.gradleに記述します。

android/app/build.gradle
+ // Flavor アプリアイコンをmain/resへコピーする
+ task copySources(type: Copy) {
+    from "src/${dartDefines.FLAVOR}/res"
+    into 'src/main/res'
+ }

+ tasks.whenTaskAdded { task ->
+   task.dependsOn copySources
+ }

これでビルドすると...

あで??なんか四角いど??

Nugget DysonNugget Dyson

Android アダプティブアイコン対応

四角になっていた原因はアダプティブアイコンに対応してない形式で作成していたのが原因みたいです。
気持ち悪い日本語のドキュメントをどうぞ↓
https://developer.android.com/develop/ui/views/launch/icon_design_adaptive?hl=ja

仕方がないので、新しいアイコンをFigmaでジェネレートしました。assets/app_icons/icon_adaptive.png

アダプティブアイコンの設定をymlファイルに記述します。
バックグラウンドはカラーコードで指定しました。

flutter_launcher_icons-dev.yaml
flutter_launcher_icons:
  image_path: "assets/app_icons/icon_dev.png"
  android: "launcher_icon"
  ios: true
  remove_alpha_ios: true
+ adaptive_icon_foreground: 'assets/app_icons/icon_adaptive.png'
+ adaptive_icon_background: '#7AABD9'
flutter_launcher_icons-prod.yaml
flutter_launcher_icons:
  image_path: "assets/app_icons/icon_prod.png"
  android: "launcher_icon"
  ios: true
  remove_alpha_ios: true
+ adaptive_icon_foreground: 'assets/app_icons/icon_adaptive.png'
+ adaptive_icon_background: '#F68C8C'

改めて、flutter_launcher_iconsの生成スクリプトを叩きます

dart run flutter_launcher_icons

ビルドします...

寝椅子ぅ🎉

Nugget DysonNugget Dyson

Flutter スプラッシュ画面を作る

スプラッシュ画面はAndroid12から新しい手法となっているみたいでこれに対応しなければなりません。アプリアイコンの時から厄介ですねえ。
どうせflutterでやるなら一つの画像で全部賄いたい。それぞれの設定したくないなぁーできるかな?

android12含む全てのデバイス共通で利用できる画像を作る

アイコン背景は無しでそのままpng画像を表示しようと思うので、
1152px正方形の中に768の円を描いてその中に収まるようにスプラッシュ画像を作成します。
https://pub.dev/packages/flutter_native_splash

  • App icon without an icon background, as shown on the left: This should be 1152×1152 pixels, and fit within a circle 768 pixels in diameter.
  • App icon with an icon background, as shown on the right: This should be 960×960 pixels, and fit within a circle 640 pixels in diameter.
    https://pub.dev/packages/flutter_native_splash#android-12-support

こんなもんか...!?

一旦これで背景色を消して、エクスポートします。
エクスポートした画像はassets/splash/splash.pngとして配置しました。

スプラッシュ画像を生成する

以下コマンドでスプラッシュをジェネレート!

flutter pub run flutter_native_splash:create

特にこれ以上の設定は不要でビルドしたらスプラッシュが表示されます。

動作確認

スプラッシュは一瞬しか表示されないので、どこかに行ったらダメですよ。

Android

iOS

メモ

  • 起動初期画面でデータの読み込み中はずっとスプラッシュ画面を表示するとか方法もありますが今回は割愛します。
  • Flavorごとに変えたりもできますが、私はそこまでする必要ないだろう派なので割愛します。
Nugget DysonNugget Dyson

ここまでmain.dartすらまだ一度も見ていません。これ以外のファイルもありません。
改めて開発準備段階でやること多いですねぇ。

現状:

Nugget DysonNugget Dyson

とはいえここからやっとコードを書いていけそうですね!
ここまでのテンプレートでもだいぶ嬉しいです

続きはまた今度にしようかな

ログインするとコメントできます