はじめに (対象読者・この記事でわかること)

この記事は、FlutterでAndroidアプリ開発をしている方、特に本番環境と開発環境でアプリの挙動を切り替えたい方を対象としています。アプリのビルドタイプごとに異なる設定を行い、デバッグ版ではデバッグ用の機能を有効にし、リリース版では不要な機能を無効化したいと考えている方に最適です。

この記事を読むことで、Flutterアプリでリリース版とデバッグ版を別々に動かす設定方法、ビルドタイプごとの設定方法、環境変数を活用したアプリの挙動切り替え方法、そして実際のコード例を交えた具体的な実装手順を理解できます。これにより、開発効率を向上させ、本番環境と開発環境で明確に区別されたアプリを提供できるようになります。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 - Flutterの基本的な開発知識 - Android Studioの基本的な操作 - Dart言語の基本的な文法

Flutterアプリのビルドタイプとは

FlutterでAndroidアプリを開発する際、リリース版とデバッグ版で挙動を切り替えたいケースは多々あります。例えば、デバッグ版ではデバッグ用のログを表示したり、テスト用のAPIエンドポイントを使用したり、リリース版では不要な機能を無効化したりといったことが考えられます。

Androidアプリでは、一般的にビルドタイプ(DebugとRelease)ごとにアプリの挙動を切り替える仕組みがあります。Flutterでもこの機能を活用することで、開発中のデバッグ版とリリース版で異なる挙動を持たせることができます。

本記事では、FlutterアプリでAndroidのリリース版とデバッグ版を別々に動かす具体的な設定方法と実装例について解説します。

ビルドタイプごとの設定方法

ステップ1:Androidのビルドタイプの確認

まずは、Androidのビルドタイプがどのように設定されているか確認します。Flutterプロジェクトでは、Androidのビルド設定はandroid/app/build.gradleファイルに記述されています。

