そーす

福岡在住のプログラマ

React NativeでマテリアルデザインのRippleエフェクトを実装する

Inputフォームに続き、マテリアルデザインのRippleエフェクトをReact NativeのJSのみで実装してみました。

コード

transformの方で最初実装してたのですが、タブを連続でタップすると表示が上手く行かないことがあったのでこちらで実装しました。

iOS

https://gyazo.com/82f5641f4ea34f1d7a0821f814b2aa9f

Android

https://i.gyazo.com/9e209d9fa1c1e98f73ad18b46aabdf0f.gif

BitriseCIでAndroidSDKのライセンスエラー

install-missing-android-toolsを使っていると、SDKがアップデートされた時に

* What went wrong:
A problem occurred configuring project ':app'.
> You have not accepted the license agreements of the following SDK components:
  [Android SDK Platform 26].
  Before building your project, you need to accept the license agreements and complete the installation of the missing components using the Android Studio SDK Manager.
  Alternatively, to learn how to transfer the license agreements from one workstation to another, go to http://d.android.com/r/studio-ui/export-licenses.html
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED

というエラーでCIが失敗する事があります。

原因はこれです。

qiita.com

これをBitrise上でやります。

android-sdk-licenseをアップロード

自身のPCのの$ANDROID_HOME/licenses/android-sdk-licenseをアップロードします。

BitriseのWorkflow EditorCode signingタブのGENERATE FILE STORAGEでアップロードします。

Workflowでダウンロードする

install-missing-android-toolのStepの前にFile DownloaderStepを追加します。

そしてDownload source urlに先程アップロードしたandroid-sdk-licenseのダウンロードURLを、

Download destination pathには

$ANDROID_HOME/licenses/android-sdk-license

を指定します。

f:id:saburesan:20171011130303p:plain

これで問題なくCIが通ると思います。

React NativeでiOS, Androidのバージョンを変更するスクリプト

毎回手動なの面倒だったのでiOS, Androidのバージョンを更新するスクリプトを描きました。

> node update_versions.js plistPath:ios/app/info.plist ios:2.0.0
> node update_versions.js gradlePath:android/app/build.gradle android:2.0.0
> node update_versions.js gradlePath:android/app/build.gradle android:2.0.0 plistPath:ios/app/info.plist ios:2.0.0

gradlePathplistPahtはプロジェクトごとに(大体)固定なので

//package.json
{
...
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "jest",
    "vup": "node update_version.js gradlePath:android/app/build.gradle plistPath:ios/app/info.plist"
  },
...
}

こんな感じで定義しておいて

>npm run vup ios:2.0.0

みたいな感じでやってます。

fastlaneにiOSの方はあるけど、Androidの方もあるのかな。

React Nativeでマテリアルデザインのテキスト入力フォームを作った

gist.github.com

https://gyazo.com/7bf7d4237f42dd5630fd1af83e062c6d

AndroidiOSでInputTextのスタイルを合わせるのが面倒だなと思ってました。

iOSもマテリアルデザインに寄せてしまうのは最近だとアリだと思っているのでちょっと試しに作りました。

ざっと30分くらいで作ったので本物の機能は足りてないです。

ReactNativeは簡単にUIが作れるのであまりUIライブラリを入れずに自作したほうが良いですね。

今年はDroidkaigiのアプリをReact Nativeで作るんだ…

React Nativeで起動時にJS側にデータを渡す

facebook.github.io

iOSの方はちゃんとドキュメントがあるのに何でAndroidは無いんですかね…

といってもやり方は簡単です。

MainActivityでcreateReactActivityDelegateをオーバーライドして

ReactActivityDelegateのgetLaunchOptionsをオーバーライドするだけです。

public class MainActivity extends ReactActivity {

    @Override
    protected ReactActivityDelegate createReactActivityDelegate() {
        return new ReactActivityDelegate(this, getMainComponentName()) {
            @Nullable
            @Override
            protected Bundle getLaunchOptions() {
                Bundle initialProps = new Bundle();
                initialProps.putString("buildType", BuildConfig.BUILD_TYPE);
                return initialProps;
            }
        };
    }
}

熱盛りSlackBot

f:id:saburesan:20170913175357p:plain

Bitriseのビルド待っている間に熱盛り絵文字を投稿すると返信してくれるようにした。

