ReactNative アップグレード対応 v0.46 -> v0.52

最近、苦行のアップグレード対応をした

RN v0.46からv0.52にアップグレードした時に色々エラーで引っかかったので

なにを修正したかのメモをする

$ react-native-git-upgrade はうまく動かなかったので使ってない

自力で差分をみてマージした

前提として、iOSのプロジェクトにはCocoapodsを導入してる

テンプレートを更新

最初からあったファイル群を更新する

以下のURLでバージョンを指定すると差分が見れる

Comparing rn-0.46.4...rn-0.52.2 · ncuillery/rn-diff · GitHub

package.json

差分を見ながら、最初から入ってたモジュールのバージョンを上げた

書き換えた後 ↓

"react": "16.2.0",
"react-native": "0.52.2",
"babel-jest": "22.1.0",
"babel-preset-react-native": "4.0.0",
"flow-bin": "0.61.0",
"jest": "22.1.4",
"react-test-renderer": "16.2.0"

.flowconfig

差分を見ながら新しく追加された設定と変わった設定を手動マージ

テストが落ちてたら、ほぼ自分が設定したところが悪いので自力で直す

.gitignore

差分をみて手動マージ

その他

build.gradle, MainApplication.java にも変更があったが、ここはいじらなかった

v0.49で、初回のエンドポイントが変わった事による変更なので

v0.49より前から開発してた場合は直さなくても問題は無い

気になるなら直しても良い

残りは必要ないので無視

サードパーティー(npm)の更新

導入した react-native-xxxx 系のモジュールを更新する

RNをアップグレードする際、リリースノートを見るとBreaking changesが幾つかあるので

たいていそれ関連のエラーが出る

サードパーティーのモジュールが対応してくれていれば、大体アップグレードするだけで直る

その過程でハマったエラーを紹介

propTypes関連のエラー

iOS/Android

2018-01-24 19:12:12.104554+0900 togetter_app[43599:6074808] undefined is not an object (evaluating 'b.propTypes.style')

このエラーはコード中にView.propTypes が書かれていると起こる

import { ViewPropTypes } from 'react-native'
// View.propTypes -> ViewPropTypes

書き方が変わったので修正するだけ

依存してるサードパーティーも影響するので、対応してくれてないと困る

未だに対応されてなかったら捨てたほうが良さそうだけど、それが無理ならフォークして直すしかない

App is crashing on launch - only on Release build · Issue #17276 · facebook/react-native · GitHub

react-native-device-info

iOSでの警告

2018-01-26 13:39:28.881 [warn][tid:NSOperationQueue 0x600000425c40 (QOS: UNSPECIFIED)][RCTModuleData.mm:250] RCTBridge required dispatch_sync to load RCTDevLoadingView. This may lead to deadlocks

podを使ってインストールしてると出る警告

回避するにはpodに書くのをやめて、通常通りreact-native link ... の方法にするしかなかった

react-native-device-infoにかぎらず、このエラーが起きた場合はこの方法で大体直る

根本的なエラーの原因はよく分からなかった

RCTBridge required dispatch_sync to load RCTDevLoadingView. This may lead to deadlocks · Issue #16376 · facebook/react-native · GitHub

react-native-vector-icons

error: bundling failed: Error: While resolving modulereact-native-vector-icons/MaterialIcons, the Haste packagereact-native-vector-iconswas found. However the moduleMaterialIconscould not be found within the package. Indeed, none of these files exist: ...(省略)

RN v0.52.1 だと出るエラー

回避策はあったけど、RN v0.52.2 で直ったので問題なくなった

the Haste package 'react-native-vector-icons' · Issue #630 · oblador/react-native-vector-icons · GitHub

requiresMainQueueSetup

iOSでの警告

f:id:rskull:20180201174251p:plain

デバッガーにいくつか警告が出た

おそらくRN v0.48から追加された変更で

ネイティブモジュールのブリッジ部分にrequiresMainQueueSetupを設定する必要ができた

こんな感じで ↓

Fix RN 0.48+ warning about requiresMainQueueSetup by doochik · Pull Request #242 · rebeccahughes/react-native-device-info · GitHub

依存してるサードパーティーが対応してくれていれば、アップグレードでエラーは消えた

しかし、ビルドインのはずのRCTImageLoaderでも同じ警告が出てた…

