ExpoのwebviewでローカルにあるHTMLを表示する

June 21, 2019,
tags:


このエントリーをはてなブックマークに追加

最近Expo使ってiPhone,Android向けのアプリの開発を行っている僕です。
最近は全然php書いてなくて切ないです。

さて、今日はExpoでWebViewを使ってローカルにあるHTMLファイルを表示するのに苦労したお話をします。

バージョンについて

"dependencies": {
  "expo": "^33.0.0",
  "react": "16.8.3",
  "react-dom": "^16.8.6",
  "react-native": "https://github.com/expo/react-native/archive/sdk-33.0.0.tar.gz",
  "react-native-web": "^0.11.4"
},

こんな感じで、Expoは33と2019/06時点の最新のものを使っています。

今日のコードのサンプルについて

GitHub - polidog/expo-webview-sample

webviewでローカルにあるHTMLを開くときに何が問題か?

例えば以下のコード。これはiPhoneならHTMLがちゃんと表示されます。
しかし、Androidの場合はテキストとして表示されてしまいます。(つまりHTMLタグから表示されてしまいます)

// LocalHtml.js

import React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import LocalHtmlView from './components/LocalHtmlView'

export default function App() {
  return (
    <View style={styles.container}>
      <LocalHtmlView />
      <Text>abc</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    paddingTop: 50,
    flex: 1,
    justifyContent: 'center',
  }
})

なぜかは知りませんが、Androidの場合はダメでした。

Androidの場合はどうするのがいいのか?

調べたところによると、Androidの場合はfile://で始まるファイルパスでhtmlの場所を指定する必要があります。
しかし単純にfile://assets/html/hello.htmlとかって指定すればいいわけではありません。

以下の手順でAndroidでHTMLを表示できるようになります。

  1. expo-assetを使ったdownload処理
  2. WebViewのプロパティの追加

expo-assetを使ったdownload処理

expot-assetを使ったダウンロード処理を以下のように記述します。
Androidの場合のみダウンロード処理を行う形です。

// LocalHtml.js

import React, { useState, useEffect } from 'react'
import { WebView, Platform } from 'react-native'
import { Asset } from 'expo-asset'

const html = require('../assets/html/hello.html')

export default () => {
  const [file, setFile] = useState(null)

  useEffect(() => {
    if (Platform.OS === 'android') {
      download().then((downloadFile) => {
        setFile(downloadFile)
      })
    }
  }, [])

  return (
    <WebView
      source={getSource(file)}
    />
  )
}

const download = async () => {
  let file = Asset.fromModule(html)
  if (file.localUri !== null) {
    return file
  }
  await file.downloadAsync() // Optional, saves file into cache
  console.log('file', file)
  return file
}

const getSource = (file) => {
  if (Platform.OS !== 'android') {
    return html
  }
  if (file === null) {
    return {}
  }

  return {
    uri: file.localUri
  }
}

サンプルではReact Hooks使っているのでuseEffectsでdownload処理させています。
Hooks使わない場合はcomponentDidMountとかでdonwload処理させるのが良さそう。(React詳しくないので適当です。)

WebViewのプロパティの追加

ダウンロード処理の記述だけだとダメで、WebViewにallowFileAccessのプロパティの設定も必要になります。
ここは単純にtrueを渡せば問題ないです。

// LocalHtml.js

return (
    <WebView
      allowFileAccess={true}
      source={getSource(file)}
    />
)

これでAndroidでも表示されるようになるかと思います。

最後に

僕はReactとかjsとかReactNativeとかアプリ開発とか大体素人なので、これが正しい実装なのか判断できません。
現状のシミュレータ使った場合だとこれで動いた程度なので、もっと良いやり方があったらコメントで教えてください。

参考URL

Embedding a local website on your Expo/React Native project
reactjs - react native (expo) load markdown files - Stack Overflow

comments powered by Disqus