笑ってもらえたので良かった(会社のSlack)

React Nativeのアニメーションで簡単なチュートリアル画面を作った

React Nativeで個人開発アプリを開発中なのですが、ちょうどアニメーション周りを結構調べていたので試しに簡単なチュートリアルを作ってみました。

f:id:saburesan:20170906143339g:plain

Component

ほとんどAnimatedを使いました。コンポーネントもデフォルトのAnimated.View, Text, Imageのみです。

一部LayoutAnimationを使っていますが、最初にLayoutAnimationを使っていた名残で変更していないだけです。

LayoutAnimationは導入も使い方も非常に楽なので気に入っていたのですが、複雑なアニメーションをしようとするとかなり面倒くさく、あまり自由度が無いところから使わなくなりました。

ARTを使ってSVGでやることも考えたのですが、Animatedでも十分パフォーマンスが出る結果だったので今回は採用していません。

saburesan.hatenablog.com

Easing

Easingもデフォルトのtimingとspringしか使っていません。

Sequence, Stagger, Parallel

アニメーションを連続で再生するためのAPIがありますが、今回はsequence, stagger, parallelを使いました。

facebook.github.io

ドキュメントを読んだだけだとStaggerの使い所があまりわからなかったのですが、

今回検索文字の入力の部分や、3回タップのアニメーションの所などで使っています。

Interpolate

今回、すべてのアニメーションはAnimated.Valueを使っています。

また、基本的にValueは0->1の変化のみとして、Animated.Valueの値をinterpolateメソッドで適宜補間する形にしました。

理由はValueの値をスタイルで直接使う値にしてしまうと同じタイミングで始まるアニメーションがあったときにAnimated.Valueを使いまわせないからです。

同じにタイミングでもEasingやDurationが違う場合などはParallelを使用していますが、

EasingもDurationも同じでいい場合が結構多くinterpolateで補間するほうがわかりやすくなりました。

Valueを0->1にしてinterpolateで補間することでアニメーション自体の定義とスタイルの定義を完全に別にして考える事ができるのでお薦めです。

ハマったこと

Animated.XXXにして無い時にエラーで上手く教えてくれない

これは私が初期にLayoutAnimationで組んで居た所をAnimatedに変更したときにコンポーネントの定義でAnimated.XXXにし忘れていたのですが、エラーが非常にわかりにくく手間取りました。

Devモードだとアニメーションが重すぎる

めっちゃ重たいです。デバッグ無理です。

Androidであれば開発モードのDev SettingsJS Dev Modeをオフにしてデバッグしましょう。

iOSAndroidと同じ事が出来ないのでReleaseビルドでデバッグしましょう。

position: ‘absolute'で指定しているAnimated.Viewを消すとそれ以降のアニメーションが重くなる

これはホントの所の原因がわかっていないのですが、フルスクリーン表示したViewをアニメーションが終わった後に描画しない処理(renderXXXがnullを返す)をすると、それ以降のアニメーションがやたら重くなる現象が起きました。

leftの値をいじって画面外に出しても重くなってしまい、完全な原因が謎です。

今回は一番最後にボタンをタップできるようにする必要があっただけのでフルスクリーンのViewにpointerEvents=“none”を設定することで解決できましたが、ちょっと謎ですね…

まとめ

今回やった範囲だとハマりどころが多いという感じはしませんでした。

JS側UIスレッド側のFPSも57~60位で特にパフォーマンスが悪くなるような事も特に無く、アニメーションは実際ネイティブで書くと面倒ですがAndroid/iOSどっちも同じように書けるのはかなりのメリットだなぁと思いました。

AnimatedのAPIはまだ使ってないものもありますし、ARTとAnimatedを組み合わせると更に自由度が上がると思いますので色々遊んでみたいと思います。

React Native ARTで遊ぶ

React Nativeでグラフを描くにはどうしたらいいのかと調べましたら、

React Native内にReact Native ARTというライブラリが在るのを知りました。

ドキュメントにも書いてないですが、開発はされているようです。

react-native/ReactNativeART.js at 0.48-stable · facebook/react-native · GitHub

Install

初期でiOSはライブラリの設定がされていないのでそれを行う必要があります。(RN47.0 現在)

