Flutterの環境切替と配布前チェック(flavor / release build / 権限確認) の次に整理したいのが、できた APK をどう渡し、どう入れてもらい、配布後に何を確認するかです。flutter build apk 自体は単純ですが、端末側のインストール経路・上書き条件・MDM との境界は曖昧になりがちです。この記事では Android に絞り、release APK の作成から手動サイドロード、MDM へ渡す項目の整理までをつなげます。
1. ゴールと非対象
対象読者
- Flutter プロジェクトを
flutter runとflutter build apkまでは試した人 - Android 向け社内アプリを Google Play ではなく、手動配布または MDM 配布で回したい人
- 署名、release build、配布前チェックまでは進んだが、利用者へどう届けるかまだ整理できていない人
この記事で到達する状態
- release APK を作り、出力先を迷わず確認できる
adb install -rで検証端末へ上書きインストールできる- ファイルアプリから手動サイドロードする流れを説明できる
applicationIdとversionCodeが更新可否を決めると理解できる- MDM 担当へ何を渡せばよいかを一覧で整理できる
非対象
- iOS の Ad Hoc 配布や TestFlight
- Google Play Console への公開
- Intune や Workspace ONE など特定 MDM 製品の画面操作
- Sentry や Crashlytics など配布後監視
今回は Android の社内配布に絞ります。iOS、ストア公開、運用監視は後続へ分け、まずは「APK を作り、配り、更新ルールまで理解する」状態を固めます。
2. 先に配布方法の全体像を掴む
社内配布では、誰が配るかで使う経路が変わります。
| 方法 | 向いている場面 | 長所 | 先に気をつける点 |
|---|---|---|---|
adb install | 開発者、検証担当、手元の実機確認 | 最短で差し替えられる | adb が使える PC とケーブル接続が必要 |
| ファイル経由の手動サイドロード | 現場端末へ単発で配る | 利用者が実際に触る導線に近い | 未知のアプリのインストール許可が必要な場合がある |
| MDM 配布 | 複数端末へ繰り返し配る | 配布先と更新タイミングを管理しやすい | MDM 担当へ渡す情報を先に整理する必要がある |
flowchart LR
A[Flutter project] --> B[release APK を作成]
B --> C[adb install で検証端末へ配布]
B --> D[Download へ送って手動サイドロード]
C --> E[version と package を確認]
D --> E
E --> F[配布後チェック]
F --> G[MDM へ渡す項目を整理]
見るべき点は「インストールできたか」だけではありません。
- 更新版を上書きできるか
- 利用者がどこで許可操作をするか
- MDM へ移すときに APK 以外の何が必要か
この 3 点を先に分けておくと、配布方法が変わっても説明がぶれにくくなります。
3. 配布確認用の最小アプリを用意する
3-1. 環境構築と前提記事をそろえる
環境構築がまだの場合は Windows 11で始めるFlutter開発環境 を先に参照してください。アプリ名、アイコン、スプラッシュも含めて Android 側の見た目と署名をまとめて整えたい場合は Flutterアプリのネイティブ設定を整える(アプリ名 / アイコン / スプラッシュ / 署名) を、flavor や配布前チェックを先に固めたい場合は Flutterの環境切替と配布前チェック(flavor / release build / 権限確認) を参照してください。
3-2. Flutter プロジェクトを作成する
今回使う最小サンプルは、配布チャネルと対象パッケージを画面に表示するものです。
このサンプルは flutter run で確認します。この記事では、このまま APK 作成とサイドロード確認まで進めるため、DartPad ではなくエミュレーターまたは実機で動かします。
次のコマンドでプロジェクトを作成します。
flutter create warehouse_distribution_sample
cd warehouse_distribution_sample
3-3. エミュレーターを起動する
利用可能なエミュレーター一覧を確認します。
flutter emulators
表示された ID を指定して起動します。
flutter emulators --launch <emulator_id>
3-4. lib/main.dart を書き換える
lib/main.dart は次の内容で書き換えます。配布チャネル、想定 APK 名、対象パッケージ名を画面へ出し、配布後の確認で見比べやすくしたサンプルです。
import 'package:flutter/material.dart';
const distributionChannel = String.fromEnvironment(
'DIST_CHANNEL',
defaultValue: 'stg',
);
const apkFileName = String.fromEnvironment(
'APK_FILE_NAME',
defaultValue: 'app-release.apk',
);
const packageId = String.fromEnvironment(
'PACKAGE_ID',
defaultValue: 'com.example.warehouse_distribution_sample',
);
void main() {
runApp(const DistributionApp());
}
class DistributionApp extends StatelessWidget {
const DistributionApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Internal Distribution Sample',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
),
home: const DistributionHomePage(),
);
}
}
class DistributionHomePage extends StatelessWidget {
const DistributionHomePage({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('社内配布サンプル'),
),
body: ListView(
padding: const EdgeInsets.all(24),
children: [
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: theme.colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
distributionChannel.toUpperCase(),
style: theme.textTheme.labelLarge?.copyWith(
color: theme.colorScheme.onPrimaryContainer,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
Text(
'この build をどの端末へ配るかを確認する画面',
style: theme.textTheme.titleLarge,
),
const SizedBox(height: 8),
Text('APK 名: $apkFileName'),
Text('packageId: $packageId'),
],
),
),
const SizedBox(height: 24),
const _InfoCard(
title: '今回の確認項目',
lines: <String>[
'release APK を作成する',
'adb install と手動サイドロードの両方を試す',
'配布後に version と package を確認する',
'MDM へ渡す項目を整理する',
],
),
const SizedBox(height: 16),
const _InfoCard(
title: '配布時のメモ',
lines: <String>[
'同じ packageId で新しい versionCode を使う',
'単発検証は adb install が速い',
'利用者配布はファイル経由の導線も確認する',
],
),
],
),
);
}
}
class _InfoCard extends StatelessWidget {
const _InfoCard({
required this.title,
required this.lines,
});
final String title;
final List<String> lines;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: theme.textTheme.titleMedium),
const SizedBox(height: 12),
for (final line in lines)
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('・'),
const SizedBox(width: 8),
Expanded(child: Text(line)),
],
),
),
],
),
),
);
}
}
コードのポイント
① String.fromEnvironment でビルド時に配布情報を注入する
const distributionChannel = String.fromEnvironment(
'DIST_CHANNEL',
defaultValue: 'stg',
);
const apkFileName = String.fromEnvironment(
'APK_FILE_NAME',
defaultValue: 'app-release.apk',
);
const packageId = String.fromEnvironment(
'PACKAGE_ID',
defaultValue: 'com.example.warehouse_distribution_sample',
);
String.fromEnvironment はビルド時に --dart-define で渡した値を定数として埋め込みます。const として宣言することで実行時に変更できないことを明示でき、defaultValue を設定しておくと --dart-define を省略した場合でも動作します。
3-5. flutter run で起動する
エミュレーターで起動して、画面に表示される配布チャネルと packageId を確認します。
flutter run --dart-define=DIST_CHANNEL=stg --dart-define=APK_FILE_NAME=app-release.apk --dart-define=PACKAGE_ID=com.example.warehouse_distribution_sample
配布用の導線では、アプリの機能そのものより「いまどの build を見ているか」が大事になります。手動配布に入る前に、画面上の識別情報を一度そろえておくと確認が早くなります。
4. release APK を作成する
4-1. pubspec.yaml の version を更新する
上書きインストールの成否に関わるので、配布前に pubspec.yaml の version を確認します。次のように versionName + versionCode を上げておきます。
version: 1.0.0+3
1.0.0 が利用者に見せる versionName、+3 が Android 側の versionCode です。前回配布より versionCode が上がっていないと、同じ applicationId でも更新版として入れ替えられません。
4-2. release 署名を設定する
release APK を社内配布に使うなら、debug build ではなく署名済み release build を使います。新規 Flutter プロジェクトへ最小限の署名設定を入れます。アプリ名、アイコン、スプラッシュもまとめて整えたい場合は Flutterアプリのネイティブ設定を整える(アプリ名 / アイコン / スプラッシュ / 署名) を参照してください。
まずキーストアを作成します。PowerShell では次のコマンドで進められます。
keytool -genkey -v -keystore $env:USERPROFILE\upload-keystore.jks `
-storetype JKS -keyalg RSA -keysize 2048 -validity 10000 `
-alias upload
ここで控えるのは 3 つです。
- キーストアの保存先パス
storePasswordとkeyPasswordalias。この例ではupload
次に android/key.properties を作成します。
storePassword=your-store-password
keyPassword=your-key-password
keyAlias=upload
storeFile=C:\\Users\\your-name\\upload-keystore.jks
Windows では storeFile のバックスラッシュを二重にします。android/key.properties は機密情報を含むので、公開リポジトリでは .gitignore に追加しておきます。
続いて android/app/build.gradle.kts を更新します。import はファイル先頭、plugins ブロックより前に置き、android { ... } には signingConfigs と buildTypes.release の設定を追記します。
import java.io.FileInputStream
import java.util.Properties
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android {
signingConfigs {
create("release") {
val storeFilePath = keystoreProperties.getProperty("storeFile")
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
storeFile = storeFilePath?.let { rootProject.file(it) }
storePassword = keystoreProperties.getProperty("storePassword")
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
}
}
}
コードのポイント
① key.properties を Properties で読み込み、署名情報をソースコードに直書きしない
val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
パスワードやエイリアスをソースコードに直書きせず、key.properties から読み込む構成です。key.properties を .gitignore に追加しておくと、ソースコード管理から除外できます。
② buildTypes.release に signingConfig を紐付ける
release {
signingConfig = signingConfigs.getByName("release")
}
buildTypes.release に signingConfig を設定することで、flutter build apk --release 実行時に署名が自動で適用されます。debug build は別の signingConfig を使うため、release との分離が維持されます。
既存の compileOptions や defaultConfig は消さずに残してください。ここまで入っていれば、このあとの flutter build apk --release で署名済み APK を作成できます。
4-3. release APK を作成する
flutter build apk --release --dart-define=DIST_CHANNEL=stg --dart-define=APK_FILE_NAME=app-release.apk --dart-define=PACKAGE_ID=com.example.warehouse_distribution_sample
前の記事で flavor を入れているなら、次のように --flavor stg を付けて読み替えます。
flutter build apk --flavor stg --release --dart-define=DIST_CHANNEL=stg --dart-define=APK_FILE_NAME=app-stg-release.apk --dart-define=PACKAGE_ID=com.example.warehouse_pocket.stg
出力先は通常、次の場所です。
build/app/outputs/flutter-apk/app-release.apk
flavor を付けた場合は app-stg-release.apk のようにファイル名が変わります。配布時に取り違えやすいので、Explorer で実ファイル名まで確認しておくと安全です。
5. まずは adb install で配布する
adb install は、開発者や検証担当が最短で差し替える方法です。USB 接続した実機へ入れる場合は、先に端末側で「開発者向けオプション → USB デバッグ」をオンにし、接続時に表示される「USB デバッグを許可しますか?」ダイアログで許可しておきます。ここが済んでいないと、adb devices に端末が出ません。
まずは接続中デバイスを確認します。
adb devices
1 台だけ接続されているなら、次のコマンドで上書きインストールできます。
adb install -r build/app/outputs/flutter-apk/app-release.apk
複数端末がつながっている場合は、device id を付けて明示します。
adb -s <device_id> install -r build/app/outputs/flutter-apk/app-release.apk
-r は既存アプリを残したまま差し替える指定です。同じ packageId で、より新しい versionCode を持つ APK なら更新版として入れ替えられます。
インストール後はホーム画面またはアプリ一覧から起動し、配布チャネルと packageId の表示が想定どおりか確認します。
6. 利用者向けの手動サイドロードを確認する
利用者へ渡す場合は、ファイルからインストールする導線も見ておいたほうが実務に合います。まず APK を端末の Download フォルダへ送ります。
adb push build/app/outputs/flutter-apk/app-release.apk /sdcard/Download/
エミュレーターと実機を同時に起動しているなど、複数デバイスが見えている場合は device id を付けて明示します。
adb devices
adb -s <device_id> push build/app/outputs/flutter-apk/app-release.apk /sdcard/Download/
ここでは APK を端末へ送る手段として adb push を使っていますが、手動サイドロード自体に USB デバッグは必須ではありません。メール、チャット、社内ストレージ、ローカル共有などで APK を端末へ渡せるなら、その経路から Download フォルダへ保存してインストールしても進められます。
その後、端末側で Download フォルダを開き、APK をタップしてインストールします。ここで初めて止まりやすいのが「未知のアプリのインストール許可」です。
- メールアプリやチャットアプリから開くなら、その提供元に対する許可が必要なことがある
- Files アプリから開くなら、Files に対する許可が必要なことがある
- 一度許可しても、別のアプリから開くと再び許可が必要になることがある
- MDM 管理端末では、この許可を利用者が触れないようポリシーで固定していることがある
設定画面を開いて確認したいときは、次のコマンドでも遷移できます。
adb shell am start -a android.settings.MANAGE_UNKNOWN_APP_SOURCES
複数デバイスがある場合は、この画面遷移も device id を付けておくと意図した端末を開けます。
adb -s <device_id> shell am start -a android.settings.MANAGE_UNKNOWN_APP_SOURCES
社内端末でこの設定変更が許可されていないなら、手動サイドロードより MDM 配布のほうが実運用に向きます。利用者に毎回この操作をお願いする運用は、台数が増えるほど維持しにくい形です。
7. 配布後に確認する項目を固定する
インストールできたら終わりではありません。更新版が入るかどうかは、applicationId と versionCode の組み合わせで決まります。
| 条件 | 結果 | どう見るか |
|---|---|---|
同じ applicationId で、より新しい versionCode | 上書き更新できる | 既存アプリを残したまま差し替えられる |
同じ applicationId で、同じまたは古い versionCode | 更新できない | APK を作り直すか、いったんアンインストールが必要 |
異なる applicationId | 別アプリとして並列インストールされる | dev と stg を共存させたいときに使う |
配布後にアプリ情報画面を開いて確認するには、次のコマンドが使えます。
adb shell am start -a android.settings.APPLICATION_DETAILS_SETTINGS -d package:com.example.warehouse_distribution_sample
エミュレーターと実機を同時に起動しているなど、複数デバイスが見えている場合は device id を付けて明示します。
adb -s <device_id> shell am start -a android.settings.APPLICATION_DETAILS_SETTINGS -d package:com.example.warehouse_distribution_sample
ここで見たい点は 4 つです。
- アプリ名が対象 build と一致しているか
versionNameとversionCodeが配布予定どおりか- 想定していない旧アプリが端末に残っていないか
stgとprodを取り違えるような並びになっていないか
前の記事で flavor を入れているなら、package: の値を com.example.warehouse_pocket.stg のように置き換えて確認してください。配布後のトラブルは、APK 生成よりここでの取り違えから起きることが多くあります。
8. MDM 配布へ広げるときの整理項目
手動サイドロードは 1 台ずつの確認には向きますが、配布台数が増えると限界が見えます。ここでいう MDM は Mobile Device Management の略で、社内端末へアプリ配布や設定反映をまとめて管理する仕組みです。次のような状況なら、MDM 配布へ寄せたほうが管理しやすくなります。
- 配布先が複数チーム、複数拠点に広がる
- 利用者へ個別にインストール操作を説明したくない
- 更新版を一斉に配りたい
- 端末ごとの許可設定を利用者へ触らせたくない
MDM 担当へ渡す項目は、少なくとも次をそろえると話が早くなります。
| 項目 | 何を書くか | 理由 |
|---|---|---|
| APK | 配布に使う signed release APK のパスまたは共有先 | 登録対象そのものになる |
applicationId | 例: com.example.warehouse_pocket.stg | 更新対象の特定に必要 |
versionName / versionCode | 例: 1.0.0+3 | 上書き更新可否の判断に必要 |
| 配布先 | 対象部署、端末グループ、検証端末か本番端末か | 誤配布を防ぐ |
| インストール方式 | サイレント配布か、利用者承認付きか | 端末ポリシーと手順が変わる |
| 更新方針 | 強制更新か、検証後に順次更新か | 配布タイミングの判断に必要 |
| ロールバック方針 | 旧版を残すか、旧 APK を別保管するか | 不具合時の戻し方を決める |
ここで注意したいのは、MDM の製品ごとに受け付ける形式や管理画面の文言が違うことです。直接 APK を登録する製品もあれば、別の保管場所や Android Enterprise 側の仕組みを使う製品もあります。この記事では特定製品の画面操作までは扱わず、製品が変わっても共通で必要になる引き継ぎ項目に絞ります。製品選定の比較に入る前に、社内で必要な項目を上の表で固定しておくと、ベンダー差を後から吸収しやすくなります。
9. まとめ
社内配布で最初に固めたいのは、APK を作ることより、どの経路で誰に渡し、更新判定を何で見るかです。開発者や検証担当には adb install -r が速く、利用者にはファイル経由の手動サイドロードが現実に近くなります。ただし手動サイドロードは導入コストを抑えやすい一方、更新操作そのものは端末ごとに残ります。配布台数が増えたら、APK に加えて applicationId、versionName / versionCode、配布先、更新方針をそろえたうえで MDM へ移ると整理しやすくなります。
配布前の flavor や release build を見直したい場合は Flutterの環境切替と配布前チェック(flavor / release build / 権限確認) を参照してください。署名設定をやり直したい場合は Flutterアプリのネイティブ設定を整える(アプリ名 / アイコン / スプラッシュ / 署名) を、端末側での回帰確認を自動化したい場合は FlutterのIntegration Test入門(ログインから一覧表示まで確認する) を参照してください。