ReactNativeアプリにて、WebView+ReactNavigationを利用するとクラッシュする問題の回避策

## 概要

2022/9時点で、特定のAndorid(確認したのは、Android9)において、[WebView](https://github.com/react-native-webview/react-native-webview)をマウントした画面から、[ReactNavigation](https://reactnavigation.org/)を使った画面遷移を行うとクラッシュする問題がある。
（navigationの遷移アニメーション機能と WebViewのレンダリング機能が競合している？）

本記事は、試行錯誤した結果動いた方法と失敗した方法をまとめた備忘録です。

## 環境
- ReactNative : v.0.69.3
- react-native-webview : v.11.23.0
- ReactNavigation : v.5.9.8

## 参考
- https://github.com/react-navigation/react-navigation/issues/9061
- https://github.com/react-native-webview/react-native-webview/issues/575
- https://reactnative-examples.com/example-of-hardwareaccelerated-in-modal-in-react-native/
- https://github.com/react-native-webview/react-native-webview/issues/1161

## ベースとなるコード
例として、ボタンをクリックすると、StackNavigatorによって`DetailPage`に飛ぶ画面を作成。
画面はWebViewコンポーネントをマウントしていることとする。

```typescript
const WebViewSample: React.VFC<Props> = (props) => {
  const param: string = props.param;
  // ボタンタップで別ページに遷移
  const goNextPage = useCallback(() => {
    navigation.navigate("DetailPage", {param});
    }
  }, [param]);
  // WebViewコンポーネントをマウント
  return (
    <View>
      <MyButton onClick={goNextPage}/>
      <WebView
        source={hoge}
        ...
      />
    </View>
  )
}
```

## 結論
ReactNavigationのhookを利用し、画面にフォーカスされた時 or 外れた時に、WebViewコンポーネントをアンマウントすることで回避。
→ フォーカスON/OFFの判定には、ReactNavigationの[useFocusEffect](https://reactnavigation.org/docs/use-focus-effect/)を利用する。

また、遷移先画面から「戻る」場合、再度WebViewコンポーネントをマウントするとクラッシュする。
そこで、「戻る」場合は、一定時間待ち合わせてからマウントすることで回避。待ち合わせには、`setTimeout`を利用するため、副作用hookを活用する。（なぜ副作用hookを使うか？などは、他の方々の記事参照のこと。本記事では割愛）

```typescript
import { useFocusEffect } from "@react-navigation/native";

const WebViewSample: React.VFC<Props> = (props) => {
  const param: string = props.param;
  const [isShow, setIsShow] = useState<boolean>(false);

  useFocusEffect(
    useCallback(() => {
      //focus後、1秒後にWebViewをマウント
      const timeout = setTimeout(() => {
        setIsShow(true);
      }, 1000);
      // focusが外れた際は、WebViewコンポーネントをunmountする
      return () => {
        clearTimeout(timeout);
        setIsShow(false);
      };
    }, [])
  );

  // ボタンタップで別ページに遷移
  const goNextPage = useCallback(() => {
    navigation.navigate("DetailPage", {param});
    }
  }, [param]);
  // WebViewコンポーネントをマウント
  return (
    <View>
      <MyButton onClick={goNextPage}/>
      // isShowフラグによってmount or unmount制御
      {isShow && <WebView
        source={hoge}
        ...
      />
      }
    </View>
  )
}
```


---

## 試行錯誤したものたち

### WebViewのstyleで回避してみる
Webviewのstyleとして、opacity: 0.99, overlay: "hidden"を指定

```typescript
<WebView style={opacity: 0.99, overlay: "hidden"}>
  source={hoge}
  ...
</WebView>
```

→ クラッシュは直らず。また画面崩れも発生したので見送り

### WebViewのpropsに [androidLayerType="software"](https://github.com/react-native-webview/react-native-webview/blob/master/docs/Reference.md#androidLayerType)指定

```typescript
<WebView>
  androidLayerType="software"
  source={hoge}
  ...
</WebView>
```
→ WebView自体表示されなくなったので見送り

### Manifestにて、[hardwareaccelerated](https://developer.android.com/guide/topics/graphics/hardware-accel?hl=ja)をオフにする

```xml
// AndroidManifest.xml
    <application android:hardwareAccelerated="true" ...>

```
→ WebView自体表示されなくなったので見送り