Flutter開発でつまずく「iOSとAndroidの挙動差」まとめ

iOS
この記事は約16分で読めます。

この記事の最終更新日: 2026年6月10日

Flutterは、1つのコードベースでiOSアプリとAndroidアプリを開発できる便利なフレームワークです。

DartでUIを書けば、iPhoneでもAndroidでも同じように動くアプリを作れます。

しかし、実際にFlutterでアプリを作っていると、こう感じる場面があります。

Androidでは動いたのに、iOSでは動かない
iOSでは通知が出るのに、Androidでは出ない
同じコードなのに、スクロールや戻る動きが微妙に違う
Flutterだけで完結すると思ったら、ネイティブ設定が必要だった

Flutterはクロスプラットフォーム開発を楽にしてくれますが、iOSとAndroidの違いを完全になくしてくれるわけではありません

この記事では、Flutter開発でつまずきやすいiOSとAndroidの挙動差を、初心者向けにまとめます。


FlutterでもiOSとAndroidの違いは残る

Flutterを使うと、UIや画面遷移、状態管理など、多くの部分を共通コードで書けます。

しかし、アプリが実際に動くのはiOSやAndroidという別々のOSの上です。

そのため、次のような部分ではOSごとの差が出ます。

  • 権限
  • 通知
  • 位置情報
  • バックグラウンド動作
  • 画面遷移
  • 戻る操作
  • スクロールの感触
  • キーボード
  • ファイルアクセス
  • ビルド設定
  • ストア申請

Flutter公式ドキュメントでも、FlutterはAndroidとiOSのOS環境に由来する動作、たとえばテキスト編集やスクロールなどについては自動適応する一方、アプリ設計上の選択が必要なプラットフォーム慣習については自動では適応しないと説明されています。(Flutter ドキュメント)

つまり、Flutter開発では、

Flutterで共通化できる部分
OSごとに理解・調整が必要な部分

を分けて考えることが大切です。


つまずき1:画面遷移や戻る操作が違う

Flutterでは Navigator.push()go_router などを使って画面遷移を実装できます。

しかし、同じ画面遷移でも、iOSとAndroidでは見え方や操作感が異なります。

Flutter公式ドキュメントでは、Androidのデフォルト遷移はAndroidの画面遷移に近い動きになり、iOSでは横方向のpush遷移やモーダル遷移など、iOSらしい動きになると説明されています。(Flutter ドキュメント)

また、戻る操作にも違いがあります。

Androidでは、OSの戻るボタンや戻るジェスチャーが重要です。

iOSでは、画面左端からのスワイプで戻る操作がよく使われます。Flutter公式ドキュメントでも、AndroidではOSの戻るボタンがFlutterのNavigatorに送られ、iOSではエッジスワイプジェスチャーで戻れると説明されています。(Flutter ドキュメント)

よくあるつまずき

  • Androidの戻るボタンを考慮していない
  • iOSのスワイプバックで想定外の戻り方をする
  • モーダル画面の閉じ方がOSごとに違って見える
  • Androidでは自然でも、iOSでは違和感のある遷移になる
  • iOSでは戻れるのに、Androidでは戻る導線が弱い

対策

画面遷移は、単に「動けばよい」ではなく、OSごとの操作感も確認しましょう。

特に、戻る操作は実機で確認した方が安全です。

import 'dart:io';

if (Platform.isIOS) {
  // iOS向けの挙動
}

if (Platform.isAndroid) {
  // Android向けの挙動
}

ただし、OS分岐を増やしすぎると保守が大変になります。

基本は共通化し、どうしても違和感がある部分だけ分岐するのがおすすめです。


つまずき2:スクロールの感触が違う

Flutterでは、ListViewやSingleChildScrollViewを使えば簡単にスクロール画面を作れます。

しかし、スクロールの感触はiOSとAndroidで違います。

Flutter公式ドキュメントでは、Flutterは現在のプラットフォームに合わせてスクロール挙動を自動調整すると説明されています。Androidでは端までスクロールしたときにglow表示が出る一方、iOSでは抵抗感を伴って伸び、戻る挙動になります。(Flutter ドキュメント)

また、iOSでは繰り返しフリックしたときにスクロールの勢いが積み上がる動作があり、Androidには同等の挙動がないとされています。(Flutter ドキュメント)