どうしようもないので修正待ち。デバッガーにしか警告はでなくて動作にも影響してないようなので

とりあえずは問題なさそう

Androidでビルドエラー

error: method does not override or implement a method from a supertype @Override ^ ...(省略)

導入してるネイティブモジュール関連でエラーが出た

これはRN v0.47で入ったAndroidのBreaking changesによるもの

これも対象のnpmのアップグレードで対応した

変更は削除だけっぽい

Remove override of createJSModules by ptomasroos · Pull Request #446 · evollu/react-native-fcm · GitHub

疲弊した

対応したのはこれくらい

色々ハマりすぎて疲れた

アップグレードをサボるほど辛くなるの厳しい

スマートコンセントを買ったので設定してみた

スタンドライトをGoogle Homeから操作したかったので、スマートコンセントを買ってみた

スマートコンセントは、自身がWifiに繋がっているのでネット経由で電源をON/OFFできる

単純な仕組みなので使える家電は限られてくる

Amazon CAPTCHA

Wifiと繋げる

説明書にも詳しく書いてるが、スマホにアプリを入れてWifiの設定をする

だいたいGoogle Homeと同じ感じで簡単だった

Google Homeから操作出来るようにする

Google Homeのアプリのスマートホームから直接連帯出来るらしいが

なんかうまくできなかったので、IFTTT経由で設定した

IFTTTの設定

if Google Assistant -> then Smart Life

ON/OFFを両方同時に設定はできなかったので

バックライトをつけてバックライトを消して の、それぞれ2つのレシピを作った

f:id:rskull:20180108014656p:plain

動作確認

IFTTT経由でも結構早い

やりたかったこと

これは完全に自分がやりたかったことだけど

部屋の電気を暗くして、今回設定したテレビの後ろのライトを付けるというのを

Google Homeから一回で操作したかった

部屋の電気の操作は前回やったやつ

rskull.hateblo.jp

流れは

Google Home -> Firebase -> ラズパイ -> 学習リモコン -> 部屋の電気を操作

なので、ラズパイまで来た時に、バックライトもラズパイから操作出来れば良い

このスマートコンセントには操作出来るAPIが公開されてなかったのでどうしようかと思ったが

IFTTTを使ってAPI的なモノを作ることができた

IFTTTの設定

if Webhook -> then Smart Life

f:id:rskull:20180108021358p:plain

Webhookを利用すると、IFTTTがエントリーポイントを用意してくれて

アクセスするとトリガーされるようになる

ただし発火まで割りと時間がかかる…

Do more with Webhooks - IFTTT

f:id:rskull:20180108021835p:plain

今回も ON/OFF の2つのレシピを作成した

curl -X POST https://maker.ifttt.com/trigger/back_light_on/with/key/xxxxxxxx

こんな感じのAPIを叩くとバックライトがつくようになったので

これでラズパイから操作できるようになった

動作確認

前回の記事電気 $ に続くフレーズを設定したので

シアターモード だと電気とバックライト同時に操作出来るように

動かしてるnodejsのコードを書き換えた

部屋の電気を薄暗くして、バックライトをつける

遅いけど、まぁやりたかったこと出来たからいいか

終わり

ラズパイ専用 学習リモコン基板を買ったので、GoogleHome経由で家電を操作してみた

GoogleHomeが安くなってたので勢いで購入

まずやりたくなるのは家電の操作

「OK Google テレビをつけて!」「エアコンを消して!」とかやりたい

調べてみると、ラズパイとリモコンの赤外線を学習して送信できるデバイスが必要とのこと

さっそく必要なものを購入してみた

Raspberry Pi 3

ラズパイ専用 学習リモコン基板(ビット・トレード・ワン)

最近 (2017/12/15) 発売したばかり。赤外線を学習してくれる

pc.watch.impress.co.jp

学習リモコンの基本操作

学習リモコンはラズパイ専用だけあって、ハンダ付けなしで差込口に装着するだけ

既存ケースと合わせるとこんな感じになった

1. 赤外線を登録する

  1. 真ん中辺りのスライドスイッチを『LEARNモード』にする
  2. 登録したいボタンを押す
  3. リモコンの登録したいボタンを押す
  4. スイッチを戻す

公式ページに動画で手順が乗ってるのでこっち見たほうが早い

登録したボタンを押すとリモコンと同じ操作が出来るようになってる

