Flutterはクロスプラットフォームなのに、なぜiOSとAndroidで動きが違うのか

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

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

Flutterは、1つのコードベースでiOSアプリとAndroidアプリを作れるクロスプラットフォーム開発フレームワークです。

Dartで画面やロジックを書けば、iPhoneでもAndroidでも動くアプリを作れます。

しかし、実際にFlutterで開発していると、こんな場面に出会います。

iOSでは動くのに、Androidでは動かない
Androidでは通知が出るのに、iOSでは出ない
同じコードなのにスクロールの感触が違う
権限ダイアログの出方がOSごとに違う

「Flutterはクロスプラットフォームなのに、なぜ動きが違うの?」と思う人も多いはずです。

この記事では、FlutterでiOSとAndroidの挙動差が起きる理由を、初心者向けにわかりやすく解説します。


結論:FlutterはOSの違いをすべて消してくれるわけではない

まず前提として、Flutterはとても強力です。

UIを共通コードで作れますし、ビジネスロジックも多くの部分を共通化できます。

しかし、FlutterはiOSやAndroidというOSの上で動いています。

つまり、アプリの見た目やロジックは共通化できても、次のような部分ではOSごとの差が残ります。

  • 通知
  • 位置情報
  • 権限
  • バックグラウンド動作
  • スクロール
  • 画面遷移
  • キーボード
  • ファイルアクセス
  • カメラ
  • ストア申請
  • ネイティブAPIとの連携

Flutter公式ドキュメントでも、FlutterはOS環境に由来する一部の動作、たとえばスクロールやテキスト編集などは自動的にプラットフォームへ適応する一方で、アプリの設計判断が必要な部分まですべて自動適応するわけではないと説明されています。(Flutter ドキュメント)

つまり、Flutterは「全部を完全に同じにする道具」ではなく、共通化できる部分を大きく増やしつつ、OS固有の部分は必要に応じて調整する道具だと考えるとわかりやすいです。


Flutterが共通化してくれる部分

Flutterを使うと、多くの部分はiOSとAndroidで共通化できます。

たとえば、次のような部分です。

画面レイアウト
ボタンやテキストなどのUI
状態管理
API通信
入力フォーム
画面遷移の基本ロジック
ビジネスロジック

たとえば、以下のようなコードは、iOSでもAndroidでも基本的に同じように動きます。

import 'package:flutter/material.dart';

class SamplePage extends StatelessWidget {
  const SamplePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('サンプル'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            print('ボタンが押されました');
          },
          child: const Text('押す'),
        ),
      ),
    );
  }
}

このように、FlutterではUIをDartで書けるため、iOS用とAndroid用でまったく別々に画面を作る必要がありません。

これは大きなメリットです。

ただし、ここで注意したいのは、Flutterのコードが同じでも、その下で動いているOSは別物だということです。


理由1:iOSとAndroidはそもそも別のOSだから

一番大きな理由は、iOSとAndroidがまったく別のOSだからです。

iOSはAppleが管理するOSです。

AndroidはGoogleが中心となって開発しているOSで、端末メーカーごとの差もあります。

そのため、同じ「通知」や「位置情報」でも、OS側の考え方が違います。

たとえば、通知権限、バックグラウンド処理、位置情報の許可、ファイルアクセス、アプリ終了時の扱いなどは、iOSとAndroidで仕様が異なります。

Flutterがその違いをある程度ラップしてくれても、OSの根本的なルールまでは変えられません。

Flutterの世界:
Dartで共通コードを書く

実際に動く世界:
iOSはiOSのルールで動く
AndroidはAndroidのルールで動く

この二重構造を理解しておくと、Flutter開発でハマりにくくなります。


理由2:Flutterは独自描画だが、OS機能はネイティブに依存する

Flutterは、画面を独自の描画エンジンで描画します。

そのため、UIの多くはiOSとAndroidで共通化しやすいです。

しかし、OSの機能を使う部分では、ネイティブAPIにアクセスする必要があります。

たとえば、次のような機能です。

通知
位置情報
カメラ
写真ライブラリ
Bluetooth
マイク
ヘルスケア
課金
広告
ファイル保存
バックグラウンド処理

Flutter公式ドキュメントでは、Flutterからプラットフォーム固有のAPIを使う場合、Platform Channelを使ってFlutter側とホスト側、つまりAndroidやiOS側のコードがメッセージをやり取りできると説明されています。ホスト側は受け取ったメッセージをもとに、Kotlin/JavaやSwift/Objective-Cなどでプラットフォーム固有APIを呼び出します。(Flutter ドキュメント)

つまり、Flutterアプリでも、裏側では次のような構造になります。