よくあるつまずき

  • Androidではスクロールが硬く感じる
  • iOSではスクロールが滑りすぎるように感じる
  • 端までスクロールしたときの見た目が違う
  • iOSでは自然だがAndroidでは違和感がある
  • スクロールバーの出方が違う

対策

スクロールの物理挙動を無理に完全一致させようとしない方がよいです。

iOSユーザーにはiOSらしい挙動、AndroidユーザーにはAndroidらしい挙動の方が自然な場合があります。

ただし、アプリのブランド体験として統一したい場合は、ScrollPhysics を明示的に指定する方法もあります。

ListView(
  physics: const BouncingScrollPhysics(),
  children: [
    // ...
  ],
)

またはAndroid寄りにするなら、

ListView(
  physics: const ClampingScrollPhysics(),
  children: [
    // ...
  ],
)

ただし、OSごとの自然な操作感を壊さないか確認しましょう。


つまずき3:通知の権限が違う

通知は、Flutterアプリでかなりつまずきやすい領域です。

ローカル通知でもプッシュ通知でも、iOSとAndroidでは権限の考え方や設定が違います。

iOSでは、通知を表示したり、音を鳴らしたり、バッジを表示したりするには、UNUserNotificationCenter を使ってユーザーに許可を求める必要があります。Apple公式ドキュメントでは、通知許可を求めるには requestAuthorization(options:completionHandler:) を呼び出すと説明されています。(Apple Developer)

一方、Androidでは長い間、通知権限の扱いがiOSと異なっていました。ただし、Android 13、APIレベル33以降では、アプリが通知を送るために POST_NOTIFICATIONS というランタイム権限が導入されています。Android公式ドキュメントでも、Android 13以降では非免除の通知を送るために POST_NOTIFICATIONS 権限が必要だと説明されています。(Android Developers)

よくあるつまずき

  • iOSで通知許可ダイアログが出ない
  • Android 13以降で通知が表示されない
  • AndroidManifest.xmlに権限を書き忘れる
  • iOSのInfo.plist設定が不足している
  • 通知チャンネルを作っていない
  • マナーモードや集中モードの影響を見落とす
  • シミュレーターでは動くが実機では通知に気づけない

対策

通知機能は、Flutterパッケージを入れるだけで終わりではありません。

必ずOSごとの設定を確認しましょう。

Android 13以降を対象にするなら、AndroidManifest.xml に通知権限が必要です。

<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

iOSでは、通知許可を求めるタイミングも重要です。

アプリ起動直後にいきなり許可を求めるより、通知機能の意味を説明したあとに許可を求める方が自然です。


つまずき4:位置情報の権限が違う

位置情報も、iOSとAndroidでかなり差が出やすい機能です。

たとえば、地図アプリ、移動記録アプリ、目的地通知アプリ、配達アプリなどでは、位置情報の扱いが重要になります。

Android公式ドキュメントでは、位置情報を使うアプリはユーザーのプライバシーを守るために位置情報権限をリクエストする必要があり、必要な権限はユースケースによって変わると説明されています。(Android Developers)

iOSでは、位置情報の許可には「使用中のみ」と「常に許可」があり、Apple公式ドキュメントでは、When In Use authorizationはアプリ使用中のみ位置情報更新を利用可能にし、Always authorizationはいつでも位置情報更新を利用可能にすると説明されています。(Apple Developer)

よくあるつまずき

  • Androidでは位置情報が取れるのにiOSでは取れない
  • iOSで「常に許可」が想定通り出ない
  • Androidでバックグラウンド位置情報が取れない
  • 正確な位置情報と概略位置情報の違いを考慮していない
  • Info.plistの説明文が不足している
  • AndroidManifest.xmlの権限が不足している
  • ストア申請時に位置情報利用の説明が弱い

対策

位置情報は、次の3つを分けて考えましょう。

フォアグラウンドで使う位置情報
バックグラウンドで使う位置情報
正確な位置情報が必要か、概略でもよいか

特にバックグラウンド位置情報は、OS側の制限もストア審査も厳しくなりやすいです。

「なんとなく便利だから常に許可を求める」ではなく、ユーザーにとって必要な理由を明確にする必要があります。


