immutableJSのMapをredux-persistで保存できないときの対処法


以前説明したimmutableJSは簡単にimmutableオブジェクトを扱うことができるのでreduxのreducerで大活躍します。
normalizr風味のreducerを作る場合は、下記のようにimmutableJSのMapとListで簡潔に書く事ができます。

一つ問題があります。immutableJSはfromJSを利用するとMapオブジェクトでstateを保存します。これが、redux-persistを使うとMapで保存できないという問題です。redux-persistはtransformsという機構でserialize, deserializeを行うことができ、ここにモジュールを適用することで保存できるようになります。redux-persistのauthorであるr2tzz氏が提供するredux-persist-immutableがありますが、READMEの情報が少なくよくわかりません。他のソースを読むと、トップレベルでMapの置換を適用するようです。私の今回のreducerではnavというReact Navigatorのreducerがあるので、ここに適用する必要はないと思っていました。

Redux Persist Transform Immutable

Redux Persist Transform Immutableというものもr2tzz氏が提供しています。これは、reducerごとにシリアライズ・デシリアライズをできるものです。これはREADMEもしっかり書いてあります:)

では、実際のコードを見てみましょう。

使い方は、transformsのvalueにimmutableTransformを入れます。whitelist/blacklistで追加します。どちらかを指定すれば片方がいらないかもしれませんが、わかりやすくするためにwhitelist/blacklist両方いれています。React Nativeでの利用なので、storage: AsyncStorageはそのままです。 ちなみにpersistStore(..).perge()でstateを消去できるので、開発初期にはよく使うことになると思います。


Redux + immutable.js + Reselectでredux reducerを安全かつ簡潔にする


日本語の技術ブログを読んでいるとImmutable.jsでmodelを作り、そこにロジックを入れる方法が書かれてるものが多いですが、英語の情報をまでみてみると、それはどちらかというと特殊な方法で多くのところでReduxのReducerに導入しています。

今回は、後者の方法を説明していきます。

Immutable.js

Reduxのstateはimmutableで操作すべきであり、そのためObject.assign()slice等を駆使してReducerを書いていきます。問題はその方法が冗長であり、人によってES6を使わないなど書き方も違うのでコードが見にくくなります。これを解決するのがImuutable.jsになります。ドキュメントを見ると(Typescript前提で書かれてるドキュメントなので、知らないと非常に読み難い…)、便利な関数が沢山生えてます。setInなどの関数を使えば、Nestされているオブジェクトに一発でデータを挿入できたりします。そして、そのオブジェクトが常にImmutableであることを保証してくれます。

Immutablejsのreducerへの組み込み方は簡単で、InitialStateをimmutableJSで作れるだけです。

上記のstate.draft.standardsはImmutable.Mapオブジェクトとなります。問題はこれをtoJS,toArray, toObjectをコンポーネント内のRedux stateの呼び出しのmapStateToPropsで使うのがReduxのアンチパターンになっていることです。

This is a particular issue if you use toJS() in a wrapped component’s mapStateToProps function, as React-Redux shallowly compares each value in the returned props object. For example, the value referenced by the todos prop returned from mapStateToProps below will always be a different object, and so will fail a shallow equality check.

で、どうするか。ここで登場するのがReselectになります。

Reselect

Reselectは、キャッシュを持ち、変更があった場合だけ新しいオブジェクトを作成するような挙動を記述できます。これをmapStateToPropsに入れ込むことにより、アンチパターンを避けることができます。 具体的なコードは、

詳しくはREADMEや他の技術ブログを参考にしていただければと思いますが、標準の比較関数は単純なオブジェクト比較(===)を利用しているため、オブジェクトのkey/valueのペアまで比較できません。ここでlodashisEqualを使って、deep比較をすることにしている(今回の場合、shallowな比較(ネストされたものは比較しない)で十分ではあるがREADMEがそうなっているので))。createDeepEqualSelectorの最初の引数が、Input Selectorと呼ばれるもので、ここが以前のもの(キャッシュ)との比較対象です。isEqualコードを読んでいないが直感的にあまりnestしすぎない方が比較自体の計算量が少なくなって良さそうですね。

これをcomponentのmapStateToPropsで直に呼び出すのは、単純です。

これでreducer内でimmutablejsで快適にstateを操作できるようになりました!


2017年Python環境設定 – andaconda/virtualenv/cookiecutter/dotenv


1. Install anaconda / conda

