Flutter開発環境をDockerで構築し、Androidアプリを実行する方法(Mac/Windows)

# モチベーション
Flutter開発において、開発環境構築は意外と手間がかかります。特に、macOSとWindowsで開発環境を統一したい場合、それぞれのOSに合わせた設定が必要となり、煩雑になりがちです。そこで、DockerとVSCodeのDev Containersを利用することで、OSに依存しない一貫した開発環境を構築し、開発効率を向上させることを目指します。

# Dockerコンテナをビルドする前にすること
### Android Studioでエミュレータをセットアップ:
Androidアプリの動作確認に必要なAndroidエミュレータを事前に設定します。

### VSCodeとDev Containers拡張機能のインストール
VSCodeと、Dockerコンテナ内で開発を行うためのDev Containers拡張機能をインストールします。

### macOS
#### OrbStackのインストール
Docker Desktopよりも高速で軽量なOrbStackを推奨します。
Appleシリコンの場合はRosettaをインストールし、有効にします。これにより、x86_64アーキテクチャのコンテナイメージをAppleシリコン上で実行できます。

homebrewでインストールするのがおすすめです。

```bash
brew install orbstack
```

### Windows
#### Docker Desktopのインストール
WindowsでDockerを利用するための標準的なツールです。


# 開発環境の構築
### 設定ファイルの作成
Dev Containersを使って、開発用のDockerコンテナをビルドするために、Dockerfile、docker-compose.yml、devcontainer.jsonを作成します。

開発用のディレクトリを作成して、VSCodeで開きます。VSCodeで以下の構成でファイルを作成します。

```bash
.
└── .devcontainer
    ├── Dockerfile
    ├── devcontainer.json
    └── docker-compose.yml
```

#### Dockerfile

```dockerfile
FROM debian:bullseye-slim

# bash を既定のシェルにする
SHELL ["/bin/bash", "-c"]

# 必要なパッケージのインストール
RUN apt-get update && \
    apt-get install -y curl git wget unzip zip xz-utils sudo && \
    rm -rf /var/lib/apt/lists/*

# 開発者ユーザーの追加とsudo権限の付与
RUN useradd -ms /bin/bash developer
RUN echo "developer ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

# 開発者ユーザーのホームディレクトリを設定
ENV HOME=/home/developer

# Java(Zulu OpenJDK)のインストール
ENV JAVA_HOME=/opt/zulu17.56.15-ca-jdk17.0.14-linux_x64

RUN wget -P /tmp/ https://cdn.azul.com/zulu/bin/zulu17.56.15-ca-jdk17.0.14-linux_x64.zip
RUN unzip /tmp/zulu17.56.15-ca-jdk17.0.14-linux_x64.zip -d /opt

# Javaのパスを設定
ENV PATH=/opt/zulu17.56.15-ca-jdk17.0.14-linux_x64/bin:$PATH

# Android SDKのインストールと設定
ENV ANDROID_SDK_ROOT=/opt/Android/sdk
ENV ANDROID_HOME=$ANDROID_SDK_ROOT

# Android SDK コマンドラインツールの展開とディレクトリ整理
RUN mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools
# Android command-line tools のダウンロード
RUN wget -P /tmp/ https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
RUN unzip /tmp/commandlinetools-linux-11076708_latest.zip -d ${ANDROID_SDK_ROOT}/cmdline-tools/ && \
mv ${ANDROID_SDK_ROOT}/cmdline-tools/cmdline-tools ${ANDROID_SDK_ROOT}/cmdline-tools/latest

# Android cmdline-tools のパスを追加
ENV PATH=${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin:$PATH
ENV PATH=${ANDROID_SDK_ROOT}/platform-tools:$PATH
ENV PATH=${ANDROID_SDK_ROOT}/platforms:$PATH

# Flutter SDKのインストールと設定
ENV FLUTTER_ROOT=/opt/flutter

# Flutter SDK のダウンロードと展開
RUN wget -P /tmp https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.24.4-stable.tar.xz && \
    tar -xf /tmp/flutter_linux_3.24.4-stable.tar.xz -C /opt/

# Flutterのパスを設定
ENV PATH=${FLUTTER_ROOT}/bin:$PATH

# 不要なファイルを削除
RUN rm -rf /tmp/*
# 各種ディレクトリの所有者を開発者ユーザーに変更
RUN chown -R developer:developer /opt/*

# 開発者ユーザーに切り替え
USER developer
# 作業ディレクトリを設定
WORKDIR $HOME

# Android SDK の各コンポーネントを sdkmanager でインストール（yesで自動応答）
RUN yes | sdkmanager --sdk_root=$ANDROID_SDK_ROOT "platform-tools" "platforms;android-34" "build-tools;33.0.1"

# Android ライセンスに同意
RUN yes | flutter doctor --android-licenses
# Flutter の初回設定（analytics無効化）
RUN flutter --disable-analytics

# コンテナ起動時にbashを実行
CMD ["/bin/bash"]
```

#### docker-compose.yml