つまずき5:バックグラウンド動作が違う

Flutter初心者が特にハマりやすいのが、バックグラウンド動作です。

「アプリを閉じても処理を続けたい」

「定期的に位置情報を取りたい」

「バックグラウンドで通知したい」

このような要望はよくあります。

しかし、iOSとAndroidではバックグラウンド実行の制限が大きく異なります。

Androidは比較的バックグラウンド処理の選択肢がありますが、近年はバッテリー最適化や権限制限が強くなっています。

iOSは、アプリがバックグラウンドで自由に動き続けることにかなり制限があります。

よくあるつまずき

  • Androidではバックグラウンドで動くがiOSでは止まる
  • iOSでアプリを閉じると処理が続かない
  • 定期実行が想定した間隔で動かない
  • バッテリー最適化でAndroidの処理が止まる
  • バックグラウンド位置情報の審査で落ちる
  • プラグインの説明通りに動かない
  • エミュレーターでは動いたのに実機で止まる

対策

バックグラウンド処理は、最初に「本当にバックグラウンドで必要か」を考えるべきです。

たとえば、次のように分けて考えます。

やりたいこと考えるべきこと
定期的にデータ同期したいOSのスケジューラーで十分か
位置情報を取り続けたいユーザーに必要性を説明できるか
通知したいローカル通知やプッシュ通知で代替できるか
アプリを閉じても常時監視したいiOSで成立する設計か

Flutterだけでなく、iOS/Androidのバックグラウンド制限を前提に設計する必要があります。


つまずき6:アプリのライフサイクルが違う

Flutterには、アプリの状態を表す AppLifecycleState があります。

たとえば、アプリが表示されている、非表示になった、バックグラウンドに入った、復帰した、といった状態を扱えます。

Flutter APIドキュメントでは、AppLifecycleState はアプリ実行中の状態を表し、プラットフォームでサポートされていない状態は、全実装で同じ状態機械を共有するためにFlutter側で合成されると説明されています。(Flutter API Docs)

つまり、Flutterは共通のライフサイクル状態を提供しますが、元になるOSの挙動は完全に同じではありません。

よくあるつまずき

  • アプリ復帰時の処理がiOSとAndroidで微妙に違う
  • 通知から戻ったときの画面復元がずれる
  • バックグラウンド移行時に保存処理が間に合わない
  • 権限ダイアログ表示後の状態遷移を考慮していない
  • アプリ終了とバックグラウンド移行を混同する

対策

ライフサイクルに依存する処理は、必ず実機で確認しましょう。

特に、次のような処理は注意が必要です。

  • 位置情報の再取得
  • 通知許可後の再判定
  • ログイン状態の再確認
  • 画面復帰時のデータ再読み込み
  • 一時保存処理
  • タイマーの再開

Flutterのライフサイクルは便利ですが、OSごとの実際の動きまで吸収してくれるわけではありません。


つまずき7:UIの見た目がOSごとに違う

Flutterでは、Material Designを使うことが多いです。

しかし、iOSにはiOSらしいUIの慣習があります。

たとえば、

  • ナビゲーションバー
  • タブバー
  • 戻るボタン
  • ダイアログ
  • ピッカー
  • スイッチ
  • スクロール挙動
  • フォント
  • アイコン

などは、iOSとAndroidで見た目や操作感が異なります。

Flutter公式ドキュメントでも、FlutterはスクロールなどOS環境由来の挙動を自動適応しますが、アプリの情報設計やデザイン選択が必要な部分は自動適応しないと説明されています。(Flutter ドキュメント)

よくあるつまずき

  • iOSアプリなのにAndroidっぽく見える
  • Androidでは自然だがiOSでは違和感がある
  • Cupertinoを使ったら今度はAndroidで違和感が出る
  • ダイアログやピッカーの使い勝手がOSごとに違う
  • アイコンの向きや意味がOSごとに違う

対策

どちらか一方のデザインに完全統一するか、OSごとに自然なUIへ寄せるかを最初に決めるのがおすすめです。

たとえば、個人開発でスピード重視ならMaterialベースで統一するのもありです。

一方で、iOSユーザーに違和感なく使ってもらいたいなら、重要なUIだけCupertino系の部品を使う選択もあります。


つまずき8:キーボードの挙動が違う

