2018年3月3日土曜日

expoでQRコードのデコードをdetachしてreact-native linkせずに行うためのコンポーネントを公開した的なお話

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

QRコードをデコードしたいときってあるわけで。
Expoにしても何にしてもカメラでQRコードを読み取ってデコードするっていうのは標準で搭載されているんだけど、
スクショした画像とかQRコードの画像からデコードをするというライブラリは少ないわけで。
さらにいうとそれらって結局のところreact-native linkする必要とかあったりで、NativeModulesで書かれてることが多い。
となるとExpoで使えないから困るよねっていうことになるわけで。

なのでdetachしたりとかExpoを諦める必要のないQRデコードライブラリを作りました的な。
ということで今回はそのライブラリを作るに当たってかなり疲労した部分があるのでそれについてのお話をば。

とりあえず作ったライブラリはこちら。
https://github.com/watanabeyu/react-native-qrimage-decoder
特徴
・react-native linkをする必要がない
・iOSに至ってはExpoにも依存しない
・ファイルURL(file:///とかhttp://とか)を渡してあげるだけでいい

依存しているライブラリ
■iOS
LazarSoft/jsqrcode
axios/axios
feross/buffer
■Android
cozmo/jsQR

■どういう仕組みなのか?

とりあえずのところとしては、WebViewを使用している。
というのもjavascriptだけで諸々やろうとしているライブラリを見つけようとしたけどもnode関連となるとfsとかそういったものが必要になったり。
react-nativeだとfsは使えないからfsを使うにしてもreact-native-fsを使う必要がある。
となるとNativeModulesを使うことになるから要件を満たさない。

で、じゃあdataURLを渡して処理するブラウザベースのライブラリって思ったんだけれども、そうなるとjsだけで処理が完了するわけでもなく。
react-native-canvasというものがあるからこれを使おうかなと思ったんだけど、
LazarSoft/jsqrcode内ではnew Image()としており、ブラウザ標準のImageとかcanvasを使っているためjsだけで処理が完了することができず。

となるとWebViewでQRコードを処理させればいいんじゃないかという流れがまず出来上がった。
なのでWebViewにdataURL形式を渡し、WebViewで処理完了後、react-native側に結果を返せばいいんじゃないかと。

で、問題点とか色々とあって処理をiOSとAndroidで若干分ける必要があったので、依存するライブラリもちょっと変えてあるっていう。
ちなみにiOSではローカルの画像パスおよびdataURL文字列を渡してもOK。
Androidの場合はローカルの画像パスを渡す必要がある感じで仕組みを確定させた。

■Androidの問題点

とりあえずそもそもAndroidでの問題点がありすぎたので、まとめるのがかなり面倒だってことに気づいた。
だからメモったものだけそのまま載せるみたいな。

// onMessageを受け取るためにjavaScriptEnabledが必要
// injectできる文字数がandroidの場合は決まってそう?
// injectで文字数が多いから、this.props.src.lengthでfor文を回し、中でcharAtして、一文字ずつ送信してっていう方式を考えたけど、そもそもthis.props.src.charAt時点でNGになるっぽい
// androidのwebviewではinput type="file"が使えない
// const html = `<html><body><p>${this.props.src}</p></body></html>`;としてsourceに直接文字列を指定した場合に、かなり時間がかかるけどいけそう?だけど無理っぽい
// androidの場合だけexpoに依存するようにさせる
// componentDidMount時にをfile://内に作成
// 画像パスを読み込んだ際にfile//内にコピー
// その画像をQR処理するようにする
// そもそもAndroidではhttp or https以外はfetchできないっぽい
// jsQRがうまく動いてないっぽい?そのあとのpostMessageが動かない -> getimagedataがcross origin問題でダメ
// DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.
// allowUniversalAccessFromFileURLsを使うことでCross-originが解決するっぽく、file://内というか相対パスのも見れるようになった(https://github.com/facebook/react-native/blob/master/Libraries/Components/WebView/WebView.android.js)
// ただgetImageData()が絶望的に遅い
// drawImage()する際に、画像のwidth/heightのままにするのでなく、縮めることでgetImageData()は実用に耐えられるようになる
// allowUniversalAccessFromFileURLsをtrueにしてもfile://を直接読み込むことはできないっぽく、相対パスにする必要あり
// rawとして引っ張ってくると面倒なので、jsにしてimport

なんていうか疲れた。
dataURL文字列を渡せないから画像をどうにか中に伝達する必要あるんだけど、
canvasで読み込んだときにfile://だからcross origin問題が発生してしまうし。
だからドキュメントを見ても情報は載っていなく、だからソースコードまで見てどうにかallowUniversalAccessFromFileURLsを使うことでいけるんだと判明。
ってな感じでとりあえず疲れた。

本当はAndroidだけfetch("./inludes/hoge.html")とかfetch("file:///xxxxx")ができないとかすごい問題があったんだけど。
いかんせん問題点が多すぎてまとめるのが辛いっていう。

■まとめ

なんか文章書いてて面倒になったのもあるし、問題多すぎだし、文章量すごいことになったしってことでここら辺で終了。
とりあえずAndroidのWebViewに関する知見は溜まった感ある。

ちなみに将来的にはExpoに依存させないような形で改造ができる目算はあるので、アップデート頑張りたい的なみたいな。

Adsense