```yaml
services:
  flutter:
    build:
      context: ./
    tty: true
    volumes:
      - workspace-data:/home/developer/workspace
    # LinuxのFlutterがx64アーキテクチャにしか対応していないため設定
    platform: linux/x86_64

volumes:
  workspace-data:
```

#### devcontainer.json

```jsonc
{
  "name": "debian_flutter",
  "workspaceFolder": "/home/developer/workspace",
  "dockerComposeFile": [
    "docker-compose.yml"
  ],
  "service": "flutter",
  "customizations": {
    "vscode": {
      "settings": {
        // VSCodeの好みの設定を追加可能
        "files.autoSave": "onFocusChange",
        "[dart]": {
          "editor.formatOnSave": true,
          "editor.formatOnType": true,
          "editor.rulers": [
            80
          ],
          "editor.selectionHighlight": false,
          "editor.tabCompletion": "onlySnippets",
          "editor.wordBasedSuggestions": "off"
        }
      },
      "extensions": [
        // 好みの拡張機能を追加可能
        "Dart-Code.flutter",
        "Dart-Code.dart-code"
      ]
    }
  }
}
```

### コンテナのビルド実行

- VSCodeでコマンドパレットを開きます
    - Mac: Shift + Command + P
    - Windows: Shift + Ctrl + P
- 「\>Dev Containers: Reopen in Container」を選択すると、コンテナのビルドが実行され、ビルド後VSCodeにコンテナが開かれ、開発作業ができるようになります


# コンテナのビルド後にやること
### Android StudioでAndroidエミュレータを起動
Androidアプリの動作確認に必要なAndroidエミュレータを起動します。

### flutter doctorの実行と確認
コンテナ内でflutter doctorを実行し、以下の表示になることを確認します。

実行コマンド：

```bash
flutter doctor
```

表示：

```bash
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.24.4, on Debian GNU/Linux 11 (bullseye) 6.12.13-orbstack-00304-gede1cf3337c4, locale en_US.UTF-8)
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.1)
[✗] Chrome - develop for the web (Cannot find Chrome executable at google-chrome)
    ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.
[✗] Linux toolchain - develop for Linux desktop
    ✗ clang++ is required for Linux development.
      It is likely available from your distribution (e.g.: apt install clang), or can be downloaded from https://releases.llvm.org/
    ✗ CMake is required for Linux development.
      It is likely available from your distribution (e.g.: apt install cmake), or can be downloaded from https://cmake.org/download/
    ✗ ninja is required for Linux development.
      It is likely available from your distribution (e.g.: apt install ninja-build), or can be downloaded from
      https://github.com/ninja-build/ninja/releases
    ✗ pkg-config is required for Linux development.
      It is likely available from your distribution (e.g.: apt install pkg-config), or can be downloaded from
      https://www.freedesktop.org/wiki/Software/pkg-config/
[!] Android Studio (not installed)
[✓] Connected device (1 available)
[✓] Network resources
```

### Flutterプロジェクトの作成
任意のプロジェクト名を指定して、Flutterプロジェクトを作成します。

実行コマンド：

```bash
flutter create sample_app
```

プロジェクトディレクトリに移動します。

実行コマンド：

```bash
cd sample_app
```

### Gradleのファイルシステム監視を無効化
ホストOSでAndroidエミュレータを起動し、コンテナ内でflutter runをする場合、Gradleのファイルシステム監視機能が使えないため、無効化します。

実行コマンド：

```bash
echo 'org.gradle.unsafe.watch-fs=false' >> android/gradle.properties
```

android/gradle.propertiesファイルを開き、org.gradle.unsafe.watch-fs=falseが追記されているか確認する。

### adbサーバへの接続
ホストのadbサーバと接続し、Androidエミュレータとの通信を確立します。

実行コマンド：

```bash
adb connect host.docker.internal:5555
```

Androidエミュレータと接続する際、「Allow USB debugging?」の許可を求められたら許可します。

再度「adb connect host.docker.internal:5555」を実行し、「already connected to host.docker.internal:5555」と表示されれば接続成功です。

### flutter runの実行
flutter runコマンドを実行し、アプリを起動します。

実行コマンド：

```bash
flutter run
```

Gradleのファイルシステム監視機能が使えないため、「Couldn't poll for events, error = 4」という警告が表示されることがありますが、アプリは正常に実行されます。

初回のアプリ起動には時間がかかる場合があります。ターミナルに以下のメッセージが表示されれば、アプリの起動は成功です。

```bash
Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
```

### Hot Reloadの確認
lib/main.dartのAppBarのタイトルを「Flutter Demo Home Page」から「Hello Flutter Demo Home Page」に変更します。
ターミナルでrキーを押下し、Hot Reloadを実行します。
エミュレータ上のAppBarのタイトルが変更されていれば、Hot Reloadは正常に動作しています。

### 開発の進め方
Flutterの開発は、libディレクトリ以下のdartファイルを編集していくことで進めます。Hot Reloadを活用することで、変更を素早く確認しながら開発を進めることができます。

# まとめ
DockerとDev Containersを利用することで、macOSとWindowsで一貫したFlutter開発環境を構築できます。これにより、環境構築の手間を省き、開発に集中することができます。

間違っている点などありましたら、コメントをいただけると幸いです。