入力フォームを作るときも、iOSとAndroidで違いが出ます。

特に、ログイン画面、検索画面、チャット画面、問い合わせフォームなどでは注意が必要です。

よくあるつまずき

  • キーボード表示時にボタンが隠れる
  • iOSでは画面が押し上がるがAndroidでは違う
  • Androidの戻るボタンでキーボードだけ閉じる
  • iOSでは完了ボタンがない入力タイプがある
  • フォーカス移動の挙動が違う
  • 日本語入力中の確定前文字の扱いで違和感が出る

対策

入力フォームでは、次のポイントを確認しましょう。

  • SafeArea を使っているか
  • SingleChildScrollView で逃がせるか
  • キーボード表示時に送信ボタンが見えるか
  • Androidの戻る操作で違和感がないか
  • iOS実機で日本語入力を試しているか

フォームはシミュレーターだけでなく、実機で確認した方がよいです。

特に日本語入力は、実機確認が重要です。


つまずき9:ファイル・写真・カメラ周りが違う

カメラ、写真、ファイル選択もOS差が出やすい領域です。

iOSとAndroidでは、写真ライブラリ、ファイルシステム、ストレージ権限の考え方が異なります。

よくあるつまずき

  • Androidでは画像選択できるのにiOSでは権限エラーになる
  • iOSで写真アクセスの説明文が不足している
  • Androidのバージョンによってストレージ権限が違う
  • ファイルパスを直接扱おうとして失敗する
  • カメラ起動後の戻り値がOSごとに違う
  • 画像の向きやメタデータの扱いが違う

対策

写真やファイルを扱う場合は、パッケージのREADMEだけでなく、iOS/Androidの設定項目も確認しましょう。

特に、

Info.plist
AndroidManifest.xml
targetSdkVersion
iOSのプライバシー説明文
Androidのストレージ権限

は必ず確認するべきです。


つまずき10:ビルド・署名・リリース手順が違う

Flutterで書いたコードは共通でも、最終的にアプリをリリースする手順はiOSとAndroidでまったく違います。

iOSではXcode、証明書、Provisioning Profile、App Store Connectなどが関係します。

AndroidではGradle、keystore、Google Play Console、targetSdkVersionなどが関係します。

よくあるつまずき

  • iOSだけビルドが通らない
  • AndroidだけGradleエラーが出る
  • CocoaPodsでエラーになる
  • Xcodeの署名設定で詰まる
  • 実機ビルドだけ失敗する
  • App Store申請で権限説明が不足している
  • Google Playでバックグラウンド権限の説明を求められる

対策

Flutter開発でも、リリース時にはネイティブ開発の知識が必要になります。

特に個人開発では、開発そのものよりもリリース作業で詰まることが多いです。

最初から次のファイルや設定に慣れておくとよいです。

ios/Runner/Info.plist
ios/Runner.xcodeproj
ios/Podfile
android/app/build.gradle
android/app/src/main/AndroidManifest.xml
key.properties

Flutterだけを見ていると、リリース直前にネイティブ設定で詰まりやすいです。


つまずき11:FlutterパッケージがOSごとに違う動きをする

Flutterには便利なパッケージがたくさんあります。

しかし、パッケージの内部では、iOSならSwift/Objective-C、AndroidならKotlin/Javaのネイティブコードを呼び出していることがあります。

Flutter公式ドキュメントでも、Flutterアプリからプラットフォーム固有のAPIを使う場合、Platform Channelを使ってFlutter側とホスト側の間でメッセージをやり取りできると説明されています。(Flutter ドキュメント)

つまり、Flutterパッケージを使っていても、実際にはOSごとのネイティブ実装に依存している場合があります。

よくあるつまずき

  • iOSだけパッケージが動かない
  • Androidだけパッケージが古いAPIに依存している
  • パッケージ更新後に片方のOSだけビルドできない
  • READMEの設定を片方のOS分しか読んでいなかった
  • pub.dev上では人気でも、最近メンテされていない
  • ネイティブ側の設定漏れに気づかない

対策

パッケージを入れる前に、次の点を確認しましょう。

  • iOS対応状況
  • Android対応状況
  • 最終更新日
  • issueの状況
  • READMEのネイティブ設定
  • 必要な権限
  • サンプルアプリの有無
  • target SDKやiOS deployment targetの条件

