はじめに
Expo SDK 52 (React Native 0.76) にアップグレードし、Androidで New Architecture (Fabric) を有効にしたところ、アプリ起動時や画面遷移時に java.lang.IllegalStateException: addViewAt という謎のクラッシュに遭遇しました。
数日間にわたるデバッグの末、意外な原因と解決策が見えてきたので共有します。
環境
- Expo SDK 52
- React Native 0.76.x
- Platform: Android (Fabric Enabled)
- Navigation: Expo Router
エラー内容
アプリを起動し、ログイン後のホーム画面を表示しようとすると以下のエラーでクラッシュする現象が発生しました。
java.lang.IllegalStateException: addViewAt: failed to insert view [ViewName] at index [X]
at com.facebook.react.uimanager.ViewGroupManager.addView(ViewGroupManager.java:X)
...
試したこと(効果が薄かったもの)
当初は「特定のUIライブラリがFabricに対応していないのでは?」と疑い、以下を試しましたが解決しませんでした。
-
react-native-screensの無効化:enableScreens(false)を試したが変化なし。 -
react-native-safe-area-contextの削除: レイアウト崩れ覚悟で外したがクラッシュは継続。 -
expo-linear-gradient等の削除: グラデーションやBlurなどの描画系ライブラリを外しても改善せず。 -
InteractionManagerによる遅延レンダリング: 画面遷移のアニメーションが終わるまで描画を待機させようとしたが、逆に不安定になったり、問題が隠蔽されたりした。
原因の特定プロセス
「特定のコンポーネント」ではなく「アプリの構造」に問題があると考え、以下の手順で切り分けを行いました。
-
最小構成にする:
app/_layout.tsxから全ての Provider (AuthProvider,ThemeProvider等) を削除し、単なる<Slot />だけにする。 -> 起動成功 - Providerを戻す: 認証やテーマのProviderを戻す。 -> 起動成功
-
Navigationを戻す:
SlotからStack、Tabsへと戻していく。 -> ここで怪しい挙動 -
画面の中身を空にする: タブ内の画面を
<View />だけにする。 -> 起動成功 - 中身を少しずつ戻す: ヘッダー、リスト、モーダル...と戻していく中で、JSエラー(dayjsの初期化漏れ) が発生していることを発見。
真の原因
今回のケースでは、単一の原因ではなく、以下の複合要因でした。
-
隠れていたJSエラー:
dayjsのプラグイン (timezone,utc) の初期化が正しく行われておらず、レンダリング中にTypeErrorが発生していた。
Legacy Architectureでは「赤画面(RedBox)」で止まっていたかもしれないが、Fabric環境下では、このJSエラーによるレンダリング中断が、Native側のShadow TreeとView Hierarchyの不整合を引き起こし、結果としてaddViewAtクラッシュとして現れていた可能性が高い。 -
複雑なレンダリング制御:
InteractionManager.runAfterInteractionsを多用してレンダリングを制御しようとしていたが、これがFabricの同期的な描画プロセスと競合し、Viewの挿入タイミングをおかしくしていた。
解決策
1. JSエラーの徹底修正
dayjs などの初期化ロジックを app/_layout.tsx の最上位(コンポーネントの外)に移動し、確実に初期化されるようにしました。
// app/_layout.tsx
import dayjs from "dayjs";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
// コンポーネント定義の前に確実に実行
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.tz.setDefault("Asia/Tokyo");
export default function RootLayout() {
// ...
}
2. レンダリングロジックの単純化
InteractionManager による無理な遅延表示をやめ、Reactの標準的なライフサイクル (useEffect) に任せるシンプルな実装に戻しました。
3. コンポーネントの再構築
一度崩して、最小構成から積み上げ直すことで、不整合を起こしていた古いキャッシュや状態をクリアにしました。
教訓
-
Fabricでの
addViewAtは「Viewの不整合」: 必ずしもネイティブモジュールのバグではなく、JS側のロジックミス(特に初期化や条件付きレンダリング)が原因で、Native側が混乱している場合がある。 -
JSエラーを疑え: Nativeクラッシュしていても、その直前にJS側でエラーが出ていないか
adb logcatで徹底的に確認する。 -
シンプル・イズ・ベスト: Fabricはパフォーマンスが良い反面、挙動が厳格。Legacy時代のハック(
InteractionManagerでの回避など)は逆に足かせになることがある。