agri-note inside

ウォーターセル株式会社 スマート農業システム開発部のブログです。

WaterCell Tech Night #2 を開催しました

こんにちは、ブラウザチームの松井 @circled9 です。

社内開催のテックトークイベントの第二回を開催したので、その様子をレポしたいと思います。

f:id:circled9:20181226093824j:plain

今回も🍕や🍺を嗜みつつ、和気藹々と発表を行いました。

ちなみにブログ初登場です、はじめまして。

来年の抱負を言ったつもりだったんですがめでたく今年達成してしまいました。

発表ダイジェスト

ここをこうしたら速くなった。最近の気付き (画像処理初心者向け?) @sug1t0m0_agrict

speakerdeck.com

画像処理でここをこうしたら処理速度が速くなったよ!という発表でした。

画像処理周りは二次元配列に対する演算になるので、桁が変わるレベルで速度が変わるんですよね...

普段画像処理のコードは書かないので、大変な世界だなぁと思いました。

今年のアウトプット50連発(NDSの練習) by @Nkzn

speakerdeck.com

今年のアウトプットを紹介するよ!という発表でした。

一年の週の数を考えると週一ペースでアウトプットした計算になるんですよね... 見習わねばと思いました。

Excelでゲームを作るときに役立つ関数5選 by @10mikiya

speakerdeck.com

Excelでゲームを作る時に役立つ関数を5つ紹介するよという発表だったのですが、1つ説明するたびに「え、ちょっとそれどうやったの?」「え?今の何?」と本題の関数以外にも学びが1つにつき5個ぐらい得られる発表でした。

Excelでゲームウォッチ風のゲームを動かすデモは正直驚きました。

R Markdownを設計に活用する by @nozma

f:id:Nkzn:20181219174343p:plain

github.com

R Markdownで設計書を書こうという発表でした。

おもむろにR Studioがブラウザ上で立ち上がった時はびっくりしました。

ルーターの選び方その2 by @kam1nchu

speakerdeck.com

前回に引き続き、ルーター選定に役立つ話、今回はルーター選定の時に出てくる用語の説明でした。

何となく名前は聞いたことがあるけど雰囲気しかわからん、という単語が結構あって勉強になりました。

Hello React hooks / Bundle Transpile Rock'n'Roll by @circled9

speakerdeck.com

speakerdeck.com

私の発表です。

Reactの次のバージョンで実装される予定のHooks APIの紹介と、NDSでも発表したJSのバンドルとトランスパイルについての発表でした。

まとめ

というわけで、第二回も無事開催されました。

年末の12月は会社の全体発表会があったり、年末でばたばたするので次回は1月を予定しています。

では次回もお楽しみに!

WaterCell Tech Night #1 を開催しました

こんにちは、モバイルチームの中川@Nkznです。

社内でテックトークイベントを開いたので、その様子をレポしたいと思います。

f:id:Nkzn:20181122172740j:plain:w600

🍕や🍺を嗜みながら、和気あいあいと発表を行いました。

割と急にやることが決まり、週初めに告知、週末に実施という短い期間の中、9名が発表する良い規模のイベントとなりました。

発表ダイジェスト

各発表を簡単に紹介します。

続きを読む

React Native for Webをプロダクションで使ってみました

こんにちは、モバイルチームの中川[twitter:@nkzn]です。

5月22日にプレスリリースがあった提携で紹介されていたアプリでは、React Native及びReact Native for Webを採用しています。こちらについて技術的な側面から(当たり障りのない範囲で*1)事例を紹介します。

経緯

5/22に、農業総合研究所さんとの業務提携契約が公開されました。

www.agri-note.jp

農業総合研究所さんは、7000件以上の農家さんから野菜を集荷し、全国各地のスーパーなどに設置された直売コーナー「農家の直売所」に野菜を出荷している、農産物の流通・販売・コンサルティングを手がける農業ベンチャーです。

f:id:Nkzn:20180718110013p:plain

(上記のスクリーンショットは2018年7月18日現在のものです)

www.nousouken.co.jp

今回の業務提携により、共同でシステム開発を行っていくことになりました。プレスリリースにある通り、第一弾として農薬使用履歴を管理するアプリを開発しています。

技術選択

様々な要件や今後のウォーターセルとしての方針を加味したところ*2、Android, iOS, Webに向けてマルチプラットフォームで開発することになりました。

