react native tutorial 日本語で(2015/10/24)
先日の記事でios、android共に実機デバッグができるようになった。
なので次のステップに。
チュートリアルを試す
reactも触ったことがないので、雰囲気掴むためにチュートリアルをやってみる。
ついでになんとなく日本語に訳しておこう。
ただ毎回二つのデバイスで試験するのも手間なので、
iosのみで実装し、最後にandroid用の修正をすることにしよう。
前置き
iosとandroidの開発をreact nativeを利用して書く。
react nativeとは、とか、facebookがreact nativeを書いた理由とかは
ここ
を参照。
Reactの経験があることを仮定としているから、もし触れたことがなければReactで確認を。
(あれ、俺このまま進んでもいいのかね。。。)
セットアップ
依存関係と基本的なセットアップはここを参照のこと。
もろもろ依存関係とか解決すると以下の二つのコマンドが使用できるreact native用の開発環境が整う。
sh npm install -g react-native-cli
コマンドラインインターフェースのインストール。sh react-native init AwesomeProject
ReactNativeのソースコードや依存関係の解決をした後に、Xcodeとgradleのプロジェクトを作成。
開発
iosの場合は、Xcodeでproject(AwesomeProject/ios/AwesomeProject.xcodeproj)を開き、⌘+Rでビルドと実行をすれば起動。ちなみにこれはライブコードリロードを可能にするnode serverが起動している。この機能によって、シミュレーター内で⌘+Rを押すと全ての変更を確認できるようになる。
androidの場合は
sh
react-native run-android
のコマンドを実行するとアプリを生成してエミュレーターか実機にインストールする。んで、ライブコードリロード用のnode serverが起動する。変更を確認するためには、rage-shake-menu (実機を振る、menuボタンを押す、F2ボタンを押す、エミュレーターならPageUpを押す、Genymotionが入っているなら⌘+M、このいずれかでメニューが出てくる)を開いて、ReloadJSを押すんだ。
Hello World
sh
react-native init
でアプリを作成。今回はAwesomePorjectという名前にした。これは簡単なhello worldアプリになっている。
iosではindex.ios.jsを編集して、⌘+Rを押せば変更が確認できる。
androidではindex.android.jsを編集して、メニューを開いて、ReloadJSを押せば変更が確認できる。
モックデータ
Rotten Tomatoes(海外の映画評論家のコメントが載っているサイトらしい)からデータを取得するコードを書く前に、ReactNativeで遊ぶためのモックデータを用意しよう。facebookでは一般的に定数はJSファイルの上の方に書く、ちょうどrequiresの下だ。しかしこの辺は好き好きで自由にしていい。
ということで、index.ios.jsかindex.android.jsに以下の記載をする。
var MOCKED_MOVIES_DATA = [ {title: 'Title' , year: '2015' , posters: {thumbnail: 'http://i.imgur.com/UePbdph.jpg'} }, ];
MovieをRenderする
さて、ここでは映画に関するtitle, year, thumbnailをrenderすることにしよう。
thumbnailがReact NativeのImage componentなので、ImageをReactのrequiresに加えて以下のようにしよう。
var { AppRegistry, Image, StyleSheet, Text, View, } = React;
次に、さきほど作ったモックデータに関してrenderするようにrender関数を変更してみよう。
render: function() { var movie = MOCKED_MOVIES_DATA[0]; return ( <View style={styles.container}> <Text>{movie.title}</Text> <Text>{movie.year}</Text> <Image source={{uri: movie.posters.thumbnail}} /> </View> ); }
⌘+R または ReloadJSを押し、Titleと2015が表示されるのを確認。ここで画像がまだ表示されていないのに気づいたかな。これはwidthとheightを指定していないからである。
このサイズの指定に関してはstyleを経由して行うよ。Styleを以下のように変更し、他の内容に関してはいらないので削除してしまおう。
var styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, thumbnail: { width: 53, height: 81, }, });
そして最後にこのstyleをImageコンポーネントに適用する。
<Image source={{uri: movie.posters.thumbnail}} style={styles.thumbnail} />
⌘+R または ReloadJSで更新だ。
styleを追加する
これでstyleを追加できたので、より良い見た目に変更していく。
画像を左寄せで、テキストを右寄せ、さらにタイトルを大きくエリア内で中央寄せにする。
まずは、containerをviewに追加しよう。これは水平方向にレイアウトするための要素となる。
return ( <View style={styles.container}> <Image source={{uri: movie.posters.thumbnail}} style={styles.thumbnail} /> <View style={styles.rightContainer}> <Text style={styles.title}>{movie.title}</Text> <Text style={styles.year}>{movie.year}</Text> </View> </View> );
そんなに大きな変更をする必要はない。Imageを移動させ、囲むだけだ。
styleは以下のようになる。
container: { flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', },
ここではFlexBoxレイアウトを指定している。
ちなみに上のコードでは、単純にflexDirection: 'row'をメインContainerに指定して、水平方向に並べるための子要素を作っている。
さらに追加で以下のstyleも追加しよう。
rightContainer: { flex: 1, },
このrightContainerの意味は、親containerに対して、余ったスペースを埋めるというものである。注意したいのはImageが余白を埋めるのではないという点である。もし理解ができなければ、backgroundColorをrightContainerに追加し、flex:1の指定を外してみれば良いだろう。
そうすれば、containerのサイズが子要素に対して最小化されるというのが確認できるかと。
最後に以下のstyleを足してみる。
title: { fontSize: 20, marginBottom: 8, textAlign: 'center', }, year: { textAlign: 'center', },
最後にいつもどおり⌘+R または ReloadJSで確認だ。
実データの取得
Rotten TomatesのAPIを利用して、リアルなデータを取得してみよう。
まぁこれはReactNativeの学習ではないので息抜き程度に気楽にやってほしい。
以下のリクエストに利用する定数REQUEST_URLをjavascriptの上部に足す。(一般的にはrequiresの下になるが)
/** * For quota reasons we replaced the Rotten Tomatoes' API with a sample data of * their very own API that lives in React Native's Github repo. */ var REQUEST_URL = 'https://raw.githubusercontent.com/facebook/react-native/master/docs/MoviesExample.json';
次に、アプリの初期状態を追加する。これは、
javascript
this.state.movies === null
をチェックすることによりコンテンツがロードされているかどうかを確認することができるようにするためだ。
このデータをセットするタイミングは、レスポンスが返ってきた時に
javascript
this.setState({movies: moviesData})
がコールされた時である。
以下のコードをrender関数の上に追加してみよう。
getInitialState: function() { return { movies: null, }; },
requestはコンポーネントのロードが終了したら送出したい。そこで、Reactコンポーネントのロードが完了したタイミングで一度だけ呼ばれるcomponentDidMountという関数を実装する。
componentDidMount: function() { this.fetchData(); },
いよいよ、このコンポーネントが利用するfetchData関数を追加する。
この関数はデータ取得に関しての責任を持つことになる。
ここで必要なことは、promise chainが解決された後に
javascript
this.setState({movies: data})
を呼ぶことである。
なぜならReactのsetState関数は再描画のトリガーとなっており、render関数におけるthis.state.moviesがnullではなくなる必要があるためである。
注意すべき事項としては、doneという関数をpromise chainの最後にコール必要がある。いつもdone()が呼ばれていることを確認しよう、さもなくば即座にerrorが飛んでくることになる。
fetchData: function() { fetch(REQUEST_URL) .then((response) => response.json()) .then((responseData) => { this.setState({ movies: responseData.movies, }); }) .done(); },
あとは、render関数をmovieデータを持っていない時のloading用viewに変更するように変更を行い、そうでなければ一番最初のmovieデータを表示するようにする。
render: function() { if (!this.state.movies) { return this.renderLoadingView(); } var movie = this.state.movies[0]; return this.renderMovie(movie); }, renderLoadingView: function() { return ( <View style={styles.container}> <Text> Loading movies... </Text> </View> ); }, renderMovie: function(movie) { return ( <View style={styles.container}> <Image source={{uri: movie.posters.thumbnail}} style={styles.thumbnail} /> <View style={styles.rightContainer}> <Text style={styles.title}>{movie.title}</Text> <Text style={styles.year}>{movie.year}</Text> </View> </View> ); },
いつもの通り⌘+R / ReloadJS を押して、"Loading movies... " と表示されることを確認し、待っていると最初の動画データが表示されるのを確認してみよう。
ListView
ListViewコンポーネントを利用して、アプリケーションを変更してみよう。
このmovie要素を描画雨する際に、ScrollViewよりもListViewの方が適しているのは何故だろうか。
Reactが早いにもかかわらず、無限の要素を描画する際には遅くなってしまう。
ListViewはViewの描画をスケジューリングすることにより、すでに描画されている要素をスクリーン上に表示し、スクリーンを外れたものはnative viewの構造から取り除くという仕組みをもっている。
まず最初にやることは、ListViewをrequireに追加することである。
var { AppRegistry, Image, ListView, StyleSheet, Text, View, } = React;
次に、render関数を単一のmovieデータを表示するロジックから、一度に取得したデータをListViewへ描画するというロジックに変更する。
render: function() { if (!this.state.loaded) { return this.renderLoadingView(); } return ( <ListView dataSource={this.state.dataSource} renderRow={this.renderMovie} style={styles.listView} /> ); },
ここでdataSourceとは、ListViewがデータの更新時に行が変わったかどうかを特定するために利用するインターフェースである。
javascript
this.state
からdataSourceが利用されているのが確認できるかと思う。
次のステップでは、getInitialStateから返されるobjectに空のdataSourceを追加する。
また、dataSourceにdataを保持することにする。そのため、データの重複保持を避けるためにthis.state.moviesは今後利用することはない。さらにthis.state.loadedのbool値を用いて、dataのfetchが終了したか否かを判断する。
getInitialState: function() { return { dataSource: new ListView.DataSource({ rowHasChanged: (row1, row2) => row1 !== row2, }), loaded: false, }; },
fetchData関数も以下のように現状に倣って変更する。
fetchData: function() { fetch(REQUEST_URL) .then((response) => response.json()) .then((responseData) => { this.setState({ dataSource: this.state.dataSource.cloneWithRows(responseData.movies), loaded: true, }); }) .done(); },
最後に、ListViewへのスタイルを追加する。
listView: { paddingTop: 20, backgroundColor: '#F5FCFF', },
これで完了だ。
ナビゲーションを追加したり、検索を追加したり、無限ローディング機能を追加したりなどやることは残っているが、(Movie Example)https://github.com/facebook/react-native/tree/master/Examples/Moviesを参照してくれ!
最終ソースコード
/** * Sample React Native App * https://github.com/facebook/react-native */ 'use strict'; var React = require('react-native'); var { AppRegistry, Image, ListView, StyleSheet, Text, View, } = React; var API_KEY = '7waqfqbprs7pajbz28mqf6vz'; var API_URL = 'http://api.rottentomatoes.com/api/public/v1.0/lists/movies/in_theaters.json'; var PAGE_SIZE = 25; var PARAMS = '?apikey=' + API_KEY + '&page_limit=' + PAGE_SIZE; var REQUEST_URL = API_URL + PARAMS; var AwesomeProject = React.createClass({ getInitialState: function() { return { dataSource: new ListView.DataSource({ rowHasChanged: (row1, row2) => row1 !== row2, }), loaded: false, }; }, componentDidMount: function() { this.fetchData(); }, fetchData: function() { fetch(REQUEST_URL) .then((response) => response.json()) .then((responseData) => { this.setState({ dataSource: this.state.dataSource.cloneWithRows(responseData.movies), loaded: true, }); }) .done(); }, render: function() { if (!this.state.loaded) { return this.renderLoadingView(); } return ( <ListView dataSource={this.state.dataSource} renderRow={this.renderMovie} style={styles.listView} /> ); }, renderLoadingView: function() { return ( <View style={styles.container}> <Text> Loading movies... </Text> </View> ); }, renderMovie: function(movie) { return ( <View style={styles.container}> <Image source={{uri: movie.posters.thumbnail}} style={styles.thumbnail} /> <View style={styles.rightContainer}> <Text style={styles.title}>{movie.title}</Text> <Text style={styles.year}>{movie.year}</Text> </View> </View> ); }, }); var styles = StyleSheet.create({ container: { flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, rightContainer: { flex: 1, }, title: { fontSize: 20, marginBottom: 8, textAlign: 'center', }, year: { textAlign: 'center', }, thumbnail: { width: 53, height: 81, }, listView: { paddingTop: 20, backgroundColor: '#F5FCFF', }, }); AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);
仕事で新しいサービスを作る事になったのでreactで遊んでみた。
Reactと戯れる
まずはinstall
前提条件として
macであること
というのがあるのだが、それ以外でのやり方は知らない。
- Homebrew — The missing package manager for OS X を入れる。
- Node.js のv4以上を入れる。
sh brew install watchman
を入れるsh brew install flow
を入れる。
こんなところかな。
watchmanとflowについてはよくわかっていないけど、 軽く眺めてみたところファイル監視とjsの静的なファイルチェッカーのようだね。
iosとandroidのプラットフォーム準備
というかsdk入れる。
iosはxcodeをandroidはandroid SDKを入れる。
今回は入っていたので省略。
プロジェクトの作成
npm install -g react-native-cli react-native init AwesomeProject
コマンドラインツール入れて、react-nativeコマンドでプロジェクト作成。
IOSでの起動
AwesomeProject/iosの下にAwesomeProject.xcodeprojがあるのでこれをxcodeで起動。
するとパッケージ構成やら実行構成やらがもろもろ詰め込まれたものが出てくる。
全然追えてないけど暇なときに漁ってみる事にします。
さて、実行といえばxcode上で[⌘ + R]くらいです。
するとios emulatorが立ち上がってアプリが確認できるはず。
ちなみにこれから手を加えるであろうjsファイルは
AwesomeProjectの直下にindex.ios.jsというファイルがあり、本体はこれです。
androidで起動
react-native run-android
というコマンドを実行。多分コンパイルとか勝手にやってくれているんだと思う。
私は
ANDROID_HOMEが見つからないよ
って怒られたので、install pathである/User/username/Library/Android/sdkを指定。
が最終的に起動が遅いせいで
device not found
とのこと。
ググったら出てきた。
んじゃ仕方ないからandroidだけ実機debugとするか。
いつもそうだけどw
android 実機デバッグ
やり方
- 実機接続
sh react-native run-android
- 多分赤い画面が出力される。(実行したconsoleには特にエラーは出ず) これに関しては知らなかったけど多分reactのデバッグの仕組みとしてサーバーにあるjsをロードして表示してて、実機デバッグするとサーバーの場所が分からなくなるから失敗してるってことかと。
- おもむろに端末を振る
- Dev Settingsを選択
- Debug server host for deviceを選択し、実行しているサーバーのIPアドレスを入力
- 再度ロードすると表示されるかと。
一応iosでも実機デバッグしておくか。
AwesomeApp/ios/AwesomeApp/AppDelegate.m
ここのlocalhostを開発中のPCのIPアドレスに置換。
macに挿して起動するだけ。
最近のアップデートでprofile入れなくても実機デバッグが可能になったみたい。
何かしらエラーが出たらfix issueで解決。
とはいえandroidやらjavaは割と触っているけどiosはまだ全然だし、xcodeとも仲良くなれる気があまりせんなー。
intel Edisonで何か作ってみよう その4
さてこれまでで、割といい感じにLチカまで持ってこれたかと。
ということで、何かしらセンサーやらボタンやらを組み込んでLチカとか何かをハックしたりとかwebサービスと連携したりとか遊んでみたいと思う。
pushボタンで点灯
hello worldの少し上のレベルかな。
制御すべきハードウェアを二つ増やし、io部分の実装も必要かな。
購入したキットにあるチュートリアルを参考に。
jsに書き換えた
/*jslint node:true, vars:true, bitwise:true, unparam:true */ /*jshint unused:true */ // Leave the above lines for propper jshinting //Type Node.js Here :) var mraa = require('mraa'); console.log('MRAA Version: ' + mraa.getVersion()); var buttonPin = new mraa.Gpio(2), ledPin = new mraa.Gpio(13); var ledState = 0, prev = 0; // setup buttonPin.dir(mraa.DIR_IN); ledPin.dir(mraa.DIR_OUT); // start loop(); function loop() { prev = ledState; ledState = buttonPin.read(); if (prev !== ledState) console.log('current led state ' + (ledState === 1 ? 'HIGH' : 'LOW')); ledPin.write(ledState); setInterval(loop, 1000); }
若干気になったのは、
- consoleの文字列連結に()が必要だったっけ?
- 標準出力に出しすぎるとXDKが無反応になる。
- DIR_OUT_HIGHの値が2である (最初HIGHとLOWの判定に使ったら違った)
- DIR_INにwriteしたり、DIR_OUTにreadしたらどうなるんだろー。
学習メモ
- INPUTとOUTPUTの方向がある。
- GPIOはGeneral purpose ioの略で、一般的なIO目的で利用されるらしい。
- readの返り値は0 or 1
- Arduinoにはloopとかsetupの関数が用意されているっぽいがjsでは自分で実装
- readでその時の状態を読んで、writeで書き出す。
intel Edisonで何か作ってみよう その3
さて、こないだの部分で電源コードを繋いでおけば、 無線で家の中どこでもedisonと繋がれる状態になった。
はかどる。
さて待望のLチカにいくとしよう。
step4: IDE(XDK)を導入する
edisonにはいくつかIDEが用意されている。
For JavaScript: Intel® XDK IoT Edition For C/C++: Eclipse For Arduino: Arduino*
とのこと。
特に縛りはないけど、Arduinoは新たにキャッチアップ必要そうだし。
nodeが使えれば割といろいろできそうだし、 多分webと繋ぎたくなるだろうからjsの方がよさそうだ。
ダウンロード先は
IoT - IDEs | Intel® Developer Zone
まぁダウンロードして、アカウント作成して、ログインすれば終了!
step5: XDKと戯れる
以下の二つを紹介しておこう。
Setting Up The Intel® XDK IoT Edition Part 1: Installation | Intel® Developer Zone
sampleプロジェクトの中にある「OnBoard LED Blink」とやらでhello world的なあれをやろう。
sampleプロジェクトの中からOnBoard LED Blinkを選ぶ
適当に保存先とproeject名称つけて保存
DEVELOPタブから下部コンソールのところに「select a device」とあるのでそこを選択
一覧の中に自動的に出てくるかと思うが、出てこなければscanからmanualで入れる。
(IPアドレスとrootとパスワードを入れればOK)
ソースコードを動かす
大きく4つできることがある。左から順に
以上でLEDチカチカしはじめたかと。
setTimeout(periodicActivity,1000); //call the indicated function after 1 second (1000 milliseconds)
この部分の1000を100とかに変えると点滅の速度が変わる。
Intel Edisonで何か作ってみよう その2
前回のでファームウェアの更新が終了したので↓
次は何か取り付けて遊んでみるか。
step3: 初期設定をする
wifiの設定やパスワードの設定やsshでの接続設定を行おう。
この設定やると無線で転送とかできるからコード不要になって コンセント近くに置いておけばいいから楽かもw
edisonの特徴の一つで、このサイズでもwifiとかbluetoothが使えるという点がある
さて、wifiにつなげてみるか
- 設定コマンド実行
configure_edison --setup
あとは、質問に答えていけば
- rootのパスワード設定
- edisonへのユニーク名の設定
- wifiの設定
ができる。
この設定が完了すると
ssh -l root 192.168.**.** # or ssh -l root hogehogeEdison.local # 自分で決めた名前
でアクセスができるようになる。
wifiの設定に関して
ズラ〜と出てくるので、自分がアクセスしたいssidを選んで 番号をタイプ、enter。
あとはパスワードの設定とかする。
ifconfig
とかで自分のIPアドレスを確認することができる。
スイッチについて
arduinoボードには全部で三つのUSBポートが存在している。
んでその真ん中に小さなスイッチがあってモードを切り替えることができるようになっている。
少し調べてみた。
この辺りがわかり易かった。
接続に方向があって、
edison -> PCとPC -> edisonの接続を切り替えるためのスイッチのようだ。
なので、これまでの状態(スイッチがmicroUSB側に設定されている場合)
デバイスモード
- microUSBはPCからのinputを受けることが可能
- microUSBに接続した時点でPCが外部HDとして認識してくれる
- edisonの設定を書き換えたり、電源を享受したりできる。
逆の状態(スイッチが標準USB側に設定されている場合)
ホストモード
- 電源の供給が必須
- edisonに繋いだ何かに電源供給したい時にはホストモードでこのUSBから電源が取れるみたい
ホストモードがよくわかっていないので、 追加でわかったことがあったら追記しよう。
どっちがいいのかとかもあまり書いていないし。。。
あぁUSBの世界の話なのか。 なるほどなるほど。
Intel Edisonで何か作ってみよう その1
Intel Edison
公式は下のやつ。
amazonで買うと2万円前後するっぽい。
こういうところで買えば12,000円くらいかなと。 あとセンサー周りも細々買えば同じ2万円でもセンサーがある状態になるし結構お得かと。
何から手をつけようか
買ってみたもののさて何から手をつけようかと。
もともと私はweb屋さんなのであまりハードのことは詳しくない。
なので初心者とっておきの「Lチカ」から始めてみようと思う。 (LEDチカチカのことらしいw)
参考にしたのは以下のサイト。 動画がたくさんあって良い。
Intel Edison Walkthrough Videos on IDZ - Intel Software and Services
step1: 組み立て
そんなに難しくないし、ここは特に詰まることはないかと思う。
step2: ファームウェアいれる
さて、これできっとboard上の緑ランプが点灯して、何かしらできるような状態になると期待している。
次はshellでmac -> edisonへ繋いでみる。
ls /dev/tty.* screen /dev/tty.usbserial******* 115200 -L # usbserialとなっているやつを選ぶ。
ログイン後
edison login: root
で入れる。
cat /etc/version
これでバージョンの確認ができる。
んで多分ファームウェア最新にした方がよいので、
この辺りを参考にしてやる。
- ターミナルから/Volume/edisonの内容全てを削除
- macのディスクユーテリティーを起動してFAT16からFAT32にする
- IoT - Intel® Edison Board Download | Intel® Developer Zoneここから最新イメージをDL
- Edisonボリュームに内容を全てコピペ
なんか途中で "cannot open line '/dev/cu.usbserial' for rw resource busy" and "sorry, could not find a PTY." errors とかいうエラーがでたらUSB全部抜いて、挿し直すと先に進めるようになったりする。
コピーして、edisonにshell login、rootで入り、reboot otaと打つ → ダメ 途中でこける。nameserverのdaemon起動のところがfailして失敗。
rubyいれて、brew入れて、downloadしたフォルダへ行き、flashall.shを実行 → ダメ うまくいったように見えるんだけど、 なぜかファームウェアは更新されず。。。
flashtool liteを入れて実行 → うまくいった
これでファームウェアの更新は終了!
これからいろいろ繋いで遊んでみることにしよう。
Azure webappsでnode アプリをdeployするときに困ったこと
AZURE WEB APPSでnodeアプリをデプロイ時にCERT_UNTRUSTEDが出る
という現象にハマった。
色々調べてみるとそもそもこのエラー自体がversionに起因するものみたいだ。
とはいえ、web appのversion指定ってどこでやるんだろうか。
まず思ったのがazure portalの設定で
アプリケーション設定に
WEBSITE_NODE_DEFAULT_VERSIONというのがあるのでここを設定。
しかしデプロイ時に使うのはここのバージョンではないようだ。
調べてみると
package.jsonに
"engines": { "node": ">=0.10.0" }
とか書くとこれが利用されるっぽい。
startしない
ライブラリにazure-storageを利用している。
この内部で
require('util')._extend
を実行していて、_extendはundefined。
確かにnodeの0.6系だとこのメソッドは存在しないようだ。
ということで、nodeを最新に上げておこう。
nodeのwindowsバイナリの新しいやつを持ってきて、
wwwroot\binに配置
いつのタイミングか分からんが自動生成される iisnode.ymlに
nodeProcessCommandLine: D:\home\site\wwwroot\bin\node.exe
という風に書き換えて再スタート!