Dart / Flutter
↓
Platform Channel
↓
iOS: Swift / Objective-C
Android: Kotlin / Java
↓
OS固有API

このため、Flutterのコードが同じでも、ネイティブ側の実装やOSの仕様が違えば、挙動も違ってきます。


理由3:Flutterパッケージも中身はOSごとに違うことがある

Flutterでは、pub.dev から便利なパッケージを入れて機能を追加できます。

たとえば、通知、位置情報、カメラ、広告、課金などのパッケージがあります。

初心者は「パッケージを入れればiOSもAndroidも同じように動く」と思いがちです。

しかし、ネイティブ機能を扱うパッケージの多くは、内部でiOS用・Android用の実装を持っています。

Flutterパッケージ
├── Dart側の共通コード
├── iOS側のネイティブ実装
└── Android側のネイティブ実装

そのため、次のようなことが起きます。

iOSだけ追加設定が必要
Androidだけ権限設定が必要
iOSではサポートされていない機能がある
Androidでは端末やOSバージョンで挙動が違う
パッケージ更新後に片方のOSだけビルドエラーになる

Flutter公式のPlatform Channelの説明からもわかるように、プラットフォーム固有機能を使う場合は、Flutter側だけでなくホストプラットフォーム側のコードや設定も関係します。(Flutter ドキュメント)

パッケージを入れたら、Dartの使い方だけでなく、READMEのiOS設定・Android設定も必ず確認する必要があります。


理由4:UIの「自然な動き」がOSごとに違う

Flutterでは同じWidgetを使って画面を作れます。

しかし、iOSユーザーとAndroidユーザーでは、慣れている操作感が違います。

たとえば、次のような違いがあります。

戻る操作
画面遷移アニメーション
スクロールの跳ね返り
ダイアログの見た目
タブバーの位置
ナビゲーションバーの考え方
スイッチやピッカーの見た目

Flutter公式ドキュメントでは、FlutterはAndroidとiOSのナビゲーションパターンを提供し、現在のプラットフォームに合わせてナビゲーションアニメーションも自動的に適応すると説明されています。(Flutter ドキュメント)

たとえば、iOSでは画面が横からスライドして戻るような動きが自然に感じられます。

Androidでは、OSの戻るボタンや戻るジェスチャーとの整合性が重要になります。

同じFlutterコードでも、プラットフォームに合わせた見た目やアニメーションが使われるため、「少し違う」と感じる場面があります。

これはバグではなく、むしろ各OSらしさを保つための挙動です。


理由5:スクロールの挙動がOSごとに違う

FlutterでListViewやSingleChildScrollViewを使うと、iOSとAndroidでスクロールの感触が違うことがあります。

これはよくある挙動差です。

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

つまり、同じFlutterコードでも、ユーザーが感じるスクロールの感触はOSごとに変わります。

Android:
端まで行くとglow表示が出る

iOS:
端まで行くとバウンスするように戻る

これはFlutterが間違っているのではなく、OSごとの自然なスクロール体験に寄せているためです。

完全に同じ挙動にしたい場合は、ScrollPhysics を指定できます。

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

またはiOS風にするなら、

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

ただし、無理に統一すると、片方のOSで不自然に感じられることもあります。


理由6:戻る操作の考え方が違う

Flutter開発で地味につまずくのが、戻る操作です。

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

iOSでは、ナビゲーションバーの戻るボタンや、画面左端からのスワイプバックがよく使われます。

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

そのため、画面遷移を作るときは、次のような違いを意識する必要があります。

Android:
OSの戻る操作で前画面に戻れるか

iOS:
左スワイプで自然に戻れるか
ナビゲーションバーの戻るボタンが自然か

FlutterのNavigatorを使えば基本的な戻る処理はできますが、モーダル、タブ、ネストしたルーティング、確認ダイアログなどが絡むと、OSごとに確認が必要です。


理由7:権限ダイアログの出方が違う

アプリでカメラ、位置情報、通知、写真、マイクなどを使う場合、ユーザーに権限を求める必要があります。

この権限まわりは、iOSとAndroidでかなり違います。

たとえば、iOSではInfo.plistに利用目的の説明文を設定する必要があります。

AndroidではAndroidManifest.xmlに権限を書いたり、OSバージョンによっては実行時に権限をリクエストしたりする必要があります。

Flutterで permission_handler のようなパッケージを使っていても、ネイティブ側の設定は必要です。

iOS:
Info.plistに説明文を書く
必要に応じてユーザーに許可を求める

Android:
AndroidManifest.xmlに権限を書く
OSバージョンによってランタイム権限が必要

この違いを知らないと、