今回は特にAndroid, iOS, Webでプラットフォームごとに要件が違うということもなく、むしろ農総研さんの中での教育コストや、農家さんへの指導のコストを考えると、UIは揃っていたほうがよい、ということで、UIレベルでのソースコード共有を行う下地が整っていました。

AndroidとiOSについてはReact Nativeを使えばよいのですが、Webも、となると話が違ってきます。React NativeにはWeb向けのターゲットが用意されていません*3

React Native for Webとの出会い

ここで目をつけたのがReact Native for Webです。

github.com

React Native for Webは「React Nativeと同じ名前のコンポーネントに同じ名前のpropsが生えていて、スタイルの再現性が極めて高い、Web向けのUIコンポーネントライブラリ」です。次のように使います。

import { View, Text, StyleSheet } from "react-native-web";

このままでは「コンポーネントの種類とスタイルのクセがReact Nativeとよく似ているUIライブラリ」でしかありません。

実際に使う場合は、BabelやWebpackを使ってパッケージ名にエイリアスを付けることで、React Native向けに書かれたコードに対してパッケージを誤認させます。

import { View, Text, StyleSheet } from "react-native";
// Web環境向けにビルドした場合はWeb向けのViewが出てくる

このへんのやりかたについては公式チュートリアルで紹介されていますので、興味がある方はご覧ください。

React Native for Webは眉唾かどうか

パッと見ではかなり眉唾感のあるライブラリだと思います。が、検討していくうちに、外堀を見ている限りでは信頼に足るものなのでは?と思えるようになりました。

  • コードを読んでも特に怪しいところがなかった
  • 作者のnecolasがnormalize.cssを作った人(これだけでスタイルへの信頼性が爆上げ)
  • 作者のnecolasがTwitter Liteのテックリード(当時)
  • Twitter Liteでプロダクション投入されている*4

眉唾物のネタプロダクトと切って捨てるには、あまりにも素性が良かったのでした。

プロトタイピング

というわけで、触ってみることにしました。ExpoとReact Native for WebでUIコードを共有しつつ、画像や音声などのリソースも管理できるプロトタイプを作成しました。

React Native for WebとExpoを組み合わせてピコピコさせてみたよ - Qiita

github.com

感想としては次のようなものになりました。

  • スタイルについてはかなり再現性が高い
  • よく使うのに足りないコンポーネントがある(FlatListとか)ので代替実装を用意する必要がある
  • React NativeそのものではないのでNativeBaseやreact-native-elementsなどのUIライブラリは導入しづらそう
  • ネイティブモジュールを使う場合はWeb版にしっかりと代替実装を用意する必要がありそう*5

チームについて

技術選択は事業とチームにフィットするものであるべきです。ということで、どういったチームがあったお陰でこの選択をする気になったのか、という話もしなければなりません。

watercelldev.hatenablog.jp

以前にも記事にしたことがありましたが、弊社モバイルチームはiOSアプリをCordova+webpack+Reactで運用しており、Webアプリの開発については多少の経験があります。また、もともとAndroidエンジニアとしてゴリゴリ書いてきた人たちなので、Android側の挙動についてもトラブルシューティングは容易です。

iOSネイティブのトラブルシューティングだけは不安が残りましたが、こちらは外部の方にお手伝いしていただくことで解決できました。ここは今後の課題です。

各プラットフォームでネイティブな手段でも開発が可能なチームを作って、トラブルシューティングができる体制を整えられたのも、Webとネイティブの同時開発という選択ができた要因になりました。

結論

React Native for Web、けっこう行けるやん。ということで、採用と相成りました。*6

採用してみて良かったこと、困ったこと

実際に開発をしてみて、React Native for Webを採用して良かったこと、困ったことを挙げていきます。環境は次のとおりです。

モジュール バージョン
react / react-dom 16.0.0
react-native-web 0.5.4
react-native 0.51.1

良かったこと:ブラウザとStorybookでコンポーネント開発ができる

f:id:Nkzn:20180522121741p:plain

最も開発効率に寄与してくれたのが、Storybookの採用でした。React Native向けのコンポーネントをブラウザ上で動作確認するイテレーションを高速に回すことができました。

Storybookは前述のプロトタイプにも組み込んでありますので、興味がある方は御覧ください。

良かったこと:Webのマークアップエンジニアをアサインできた

Storybookを使ったことによるもう一つのメリットとして、普段WebフロントエンドのチームでReactコンポーネントの作成を担当しているマークアップエンジニアをアサインするのが、容易に実現できました。

  • HTML+CSSが主戦場
  • JSは得意ではない
  • Reactコンポーネントもレイアウトだけで良ければ作れる
  • CSS in JSは最近aphroditeで覚えた