特に通知、位置情報、Bluetooth、カメラ、課金、広告、バックグラウンド処理系のパッケージは、OS差分が出やすいです。


つまずき12:エミュレーターと実機で挙動が違う

Flutter開発では、シミュレーターやエミュレーターで動作確認することが多いです。

しかし、ネイティブ機能を使う場合、実機でしか確認できない挙動があります。

よくあるつまずき

  • 通知が実機でしか正しく確認できない
  • 位置情報の精度がシミュレーターと実機で違う
  • カメラや写真アクセスが実機依存になる
  • Apple Watch連携はシミュレーターだけでは判断しづらい
  • バッテリー最適化の影響は実機でしか見えにくい
  • 権限ダイアログの体験が実機と違う

対策

次の機能を使う場合は、早い段階で実機確認しましょう。

通知
位置情報
バックグラウンド処理
カメラ
写真
Bluetooth
マイク
ヘルスケア
Apple Watch連携
課金
広告

「最後に実機で確認すればいい」と考えると、リリース直前に大きな手戻りが発生しやすいです。


Flutter開発でOS差分に強くなる考え方

FlutterでiOSとAndroidの挙動差に強くなるには、次の考え方が大切です。

1. Flutterが吸収する差分と、吸収しない差分を分ける

FlutterはUI開発をかなり楽にしてくれます。

しかし、権限、通知、バックグラウンド、ストア申請などはOSのルールに強く依存します。

Flutterが得意:
UI、画面構築、状態管理、共通ロジック

OS理解が必要:
権限、通知、位置情報、バックグラウンド、リリース設定

この切り分けを意識するだけで、ハマり方が変わります。


2. パッケージを入れたらネイティブ設定まで見る

Flutterパッケージを入れると、Dart側のコードだけで動きそうに見えます。

しかし、多くのパッケージでは、iOSやAndroid側の設定が必要です。

READMEの次の項目は必ず読みましょう。

  • iOS setup
  • Android setup
  • Permissions
  • ProGuard
  • Info.plist
  • AndroidManifest
  • Background modes
  • Notification channel

Dartコードだけ見ていると、設定漏れに気づきにくいです。


3. iOS実機とAndroid実機の両方で早めに確認する

Flutter開発では、片方のOSだけで動作確認していると危険です。

特に個人開発では、iPhoneだけ持っていてAndroid実機確認が遅れる、またはその逆が起きがちです。

最低限、次のタイミングでは両OSで確認した方がよいです。

  • 権限を追加したとき
  • 通知を追加したとき
  • 位置情報を追加したとき
  • バックグラウンド処理を追加したとき
  • 画面遷移を大きく変えたとき
  • リリース前

4. OS差分を仕様として書いておく

OS差分は、バグではなく仕様の場合があります。

たとえば、

iOSではこう表示する
Androidではこう表示する
iOSではこの権限説明を出す
Androidではこの権限説明を出す

と仕様として決めておくと、開発中に迷いにくくなります。

特にチーム開発では、OS差分をドキュメント化しておくと便利です。


まとめ

Flutterは、iOSとAndroidのアプリを共通コードで開発できる強力なフレームワークです。

しかし、Flutterを使えばすべてのOS差分が消えるわけではありません。

実際の開発では、通知、位置情報、バックグラウンド処理、画面遷移、スクロール、キーボード、ファイルアクセス、ビルド設定、ストア申請などで、iOSとAndroidの違いに直面します。

特につまずきやすいのは、次の領域です。

権限
通知
位置情報
バックグラウンド処理
ライフサイクル
ネイティブ設定
ビルド・リリース

Flutter開発で大切なのは、Flutterだけを学ぶことではありません。

Flutterで共通化できる部分と、iOS/Androidそれぞれの仕様を理解すべき部分を切り分けることです。

Flutterはクロスプラットフォーム開発を楽にしてくれます。

しかし、良いアプリを作るには、最終的にiOSらしさ、Androidらしさ、そして各OSの制約を理解する必要があります。

「同じコードなのに動きが違う」と感じたときは、Flutterの問題だけでなく、OSごとの仕様差を疑ってみましょう。

コメント

タイトルとURLをコピーしました