◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/開発ツール
⬡ 開発ツール/2026-05-25中級

Rork で targetSdkVersion 35 にしたら Android のレイアウトが下まで埋まる問題の直し方

Android 15 (API 35) で強制されるエッジ・トゥ・エッジ表示によって、Rork で生成したアプリの下部タブやヘッダーがシステムバーに食い込む問題を、react-native-edge-to-edge と useSafeAreaInsets を使って実装パターン別に解決します。

Rork585Android 15edge-to-edgeReact Native239Expo219SafeArea3トラブルシューティング85

Google Play Console で targetSdkVersion を 34 から 35 に上げた直後、Pixel 7 の実機で確認したら下部タブバーがジェスチャーナビゲーションのインジケータの裏に潜り込んでいました。プレビューでは何も問題なかった画面が、Android 15 を載せた端末だけで崩れる — このパターンに最近よく出会います。

これは Android 15 から targetSdkVersion 35 以上のアプリに対してエッジ・トゥ・エッジ表示が強制されるようになったことが原因です。Rork で生成された初期コードは React Native の SafeAreaView を画面上部にだけ使っているケースが多く、Android 15 のシステムバー領域の扱いが変わったことに引きずられてレイアウトが下まで伸びてしまいます。

2014年からアプリを個人で出してきた中で、OS のメジャーアップデートに伴うレイアウト崩れには何度も振り回されてきました。今回の Android 15 はその中でもインパクトが大きい部類で、累計5,000万ダウンロードを支えてきた既存アプリ群のいくつかも、targetSdkVersion 引き上げのタイミングで一度作り直しました。ここでは私が実際にハマって整理した対処パターンを、Rork で生成したアプリにそのまま適用できる形でまとめます。

まず確認すべきこと — targetSdkVersion と Android バージョン

問題が起きているのが本当にエッジ・トゥ・エッジ強制が原因なのか、最初に切り分けます。

# app.json または app.config.ts で確認
"android": {
  "targetSdkVersion": 35,
  "compileSdkVersion": 35
}

targetSdkVersion が 34 以下なら、エッジ・トゥ・エッジは強制されません。Android 15 の端末でも従来通り「システムバーを避ける」挙動になります。

一方で、Google Play は 2026年8月以降に提出するアプリに targetSdkVersion: 35 を必須化したため、ストア更新を予定しているなら避けては通れません。Rork が生成する Expo プロジェクトでは、expo prebuild 後の android/app/build.gradle で targetSdkVersion が決まります。

実機での再現は次のコマンドで Android のバージョンを確認するのが早道です。

adb shell getprop ro.build.version.release
# 15
adb shell getprop ro.build.version.sdk
# 35

これで「Android 15 端末で targetSdkVersion: 35 のビルド」だと確定したら、以下の対処に進みます。

対処 1: react-native-edge-to-edge を導入する

Expo SDK 53 以降では react-native-edge-to-edge が Expo の推奨対応として組み込まれました。Rork が出すコードがこれを使っていない場合は、まずインストールから入ります。

npx expo install react-native-edge-to-edge

app.json の android セクションに次を追記します。

{
  "expo": {
    "android": {
      "edgeToEdgeEnabled": true
    }
  }
}

これだけでは画面下端のレイアウトは直りません。エッジ・トゥ・エッジは「システム UI と重なる前提でアプリ側がインセットを処理する」という思想なので、コンポーネント側の対応が必要です。

対処 2: useSafeAreaInsets で下部インセットを取る

SafeAreaView だけで囲っている画面では、Android 15 の動作モデルの変更で下部インセットが思った通りに取れません。useSafeAreaInsets で実数値を取り、paddingBottom に渡す方法が確実です。

// app/(tabs)/_layout.tsx
import { Tabs } from "expo-router";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Platform } from "react-native";
 
export default function TabLayout() {
  const insets = useSafeAreaInsets();
 
  return (
    <Tabs
      screenOptions={{
        tabBarStyle: {
          // Android 15 のジェスチャーナビゲーション領域を確保
          paddingBottom: Platform.OS === "android" ? insets.bottom : 0,
          height: 56 + (Platform.OS === "android" ? insets.bottom : 0),
        },
      }}
    >
      <Tabs.Screen name="index" options={{ title: "Home" }} />
      <Tabs.Screen name="settings" options={{ title: "Settings" }} />
    </Tabs>
  );
}

ジェスチャーナビゲーションを使っている端末は insets.bottom が 0 に近い値(多くは 16dp 程度)になり、3 ボタンナビゲーションでは 48dp 前後になります。固定値ではなく、必ず insets から動的に取ってください。

対処 3: 画面コンテンツのスクロール領域に下端余白を持たせる

タブバーを直したあとに次にぶつかるのが、ScrollView や FlatList の最終アイテムがタブバーの裏に隠れる問題です。contentContainerStyle で paddingBottom を確保します。

import { ScrollView } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
 