くらいのスペックです。

実はStorybookにはReact Native向け実装がありまして、Androidエミュレータを立ててネイティブUIによる動作確認を行う方法もあります。しかし、今回彼をアサインするにあたり「今回のためだけにAndroid StudioやXcodeを入れてもらうのは申し訳ないな〜」という気持ちがありました*7

というわけで、今回は思い切って、彼にはブラウザのStorybookだけでコンポーネントの動作確認をしてもらうことにしました。

色々ありましたが(後述)、概ね上手くいったと思っています。当人も「 <View><Text> はdivとspanみたいなものだし、この2つのスタイルのクセを把握すれば、あとはその派生だったから問題なかった。React Nativeのコンポーネントづくりは、十分にマークアップエンジニアの土俵だった」という主旨の話をしており、手応えはあったようです。

困ったこと:本当にWebのノリでスタイルを書くとネイティブが壊れる

というわけで、マークアップエンジニア氏にはかなりの量のコンポーネントを作ってもらいました。そんな中で初期に困ったのが、「Webでしか使えないスタイル」の存在です。

React NativeのレイアウトシステムはYogaが使われているわけですが、これはCSSを再現するものではなく、Flexboxを再現するものです。そのため、Webでは一般的な記法でも、React Nativeでは使えないものがあります。当時に遭遇したものは widthmargin の値でした。Webでは良くてもReact Nativeではダメな例を挙げます。

// 値に単位をつけるのはNG
{
  width: '100px',
  marginLeft: '16px'
}

// 値を直接指定するのはOK
{
  width: 100,
  marginLeft: 16
}
// %やautoを使うのはNG(だった。現在では使えるらしい。後述)
{
  width: '100%',
}
{
  marginLeft: 'auto',
  marginRight: 'auto'
}

// Flexboxを使うのはOK
{
  flexDirection: 'row',
  flexGrow: 1
}
{
  justifyContent: 'center'
}
// ViewにText系のスタイルをつけるのはNG
<View style={{ color: "#F00" }}>
  <Text>ここには色がつかない</Text>
</View>

// 直接指定するのはOK
<View>
  <Text style={{ color: "#F00" }}>ここには色がつく</Text>
</View>

最後のTextスタイルの話は、違いを如実に表しています。CSSは内側に向かって再帰的に効きますが、React Nativeの装飾系のスタイルは適用したコンポーネントそのものにしか効きません。

当初、この問題は発覚しませんでした。ブラウザ上で動かしている分には普通のReactとReactDOMによる挙動に準拠するため、ちゃんと動いてしまっていたのです。その後、「そろそろAndroidでも動作確認するかー」と言い出したときにレイアウト崩れが起きて発覚した次第です。

発覚して以降は「Flexboxしか使えないならそれはそれで」ということで納得して書いてもらうことで、ほとんど問題は起きなくなりました。React Native公式ドキュメントでViewTextのスタイルを見ながらマークアップしていく様はとても頼もしいものでした。

アップデート:margin: autoや%表記について

検証した当時は marginLeft: 'auto'% 表記をするとエラーが出ていた記憶があるのですが、本記事を書くにあたって再度検証してみたところ、問題なく使えました。私たちは幻を見ていたのか……

v0.52.0でYogaのmarginLeft: auto%の扱いに手が入ったようなので、このときに直ったのかもしれません。

先程、マークアップエンジニア氏に報告したところ、強くガッツポーズをしておられました。

困ったこと?:flex-directionのデフォルトがcolumn

ネイティブとWebで挙動が逆で戸惑ったパターンとして、flex-directionのデフォルト値が違う、というものがありました。

プラットフォーム flex-direction
React Native (for Web) column
Web row

これに大きくハマったのがマークアップエンジニア氏で、align-itemsjustify-contentの効き方が逆になって、当初は大混乱していました(すぐに慣れました)。

普通のReact Nativeに慣れていた筆者にとってはいつもどおりだったので、まったく気になりませんでした・・・(逆に、たまに普通のWeb開発をするときにブラウザのデフォルトがrowなことに戸惑っています)

困ったこと:画面遷移は共通化できない(しづらい)

React Nativeの頻出課題として、画面遷移をどうやって実現するかという問題があります。AndroidとiOSで画面遷移の考え方が違っていて、どちらかというとiOSよりの考え方で作られていることが多いので、Android出身の筆者はよく混乱しています。

