😮
FlutterでのiOSアプリ開発 - 学びと気づき
はじめに
本記事では、Flutterを使用したiOSアプリケーション開発を通じて得られた知見を共有します。
開発環境情報
執筆時点での環境情報は以下の通りです。
[✓] Flutter (Channel stable, 3.22.3, on macOS 15.3 darwin-arm64)
• Flutter version 3.22.3 on channel stable
• Framework revision b0850beeb2 (2024-07-16)
• Engine revision 235db911ba
• Dart version 3.4.4
• DevTools version 2.34.3
[✓] Xcode - develop for iOS and macOS (Xcode 16.1)
• Build 16B40
• CocoaPods version 1.16.2
[✓] VS Code (version 1.96.4)
• Flutter extension version 3.104.0
課題と解決策
1. CupertinoActivityIndicator の色設定の制限
課題:
- 指定した色よりも若干薄く表示されます。
-
CupertinoActivityIndicator
クラスでアルファ値が固定されているため、完全に一致させることが困難です。
コード詳細:
// activity_indicator.dart 内のアルファ値設定
const List<int> _kAlphaValues = <int>[
47, 47, 47, 47, 72, 97, 122, 147,
];
// 部分的に表示される場合のアルファ値
const int _partiallyRevealedAlpha = 147;
対応策:
- カスタムの ActivityIndicator を実装する選択肢もありますが、デザインチームと協議の上、今回は色の差異を許容することとしました。
2. アプリ非起動時のPush通知ハンドリング
課題:
-
firebase_messaging
パッケージを使用していますが、アプリ非起動時のPush通知ハンドリングに制限があります。 - バックグラウンドメッセージハンドラのコードを確認すると、Android以外のプラットフォームでは処理が早期リターンしていました。
Future<void> registerBackgroundMessageHandler(
BackgroundMessageHandler handler) async {
if (defaultTargetPlatform != TargetPlatform.android) {
return; // Android以外は早期リターン
}
// 以下Android向け処理
// ...
}
解決策:
- iOS側の AppDelegate.swift に処理を追加し、Flutter側とのブリッジを構築しました。
import Flutter
import UIKit
import FirebaseMessaging
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
// メソッドチャネル名の定義
let methodChannelName = "com.example.app/notification"
override func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Flutterプラグインの登録
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
if let controller = window?.rootViewController as? FlutterViewController {
// Flutterメソッドチャネルの初期化
let methodChannel = FlutterMethodChannel(name: methodChannelName, binaryMessenger: controller.binaryMessenger)
// Dart側に通知データを渡す
methodChannel.invokeMethod("firebaseMessagingBackgroundHandler", arguments: userInfo) { _ in
completionHandler(UIBackgroundFetchResult.newData)
}
} else {
completionHandler(UIBackgroundFetchResult.noData)
}
}
}
Flutter側で完結できたシーン
1. 画面レイアウト作成
基本的にMaterialデザインのコンポートを使用しました。
ProgressIndicatorやアラートなど一部のUIは、ユーザが慣れ親しんだものを利用する方針としたので、iOSとAndroidで出し分けることとしました。
2. 通信周り
http
パッケージやhttp_proxy
パッケージを使用することで、ネットワーク通信のロジックを共通化できました。ネットワーク接続状態の監視もconnectivity_plus
パッケージにより実現しています。
3. 権限管理
permission_handler
パッケージを使用することで、デバイス権限を管理しています。
カメラ利用やデータ収集許可などの説明文は Info.plist
に追加する必要があります。
4. データ保存
以下のパッケージを利用し、データ保存機能を実装しました。
-
キーバリューストア:
shared_preferences
パッケージ -
SQLiteデータベース:
drift
パッケージ
5. ビルド・デプロイ
Fastlaneを活用し、以下のプロセスを自動化しました。
- 証明書・プロビジョニングの管理
- ビルドの自動化
- テスト配布処理
6. 環境設定の管理
Flutter標準の--dart-define-from-file
オプションと環境設定JSONファイルを活用し、開発・ステージング・本番環境の設定を管理しています。
- 環境変数定義ファイル(
.json
)を用意
{
"apiKey": "example_key",
"baseUrl": "https://api.example.com",
"bundleId": "com.example.app",
"flavor": "dev"
}
- ビルド時に
-dart-define-from-file
オプションを使用 - 生成された設定は
Generated.xcconfig
ファイルに反映されます。
まとめ
すべての機能をFlutter側で完結させることはできませんでしたが、ロジックや画面作成など大半は共通化できたことで、全体的には効率的な開発ができたと考えています。
Discussion