2. コマンドラインから操作する

物理ボタンを押して送信されたら、まぁそうなるわなって感じで

ラズパイにsshで入って、そこから操作する方法が分からなかったので調べた

学習リモコンの説明書に GPIO というワードが書いてあった

これまで知らなかったが、この規格を通してハードである学習リモコンを操作出来る

GPIOの操作はこの記事が分かりやすかった

ツール・ラボ » 第24回 Raspberry PiのGPIOを制御する(コマンド編)

学習リモコンには10個のボタンがあり、それぞれに番号が振られていてGPIO番号と紐付いてる

f:id:rskull:20171224005529j:plain

ボタンの1番目(SW番号1)に登録したとして、操作したい場合はGPIO番号が4になる

$ cd /sys/class/gpio/
$ ls
export  gpiochip0  gpiochip100  gpiochip128  unexport

GPIO4が欲しいのでexportする

$ echo 4 > export
$ ls
export  gpio4  gpiochip0  gpiochip100  gpiochip128  unexport

gpio4 ができた

ここからの挙動がいまいち分かって無いが、こんな感じで操作?できた

$ echo out > gpio4/direction // 送信される
$ echo 1 > gpio4/value
$ echo 0 > gpio4/value  // 送信される
$ echo 1 > gpio4/value
$ echo 0 > gpio4/value  // 送信される

多分何か間違ってるけど、送信することは出来た

不要になったらunexportする

$ echo 4 > gpio4/unexport

3. nodejsで操作する

今のようなコマンド操作をラップしたライブラリが各言語何種類か出ている

今回はnodejsで操作したかったのでrpi-gpioを使ってみた

GitHub - JamesBarwell/rpi-gpio.js: Control Raspberry Pi GPIO pins with node.js

さっそくボタン1に登録した赤外線を送信するコードを書いた

demo.js

const gpio = require('rpi-gpio')
const number = 7

gpio.setup(number, gpio.DIR_OUT, () => {
  gpio.write(number, 1, () => {
    gpio.destroy() // これを書かないとシェルが戻ってこなかった
  })
})

const number = 7 の部分、4ではなくなぜ7なの?とハマったところ

BCM 4 at Raspberry Pi GPIO Pinout

このサイトでBCM 4の項目を見ると7と対応してるから7‥.と言うことっぽい

f:id:rskull:20171225234544p:plain

$ node demo.js

これで実行してみると1回だけ赤外線が送信される

GoogleHomeから操作出来るようにする

話しかけた言葉に対して何かをするのに、IFTTTを使うのが一番簡単

大体こんな流れ

GoogleHome -> IFTTT (Google Assistant) -> (Webhook) -> Firebase -> RasPi -> 学習リモコンから信号を送信 -> 家電が起動!

GoogleHomeに特定のフレーズで喋った言葉をIFTTTが拾ってWebhookでFirebaseにフレーズを送信

ラズパイ上で動かしてるnodejsでFirebaseのリアルタイムDBを監視して

変更があればそのワードを正規表現でマッチさせ、あとは任意のコードを実行させる。という手順

1. Firebaseを準備

https://firebase.google.com

趣旨じゃないので登録手順は省く

プロジェクトに入ってデータベースを以下のような構成にする

f:id:rskull:20171226000941p:plain

自分の場合は操作したい家電名の下にコマンドを置く階層構造にした

2. IFTTTでレシピを設定

まずはGoogleHomeに話しかけた言葉を認識してもらうために

GoogleHomeと同じアカウントでIFTTTに登録する

New Appletで新しいレシピを作成

最初のifで Google Assistant -> Say a phrase with a text ingredient を選択

f:id:rskull:20171226003710p:plain

「電気...」に続く言葉をすべて取る

thenで Webhook を選択

f:id:rskull:20171226003732p:plain

URLはhttps://xxxxx-firebaseio.com/light/command.js という感じになる

lightの部分は自分が設定したキーに合わせて変える

この内容でレシピを作成して有効にする

3. ラズパイ上で変更を監視する

GoogleHomeに『テレビつけて』などと話しかけると

IFTTTからFirebaseに喋ったフレーズを投げてくるようになった

その変更をラズパイ上でnodeを動かして監視する

ついでにさっきの学習リモコンの操作も合わせて使ってみる

わかりやすく愚直にコードを書いた

