CordovaによるReact.jsアプリのネイティブアプリ化でハマったところ

React.jsで作ったスライドショーアプリをCordovaでネイティブアプリ化しました。ƪ(•◡•ƪ)

f:id:sanshonoki:20170815204337p:plain:w200

github.com

いくつかハマった点があったのでまとめておきます。

なお、アプリ化の手順に関しては以下のサイトが大変参考になりました。♪(・ω・)ノ

qiita.com

Cordovaの導入やコマンドの使い方に関してはこのあたり

ハマった点… ><

react-routerを使ったときに起動ページのパスが違う

対策

各環境ごとに起動ページのパスが違うのでルーティングを複数用意する必要があります。

起動ページのパス
React.jsアプリ /
cordova serve ios /ios/www/index.html
cordova emulate ios /Users/xxx/Library/Developer/CoreSimulator/De…25B7D/CordovaReactSlickExample.app/www/index.html

なので、react-routerを使うときは

<Router>
    <Route exact path="/" component={App} /> <!-- Reactのwebアプリでの確認用 -->
    <Route path="*/index.html" component={App} /> <!-- cordovaアプリ用 -->
</Router>

のように2種類の初期ページ表示用のルーティングをもっておく必要がありました。

public/以下のassetsへのアクセス

これに関連して、React.jsアプリのpublic以下のassetsへの参照も少し工夫する必要がありました。

  1. 初期ページを起動時パス取得用のダミーページ(Homeコンポーネント)にする

     <Router>
         <Route exact path="/" component={Home} />
         <Route path="*/index.html" component={Home} />
     </Router>
    
  2. ダミーページで起動時のパスを取得し、Globalな state に保存する

     class Home extends React.Component {
         constructor(props) {
             super(props)
         }
    
         componentDidMount() {
             // 起動時のパスを取得する
             const root = path.dirname(this.props.location.pathname)
    
             // stateに保存するアクションを呼ぶ
             this.props.setRootPath(root)
         }
    
         render() {
             // 実際の初期ページへリダイレクトする
             return (
                 <Redirect to="/login" />
             )
         }
     }
    
     const mapDispatchToProps = dispatch => {
         return {
             setRootPath: (path) => dispatch(setRoot(path)) // 起動時パスを保存するアクション
         }
     }
     export default connect(null, mapDispatchToProps)(Home)
    
  3. assetsにアクセスするときは起動時パスを考慮した形で行う

    const root = getState().home.root // 起動時のパスをstateから取得
    const url = `${root === '/' ? '' : root}/images/slick/pictures.json`
    fetch(url)
    

Fetch API cannot load file:///android_asset/www/xx/xxx.json. URL scheme “file” is not supported のエラーが出た(Android

対策

fetchは file:// をサポートしてないらしく XMLHttpRequest を使って記述します。

https://github.com/github/fetch/pull/92#issuecomment-140665932

function fetchLocal(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest
    xhr.onload = function() {
      resolve(new Response(xhr.responseText, {status: xhr.status}))
    }
    xhr.onerror = function() {
      reject(new TypeError('Local request failed'))
    }
    xhr.open('GET', url)
    xhr.send(null)
  })
}

fetchをfetchLocalに置き換えればok。

このエラーはiOSのときには出ませんがfetchLocal置き換えの副作用はありません。

Unable to post message to https://www.youtube.com. Recipient has origin file://.のエラーが出た

対策

config.xml に以下を追加

<allow-navigation href="https://*youtube.com/*" />

https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-whitelist/