flutterでアプリ開発する時に最初にすること


自分用のメモです。
後々どうせやるけど面倒くさくなりそうな最低限のタスク達。

## 環境
<table>
    <thead><th></th><th>スペック</th></thead>
    <tr>
    <td>pc</td><td>macbook air</td>
    </tr>
    <tr>
    <td>メモリ</td><td>M2</td>
    </tr>
    <tr>
    <td>OS</td><td>Sonama 14.2</td>
    </tr>
        <tr>
    <td>flutter</td><td>3.19.6</td>
    </tr>
        <tr>
    <td>dart</td><td>3.3.4</td>
    </tr>
        <tr>
    <td>devtools</td><td>2.31.1</td>
    </tr>
        <tr>
    <td>xcode</td><td>15.3</td>
    </tr>
        <tr>
    <td>android studio</td><td>2023.3.1 (Jellyfish)</td>
</table>

## bundle identifier
プロジェクト作る時に
``flutter create --org com.your.orgname my_app_name``で作るか、
vscodeのSetting.jsonで自動的に入力するようにしておくと楽。

↓参考
[Flutterで作ったプロジェクトのBundleID・PckageNameを変更する方法と対策](https://www.tsuzuki817.com/posts/flutter/flutter-package-bundle/)

## 画面の回転設定
[Flutter でアプリの画面を Portrait 固定にする](https://starhoshi.hatenablog.com/entry/2022/05/11/140819)がすごい簡潔でよかった。

### ios
Runner > General で設定。回転しないならportraitのみにする。

### android
android/app/src/main/AndroidManifest.xmlのactibityタグに``android:screenOrientation="portrait"``を追加する。

### 共通
スクリーンによる回転の可・不可、回転時のUIの切り替えは[【Flutter】画面回転に対応する](https://qiita.com/MLLB/items/d2fdc69ce375597abcb5)が参考になりそう（ちゃんと確かめてはない）

### エミュレータで確認しておく

#### ios
1\. `open -a Simulator`でシミュレータを起動

2\. `flutter run`でアプリを起動

3\. -dをつけると何で起動するか選べる
`flutter devices`で使えるデバイスを調べる。
機種名の右側の文字列の先頭一文字を指定することで選べる。
```
iPhone 15 Pro Max (mobile) • 000000000000-000000-000000
```
だったら`flutter run -d 0`

#### android

<details open><summary>初回(AVDがない)の場合</summary>

[Start building Flutter Android apps on macOS](https://docs.flutter.dev/get-started/install/macos/mobile-android?tab=first-start)に従って進めていく。

1\. android studioを立ち上げる。

2\. 「Get from VCS」の右隣の3点リーダーの「Virtual Device Manager」を選択。
![スクリーンショット 2024-05-04 22.09.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3776694/8a913de0-a0ca-ff70-05c7-1a1c62ab2d65.png)

3\. +ボタンをクリック。(ホバーすると「Create Virtual Device」が表示される)
![スクリーンショット 2024-05-04 22.32.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3776694/aaba27cb-0e89-7842-0648-15e32e4322b0.png)

4\. 任意のデバイスを選択して「Next」
![スクリーンショット 2024-05-04 22.33.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3776694/bfef6796-2de8-7e33-8168-a81cbf01c94d.png)

5\. 任意のイメージを選択してダウンロードボタンをクリック。
![スクリーンショット 2024-05-04 23.01.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3776694/9432134b-9f07-8a92-5424-10398e76b139.png)
少し待つとインストールが終わるので「Finish」をクリック。

6\. インストールされたイメージを選択して「Next」

7\. 細かい設定ができるが多分特に何もしなくていいので「Finish」

8\. command-line toolsがない場合、android studioのsdk managerのsdk toolsから最新のものをインストールする。アコーディオンメニューのようになってるので、余分なものはしまわないと出てこない。
↓参考
[[基礎知識]Android Studio(Cnary6ビルド(Jellyfish))インストール記録](https://zakku-spot.com/2024/03/23/bk-android-studiocnary6-jellyfish-install-001/#%EF%BC%88%EF%BC%91%EF%BC%89Android_Emulator_%E7%92%B0%E5%A2%83%E7%A2%BA%E8%AA%8D)
</details>


1\. エミュレータの一覧を取得
```
emulator -list-avds
```
※ `$ANDROID_SDK_ROOT/emulator`にパスを通しておかないとできないので　注意。

2\. `emulator -avd {avd_name}`もしくは`emulator @{avd_name}`で起動
```
emulator -avd pixel_8_pro
```
もしくは
```
emulator @avd pixel_8_pro
```
など

↓参考
[Android Emulatorのみを起動する](https://qiita.com/S310/items/7f305406c90b70a4269c)

## flutter_dotenv
[flutter_dotenv](https://pub.dev/packages/flutter_dotenv)を入れておく。
のちのちIDとか管理することになる。

以下公式のドキュメントの引用。

1\. `pub add`か`pub get`で追加。

2\. assetsに``.env``を追加する。
```pubspec.yaml
assets:
  - .env
```

3\. プロジェクトのルートディレクトリに.envファイルを作る。

4\. `main.dart`でenvファイルをロードする。
(このステップ以降コードはv5.0.0以降のもの)
```main.dart
import 'package:flutter_dotenv/flutter_dotenv.dart';

// DotEnv dotenv = DotEnv() is automatically called during import.
// If you want to load multiple dotenv files or name your dotenv object differently, you can do the following and import the singleton into the relavant files:
// DotEnv another_dotenv = DotEnv()

Future main() async {
  // To load the .env file contents into dotenv.
  // NOTE: fileName defaults to .env and can be omitted in this case.
  // Ensure that the filename corresponds to the path in step 1 and 2.
  await dotenv.load(fileName: ".env");
  //...runapp
}
```

5\. 環境変数にアクセスする
```dart
import 'package:flutter_dotenv/flutter_dotenv.dart';
dotenv.env['VAR_NAME'];
```

## アプリアイコン
[flutter_launcher_icons](https://pub.dev/packages/flutter_launcher_icons)を追加

任意のフォルダに画像を保存 e.g.) assets/images
``` pubspec.yaml
assets:
    - assets/
    - assets/images/
    - assets/images/launcher/
    - assets/images/splash/
```

アイコンの設定を追記
```pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter

# アイコンの設定
flutter_launcher_icons:
  ios: true
  image_path: "assets/images/launcher/icon.png"
  remove_alpha_ios: true
  android: true;
  adaptive_icon_background: "assets/images/launcher/icon_android_background.png"
  adaptive_icon_foreground: "assets/images/launcher/icon_android_foreground.png"
```

:::note warn
アイコンに使用する画像ファイルの名前には'-'は使えないので注意が必要。
:::


``flutter_launcher_icons``を実行する。
```
dart run flutter_launcher_icons
```
シミュレーターでアプリ走ってる場合は再起動orシミュレータからアプリを削除して再度run。

↓参考
[【Flutter】アプリアイコンに必要な画像と追加方法（flutter_launcher_icons,Figma）](https://zenn.dev/hott3/articles/flutter-launcher-image)

### 画像が見切れてるアイコンを生成したい時

↓みたいな画像
![app-icon-ios.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3776694/4bd7c5d0-fff9-79ef-86ea-b55573e8648f.png)

iosではこのままの画像を用意するだけ。しかしandroidのadaptive iconは勝手に画像をマスクしにくるので工夫が必要。

何もせずにios用に用意した画像をそのまま使うと↓みたいな感じになる（pixel 8 proのエミュレーターで検証）。
![無題58_20240506185044.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3776694/7a36d5d8-2b8a-92f9-25b8-0c71ab550c2c.png)


以下解決策
1\. andorid用に画像を用意。機種によってアイコンの形が変わり、それによってマスクの仕方が変わる(っぽい?)ので、余裕を持たせてios用のものよりも画像が見える範囲を少し広めにしておいた方がいいかもしれない。ただし、四角くマスクされる場合もあるので要検討。
(検証できてないけど108x108のサイズで作っておいた方が良さそう。)

2\. 1で用意した画像を``adaptive_icon_background``に割り当てる。そうすることでマスクされるのを回避する。

3\. ``adaptive_icon_foreground``には透明なだけの透過画像を割り当てる。この画像は透明にする以外特に工夫は要らなそう。

3のforegroundの画像になんか書いたら遊び心があっていい感じのアイコンが作れるかもしれない。けどどっちにしろiosには適用できなさそう(?)


## スプラッシュ画面

### スプラッシュ画面作成

[flutter_native_splash](https://pub.dev/packages/flutter_native_splash)を追加。

アイコン同様に画像を配置。

:::note info
androidに対応するため画像は直径768pxの円に収まるものを用意しておくと楽
:::


設定を追記。
```pubspec.yaml
# アイコンの設定
flutter_launcher_icons:
  ios: true
  image_path: "assets/images/launcher/icon.png"
  remove_alpha_ios: true
  android: true;
  adaptive_icon_background: "assets/images/launcher/icon_android_background.png"
  adaptive_icon_foreground: "assets/images/launcher/icon_android_foreground.png"

# スプラッシュ画面の設定
flutter_native_splash:
  image: "assets/images/splash/splash.png"
  color: "#f5f5f5"
  android: true
  ios: true
  fullscreen: true
  android_12:
    image: "assets/images/splash/splash.png"
    color: "#f5f5f5"
```
``flutter_native_splash:create``を実行する。
```
dart run flutter_native_splash:create
```

アイコン同様再起動。

↓参考
[【Flutter】コマンド一発でスプラッシュ画面を実装する【flutter_native_splash】](https://zenn.dev/susatthi/articles/20220406-061305-flutter-native-splash?redirected=1)

### アプリ内での処理
main.dartのmain関数内にコードを追記。
スプラッシュ画面を正常に表示する。

``` dart
void main() {
  // 追記
  WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
  
  // 追記
  FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);

  runApp(const MyApp());
}
```

任意の位置（initStateとか）にコードを追記。
データのロードなどの処理を行った後など適切なタイミングでスプラッシュ画面を終了する。
flutter_native_splashをフルスクリーンで表示すると、終了後ステータスバー(画面上部の時間とか表示する領域)が表示されなくなったりするので、表示する処理を記述する。
``` dart
// スプラッシュ画面終了
FlutterNativeSplash.remove();

// splash screen 終了後にステータスバーを表示する
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
    overlays: SystemUiOverlay.values);
```
:::note warn
↓の２つをインポートしなきゃいけない。

```
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:flutter/services.dart';
```
``services.dart``は``SystemChrome``を使うため。
:::


## 環境切り替え
とにかく↓の記事通り進めていく。

https://zenn.dev/altiveinc/articles/separating-environments-in-flutter#android%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

↓のコマンドで実行(`build`も)することで切り替わる。
```
flutter run --dart-define-from-file=path/filename
```

:::note warn
この設置をした後xcodeでビルドしようとすると、product bundle identifireがないと言われて失敗することがある。

ターミナルなどで一度`--dart-define-from-file`をつけてビルドしなきゃいけない。
（product bundle identifierが、環境ファイルに書かれたもので上書きされる。）
:::



## firebase
https://zenn.dev/kingu/articles/9192b91062841b8e0bba

https://youtu.be/BWTQkkLC-Mo?si=zYylSI-PEqihh096

<details open><summary>firebase初めての時はcliをインストトール</summary>

[Firebase CLI リファレンス](https://firebase.google.com/docs/cli?hl=ja#macos)に従って進める

1\. mac or linuxなら
```
curl -sL https://firebase.tools | bash
```
npmを使ってるなら
```
npm install -g firebase-tools
```
でインストールするのが良さそう。

どちらにも当てはまらないならバイナリをダウンロードして頑張ってパスを通す。

2\.　正しくインストールできたか確かめる。
```
firebase --version
```

3\. firebaseにログインする
```
firebase login
```
firebaseのサイトに飛ばされるので、任意のアカウントでログイン。

</details>

### プロジェクトを作る
[firebase](https://console.firebase.google.com/)にログインして、「プロジェクトを追加」から作成。
プロジェクト名を入力して進む。
プロジェクトIDは重複がある場合適当に割り振られるorいい感じのものをつける。

プロジェクトは環境の数だけ作る。dev, stg, prdがあるなら、appname-dev, appname-stg, appname-prdを作る。

それぞれのプロジェクトで、androidとiosのアプリを作る。


### flutterにfirebaseを追加

`android/app/src/`と`ios/`に環境分のディレクトリを作って、firebase sdkの構成ファイルを配置する。
androidとiosでそれぞれ環境に応じて構成ファイルを参照できるようにする。

- [Firebase対応 (iOS)](https://zenn.dev/altiveinc/articles/separating-environments-in-flutter#firebase%E5%AF%BE%E5%BF%9C-(ios))

- [Firebase対応 (Android)](https://zenn.dev/altiveinc/articles/separating-environments-in-flutter#firebase%E5%AF%BE%E5%BF%9C-(android))

を参考にする。
というか完全に↓（同記事）を参考にしたらいい。

https://zenn.dev/altiveinc/articles/separating-environments-in-flutter#android%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


flutterにfirebaseを使うためのプラグインを追加する。

```bash
flutter pub add firebase_core
```

:::note warn
この段階でiosのビルドをしようとすると、pod installしろと言われる場合がある。

```
Specs satisfying the `firebase_core (from `.symlinks/plugins/firebase_core/ios`)` dependency were found, but they required a higher minimum deployment target.
```
と言われた場合はビルドターゲットのバージョンが低い。firebaseは2024/7/24時点では13以上が必要っぽい。ios/Podfileの先頭に↓を追記する。

```
platform :ios, '13.0'
```

podを更新する

```
pod install --repo-update
```

:::


### flutterfire_cliの追加

```
dart pub global activate flutterfire_cli
```

(※`flutter pub`とは異なるコマンド)

↓参考

https://note.com/gorojy/n/n63776d264e8d

:::note warn
追加しただけでは`flutterfire`コマンドは使えないので注意。
コンソールに出てくる`export PATH="$PATH":"$HOME/.pub-cache/bin"`でパスを通す。
:::

### 環境毎にflutterfire configureする
次項のfirebaseの初期化に用いられる`firebase_options`が、環境によって適切に使われるようにしたい。
例えば開発環境なら、`firebase_options_dev.dart`を使う。

`flutterfire configure`で毎にファイルを生成する。この時生成される`firebase_options`ファイルは特に書き換えたりしない。
project idは`firebase projects:list`で確認できる。

```zsh
# dev環境
flutterfire configure --project=<firebase-dev-project-id> --out=lib/firebase_options_dev.dart
# stg環境
flutterfire configure --project=<firebase-stg-project-id> --out=lib/firebase_options_stg.dart
# prd環境
flutterfire configure --project=<firebase-prd-project-id> --out=lib/firebase_options_prd.dart
```

`firebase.json`を編集して環境毎のアプリidを記述する。
プロジェクトidは`flutterfire configure`の時のものと同じ。アプリidは、firebaseのコンソールから「プロジェクトの設定」を開き、下の方の「マイアプリ」のところに書いてある「アプリID」をコピペする。
"default"はどの環境を書いても（多分）大差ないので適当に書いておく。

```json
{
	"flutter": {
		"platforms": {
			"android": {
				"default": {
					"projectId": "{プロジェクトid}",
					"appId": "{アプリid}",
					"fileOutput": "android/app/google-services.json"
				}
			},
			"ios": {
				"default": {
					"projectId": "{プロジェクトid}",
					"appId": "{アプリid}",
					"uploadDebugSymbols": false,
					"fileOutput": "ios/Runner/GoogleService-Info.plist"
				}
			},
			"dart": {
				"lib/firebase_options_dev.dart": {
					"projectId": "{プロジェクトid}",
					"configurations": {
						"android": "{アプリid}",
						"ios": "{アプリid}"
					}
				},
				"lib/firebase_options_stg.dart": {
					"projectId": "{プロジェクトid}",
					"configurations": {
						"android": "{アプリid}",
						"ios": "{アプリid}"
					}
				},
				"lib/firebase_options_prd.dart": {
					"projectId": "{プロジェクトid}",
					"configurations": {
						"android": "{アプリid}",
						"ios": "{アプリid}"
					}
				}
			}
		}
	}
}

```

:::note info
firebaseのapiキーは公開されても大丈夫らしい。

参考
[Firebaseのセキュリティについて - そのAPI キー本当に公開しても大丈夫ですか？](https://techblog.zest.jp/entry/2023/12/27/091134)

:::

### firebaeを初期化

`firebase_core`とそれぞれの`firebase_opsions`をインポート。使い分けられるように
```main.dart
import 'package:firebase_core/firebase_core.dart';

import 'firebase_options_dev.dart' as dev_default_firebase_options;
import 'firebase_options_stg.dart' as stg_default_firebase_options;
import 'firebase_options_prd.dart' as prd_default_firebase_options;
```

`WidgetsFlutterBinding.ensureInitialized();`の後に`Firebase.initializeApp`を追加して初期化する。この時環境毎に適切な`firebase_options`を引数に渡す。

```main.dart
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();

const flavor = String.fromEnvironment('flavor');

FirebaseOptions options;
switch (flavor) {
  case "prd":
    options =
        prd_default_firebase_options.DefaultFirebaseOptions.currentPlatform;
    break;
  case "stg":
    options =
        stg_default_firebase_options.DefaultFirebaseOptions.currentPlatform;
    break;
  default:
    options =
        dev_default_firebase_options.DefaultFirebaseOptions.currentPlatform;
}

// firebaseを初期化
await Firebase.initializeApp(
  options: options,
);
```

:::note warn
chatGPTが教えてくれたので、これがいい方法なのかはわからない。今度リサーチしたい。
:::

### flutterに機能を追加
pubspec.yamlに使いたいfirebaseの機能を追加。
```pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
    
  # 省略
  
  firebase_core: ^2.31.0
  firebase_analytics:
  firebase_crashlytics:
```

認証を使いたいなら`firebase_auth`など。


## admob（広告）
この記事ではバナー広告だけ扱う。

[google_mobile_ads](https://pub.dev/packages/google_mobile_ads)を追加。

:::note info
info.plistなどに設定の記述がないままpubspec.yamlに追加して実行(コンパイル)しようとするとエラーが出て失敗するので、後で追加するか一旦コメントアウトしておくといい
:::

[admob](https://admob.google.com/intl/ja/home/)に登録する必要があるが簡単なので割愛。

手順は公式の[Flutter アプリに AdMob 広告を追加する](https://codelabs.developers.google.com/codelabs/admob-ads-in-flutter?hl=ja#0)で確認するのが良さそう。ただし、最終更新日が2022年なのはちょっと不安。

### admobで広告を作成
これも簡単な作業なので一旦割愛。

### firebaseとリンク
admobの設定画面の「アプリリンクの管理」からリンク
すでにfirebaseでプロジェクトを作ってる場合は、identifierを入力すると該当のプロジェクトが出てくる。

### IDを記述
設定ファイルに色々記述がないと表示ができない。というかそもそもコンパイルできない。

開発時は動作確認用にテスト用の広告を使う。

:::note info
本番の広告の確認をするにはテストデバイスの登録をする必要がある。[テスト広告を有効にする](https://developers.google.com/admob/android/test-ads?hl=ja)を参考にするといい。
:::

テスト用のアプリIDと広告ユニットIDは[3. AdMob アプリと広告ユニットを設定する](https://codelabs.developers.google.com/codelabs/admob-ads-in-flutter?hl=ja#2)に書かれている↓の表から持ってくる。
![スクリーンショット 2024-05-06 20.30.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3776694/288614ee-1400-3618-a2f6-8c3102763049.png)

#### ios

ios/Runner/Info.plistを編集
`GADApplicationIdentifier`にアプリIDを割り当てる。
(↓のコードはテスト用ID)
``` Info.plist
...
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-3940256099942544~1458002511</string>
...
```

##### テスト用アプリID

```
ca-app-pub-3940256099942544~1458002511
```

##### テスト用広告ユニットID（バナー）
```
ca-app-pub-3940256099942544/2934735716
```

#### android

android/app/src/main/AndroidManifest.xmlを編集
ios同様アプリIDを割り当てる。
(↓のコードはテスト用ID)

``` AndroidManifest.xml
<manifest>
    ...
    <application>
       ...
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-3940256099942544~3347511713"/>
    </application>

</manifest>
```

##### テスト用アプリID

```
ca-app-pub-3940256099942544~3347511713
```

##### テスト用広告ユニットID（バナー）

```
ca-app-pub-3940256099942544/6300978111
```

##### ユニットID

ad_helper.dartにユニットIDを記述しておく。（名前はなんでもいい）

デバッグビルドとリリースビルドで返すIDを切り替えるようにしておくと便利。

```ad_helper.dart
import 'dart:io';

import 'package:flutter_dotenv/flutter_dotenv.dart';

class AdHelper {
  static String get BanneradUnitId {
    bool isDebug = false;

    // debug時のみ実行
    assert(isDebug = true);

    if (isDebug) {
      // デバッグ時
      if (Platform.isAndroid) {
        return "a-app-pub-3940256099942544/6300978111";
      } else if (Platform.isIOS) {
        return "ca-app-pub-3940256099942544/2934735716";
      } else {
        throw UnsupportedError("Unsupported platform");
      }
    } else {
      // リリース時
      if (Platform.isAndroid) {
        return dotenv.env['ANDROID_UNIT_ID']!;
      } else if (Platform.isIOS) {
        return dotenv.env['OS_UNIT_ID']!;
      } else {
        throw UnsupportedError("Unsupported platform");
      }
    }
  }
}

```

### 広告のロード
広告を表示するwidgetを作って、initState内で広告をロードする。
ロードが成功した時のみバAdWidgetを生成する。

:::note info
使われなくなったバナーは`dispose()`でリリースする。
```dart
@override
  void dispose() {
    adState.banner.dispose();

    super.dispose();
  }
```
:::

```dart
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';

import 'package:path/to/ad_helper.dart';

class BannerAdContainer extends StatefulWidget {
  const BannerAdContainer({super.key});

  @override
  State<BannerAdContainer> createState() => _BannerAdContainerState();
}

class _BannerAdContainerState extends State<BannerAdContainer> {
  BannerAd? banner;

  @override
  void initState() {
    super.initState();

    BannerAd(
      size: AdSize.banner,
      adUnitId: AdHelper.BanneradUnitId,
      listener: BannerAdListener(
        onAdLoaded: (ad) {
          debugPrint('Ad loaded: ${ad.adUnitId}');
          setState(() {
            banner = ad as BannerAd;
          });
        },
        onAdClosed: (ad) {
          debugPrint('Ad closed: ${ad.adUnitId}');
        },
        onAdFailedToLoad: (ad, error) {
          debugPrint('Ad failed to load: ${ad.adUnitId}, $error');
          ad.dispose();
        },
        onAdOpened: (ad) {
          debugPrint('Ad opened: ${ad.adUnitId}');
        },
        onAdClicked: (ad) {
          debugPrint('Ad clicked: ${ad.adUnitId}');
        },
        onAdImpression: (ad) {
          debugPrint('Ad impressed: ${ad.adUnitId}');
        },
        onAdWillDismissScreen: (ad) {
          debugPrint('Ad screen dismissed: ${ad.adUnitId}');
        },
      ),
      request: const AdRequest(),
    ).load();
  }

  @override
  void dispose() {
    banner?.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey,
      height: 100,
      child: Center(
        child: banner != null
            ? AdWidget(ad: banner!)
            : const Text(
                '広告',
                style: TextStyle(color: Colors.white),
              ),
      ),
    );
  }
}
```

### google mobile adsを初期化

広告をロードする前の適当なところで初期化処理を行う。
とりあえずmainでやっておけばいい。
```main.dart
import 'package:google_mobile_ads/google_mobile_ads.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  MobileAds.instance.initialize();
  runApp(MyApp());
}
```

### 広告を表示
適当なところに表示する。バナーを画面上部か下部に表示するならColumn使っとく。

```dart
 body: Column(
        children: [
          Expanded(
            // 省略
          ),
          const BannerAdContainer(),
        ],
      ),
```

### テストデバイスの設定
テストデバイスを設定して安全に本番広告を配信する。

admobにログインして、設定　> テストデバイスからテストデバイスを追加する。


↓参考

https://support.google.com/admob/answer/9691433?hl=ja

### app idを切り替える

[環境切り替え](#環境切り替え)の要領で切り替える。

dev.json, stg.json, prd.jsonにそれぞれapp idを追加する。
prd.jsonだけ(場合によってはstg.jsonも)idを本番用idにする。
（↓はテスト用app id）

```dev.json
{
    // 省略
    "admobAppIdIos": "ca-app-pub-3940256099942544~1458002511",
    "admobAppIdAndroid": "ca-app-pub-3940256099942544~3347511713"
}
```

:::note warn
prd.jsonは必ず.gitignoreに追加する！！！！
先にprd.jsonがpushされている場合などは[gitignore後で追加した時](#gitignore後で追加した時)の方法で削除する。
:::

本当はprd.jsonに直接idを書きたくなかったけど、これ以外のいい方法がわからなかったので何か他にあったら知りたい。

#### ios

info.plistの`GADApplicationIdentifier`の値を↓のように変更する。
```info.plist
		<key>GADApplicationIdentifier</key>
		<string>$(admobAppIdIos)</string>
  	</dict>
</plist>
```
これでiosのapp idが切り替わる

#### android

android/app/build.gradleの`defaultConfig`に`resValue`を追加する。

```build.gradle
    defaultConfig {
        // 省略
        applicationId "${dartDefines.appId}"
        resValue "string", "app_name", "${dartDefines.appName}"

        // ↓追加
        resValue "string", "admob_app_id_android", "${dartDefines.admobAppIdAndroid}"
    }
```

android/app/src/main/AndroidManifest.xmlの`com.google.android.gms.ads.APPLICATION_ID`の値を`@string/admob_app_id_android`に書き換える。

```AndroidManifest.xml
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="@string/admob_app_id_android"/>
    </application>
```

これでandroidのapp idが切り替わる。

### 実機デバイス用の広告リクエス設定
このままではデバイスIDを設定したスマホでテストモードの本番広告（正式な名称がわからない）を表示することが出来ない。
test device identifier（デバイスIDをハッシュ化したもの？）をコード上で設定してやらなければいけない。
（xcodeでログを見た時に初めて気づいたので、実機で広告を表示するのに随分時間がかかった。）

```bash
<Google> To get test ads on this device, set: 
Objective-C
	GADMobileAds.sharedInstance.requestConfiguration.testDeviceIdentifiers = @[ @"00000000000000000000000000000" ];
Swift
	GADMobileAds.sharedInstance().requestConfiguration.testDeviceIdentifiers = [ "000000000000000000000000000000" ]
```

#### デバイスIDを取得する

```dart
import 'package:device_info_plus/device_info_plus.dart';

DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
String? testDeviceId;

if (Platform.isIOS) {
  IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
  testDeviceId = iosInfo.identifierForVendor;
} else if (Platform.isAndroid) {
  AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
  testDeviceId = androidInfo.id;
}

debugPrint('Test Device ID: $testDeviceId');
```

#### identifierを設定する

```dart
// デバイスIDをMD5でハッシュ化
var bytes = utf8.encode(testDeviceId);
var digest = md5.convert(bytes);
var id = digest.toString();
RequestConfiguration requestConfiguration = RequestConfiguration(
  testDeviceIds: [id],
);
await MobileAds.instance
    .updateRequestConfiguration(requestConfiguration);
```

これだけだと、設定が終わる前にロードしてしまって失敗するので、非同期に制御してあげた方がいい。

## ステータスバーの色を変更する(android)
(特にこだわりが無ければやらなくてもいい。けどiosとデザイン変わってしまうのでやった方がいいかも。)

androidは自分でステータスバーの色を変えてあげなきゃいけない(っぽい)

``Platform``を使うため``dart:io``をインポート。

```dart
import 'dart:io';

// 省略

void main() {
  // 省略

  if (Platform.isAndroid) {
    SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
      statusBarColor: Colors.white, // ステータスバーの背景色を設定
    ));
  }

  runApp(const MyApp());
}
```

## 番外編

### gitignore後で追加した時

.gitignoreにignoreしたいものを追加する。

```
git rm --cached ファイル名
```
or 
```
git rm --cached -r ディレクトリ名
```
で削除。
commit & push するとリモートからgitignoreしたいものが消える。

↓参考

https://qiita.com/yutosa3/items/25ab031c8061e8c9a4c4

### admobログイン
なぜかarcではページを読み込めないのでchromeとかでログインする。
（↑これマジでなんで？）