さて、Webの、しかもシングルページアプリケーションの画面遷移となると、ネイティブのそれとは輪をかけて違います。内部的にURIを定義したり、History APIでpushStateするくらいなら、ネイティブもWebも大差ない動きをするのですが、決定的に違う点として、Webでは直リンクやURL削りがカジュアルに行われます。どんなURLで来られても対応できるような対策がWebでの画面遷移には求められます。

画面遷移に関する考え方が違いすぎるので、画面遷移ライブラリはネイティブとWebで別々に用意して、Screenコンポーネントもネイティブ用とWeb用を用意し、Screenの中に配置するコンポーネント(or コンテナ)だけをネイティブとWebで共有することにしました。

それぞれのプラットフォーム向けに採用したライブラリは次のとおりです。

プラットフォーム ライブラリ
ネイティブ React Navigation
Web React Router

実はReact NavigationはWebで使えないこともないですし、React RouterはReact Nativeに正式対応しているのですが、下手に共通化するとまずそうな気配を感じたので避けました。もし今後、1つのライブラリで済ませる日が来るとしても、Screenコンポーネントはプラットフォームごとに分けると思います。

困ったこと:FlatListがない

React Native for Webのコンポーネントは、順次拡充されています。そのため、たまにコンポーネントが実装されていないことがあります。

一番困ったのはFlatListが未実装だったことです。リスト表示のパフォーマンスを向上させるためにも、できればあってほしかったのですが・・・

無いものは仕方ないので、内部実装を分岐することにしました。

// ChemicalList.tsx
export function ChemicalList(props: { chemicals: Chemical[] }) {
  return Platform.OS === "web"
    ? <ChemicalListWeb chemicals={props.chemicals} />
    : <ChemicalListNative chemicals={props.chemicals} />;
}

function ChemicalListNative(props: { chemicals: Chemical[] }) {
  return <FlatList ... />
}

function ChemicalListWeb(props: { chemicals: Chemical[] }) {
  return <ScrollView>
    { chemicals.map(chemical => ...) }
  </ScrollView>;
}

Web版の実装はデータ数が少ないので何とかなっているのですが、データが増えるとパフォーマンスに影響が出るので、時間を見つけてreact-virtualizedあたりに置き換えたいなあという気持ちはあります。

また、ChemicalList.native.tsxChemicalList.web.tsx にそれぞれ実装すれば勝手に切り替わる、くらいまでビルド環境が整備できればよかったのですが、時間切れで諦めてしまいました。そもそも native.tsnative.js という接尾辞をReact Nativeそのままでは認識できないはずなので、難易度は高そうです。サードパーティバンドラーのHaulにts-loaderを食わせる例ではnative.tsxを認識させようとしているので、今度はこの方向でチャレンジしてみたいです。

困ったこと:UIライブラリが使えなかった

前述のとおり、NativeBasereact-native-elements*8react-native-paperなどのUIライブラリがWeb側では使えませんでした。ボタンやヘッダーなどの共通コンポーネントはすべて自作しています(ここでもマークアップエンジニア氏にはめちゃくちゃ活躍してもらいました)。

困ったといえば困ったのですが、UIライブラリが使えたとしてもかなりのカスタマイズが入っていたことが予想されるので、もしかすると自作で良かった部分もあるのかもしれません。

こちらについても、.(android|ios|native).tsx.web.tsxの切り替えによって、各プラットフォーム向けのUIライブラリを採用できるビルド環境の整備を研究しているところです。

良かったこと:かなりの部分のソースコードが共有できた

結果的に、ソースコードのかなりの部分が共有できました。実際のファイル数を見ていきましょう。

今回のアプリケーションでは、Lernaでmonorepo化して依存性の方向を整理した、レイヤードアーキテクチャライクな構造になっています。プレゼンテーション層、アプリケーション層、ドメイン層、インフラストラクチャ層の4層になっています。ディレクトリ構造は次のとおりです。

  • packages/
    • presentation/src : UI周り。アプリとしての本体。
      • native/ : ネイティブにしか関心がない。
      • shared/ : 共有するコンポーネントやコンテナ。⭐
      • web/ : Webにしか関心がない。
    • application/src : 処理の交通整理を担当。⭐
    • domain/src : 仕様を直訳した型定義やアルゴリズムを担当。⭐
    • infrastructure/src : 外界との通信を担当。⭐

これらのうち、⭐が付いているディレクトリはネイティブからもWebからも参照される共有コードになっています。手元の最新リリースバージョンでファイル数をカウントしてみます。*9