Flutterコードは正しいのに権限ダイアログが出ない
iOSだけクラッシュする
Androidだけ許可後に動かない

といった問題が起きます。


理由8:通知の仕様が違う

通知も、iOSとAndroidで違いが出やすい領域です。

iOSでは、通知を表示するにはユーザーの許可が必要です。Apple公式ドキュメントでも、通知許可を求めるには UNUserNotificationCenterrequestAuthorization を使うと説明されています。(Flutter ドキュメント)

Androidでも、OSバージョンによって通知権限の扱いが変わります。特にAndroid 13以降では、通知を送るために POST_NOTIFICATIONS というランタイム権限が必要になっています。(Flutter ドキュメント)

そのため、Flutterで通知機能を作る場合、次のような差分に注意が必要です。

iOS:
通知許可が必要
バッジ、音、通知表示の扱いを確認する
集中モードなどの影響もある

Android:
通知チャンネルが必要
Android 13以降は通知権限が必要
端末メーカーごとの通知制御もある

Flutterの通知パッケージを使っても、OS側のルールは避けられません。

「Androidでは通知が出るのにiOSでは出ない」「iOSでは出るのにAndroid 13で出ない」という問題は、FlutterよりもOS権限やネイティブ設定が原因であることが多いです。


理由9:位置情報の扱いが違う

位置情報も、Flutter開発でかなりつまずきやすい分野です。

iOSでは「アプリの使用中のみ許可」と「常に許可」のように、位置情報の許可レベルがあります。

Androidでも、フォアグラウンド位置情報、バックグラウンド位置情報、正確な位置情報、概略位置情報など、OSバージョンごとに考慮すべき点があります。

Flutterで位置情報パッケージを使っていても、iOSとAndroidで次のような差が出ます。

位置情報許可ダイアログの出方
バックグラウンド位置情報の制限
正確な位置情報の扱い
省電力モードの影響
位置情報取得頻度
ストア審査で求められる説明

特に、目的地通知アプリや移動記録アプリ、地図アプリなどでは、位置情報の扱いがアプリの品質に直結します。

Flutterだけで考えるのではなく、iOSとAndroidそれぞれの位置情報仕様を理解する必要があります。


理由10:バックグラウンド動作の制限が違う

Flutterでよくある要望に、次のようなものがあります。

アプリを閉じても処理したい
定期的に位置情報を取得したい
バックグラウンドで通知したい
一定間隔でAPI通信したい

しかし、バックグラウンド動作はiOSとAndroidで大きく違います。

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

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

そのため、

Androidではバックグラウンドで動く
でもiOSでは止まる

ということが起こります。

これはFlutterの問題ではなく、OSの設計思想の違いです。

iOSでは、バッテリーやプライバシー、ユーザー体験を守るため、バックグラウンド実行が厳しく制限されています。

Flutterアプリでも、この制約を前提に設計する必要があります。


理由11:キーボードや入力フォームの挙動が違う

入力フォームも、OS差分が出やすい部分です。

ログイン画面、検索画面、チャット画面、問い合わせフォームなどを作ると、次のような違いに気づくことがあります。

キーボード表示時の画面の押し上げ方
戻るボタンでキーボードが閉じる挙動
日本語入力中の確定前文字
入力候補の表示
フォーカス移動
キーボードタイプごとの見た目

Flutterで同じTextFieldを使っていても、実際に表示されるキーボードはOS側のものです。

そのため、入力体験はiOSとAndroidで完全には一致しません。

特に日本語入力やキーボード表示時のレイアウト崩れは、実機で確認するのがおすすめです。


理由12:ストア申請のルールが違う

Flutterで同じアプリを作っても、App StoreとGoogle Playでは申請ルールが違います。

iOSではApp Store Reviewの観点があります。

AndroidではGoogle Play Consoleでの権限申告やポリシー対応があります。

特に、次のような機能を使う場合は注意が必要です。

位置情報
バックグラウンド位置情報
通知
広告
課金
ユーザーデータ収集
カメラ
マイク
健康情報

アプリ自体はFlutterで共通でも、ストア申請時に求められる説明や審査観点はOSごとに異なります。

そのため、リリース直前になってからネイティブ設定やストアポリシーに気づくと、手戻りが大きくなります。


Flutterで挙動差が出やすい機能まとめ

FlutterでiOSとAndroidの違いが出やすい領域をまとめると、次のようになります。