condaによるポータブルなPython環境構築のすすめを参考にすると、(ana)condaが最近の主流とのこと。
下記にアクセスし、各OSのパッケージをダウンロードしてインストール。3系を使います。

https://www.continuum.io/downloads

あとでLinux上で動かす予定なので、なるべく依存関係を作りたくないので、pyenvの導入はしないことにしました。

2. Create virtualenv

Rubyのbundler相当なのが、virtualenvらしいので、仮想環境を作り、その環境に入る。

3. Install packages

基本、conda installでパッケージを入れていくだが、パッケージがないことも多く、そんなときは、conda-forgeを入れるようになるので、最初から追加しておく

とはいえ、virtualenv変更後はpipでも良いので、 conda installでなかったら、pip installみたいな感じ。

virtualenvでのパッケージはcondaで管理できるので、新しいプロジェクトをコピーするときは、下記のようにexport/importする

(参考)http://qiita.com/Hironsan/items/4479bdb13458249347a1

4. Create Project

調べてみるとプロジェクトのディレクトリ構造の自由度が高い。
つまり、人々が各々ディレクトリを作ったり、ファイルを作ったりしている。それがフレームワークを使ってる私みたいな人には、非常に気持ちが悪い。
プロジェクトの雛形が欲しいので、調べるとcookiecutterというのがあるのでそれを使う。

今回はデータサイエンス用のテンプレを使用しているが、テンプレートはpythonだけでなく多言語のもある。
詳しくは、githubのcookiecutterを参照してほしいが、
下記のようなboilerplateができるので自分で考えなくてよいのは非常によい。

作成方法は、

プロジェクト名のディレクトリが作成されるので、そこに入り、パッケージをインストール。

5. python-dotenv

Githubに載せたくないAPIのキーなどを管理する
cookiecutterのテンプレートにあるのに、requirements.txtにないのでインストール。

使い方は、下記のような感じ。

以上が、色々なページを参考にして構築した環境です。
こういうのって時間がかかるので、参考になれば幸いです。


macOSのelasticserachの使用メモリサイズの変更方法


気づいたら、2G使ってたので変更。そもそも開発用なのでそんなに必要ない。

256Mも使ってないけど一応。


Sublime Text 3のSwitch Projectの編集方法・削除方法


消したはずのプロジェクトが残る現象発生。
削除するのは現セッションのRecent workspacesから取り除く必要あり。

Sublime Textを閉じたあとに

で、recent workspacesを消す。


[React Native] HTTP requestのRetryを実装する


Mediumに英語のエントリRetry POST request when it’s failed on React Nativeを書いた内容と同じですが、地下鉄で地下に潜るとreact-native-oauthmakeRequestが失敗するケースが発生しました。Twitterの2つ目の投稿が投げられていなかったり、FBへの投稿も落ちていました。そこで、リトライするコードを書きました。

Async Retryを使います。これはnode-retryをラップしたもので、例がシンプルで良さそうだったので使います。(async/awaitの文法にも慣れているので)

問題なくインストールできます。そして、コードは、下記のような感じ。

2つ目の引数にnode-retryのオプションを書いてくイメージです。今回はエラーハンドリングは特にしないで5回リトライする単純なコードです。iphoneのデベロッパーのところにある”Very bad network”で試したところ、コンソールに数回トライしているのを確認しました。(Testをうまく書けるのかな。。)

以上、リトライを実装したい方は参考にしてみてください。


[React Native]日本語環境下で日本語が垂直のセンターを取れない問題(vertical-align: middle)の対処法


地味に辛いこの問題。vertical-align: middle問題。 iOSが英語環境(設定 -> 一般 -> 言語と地域が英語)の場合は問題なくセンターとれるのですが、日本語環境にするとflex-startみたいにpaddingTopがゼロになってしまう問題があります。やっかいなのは、paddingで調整するとAndroidはこの問題がないため表示がずれます。。

コードはボタンだったら、下記みたいな感じ。ボタンに限らず、この問題はおきていると思います。

解決方法は、fontサイズと同じViewでラップする。(試行錯誤後、閃きました)


[React Native] Codepushを導入してみて


Codepush

Mircosoftが提供するプラットフォームで、CordovaとReactNative用のバイナリを変えずに該当コードだけを変更するものです。React Nativeの場合は、JSバンドルだけをダウンロードして差し替えることができます。何がうれしいのかというとレビューを通さずにコードを変更*1することです。とはいえ、最近は3日以内にAppleのレビューが終わりますし、そんなに必要ではない仕組みかもしれません。