Xcodeでプロジェクトを開き、

  1. Build Phases -> Link Binary with Libraries -> + (Add items) -> Add Other -> /node_modules/react-native/Libraries/ART/ART.xcodeproj

  2. Build Phases -> Link Binary with Libraries -> libART.a

で追加します。

これで完了です。Androidは最初から動きます

直線を描く

import React, { Component } from 'react';
import {
  AppRegistry,
  ART,
  Dimensions
} from 'react-native';

const {
  ClippingRectangle,
  Group,
  Surface,
  LinearGradient,
  Path,
  RadialGradient,
  Shape,
  Transform,
  Text
} = ART;

export default class ArtSample extends Component {
  render() {
    const { width, height } = Dimensions.get('window');
    const path = Path();
    path.moveTo(width / 2, height / 2);
    path.line(0, -100);

    return (
      <Surface width={width} height={height} >
        <Shape d={path} stroke="#AAA" strokeWidth="4" />
        <Shape d={`m${width / 2},${height / 2} v100`} stroke="#333333" strokeWidth="8" />
        <Text x={width / 2 + 30} y={height / 2} stroke="#444" strokeWidth="1">ポッキーだよ</Text>
      </Surface>
    );
  }
}

f:id:saburesan:20170831144354p:plain

ポッキーを描きました。

svgに慣れてるのであればあまり難しくなさそうですね。(pathじゃなくてShapeだけど)

ベジエ

2次ベジエ、3次ベジエ。

return (
      <Surface width={width} height={height} >
        <Shape d={path} stroke="#AAA" strokeWidth="4" />
        <Shape d={`m${width / 2},${height / 2} v100`} stroke="#333333" strokeWidth="8" />
        <Text x={width / 2 + 30} y={height / 2} stroke="#444" strokeWidth="1" font="bold 20px Futura">ポッキーだよ</Text>
        <Shape d={`m0,0 q0,${height},${width},${height} `} stroke="#777" strokeWidth="2" />
        <Shape d={`m0,0 c0,${height},${width},${height},${width},0`} stroke="#777" strokeWidth="2" />
      </Surface>
    );

f:id:saburesan:20170831151543p:plain

LayoutAnimation

LayoutAnimationも効きますね(意味のないアニメーション)

f:id:saburesan:20170831153914g:plain

ServiceWorkerのデバッグ時にはキャッシュを

ServiceWorker勉強中です。

saburesan.hatenablog.com

これやった時に通知の表記のデバッグ時に何変えても表記が上手くいかなくて悩んだんですが、

結局ServiceWorkerのキャッシュのせいでファイル更新しても上手く更新されなかったようです。

Chromeデバッグする際には

http://chrome://serviceworker-internals/

から対象のServiceWorkerをunregisterすると最新のファイルが読み込まれました。

Firebase Cloud Messagingを使ってWebサイトでPush通知を受け取る

仕事でReact SPAにPush通知を実装することになったので備忘録。

Firebase Cloud Messaging(以下FCM)をWebにやるにはHttpsに対応のサイトじゃないとだめなので、Firebase Hostingで構築するところまでを記載します。

Firebaseの説明はありません。

Firebase Hostingの準備

先にFirebase Hostingを使ってサイトを作る準備をします。

Firebaseのプロジェクトを作成する

Firebaseのアカウントを持ってないならば作りましょう。

firebase.google.com

プロジェクトを作ります。

f:id:saburesan:20170817224342p:plain

Command Line Toolsのインストール

github.com

Firebaseをコマンドで扱うためのツールです。Firebase Hostingを使う時に使用します。

npm i -g firebase-tools

インストールが完了したらログインします。

firebase login

ブラウザが開きますので連携を許可しましょう。

Firebase Hostingのプロジェクトの初期化

適当に今回プロジェクトを作成するディレクトリを作成します。

そこで以下のコマンドで初期化します

firebase init

Firebaseのどのサービスの初期化か聞かれますので一番したのHostingを選びましょう

f:id:saburesan:20170818134047p:plain

すると、プロジェクトの選択があるので、最初に作ったプロジェクトを選択します。

初期化は以上です。

下記の2つのファイルが作成されます。

.firebaserc
firebase.json

firebase.jsonは空のJSONオブジェクトしか入ってないと思います。