領域挙動差が出る理由
通知OSごとに権限・通知チャンネル・表示制御が違う
位置情報許可レベル・バックグラウンド制限・精度設定が違う
バックグラウンド処理OSごとの制限が大きい
画面遷移iOSとAndroidで自然な戻る操作が違う
スクロールOSごとの標準的なスクロール感が違う
キーボードOSネイティブのキーボードが使われる
カメラ・写真権限やストレージ仕様が違う
ファイル操作ファイルシステムや権限の考え方が違う
課金App StoreとGoogle Playで仕組みが違う
ストア申請審査基準や申告内容が違う

この表を見るとわかる通り、違いが出るのは主に OSの機能に触れる部分 です。


「同じにするべき差」と「違ってよい差」がある

iOSとAndroidで挙動差があると、すべてを同じにしたくなるかもしれません。

しかし、すべてを完全一致させる必要はありません。

むしろ、OSごとに違ってよい部分もあります。

たとえば、スクロールの感触や戻る操作は、OSごとの自然な挙動に任せた方がユーザーにとって使いやすいことがあります。

一方で、通知が届かない、位置情報が取れない、課金できない、といった違いは問題です。

違ってよい差:
スクロールの感触
画面遷移のアニメーション
OS標準UIの見た目

揃えるべき差:
通知が届くか
位置情報が取れるか
データが保存されるか
課金できるか
主要機能が使えるか

Flutter開発では、すべてを同じにするのではなく、ユーザー体験として問題がある差分を潰すことが大切です。


OS差分でハマらないための考え方

1. ネイティブ機能は必ず両OSで確認する

通知、位置情報、カメラ、マイク、写真、Bluetooth、課金などは、必ずiOSとAndroidの両方で確認しましょう。

片方で動いたからといって、もう片方でも動くとは限りません。

特に実機確認が重要です。

シミュレーターやエミュレーターでは確認できない挙動もあります。


2. パッケージのREADMEを最後まで読む

Flutterパッケージを入れるときは、Dartのコード例だけでなく、iOS設定とAndroid設定も確認しましょう。

特に見るべき項目は次の通りです。

iOS setup
Android setup
Permissions
Info.plist
AndroidManifest.xml
Background modes
Notification channel
ProGuard / R8
Minimum iOS version
targetSdkVersion

「Dart側のコードは正しいのに動かない」という問題は、ネイティブ設定漏れであることが多いです。


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

チーム開発では、OS差分をその場その場で判断すると混乱します。

たとえば、次のように仕様として書いておくとよいです。

iOSではスワイプバックを許可する
Androidでは戻るボタンで前画面へ戻る
通知許可は機能説明後に求める
位置情報は使用中のみ許可から始める
バックグラウンド位置情報は必要な画面で理由を説明する

OS差分をバグとして扱うのではなく、設計上の判断として管理するのが大切です。


4. 「Flutterでできるか」ではなく「OSで許されるか」を考える

Flutterで実装できそうに見えても、OS側で制限されることがあります。

特にバックグラウンド処理や位置情報はそうです。

Flutterで実装できる
でもiOSでは制限される
Androidではバッテリー最適化で止まる
ストア審査で説明が必要になる

このようなケースがあります。

機能設計の段階で、「Flutterで書けるか」だけでなく、「iOSとAndroidで成立する仕様か」を考えましょう。


Flutterはクロスプラットフォームだが、ネイティブ知識は必要

Flutterを使うと、iOSとAndroidの開発効率は大きく上がります。

しかし、Flutterだけを知っていれば十分というわけではありません。

特に実務や個人アプリのリリースでは、次の知識が必要になります。

iOSの権限
Androidの権限
通知の仕組み
位置情報の制限
バックグラウンド動作
ストア申請
ビルド設定
ネイティブAPIとの連携

Flutterは、ネイティブ開発を不要にするものではありません。

正確には、ネイティブ開発の多くを共通化しつつ、必要なところではネイティブ理解が求められる技術です。


まとめ

Flutterはクロスプラットフォーム開発に非常に便利なフレームワークです。

1つのコードベースでiOSとAndroidのアプリを作れるため、開発効率は大きく上がります。

しかし、Flutterを使ってもiOSとAndroidの違いが完全になくなるわけではありません。

その理由は、Flutterアプリが最終的にはiOSやAndroidという別々のOSの上で動いているからです。

特に、通知、位置情報、バックグラウンド処理、権限、キーボード、画面遷移、ストア申請などでは、OSごとの仕様差が出ます。

Flutter開発で大事なのは、次の考え方です。

UIやロジックはFlutterで共通化する
OS固有機能はiOS/Androidの違いを理解する
違ってよい差と、潰すべき差を分ける

Flutterは、iOSとAndroidを完全に同じにする魔法ではありません。

しかし、OSの違いを理解したうえで使えば、効率よく高品質なクロスプラットフォームアプリを作れる強力な選択肢になります。

コメント

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