light.js

/**
 * 電気の操作
 */
const firebase = require('firebase')
const gpio = require('rpi-gpio')

// 自分の環境
const config = {
  apiKey: 'xxxxxxxxxxxxx'
  authDomain: 'xxxxx.firebaseapp.com',
  databaseURL: 'https://xxxxxx.firebaseio.com',
  projectId: 'xxxxx'
  storageBucket: 'xxxxx.appspot.com',
  messagingSenderId: '123456789'
}

// ボタン1
const number = 7

firebase.initializeApp(config)

const db = firebase.database()
const ref = db.ref('/light') // DBで最初の階層に設定したキー

ref.on('value', function(changedSnapshot) {
  const key = changedSnapshot.key
  const command = changedSnapshot.child('command').val()

  if (command !== '') {
    switch (true) {
      case /起動|つけ.*て/.test(command):
        // 学習リモコンから赤外線を送信
        gpio.setup(number, gpio.DIR_OUT, () => {
          gpio.write(number, 1, () => {
            gpio.destroy()
          })
        })
        break
      case /消して|止めて/.test(command):
        gpio.setup(number, gpio.DIR_OUT, () => {
          gpio.write(number, 1, () => {
            gpio.destroy()
          })
        })
        break
    }

    // リセット
    ref.set({ command: '' })
  }
})

$ node light.js

これで家電が操作出来るようになった!

赤外線とは別だけどテレビも出来きた

GoogleHomeが1回で認識してくれなくて、何回も声を出すと徒労感しかないけど

なぜこんな事するのかって言われると、それはロマンですね

こんな感じでスマート?ホーム化がはじまったのです

なお一ヶ月前

おわり

参考リンク

igniteで自分専用のボイラープレート環境を作る

この記事は React Native Advent Calendar 2017 16日目です。


こんにちは。Webエンジニアの@DotEarlです。

Togetterという会社で、自社アプリをReactNativeで開発しています。

目次

モチベーション

新しくReactNativeでアプリを作り始める時

最初にお決まりの構成、つまりボイラープレートを毎回作るのは面倒くさいと思います

例えばredux入れてmiddleware入れてデバッグツール組み込んで…など

出来ればinitでプロジェクト作ったら後は書くだけの環境が整って欲しいところ

igniteというコマンドラインツールを利用すると

自分専用のボイラープレート環境を

コマンド一発で生成することが出来るようになります

今回はその作り方を説明したいと思います

igniteとは

簡単に説明すると、react-native initする感覚でignite newすると

自分が設定したパッケージ/ディレクトリ構成と、react-native-xxxのようなネイティブモジュールを

入れるか入れないかyes/noで選びながらプロジェクトの初期化が出来るツールです

f:id:rskull:20171117012631g:plain

詳しくは以前に書いたのでこちらを

rskull.hateblo.jp

igniteでプロジェクトを始める時

新しいRNプロジェクトを作成する時は $ ignite new myApp の用に実行します

何も指定しないと公式自家製の環境がセレクトされますが

他に用意されたボイラープレートを使いたい時は

$ ignite new MyApp -b ir-boilerplate のように -b オプションで指定します

指定できるボイラープレート一覧はこちら

ignite/BOILERPLATES.md at master · infinitered/ignite · GitHub

この-bで指定するボイラープレートを今から自作していきます

前置きが長くなりました

自分用のボイラープレートを作る

時間がなくて適当になってしまいましたが

主に redux-sagaredux-actions を使った基盤を用意しました

GitHub - rskull/rn_redux_saga_sample

今から $ ignite new myApp した時にこの構成でプロジェクトが生成されるようにボイラープレートを作っていきます

1. 基盤を生成する

新しいボイラープレートを作る時は以下のようにコマンドを実行します

$ ignite plugin new ignite-sample-biilerplate

? Is this an app boilerplate plugin? -> Yes
? Will your plugin have an example component? -> No
? Will your plugin have a generator command?  -> Yes
Creating new plugin:  ignite-sample-biilerplate

$ cd ignite-sample-biilerplate
$ npm install
$ tree -L 1

├── README.md
├── boilerplate/ 
├── boilerplate.js
├── commands/
├── ignite.json
├── node_modules/
├── package-lock.json
├── package.json
├── plugin.js
├── templates/
└── test