リファレンスでは

アプリを初期化する際、公開ルートとして使用するディレクトリについて確認を求められます(デフォルトは「public」)。その時点で公開ルート ディレクトリに有効な index.html ファイルがない場合、自動的にファイルが作成されます。

とありますが、私の環境(バージョン?)では聞かれなかったので手動で公開ルートを設定します。

とりあえずpublicにしましょう。

{
  "hosting": {
    "public": "public"
  }
}

あとはpublicフォルダを作ってindex.htmlを仕込んでデプロイします。

f:id:saburesan:20170818135253p:plain

ちなみに、デプロイ前の確認としてローカルサーバを建てる機能もあります。

firebase serve
firebase deploy
=== Deploying to '#####'...

i  deploying hosting
i  hosting: preparing public directory for upload...
✔  hosting: 1 files uploaded successfully
i  starting release process (may take several minutes)...

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/#####/overview
Hosting URL: https://#####.firebaseapp.com

Hosting URLをブラウザで開くとちゃんとデプロイされてるのが確認出来ます。

FCMの設定

ブラウザからプロジェクトを開くとウェブアプリにFirebaseを追加というボタンがあるので

クリックすると設定のスクリプトが書いてあるのでそれをコピペしてindex.htmlに貼り付けましょう。

通知の許可を取るまではローカルサーバでも大丈夫です。

index.html

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>FCM Sample</title>
</head>

<body>
  <script src="https://www.gstatic.com/firebasejs/4.3.0/firebase.js"></script>
  <script>
    // Initialize Firebase
    var config = {
      apiKey: ###
      authDomain: ###,
      databaseURL: ###,
      projectId: ###
      storageBucket: ###,
      messagingSenderId: ###,
    };
    firebase.initializeApp(config);
  </script>
</body>

</html>

これでプロジェクトの初期設定が完了です。

ServiceWorkerの作成

firebase-messaging-sw.js

FCMはルートディレクトリにあるfirebase-messaging-sw.jsというファイルをServiceWorkerに読み込ませます。

なので、このファイルをpublic以下に作成して中身を以下にします。

importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-messaging.js');

firebase.initializeApp({
  messagingSenderId: "###", // index.htmlにあるconfigのmessagingSenderIdと同じもの
});

const messaging = firebase.messaging();

manifest.json

manifest.jsonもpublic以下に作成します。manifest.jsonの詳しい説明はこちら

ウェブアプリ マニフェスト  |  Web  |  Google Developers

とりあえず今回は中身を以下にします。

{
  "gcm_sender_id": "103953800507"
}

gcm_sender_idは固定値で、全ての環境で同じ値を使います。

通知の許可を取る

次にユーザに通知を受け取るかどうかの許可を取ります。

    firebase.initializeApp(config);

    const messaging = firebase.messaging();
    messaging.requestPermission()
      .then(() => console.log('granted'))
      .catch(e => console.log(e));

これで実行すると通知の許可を取るダイアログが表示されます。

f:id:saburesan:20170818164215p:plain

InstanceIDトークンの取得

InstanceIDとはFirebaseのプロジェクトごとにユーザに割り当てられるIDのことです(多分)

それを元にしたトークンを使って個人ユーザにメッセージを送信することができます。

   messaging.requestPermission()
      .then(() => messaging.getToken())
      .then((token) => console.log(token))
      .catch(e => console.log(e));

Push通知はサーバから送ると思うのでこの時点でtokenをサーバに送りましょう。

メッセージの受信設定

フォアグラウンド通知(サイトを開いている状態)を受け取るためには以下の方法で受け取れます。

    messaging.requestPermission()
      .then(() => messaging.getToken())
      .then((token) => {
        console.log(token);
        messaging.onMessage(payload => alert(JSON.stringify(payload)));
      })
      .catch(e => console.log(e));

Push通知を送ってみる

ではPush通知を送ってみましょう。

送るのに必要なのはFirebaseプロジェクトのサーバーキーとInstanceIDトークンです。

https://fcm.googleapis.com/fcm/send
Content-Type: application/json
Authorization: key="Firebaseプロジェクトのサーバーキー"

{ "notification": {
    "title": "Hello",
    "body": "body",
    "click_action" : "http://saburesan.hatenablog.com/"
  },

  "to" : "// InstanceIDトークン"
}