実際導入してみると、運用のオーバーヘッドは大きめです。数人で開発・運用しているアプリなら問題ないと思いますが、個人プロジェクトだとメンテが大変です。そして、気づくことはitunes connectやGoogle playでの変更は必要ということです。イメージ的に大きめの変更はアプリレビューを通して、小さなもの、もしくは致命的なバグはCodepushしてくのが実際の使いどころになりそうだと感じています。

*1 アプリの意図を変えないようにしましょう。

では、さっそくインストールしましょう。

インストール

README通りです。

CLIインストール

Deployment keyが表示される。
READMEとは前後してしまいますが、下記のようなことが後で言われるので, iosとandroid両方作りたい場合は、両方のアプリを作っておきます。

Note, if you are targeting both platforms it is recommended to create separate CodePush applications for each platform.

アプリ側の設定

Githubに飛んでReadme通りに設定を進める。注意点としては、READMEがReact Nativeのバージョンごとに違うこと。 自分が使ってるReactNativeに相当するブランチをTagから選択。(ということは、将来的にRNをアップグレードしたらCodePushも変更しなくてはいけない。。)

このあとios/androidの設定ファイル(xcode, gradle)の変更する。ここは各READMEを参照のこと。

アプリにcodeプッシュを適応するのは簡単で、単純にWrapするだけ。
Reduxを利用してるので少しReadmeとは違うが、単純にRegisterComponentの前にラップするだけ問題ない。

またcodePushOptionsでオプションを指定できる。上記は、悩んだ末に決めたバックグランドからの復帰時にアプリのバイナリを確認・そして次期バージョンがあったら、次のバックグランドからの復帰時にインストールする。

すぐにインストールする等もできるが、バスンとアプリが落ちて起動する感じになるので、ユーザは気分がよくないでしょう。。

アプリのリリース方法

これでStaging環境にアップロードされる。
ベータ版を端末に入れておいて、テストしてみて、オッケーなら本番環境にあげる。

開発環境

README通りにやると下記のようになる。

iOS

xcode project schema

  • debug: ローカルでシミュレーターを利用して開発する。code pushは利用しない。
  • staging: SchemaをStagingに変更し、xcodeからデバイスへ転送。iPhoneにStagingバイナリを転送済の場合、USBなしで実機テストができる。
  • production: SchemaをStagingに変更し、xcodeからデバイスへ転送。または、アーカイブしてitunes connectにアップロード。エンドユーザが使う。iTunes storeから配布されるアプリが参照する。

Android

  • debug: react-native run-androidでemulatorでテストする。
  • releaseStaging: react-native run-android --variant=releaseStaging
  • release: cd android && ./gradlew assembleRelease でAPKをGoogle Playにアップロード。

android/app/build.gradleは下記のようになるはず。

Deployment状況の確認

AndroidのProduction環境のバイナリの作り方

リリース方法はiosと同じ

Troubleshooting

基本的に難しい。迷った場合は、下記を確認する。

  • Versionが正しくない
  • オプションのパラメータが正しくない
  • 同じバージョンのバイナリだとプッシュされない(?)

デフォルトパラメータで設定する場合、Pushしたときに、そのオプションが次のcodepushの振る舞いになるので。現在codepush側のパラメータが何であるか覚えておかないといけません。

まとめ

ただでさえ、React Nativeのバージョンに追いつくのに必死なのに、Codepushのバージョンも考えないといけないのはつらい。起動時にcodepush側に最新があるか見にいく仕様なので、app storeで配布しているバイナリに欠陥があってはいけない。例えば、Sign UpのViewにバグをcodepushでは直しにくい。なぜなら、Resumeの場合、バックグランドから戻るタイミングがないし、再起動を強制するのは、(ダウンロードする時間があるので)サインアップの途中で再起動してしまう可能性がある。またApplinkでFacebookログインを外部に行く場合は、そこでResumeが起こってしまうのも注意。つまり、Facebook認証後戻ってくるときにアプリが再起動してしまう。

やはり、使い所はApp storeのレビューが終わるまで直せないような致命的なバグ。赤い画面で落ちてしまうようなバグはリスタートが必要なので、緊急パッチを当てることができる。またStaging環境の実機テストもパッケージをアップロードしなくていいので楽になる。

codepushをセットアップしても必ず使う必要はない。release等がなくても元のバイナリでユーザは使えることができる。なので、余力のあるチームは、導入を検討してみてはどうでしょうか?