export default function HomeScreen() {
  const insets = useSafeAreaInsets();
  const TAB_BAR_HEIGHT = 56;
 
  return (
    <ScrollView
      contentContainerStyle={{
        paddingBottom: TAB_BAR_HEIGHT + insets.bottom + 16,
      }}
    >
      {/* リストの中身 */}
    </ScrollView>
  );
}

ここで TAB_BAR_HEIGHT を定数として外に切り出しておくと、後から高さを変えた時に複数画面の同期が楽になります。私は constants/layout.ts のような場所に集めるようにしています。

対処 4: ステータスバー領域の重なりを直す

下部だけでなく、上部のヘッダーがステータスバーに食い込むケースもあります。Rork が expo-status-bar を使って translucent を true にしている時に多発します。

// app/_layout.tsx
import { StatusBar } from "expo-status-bar";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
import { Stack } from "expo-router";
 
export default function RootLayout() {
  return (
    <SafeAreaProvider>
      <SafeAreaView style={{ flex: 1 }} edges={["top"]}>
        <StatusBar style="auto" />
        <Stack />
      </SafeAreaView>
    </SafeAreaProvider>
  );
}

SafeAreaProvider を最上位に、SafeAreaView の edges を ["top"] に絞るのがポイントです。edges を指定しないと iOS の下部インジケータ領域までインセットを取りに行ってしまい、Android との挙動差が大きくなります。

対処 5: モーダルとボトムシートの底面処理

react-native-modal や @gorhom/bottom-sheet を使ったボトムシートも、Android 15 では下端の挙動が変わります。シートの中身に下部インセットを足し忘れると、CTA ボタンがジェスチャー領域と被って誤タップを誘発します。

import BottomSheet, { BottomSheetView } from "@gorhom/bottom-sheet";
import { useSafeAreaInsets } from "react-native-safe-area-context";
 
export function CommentSheet() {
  const insets = useSafeAreaInsets();
  return (
    <BottomSheet snapPoints={["50%", "90%"]}>
      <BottomSheetView style={{ paddingBottom: insets.bottom + 24, flex: 1 }}>
        {/* シートのコンテンツ */}
      </BottomSheetView>
    </BottomSheet>
  );
}

ボトムシート系は内部でステータスバーやキーボードの処理を持っているライブラリが多いため、ライブラリの README に「edge-to-edge 対応」「Android 15 対応」の項目があるかも合わせて確認してください。

動作確認のチェックリスト

実機で確認する際は、以下の3パターンを最低限カバーしておくと安心です。

  • ジェスチャーナビゲーション有効の Pixel 系(insets.bottom が小さい)
  • 3 ボタンナビゲーション固定の Samsung 端末(insets.bottom が大きい)
  • Android 14 以下の端末(targetSdkVersion 35 でもエッジ・トゥ・エッジは強制されない)

Pixel エミュレータでも再現できますが、Samsung One UI のような OEM カスタマイズではインセットが微妙に違うため、可能なら実機を一台確保しておくと心強いです。私は中古の Galaxy A シリーズを 1 台、検証専用に置いています。

予防策 — レイアウトの最初の一行から考える

Android 15 のエッジ・トゥ・エッジ強制は、新規プロジェクトでは最初から前提として組み込めば困りません。新しい画面を作るたびに「この画面のインセットは誰が処理するか」を意識して書いておくと、後で OS が変わってもまとめて直す手間がほぼゼロになります。

私の場合は、画面ごとに SafeAreaView または useSafeAreaInsets のどちらを使うかを最初に決めて、画面ファイルの先頭にコメントで残すルールにしています。チーム開発でなくても、半年後の自分への申し送りとして効果があります。

OS のアップデートは止められませんが、レイアウトの考え方を整えておけば、変更点ごとに振り回されることは減らせます。同じ問題に取り組んでいる方の参考になれば幸いです。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⬡ 開発ツール2026-10-15
EXPO_PUBLIC_ の値が、開発では読めて本番だけ undefined になる書き方を、変換結果で見分けます
API の URL が開発中は読めるのに、ビルドした途端に undefined になる。原因は process.env の書き方でした。babel-preset-expo の変換結果を開発・本番で並べ、置き換わる書き方と残ってしまう書き方を表にしました。
⬡ 開発ツール2026-10-07
日本語の見出しだけ太字にならない — Android で fontWeight が効かないときの切り分けと直し方
Rork で作ったアプリで、iPhone では太字なのに Android では日本語の見出しが細いままになる症状の切り分け記事です。原因の見分け方、fontFamily を重みごとに分ける直し方、確認用コードを載せています。
⬡ 開発ツール2026-09-21
exit code 0 で止まるビルド — ログを黙らせていた設定と、空ファイルを通した存在チェック
eas build --local が exit code 0 but produced no further output で止まる件と、0 バイトのスタブが埋め込まれず dyld で落ちる件。どちらも原因は出力を減らす設定でした。自分のスクリプトを棚卸しする手順まで書き残します。