$ find packages/presentation/src/native -type f | wc -l
      49
$ find packages/presentation/src/shared -type f | wc -l
     251
$ find packages/presentation/src/web -type f | wc -l
      65
$ find packages/application/src -type f | wc -l
      21
$ find packages/domain/src -type f | wc -l
      59
$ find packages/infrastructure/src -type f | wc -l
      87

(251+21+59+87)/(49+251+65+21+59+87) = 0.78571... で、約78.6%が共有できているようです。

共有したことに由来する問題は今のところチーム内でも聞いていませんし、まずまず共通化については大きな成果が出ているといえそうです。

良かったこと:普通に便利なコンポーネントがあった

ネイティブ開発の文脈だと「Flutterに比べて充実してないなあ」という気持ちになるReact Nativeのコンポーネントのラインナップですが、Webで使ってみると普通に嬉しいものがいくつかありました。

  • ScrollView
    • 何も考えなくても慣性スクロールがついてるの便利
    • スクロールを始めるとソフトウェアキーボードが隠れてくれる機能(keyboardDismissMode)が便利
  • ActivityIndicator
    • とりあえず置いとこう、みたいな気持ちでサクサク使える
    • ライブラリを見つけてきてもいいけど、ライブラリを探さなくても使えるのが重要
  • Switch
    • ライブラリを見つけてきてもいいけど、ライブラリを探さなくても使えるのが重要
  • TouchableOpacity
    • 指タッチに対応した、押すと透明度が変わる領域を簡単に作れる
    • これもCSSで弄りだすと手間がかかるので標準搭載されてるのが地味に嬉しい

今回の開発をしながら気付いたのですが、任意のUIフレームワークを自作していく際の材料としては、React Nativeのコンポーネント群は良いバランスをしているんじゃないかと思いました。Webしか開発しない場合にも使ってみたいです。

良かったこと?:contentContainerStyle問題がWebでもネイティブでも起きる

ScrollViewのスクロール部の内部にパディングをつけたい場合は、styleに指定するのではなく、 contentContainerStyle に付ける、というReact NativeのTIPSがあります。

なんとこのTIPS、React Native for WebでScrollViewを使っている場合にも有効です。

この挙動に気付いたときは「nicolasさんはどこまで再現しとるんや・・・」と戦慄したものでした。

まとめ

あまりReact Native for Webの導入事例を聞かないので、プレスリリースが出た記念にこれまでの開発を振り返ってみました。

一昔前までは眉唾または相当の無茶をしないと得られないと思っていた「Webとネイティブのコード共有」というテーマでしたが、そこまで無理をせずに実現できてしまい、戸惑いつつも嬉しく思っています。

「画面のサイズが同じなら、Webとネイティブでまったく同じ動きをしていい」という要件があったからこそ成り立ったものだったので、他所でも同じような選択ができるとは限らないと思いますが、参考になれば幸いです。

今後の保守や機能改善を行なっていく中で、また課題が見つかることもあるかと思いますが、各個撃破していきます。

We are Hiring!

ウォーターセル株式会社では、一緒にクロスプラットフォームなアプリケーションを作ってくれる、ReactエンジニアやAndroidエンジニアやiOSエンジニアを募集しています。

www.agri-note.jp

*1:弊社・農総研さんともに広報チェック済みです

*2:諸事情によりふわっとした言い方にしています

*3:などと言っていたら本当にそれっぽいものをシマンテックの人が作ってしまったのが5月半ばの話なのですが、これについては、個人ブログで紹介しましたので、興味のある方は御覧ください https://blog.nkzn.info/entry/2018/05/26/020312

*4:https://twitter.com/necolas/status/913877194199359488

*5:なお、ピコピコサンプルで音を出したときは、予想に反してWebのほうが実装は楽でした

*6:内容的に本記事と被るところも多いですが、導入の意義については個人ブログにもまとめさせてもらいました https://blog.nkzn.info/entry/2018/05/29/210030

*7:ディスク容量を結構持っていかれるのです

*8:Webへの対応を進めてはいるそうです

*9:本来であればandroid.js, ios.js, web.jsがshared内にある可能性を考慮すべきですが、今回は存在しないことを確認できているので、このカウント方法を採用しています

AndroidのProduct FlavorsっぽいものをCreate React Appで再現したかった

こんにちは、モバイルチームの中川です。今回はJavaScript文化圏の記事になります。モバイルチームはユーザーの手のひらに価値を届けるためなら何でもするチームなので、必要ならJavaScriptだってやるんです(自分に言い聞かせるように)。それでは始めます。

