react-nativeでAndroidのAPIを扱う（Native Modules編）

この記事は[CureApp Advent Calendar 2016](http://qiita.com/advent-calendar/2016/cureapp)の９日目の記事です。

## この記事の目的

まだReactNativeがサポートしていないAPIを使いたい場合、既存のJavaコードを再利用したい場合、あるいはマルチスレッドなどを用いたい場面では、Android-Java APIを直接叩ける必要があります。

そこに対して、React Nativeでサポートしているのですが、[公式の説明](https://facebook.github.io/react-native/docs/native-modules-android.html)やコード例が十分でない＆日本語の資料もなさそうなので、まとめてみます。

※公式が不十分でソースコードを読みつつの理解となるため、正しくない内容になってしまうかもしれない点をご了承ください。（そもそも正解がない）

※iOS版はこちらをどうぞ→[react-nativeでObjective-C/SwiftのAPIを扱う（Native Modules編）](http://qiita.com/uryyyyyyy/items/210487915d009f91f277)

## 必要なこと

最終的にはJSコードからAndroid-Java APIを叩けるようにしたいですよね。
そのためにReact Nativeでは、

1. ReactContextBaseJavaModuleを継承したクラスを作り、React Native用のアノテーションを付けたメソッドでAndroid-Java APIの操作を書く
1. ReactPackageを継承したクラスを作り、そこで上記１で作ったクラスを読ませるようにする。
1. 上記２で作ったクラスを `MainApplication.java` で読み込む
1. JSコードで、nativeコードを呼ぶ宣言を書いておく。

以上を行うことで、実行時にマクロで良い感じに解決してくれます。（詳細はまだ調べてないです。。。）

ここでは、公式にもあるAndroidのToastを例に取って、Android-Java APIを呼ぶところまでを順番に見ていきましょう。

ちなみに、ソースコードはこちらに置いています。
https://github.com/uryyyyyyy/RNBindingSample/tree/android-native-module

### ReactContextBaseJavaModuleを継承したクラスを作る

ここで作るAPIは、JS側から呼ぶときは `MyToastAndroid` としたいので、クラス名は`MyToastModule.java` などとしておきます。

コードはこのような形です。（今回呼びたい処理は `Toast.makeText()` の箇所です。）

```java:MyToastModule.java
import android.view.Gravity;
import android.widget.Toast;

import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.MapBuilder;

import java.util.Map;

//①
public class MyToastModule extends ReactContextBaseJavaModule {

    public MyToastModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    //②
    @Override
    public String getName() {
        return "MyToastAndroid";
    }

    //③
    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = MapBuilder.newHashMap();
        constants.put("SHORT", Toast.LENGTH_SHORT);
        constants.put("LONG", Toast.LENGTH_LONG);
        return constants;
    }

    //④
    @ReactMethod
    public void show(final String message, final int duration) {
        UiThreadUtil.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getReactApplicationContext(), message, duration).show();
            }
        });
    }
}
```

コードの説明をします。

①でReactContextBaseJavaModuleを実装していますね。

②では、 `ReactContextBaseJavaModule` を実装するにあたって `getName()` をoverrideする必要があるため記述しています。
ここで付けた `MyToastAndroid` は、後述するJSコードの中でNativeモジュールを呼ぶ時の識別子となります。

③では、 `getConstants()` を実装していますが、こちらの記述は任意です。今回は表示時間の制御フラグに使えるため記述しています。

④では、定義している `show` というメソッドをJSコードから参照できるようにするため、 `@ReactMethod` というアノテーションを付与しています。

ちなみに、durationは`getConstants()`から取れそうだというのは問題ないとして、JavaのStringなどはJSではどの型で表現すればいいのでしょうか？対応表はこちらです。

```
Java -> JS
----
Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array
```

### ReactPackageを継承したクラスを作る

次に、作ったクラスをアプリケーションに登録する必要があります。その時にはパッケージングを刷る形になります。
今回は１ファイルだけなので冗長に感じるかもしれませんが、複数ファイルをまとめて一機能としたいケースに有用そうです。

```java:MyToastPackage.java
package com.rnbindingsample.mytoast;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MyToastPackage implements ReactPackage {

    //①
    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    //②
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    //③
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new MyToastModule(reactContext));
        return modules;
    }
}
```

①、②はそれぞれ「JavaからJSコードを呼ぶ場合」「JavaのNative Componentを扱う場合」に使う箇所のようでして、今回は扱いません。

③のところで、先ほどのMyToastModuleを設定していることがわかりますね。

### MainApplicationに組み込み

```java:MainApplication.java
    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.asList(
              new MainReactPackage(),
              new MyToastPackage()
      );
    }
```

作ったパッケージ（`MyToastPackage`）をココで読み込ませることで、このアプリ内でパッケージが使えるようになります。

ここまででJava側の記述は完了です。最後にJSコードを見ていきましょう。

### JS側でラッパーを用意する

上記の記述で普通にJSで動かせるようになります。ただ、NativeModulesを呼ぶコードがあちこちに散乱するのも嫌なので、大抵はそれをラップしたクラスを噛ませて、アプリではそこからしかNative APIを呼ばないようにしています。（こうすることで、ライブラリ化やAndroid/ios出し分けがしやすくなります。何より責務がちゃんと分かれます。）

```js:MyToastAndroid.js
import {NativeModules} from 'react-native';
export default NativeModules.MyToastAndroid;
```

これで全ての準備が完了です。

## Native APIをJSから呼んでみる

```js:index.android.js
...
import MyToastAndroid from './MyToastAndroid';

...

  componentDidMount(){
    MyToastAndroid.show('Awsome', MyToastAndroid.SHORT)
  }
```

このように使えば、アプリ起動時にtoastで `Awsome` が表示されるようになります。

## まとめ

上記のようにして、比較的簡単（かつ大きくなっても破綻しないように考えられている）にNative APIを触れるようになりました。

ドキュメントではcallbackの例があったのでリポジトリの方では試してみています。よければ合わせてご覧ください。
再掲：https://github.com/uryyyyyyy/RNBindingSample/tree/android-native-module

他にも、PromiseやThreading、Events発火、アプリのライフサイクル管理なども組み込むことが出来るそうです。折をみてこちらの資料も更新していければと思っています。

## おまけ

- Javaコードを頻繁に更新する場合、Gradle Daemonを立てておくとビルドが早くなります。
- Androidエミュレータでのデバッグは「Ctrl + M」です。