Gradle
android { compileSdkVersion 33 sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.myapp" minSdkVersion 21 targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } }

このファイルでは、buildTypesセクションでビルドタイプを定義しています。デフォルトではdebugreleaseの2つのビルドタイプが設定されています。

ステップ2:ビルドタイプごとの設定を追加

次に、ビルドタイプごとに異なる設定を追加します。build.gradleファイルを編集して、デバッグ版とリリース版で異なる設定を追加します。

Gradle
android { // ... (前略) buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug // リリース版の設定 minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { // デバッグ版の設定 applicationIdSuffix ".debug" debuggable true } } }

この例では、リリース版ではコードの圧縮(minifyEnabled)とリソースの削除(shrinkResources)を有効にし、デバッグ版ではアプリケーションIDに".debug"サフィックスを追加しています。これにより、デバイス上で両方のバージョンを同時にインストールできます。

ステップ3:Dartコードでのビルドタイプの判定

次に、Dartコードで現在のビルドタイプがデバッグ版かリリース版かを判定する方法を追加します。これにより、アプリの挙動をビルドタイプごとに切り替えることができます。

Dart
import 'package:flutter/foundation.dart'; class BuildConfig { static const bool isDebug = kDebugMode; static const bool isRelease = kReleaseMode; static String get buildType { if (kDebugMode) return 'debug'; if (kReleaseMode) return 'release'; return 'unknown'; } }

このコードでは、Flutterのfoundationパッケージに含まれるkDebugModekReleaseModeを使用して、現在のビルドタイプを判定しています。

ステップ4:ビルドタイプごとの挙動の実装

次に、ビルドタイプごとに異なる挙動を持つコードを実装します。例えば、デバッグ版ではデバッグ用のログを表示し、リリース版ではログを非表示にする場合の実装例を以下に示します。

Dart
import 'package:flutter/material.dart'; import 'package:logger/logger.dart'; void main() { // ビルドタイプごとの設定 Logger.level = BuildConfig.isDebug ? Level.debug : Level.error; runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(), ); } } class MyHomePage extends StatelessWidget { const MyHomePage({super.key}); @override Widget build(BuildContext context) { // ビルドタイプごとに異なるUIを表示 return Scaffold( appBar: AppBar( title: Text('Build Type: ${BuildConfig.buildType}'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headlineMedium, ), const SizedBox(height: 20), // デバッグ版のみ表示されるウィジェット if (BuildConfig.isDebug) ElevatedButton( onPressed: () { // デバッグ用の機能 debugPrint('Debug button pressed'); }, child: const Text('Debug Button'), ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } }

この例では、BuildConfig.isDebugを使用して、デバッグ版のみで表示されるボタンを条件付きで表示しています。また、Loggerのログレベルもビルドタイプごとに変更しています。

ステップ5:環境変数の使用

さらに、環境変数を使用してビルドタイプごとに異なる設定を適用する方法もあります。まず、android/app/build.gradleファイルに環境変数を追加します。

Gradle
android { // ... (前略) defaultConfig { // ... (前略) // 環境変数の設定 buildConfigField "String", "API_BASE_URL", "\"https://api.example.com\"" buildConfigField "boolean", "ENABLE_DEBUG_FEATURES", "true" } buildTypes { release { // ... (前略) // リリース版の環境変数 buildConfigField "String", "API_BASE_URL", "\"https://api.prod.example.com\"" buildConfigField "boolean", "ENABLE_DEBUG_FEATURES", "false" } debug { // ... (前略) // デバッグ版の環境変数 buildConfigField "String", "API_BASE_URL", "\"https://api.dev.example.com\"" buildConfigField "boolean", "ENABLE_DEBUG_FEATURES", "true" } } }

次に、Dartコードでこれらの環境変数にアクセスします。

Dart
import 'package:flutter/services.dart'; class AppConfig { static const MethodChannel _channel = MethodChannel('com.example.myapp/app_config'); static Future<String> get apiBaseUrl async { final String url = await _channel.invokeMethod('getApiBaseUrl'); return url; } static Future<bool> get enableDebugFeatures async { final bool enabled = await _channel.invokeMethod('getEnableDebugFeatures'); return enabled; } }

そして、AndroidのMainActivity.javaファイルで、これらの値を提供するコードを追加します。

Java
package com.example.myapp; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.plugin.common.MethodChannel; import android.content.Context; public class MainActivity extends FlutterActivity { private static final String CHANNEL = "com.example.myapp/app_config"; @Override public void configureFlutterEngine(FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL) .setMethodCallHandler( (call, result) -> { if (call.method.equals("getApiBaseUrl")) { // BuildConfigからAPI_BASE_URLを取得 result.putString(BuildConfig.API_BASE_URL); } else if (call.method.equals("getEnableDebugFeatures")) { // BuildConfigからENABLE_DEBUG_FEATURESを取得 result.putBoolean(BuildConfig.ENABLE_DEBUG_FEATURES); } else { result.notImplemented(); } } ); } }

これにより、DartコードからAndroidのビルド設定に基づいた環境変数にアクセスできるようになります。

ハマった点やエラー解決

問題1:ビルドタイプごとにアプリケーションIDが同じでインストールできない

デフォルトの設定では、デバッグ版とリリース版でアプリケーションIDが同じであるため、デバイス上に同時にインストールできません。

解決策:

デバッグ版には".debug"のようなサフィックスを追加して、リリース版と区別します。

Gradle
buildTypes { release { // ... (前略) } debug { applicationIdSuffix ".debug" } }

問題2:Dartコードからビルドタイプを正しく判定できない

kDebugModekReleaseModeが期待通りに動作しない場合があります。

解決策:

Flutterのバージョンやビルド設定を確認し、必要であればdart-defineを使用して明示的にビルドタイプを指定します。

Bash
flutter run --debug flutter run --release

または、dart-defineを使用してカスタムフラグを渡します。

Bash
flutter run --dart-define=DEBUG_MODE=true

解決策

これらの問題を解決するためには、ビルド設定を正しく構成し、Dartコードでビルドタイプを適切に判定することが重要です。また、必要に応じてdart-defineを使用してカスタムフラグを渡すことで、より柔軟なビルド管理が可能になります。

まとめ

本記事では、FlutterでAndroidアプリのリリース版とデバッグ版を別々に動かす方法について解説しました。

  • ビルドタイプごとの設定方法を学んだ
  • Dartコードでビルドタイプを判定する方法を学んだ
  • ビルドタイプごとに異なるUIや機能を実装する方法を学んだ
  • 環境変数を使用してビルドタイプごとに異なる設定を適用する方法を学んだ

この記事を通して、Flutterアプリの開発において、デバッグ版とリリース版で異なる挙動を持たせるための具体的な手法を理解できたかと思います。これにより、開発効率の向上や品質の向上に繋がるでしょう。

今後は、iOSアプリでの同様の設定方法や、CI/CDパイプラインでのビルドタイプの管理方法についても記事にする予定です。

参考資料