アプリの細部を切り替えたい

ソースコードとしてはほとんど同じなんだけど、次のような振り分けをしてアプリとしては別々のものにしたいことってありますよね。

  • ビジネス上の都合でテーマを切り替えた版を作りたい
  • ボタンを出したり出さなかったりしたい
  • 接続先を切り替えたい

Androidアプリの開発では、build.gradleの中でBuild VariantsのProduct Flavorsとして定義することで、無料版と有料版を切り替えるみたいなことが簡単に行えます。iOSでも(本来の使い方からは外れるかもしれませんが)SchemeやTargetを切り替えることで似たようなビルド体験を実現できます。

Webでもやりたい

以前にも少し触れましたが、弊社にはCordova+ReactなiOSアプリがあります。最近は少しビルド環境を整理して、webpack2でゴリゴリ書いていたのを、Create React Appに閉じ込めることに成功しています。webpack.config.jsをメンテしなくていい生活は最高です。

さて、このたび、このアプリにもProduct Flavors的な実装をすることになりましたが、少し困ったことになりました。webpackならwebpack.config.jsごと差し替えるなり、内部で動的にエントリーポイントを差し替えるなりすればよいのですが、CRAではビルド環境に手を加えられる場所はほとんどありません。

早めの結論

Androidと全く同じくProduct Flavorsと同名のフォルダにコードを入れておけば自動で切り替えてくれるような、そういったものはできませんでした。webpackなら頑張ればできそうな気もしますが、少なくともCRAでは無理です。

ソースコードやリソースごと切り替えるというのは諦めて、アプリケーション内部で動的に差し替える方法を模索することになりました。

伝統と信頼の環境変数

動的に差し替えるにしても、アプリケーションに自分がどちらの環境で動いているのか教えてあげなければいけませんね。

そんなときこそ環境変数です。ビルド時点で外から依存性を突っ込んであげましょう。Androidから名前を借りて、今回も依存性の名前をflavorと呼んでいきたいと思います。

package.jsonにflavorを定義する

まずは変更前の状態です。dev serverをhttpsで動かしたかった関係で、 HTTPS=true が入っていたりしますが、至って普通のCRAです。

"scripts": {
  "start": "HTTPS=true react-scripts start",
  "build": "react-scripts build",
  "test": "react-scripts test --env=jsdom",
  "eject": "react-scripts eject"
}

ここに、appAとappBという2つのflavorを環境変数で定義します。REACT_APP_から始まる環境変数はビルドに取り込んでもらえる機能があるので、これを活用した形になります。

 "scripts": {
-  "start": "HTTPS=true react-scripts start",
+  "start:appA": "REACT_APP_FLAVOR=appA HTTPS=true react-scripts start",
+  "start:appB": "REACT_APP_FLAVOR=appB HTTPS=true react-scripts start",
-  "build": "react-scripts build",
+  "build:appA": "REACT_APP_FLAVOR=appA react-scripts build",
+  "build:appB": "REACT_APP_FLAVOR=appB react-scripts build",
   "test": "react-scripts test --env=jsdom",
   "eject": "react-scripts eject"
 }

実用してみる

REACT_APP_ な環境変数は process.env に生えているので、次のように呼び出せます。

class App extends Component {
  render() {
    return (
      <div className="App">
        {/* 省略 */}
        <p>
          This is {process.env.REACT_APP_FLAVOR}
        </p>
      </div>
    );
  }
}

それでは、まずはappAとして実行してみましょう。

$ yarn start:appA # or npm run start:appA

f:id:Nkzn:20180307123746p:plain

ちゃんとappAとして表示されましたね。

次はappBとして実行してみましょう。

それでは、まずはappAとして実行してみましょう。

$ yarn start:appB # or npm run start:appB

f:id:Nkzn:20180307123848p:plain

上手くいきました。

余談: デバッグ・リリース・テストで環境を切り替えたい

デバッグ時とリリース時とテスト時で切り替えたい値としては、接続先のホスト名などがありますね。今回の記事の内容を踏まえると、これもnpm scriptsに環境変数を仕込めばいいように思えますが、実はこのへんには専用の機能が用意されています。

.env(.development|.production|.test)?(.local)? みたいな名前のファイルに REACT_APP_ な環境変数を書いておくことで、ビルドの種別に応じてreact-scriptsが勝手に採用する環境変数を切り替えてくれるのです。ファイル名の全パターンはこちらを見てください。