フォアグラウンド通知(サイトを開いている状態)

f:id:saburesan:20170818172817p:plain

バックグラウンド通知(サイトが開かれていない状態)

f:id:saburesan:20170818173244p:plain

通知のpayloadを

"notification : {
   "title: " string, // 通知のタイトル
   "body": string, // 通知の本文
   "click_action": string, // タップした時に開きたいリンク
   "icon": string // 通知のアイコンに設定したい画像のパス
}

の形式にしておくと上記のスクリーンショットのように勝手に設定されます。

これでPush通知の基本的な実装は終わりです。

Topicsの購読

Firebaseには複数のユーザに同時にPushを送るTopicsという機能があります。

そのTopicsを購読するには以下のようにしてIID_TOKEN, TOPIC_NAME, PROJECT_SERVER_KEYを用いてリクエストを送る必要ことで購読することが出来ます。

fetch(`https://iid.googleapis.com/iid/v1/IID_TOKEN/rel/topics/TOPIC_NAME`, {
    method: 'POST',
    headers: {
      Authorization: PROJECT_SERVER_KEY,
    },
  }),

まとめ

纏まってない。

React Native version 0.47.0 is out

github.com

Breaking changes

Android

Remove unused createJSModules calls (ce6fb33, 53d5504) - @javache

ReactPackageというinterfaceからcreateJSModulesが削除されました。

これ結構ヤバイ変更だと思っています。

Androidのネイティブにアクセスするライブラリは軒並みこの変更に対応しないとビルド通りません。

ほぼすべてのライブラリで変更が必要なんじゃないかな

前バージョンではDeprecatedですらなかったのにこの変更は辛い。

(タグはv0.48.0-rcなんだけどな…)

自前のブリッジモジュールもそうですが、ライブラリの対応に追われますね…

Kotlinのrunとalsoの使い所

f:id:saburesan:20160906091652j:plain

twitterのタイムラインに

「runとalsoの使い所分からん」

というツイートが流れてきたので。

Kotlinには便利な拡張関数があるのですが、それぞれが微妙に違うので用途を結構迷います。

そこでrun, alsoに加えよく使うであろうletとapplyの4つの特徴から用途を考えていこうと思います

定義の確認

run

public inline fun <T, R> T.run(block: T.() -> R): R = block()
  • レシーバの拡張関数
  • 任意の型を返す
  • thisはレシーバ

let

public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
  • レシーバの拡張関数
  • 任意の型を返す
  • スコープ内外でthisが同じ

apply

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
  • レシーバの拡張関数
  • 返り値はレシーバ
  • thisはレシーバ

also

public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
  • レシーバの拡張関数
  • 返り値はthis
  • スコープ内外でthisが同じ

違い

返り値がレシーバ(apply, also)

apply, alsoは返り値がレシーバに固定されます。 なのでレシーバの内部状態を変えるような処理(初期化など)が想定されます。

返り値が任意(run, let)

run, letは返り値がスコープ内の最後の処理の型になります。 つまり、レシーバを加工したい場合に使う事ができます。 加工はapply, alsoでは出来ない処理です。

スコープ内thisがレシーバ(run, apply)

レシーバ内のthisがレシーバに固定されます。 レシーバのメソッドを呼んだり、レシーバに対する処理を行うときは使いやすい。 反面、スコープ外のthisに参照したいときはthis@~~と各必要がある。

スコープ内外でthisが同じ(let, also)

tスコープ内のthisがスコープ外と同じです。thisが変わらないのでthis@~~と書く必要が無いので楽。 反面、レシーバはitなどで受け取らないといけない。 スコープ内でthisを多用するときは良いかも。

用途

上記を踏まえて私が思った使いどころ

let

ある値の加工に使う

val age = 10
val ageStr = age.let{ "age : $it" }

//Nullable時とかよく見るよね
age?.let { " age: $it" }

letはitでレシーバにthisでスコープ外にアクセスできて任意の型を返せるので、

レシーバの加工には一番適していると思います。

apply

初期化処理、メソッドの複数呼び出し

val human = Human().apply { 
   name = "sabure"
   age = "30"
   context = this@HogeActivity
}

applyはthisがレシーバでレシーバを返すので内部状態の変更・初期化処理などに向いているかと思います。