まずはboilerplate/の中に入ってるignite/フォルダ以外を全て削除します

次に、用意したRNの環境からボイラープレートなJS部分を boilerplate/ フォルダにコピーします

このあたりは人によって構成が違うと思いますが、とりあえず上で貼ったRNのサンプルを元に説明します

ルートにあった index.jsindex.js.ejs に変更してboilerplate/にコピー

続いてそのファイルを開き、アプリ名が直書きされてる部分を書き換えます

// index.js.ejs
import { AppRegistry } from 'react-native';
import App from './src/App';

// $ ignite new myApp ←
// 生成した時のアプリ名が渡ってくるので、差し替わるようにする
AppRegistry.registerComponent('<%= props.name %>', () => App);

package.jsonpackage.json.ejsに名前を変更して、差し替わる部分があれば

同じように <%= props.name %> に書き換えます

今回はこれだけですが、他に.eslintや.flowconfigなどがあったら同じルールでコピーすればOKです

f:id:rskull:20171213000528p:plain

2. boilerplate.jsを書き換える

次はルートにあるboilerplate.jsを書き換えます

このファイルは、newした時に実行されるタスクの手順が書かれています

// boilerplate.js

// ...

// 単純に何をコピーするか指定するだけ
// 自分の環境にパスを合わせる
filesystem.copy(`${PLUGIN_PATH}/boilerplate/src`, `${APP_PATH}/src`, {
  overwrite: true
})
spinner.stop()

// ...

// 自分の環境に合わせて必要な設定を追加
const templates = [
  { template: 'index.js.ejs', target: 'index.js' },
  { template: 'package.json.ejs', target: 'package.json' },
  { template: 'ignite/ignite.json', target: 'ignite/ignite.json' }
]
await ignite.copyBatch(context, templates, { name: name }, {
  quiet: true,
  directory: `${PLUGIN_PATH}/boilerplate`
})
spinner.stop()

// ...

// 最初から入ってて不要なものがあれば消す
filesystem.remove('__tests__')
filesystem.remove('App.js')

// run npm install
spinner.text = '▸ installing ignite dependencies'

3. plugin.jsを書き換える

このファイルはreact-native-xxxxのようなnpmモジュールを適用させる設定を書きます

今回は追加してないので特に設定はありませんが、サンプルコードが書かれてるで不要な部分を消します

async function add (context) {
  // await screenExamples.add(context)
}

async function remove (context) {
  // await screenExamples.remove(context)
}

module.exports = { add, remove }

機会があればこのあたり深掘りする記事も書きたいと思います

4. 使ってみる

ひとまずこれで完成です!

このボラープレートを使ってプロジェクトを生成してみます!

適当なワークスペースに移動したら-bオプションで今作ったフォルダを指定して実行

$ ignite new myApp -b ./ignite-sample-biilerplate

これで用意した環境が適用されたプロジェクトが生成されたと思います

いつも通りreact-native run-iosなどして正常に動いていれば成功です

これで煩わしい環境構築を忘れて素早く開発がスタートできますね!!

ちなみに作成したボイラープレートはnpmに登録すると簡単に利用出来るようになります

ルールとしてパッケージ名はignite-のプリフィックスを付ける決まりです

指定するときはignite-を省略できます

今回はサンプルなので登録はしませんでしたが、もし登録してたら

どこにいても $ ignite new myApp -b sample-biilerplateで生成可能になります

今回つくったボイラープレートのリポジトリ

github.com

その他の機能

今回ふれなかった機能の紹介

igniteにはgenerateオプションがあります

例えば$ ignite generate reducer user の用に実行すると

reducer/フォルダにuser.jsテンプレートを作ってくれるなど

自分でコマンドをカスタマイズすることが出来ます

このエントリーでいじらなかったフォルダはこのあたりの設定を入れるやつです

templates/ -> generateで生成する元となるテンプレートファイル群を入れる

commands/ -> generateで何をどこにコピーするかなどの設定を入れる

詳しくは公式のボイラープレートのリポジトリを眺めてみて下さい

github.com

この辺りもいずれ書きたいと思います

感想

自分専用のボイラープレートを作って生成するところまでやってみました

generateオプションでファイルを自動生成するようにすればもっと開発効率上がりそう?

用意さえしてしまえば後々楽ができそうですね

以上で終わります

Happy boilerplating!

参考URL