Androidアプリ開発ではBuild Typeに近い概念で分かりやすかったので、やはり開発時には重宝しています。

使い方

例として、接続先のホストを切り替える場合を考えてみましょう。

開発用の .env.development には次のような設定を書いておきます。

REACT_APP_HOST=https://dev.your-domain.com

そして、プロダクション用の .env.production には次のような設定を書いておきます。

REACT_APP_HOST=https://your-domain.com

これで、アプリ内では process.env.REACT_APP_HOST を参照して接続先を決定できるようになりました。

やはり便利なので使っていきたい機能です。

まとめ

REACT_APP_ な環境変数は、アプリケーション内でグローバルに参照できて、環境を振り分けるには便利なので、使っていきたい機能ですね。

DroidKaigi 2018にウォーターセルのエンジニアが登壇します

こんにちは。ウォーターセルでモバイルエンジニアとして働いている@Nkznです。

droidkaigi.jp

Android開発のお祭り、DroidKaigi 2018が明日に迫ってまいりました。2/8(木), 2/9(金)の2日間開催されるこのカンファレンスに、ウォーターセルからエンジニア(筆者)が登壇しますので、セッション内容について紹介します。

React Native Androidはなぜ動くのか / @Nkzn

2/9(金) 12:50-13:40にRoom 2で講演します。

マルチプラットフォームなツールは世に数多ありますが、モバイルアプリを作っている皆さんの感覚としては「謎の魔法で動いている」「何かあったときに原因が分からない」といった不安から、採用を躊躇することが多いと思います。

残念ながら、その懸念を完全に払拭することは難しいです。しかし、軽減することはできます。

このセッションでは、React NativeがAndroid上で動くとき、Android Javaのエンジニアにとってはどこからどこまでは魔法で、どこからはAndroidやJavaの理屈で理解できるのかをお話したいと思います。Androidエンジニアにとっては、React Native Androidは(ほぼ)魔法ではない、手のひらに収まる技術である。このセッションを聞いた後、そう思っていただけば幸いです。

Reactの基本のキにあたる部分は説明しますので、Reactや近代JavaScriptについての知識が無い状態で来ていただいてもある程度は理解できると思います。

それでは、皆さんのお越しをお待ちしております。

合わせて読みたい

ウォーターセル株式会社では、農業の役に立つアプリの開発にReact Nativeを活用しています。

watercelldev.hatenablog.jp

データバインディングの観点からiOSアプリ開発にReact Nativeを選択する

モバイルチームの中川@Nkznです。

弊社では、小さなプロジェクトから、少しずつReact Nativeのプロダクション投入を試しています。

watercelldev.hatenablog.jp

DroidKaigi 2017では、2016年2Q頃にReact Nativeを選択した経緯や成果についてのお話をしました。2017年2Qのタイミングでもそれは変わらず、「できる範囲でWrite Once, Run Anywhereしたいなー」といったモチベーションで採用しています。

一方で、異なる観点からも採用のモチベーションがチーム内に生まれていることに気がついたので、紹介しようと思います。

免責事項

この観点は、チームの歴史と密接したものなので、技術の話というよりは思い出話やポエムの類になってしまうことをご了承ください*1

Androider meets ReactJS

ウォーターセルのモバイルチームは、なかなかiOSエンジニアに恵まれない時期が長かったことなどから、アグリノートのiOS版をCordovaで開発することを選択しました*2

当初はIonicでAngularによる開発を想定していたのですが、WebフロントエンドのチームがReact+Reduxを採用しているため、テクノロジを合わせたほうがノウハウを共有しやすいだろうということで、React+Reduxにmaterial-uiを組み合わせる形になりました*3

堅牢なJavaに守られながら生きてきたメンバーたちは、最初こそおっかなびっくりといった様子で慣れないES201x(Babel)に触っていましたが、ふと気づけば皆、ラムダやLodashやPromiseがないと生きていけない近代JSerな身体になっていました*4。後述しますがこれが嬉しい誤算を生み出しました。

また、当時は気づかなかったのですが、Reactの特徴のひとつである「レイアウトを表すXML(JSX)とそこに埋め込んだ変数が相互に作用する」というデータバインディングの世界観も、着々とチームに浸透していきました。

Androider meets DataBinding

さて、Cordova製iOSアプリの開発が一段落付いたら、Androidアプリのメンテにも人を戻していかないといけません。