あとはthis@~が複数在るような場合はalsoだと混乱しそうなのでapplyが良いかと思います。

run

エルビス演算時のnullの時の処理?

val name = n ?: run {
   ...
   ...
}

KotlinのNullチェックはエルビス演算子を使うと思うのですが、

nullの時に幾つか処理をして返り値が必要な時などに使えるのではないでしょうか。

also

applyで処理するにはthisの参照が多そうなときかな…

val human = Human().also { 
   setOnClickA { this.startActivity(...) }
   setOnClickB { this.startActivity(...) }
   setOnClickC { this.startActivity(...) }
}

うーん、alsoはあんまり良い使い方思いつきませんね…

返り値がレシーバ固定なのでレシーバに関係ない事をするあまり良くないですし、

そう考えるとthisもレシーバのapplyの方がalsoに比べると使う機会が多そうな気がします。

まとめ

Kotlinの拡張関数は基本的に使ってて気持ちいいくらいなんですが、

それぞれの特徴を踏まえて用途を考えて使わないと、メンバー内などで書き方の統一が難しくなるので気をつけないといけませんね。

全部letでもいけるっちゃいけますからね。

「新しい技術の導入に関する勉強会」で発表してきました。

www.slideshare.net

SlideShareにアップロードしてます。

Keynoteで作ったのをPowerPointに変換して上げてるので若干レイアウト崩れてます(修正する元気無い)

React Nativeに関する簡単な紹介です。

福岡ではあまりReactやReact Nativeの話聞かないので少しでも知って貰えればなーという感じで作ったのですが、

結構Web,ネイティブアプリエンジニアじゃない方が多くて

「ReactとReact Nativeって違うんですか?」

「Viewって何?」

みたいな人もいたっぽいので若干内容ミスってました。

スライドも結構お粗末な感じになっちゃいました…

30分発表とか修論発表以来だわーー…反省

結構細かい所を濁しての発表で、マサカリチャンス結構あったのでビクビクしてました(笑)(ReactJSの説明のところとか)

どこまでの前提知識でスライド作るかって結構難しいですね。

発表は基本的に辛い話をしたほうが良い

私の発表を聞いた人は多分、誰かに「React Nativeってどんな感じだった?」って聞かれたら

「なんか辛そう…」

って言う人が居ると思います。

というかそういうふうに思ってもらうために発表の最後に辛い話をして、辛い話が記憶に残るようにしています。

新しめのフレームワークはやはり、新しいだけあって流行りの技術やニーズに対応した機能などが豊富です。

しかし、そのような良い特徴は公式がまず絶対紹介しています。

そりゃ使ってほしいから特徴や導入メリットなどはちゃんと書いてくれますよね。

しかし、公式は辛い話は書きません。

そりゃ使ってほしいからあえて辛い話とか書きませんよね。

私はその辛い話というのか一番の「知見」だと思っています。

辛い話は使ってみないとわからないです。

仮に、私の発表を聞いた人がReact Nativeの導入を考えた時に

公式が紹介する良い所と、

私が伝えた辛い所

両方を考慮して導入を検討してくれることを期待しています。

何はともあれ発表たのしい!またやりたい。

AndroidのカスタムOSが使われている端末でDevメニューが出ない時の対処

もー、クソん。

Oppo。

KeyEvent.KEYCODE_MENU(82番) がシステム側で受け取られててアプリまで来ない。

ちなみにOppoはシェイクしてもDevモード出ない。

しかたなく他の空いているキーコードをアプリで受け取って書き換えることに…

処理は簡単で、ReactActivity内でonKeyUp(int keyCode, KeyEvent event)をオーバーライドするだけ。

とりあえず300番代は空いていたので300を設定してみた。

public class MainActivity extends ReactActivity {

    /**
     * Returns the name of the main component registered from JavaScript.
     * This is used to schedule rendering of the component.
     */
    @Override
    protected String getMainComponentName() {
        return "MyApp";
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        MainApplication.getCallbackManager().onActivityResult(requestCode, resultCode, data);
    }

    // 追加
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        return super.onKeyUp(keyCode == 300 ? KeyEvent.KEYCODE_MENU : keyCode, event);
    }
}
> adb shell input keyevent 300

これでデバッグできる。