各メンバーの中で自然と、JSやReactでの開発で出会ったパラダイムをAndroidアプリ開発にも取り入れたいという意識が高まっていました。RetrolambdaLightweight Stream APIRxJavaなどを採用する際に心理的な障壁が非常に低くなっていたのは嬉しい誤算です。この頃にはAndroidにもデータバインディングライブラリが登場しており、早い段階で採用されていました。

「複雑に状態が変わる画面を、以前はデータバインディング(React含む)無しでどうやって開発していたのか思い出せない」

こんなセリフが聞こえるようになるまで、そう長くはかかりませんでした。

iOSには(俺たちの求める)データバインディングがない

さて、そんなこんなをしているうちに、iOSネイティブ開発ができる人材の都合が付きはじめまして、新規事業のアプリを、AndroidはJava、iOSはSwift3で作ることになりました。

しかしここでつらいことが。iOSには、レイアウトに変数を埋め込むタイプのデータバインディングがないのです。React脳になっていたメンバーがアサインされていたため、非常につらそうでした。

結局、RxSwiftを使って、 myTextField.rx.text のようなObservableを起点にしたRxのストリームで連鎖的にビューを更新する方式を取りました。「違うんだ……俺が欲しかったデータバインディングはこれじゃないんだ……」という怨嗟の声が聞こえました。

iOS meets React

そして2017年春。DroidKaigiでの筆者の発表を見たり、実際に触ってみたメンバーから、こんな意見が聞こえてきました。

「iOSアプリだけでもReact Nativeで開発するのもアリなんじゃないか」

そのときは「それだと結局AndroidとiOSのUI仕様を合わせる手間がかかって、React Nativeの旨みが減るじゃないか」と突っぱねたのですが、一ヶ月ほど経って、これが実は面白い観点を含んでいたことに気がついたのです。

それは、「俺たちがやりたかったデータバインディングをiOSアプリ開発でやれる」というものでした。

どうせReact NativeでiOSアプリを開発していれば、なんだかんだとObjective-CやSwiftを書くことになります。iOS SDKを触ることになります。それはもう仕方ないのです。書くべきところはObjective-CだろうとSwiftだろうと書くしかないです。

ただ、UIだけはデータバインディングが使えるフレームワークで書かせてほしい。そういう理由だけでiOSアプリ開発に使うのも、技術選択のひとつの選択肢として、アリなのかもしれないと思いました。

まとめ

Androider上がりのエンジニアが、どうしてもInterface BuilderやStoryboardがどうしても気に食わない、レイアウトに変数を埋め込むタイプのデータバインディングを扱いたい、というモチベーションを持ってiOSアプリを開発するときには、選択肢のひとつにReact Nativeがあってもいいのでは、という提案でした。

*1:これが理由でQiitaに書くのを断念しました

*2:「素直にiOS SDK覚えなさいよ」と思われてしまいそうですが、当時は「最近はWebViewもパフォーマンス悪くないし、全部Webに寄せたら効率いいんじゃないか」というチャレンジをしてみたかったのです

*3:CSSフレームワークの美しさやTypeScriptという観点ではIonic2を使う案もあったのですが、2015年末のIonic2はプロダクションで使っていいものではなかったため、断念しました

*4:実は堅牢さについても、ESLintを厳しくしすぎてJavaより構文に厳しい環境になっていました

SimpleAppsシリーズ(仮称)始めました

お久しぶりの投稿になります。中川@Nkznです。

近年は弊社メンバーの主戦場がQiitaになってきており、こちらの更新をする頻度が減ってきておりました。

qiita.com

(EXCEL・R勢の更新頻度が高いのでWeb・モバイル勢も頑張ろうな……!)

今回は会社色の強い内容になったため、久々にこちらを稼働させることにしました。

SimpleAppsシリーズ(仮称)とは

※React Nativeにしか興味が無い人は読み飛ばしてください

特定の小さいドメイン(使用目的)に寄り添った小機能なアプリです。社内的には「単機能アプリ」と呼んでいることが多いのですが、味気ないので一部ではSimpleAppsと呼ばれています。

費用対効果の高い新事業を模索する一環として始まった、社長直轄プロジェクト*1です。

今回は次の3つのアプリをAndroidとiOSそれぞれに向けてリリースしました。

  • 水門アプリ
  • 面積計算アプリ
  • 距離計算アプリ

これらのアプリがどんなモチベーションで作られたのか、ご紹介していきます。

*1:実は社長が付かない"代表取締役"なので当人は社長と呼ばれると嫌がるんですが、このほうが通りがいいので勘弁してください

続きを読む