20分でわかる React Native Modules 開発

# Native Modules とは？

* React Native 標準で用意されていない機能をネイティブで作成するための機構
* iOS:Objective-C(Swiftも可能）、Android:Java(Kotlinも可能）
* React Native における常套手段かつ最終手段
* UI がからむ場合は、Native UI Components を使用する

---
リソース
---
* React Native Modules
 - https://facebook.github.io/react-native/docs/native-modules-ios
 - https://facebook.github.io/react-native/docs/native-modules-android
* Native UI Components
 - https://facebook.github.io/react-native/docs/native-components-ios
 - https://facebook.github.io/react-native/docs/native-components-android

---
Native Modules で Native側から、OS名、OSバージョン名を取得する関数の実装を通して説明します。
---
---
デモのソース
---
 * https://github.com/lazyppp/NativeModulesTest

---
JavaScript と ネイティブ間のデータの受け渡し
---
React Native Modules　がよしなにマッピングしてくれる

|型     |Objective-C  |Java|
|-------|-------------|------|
|string |NSString     |String|
|number |NSInteger,float,double,CGFloat,NSNumber|Integer,Double,Float|
|boolean|BOOL,NSNumber|Boolean|
|array  |NSArray       |ReadbleArray|
|object |NSDictionary|ReadbleMap|
|function|RCTResponseSenderBlock|Callback|

---
前提
---
* XCode, Android Studio インストール済み(React Nativeの環境構築で入る）
* react-native init NativeModulesTest

---
仕様
---
* モジュール名
 - DeviceInformation

* 関数名
 - getOSInfo

* 返り値
 - 返り値1:OS名
 - 返り値2:Version名

* 返り値例
 - "iOS", "11.0"
 - "Android", "8.1"

---
iOS
---
---
1分でわかる Objective-C
---
---
特徴
---
* C言語をベースに開発されたオブジェクト指向言語。
* Cの記法・ライブラリが全部動く。
* C言語にコンパイルされるので、激早。関数呼び出しはメッセージ呼び出し的になるのでネイティブCより少し遅い。

---
型
---
```
// ポインタを利用する。 文字列には、@ をつけて CStringと区別する。
NSString* string = @"hogehoge";
NSArray*  array = @[@"hoge",@"fuga"];
NSDictionary* dictionary = @{@"key1":@"value1",@"key2":@"value2"};
```
---
クラス/関数の定義
---
* Hoge.h(宣言を書く）

```
@interface Person : NSObject
-(NSString*)nameWithType:(NSInteger)type;
@end
```

* Hoge.m（実装を書く）

```
@implementation
-(NSString*)nameWithType:(NSInteger)type {
   return type == 0 @"Taro Suzuki" ? @"鈴木太郎";
}
@end
```
---
関数呼び出し
---
```
[person walk];

```
---
ブロック
---
* JavaScript でいうクロージャー。
* 厳密に型の定義がされる

```
[viewController presentViewController:childViewController animated:YES completion:^{
        NSLog(@"completed");
    }];
```
---
手順
---
① iOS用の プロジェクトを XCode で開く
② ググって OSVersion を取得する関数を調べる
③ DeviceInformation クラスの作成
④ DeviceInformation.h を編集
⑤ DeviceInformation.m を編集
⑥ JavaScript で 作成した Native Module を呼び出す
⑦ 実行

---
① iOS用の プロジェクトを XCode で開く
---

```
プロジェクトルート（NativeModulesTest) > ios > NativeModulesTest.xcodeproj
```
---
② ググって OSVersion を取得する関数を調べる
---
```
#import <UIKit/UIKit.h>
[UIDevice currentDevice].systemVersion
``` 

---
③ DeviceInformation クラスの作成
---
* NativeModuleTest ってなっているぐらいのところで右クリックして `New File` をクリック
* Cocoa Touch Class を選択
* Class のところに `DeviceInformation` と入力して、右下 `Next` をクリック
* ファイルを作成する場所を選んで（そのままでOK)、右下 `Create` をクリック

---
④ DeviceInformation.h を編集
---
```
#import <Foundation/Foundation.h>

// 次の行を追加
#import <React/RCTBridgeModule.h>

// <RCTBridgeModule> を追加
@interface DeviceInformation : NSObject<RCTBridgeModule>
@end
```
---
⑤ DeviceInformation.m を編集
---
```
// 次の行を追加。UIDevice クラスを利用するのに必要
#import <UIKit/UIKit.h>
@implementation DeviceInformation
// JavaScript側にこのモジュール(DeviceInformation)を公開するよというマクロ。
// マクロとはC言語系の言語で使われるコンパイル時に置き換えて使用できる関数や定数のこと。
RCT_EXPORT_MODULE();

// JavaScript側に公開する関数の定義。getOSInfo(最初の:まで） が公開される関数名となる。
RCT_EXPORT_METHOD(getOSInfo:(RCTResponseSenderBlock)callback) {
  // callbackに返り値となる値を配列で渡す
  callback(@[@"iOS", [UIDevice currentDevice].systemVersion]);
}
@end
```
---
⑥ JavaScript で 作成した Native Module を呼び出す
---
```
// 中略
import {NativeModules} from 'react-native';
// 中略
export default class App extends Component<Props> {
  constructor(props:Props) {
    super(props);
    this.state = {osInfo:''};    
    // 定義したモジュール名が、NativeModules に存在するようになる。
    var DeviceInformation = NativeModules.DeviceInformation;
　　
    // 定義した関数名で呼び出し。ネイティブ上で callback に渡した引数の値がその順序で引数として渡される。
    DeviceInformation.getOSInfo((os, version) => {
        this.setState({osInfo: `${os} ${version}`});
    });
  }
// 中略
 render() {
    return (
      <View style={styles.container}>
        <Text>OS:{this.state.osInfo}</Text>
// 中略
```
---
⑦ 実行
---
* XCode 上で Product > Run (Cmd+R)

or 

* react-native run-ios

---
<img width="200" alt="スクリーンショット 2018-07-25 0.37.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/2273/acf3776c-1137-a3eb-2315-b2d6e3692378.png">

---

Android

---

---
手順
---
① Android用の Project を Android Studio で開く
② ググって OSVersion を取得する関数を調べる
③ DeviceInformationModule クラスの作成
④ DeviceInformationPackage クラスの作成
⑤ MainApplicationを修正
⑥ JavaScriptの実装(iOSの実装で作成済み）
⑦ 実行

---
① Android用の Project を Android Studio で開く
---
1. Android Studioを起動
2. Open an Existing Android Studio project を選ぶ
3. プロジェクトルート（NativeModulesTest） > android ディレクトリを選ぶ
4. Gradleの設定で、Update してね的なメッセージがでるので迷わずupdate.

---
② ググって OSVersion を取得する関数を調べる
---

```
import android.os.Build;
Build.VERSION.RELEASE
```
---
③ DeviceInformationModule クラスの作成
---
* app > java > com.nativemoduletest ってなっているぐらいのところで右クリックして `New` -> 'Java Class' をクリック
* Nameのところに `DeviceInformation` 、Superclass に `ReactContextBaseJavaModule` と入力して `OK` をクリック
* Class のところに  と入力して、右下 `Next` をクリック
* ファイルを作成する場所を選んで（そのままでOK)、右下 `Create` をクリック

---
```
package com.nativemodulestest;

// 次のimportを追加
import android.os.Build;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;

// ReactContextBaseJavaModule を継承する
public class DeviceInformationModule extends ReactContextBaseJavaModule{
    // コンストラクタの定義。super(reactContext)するだけでよい。
    public DeviceInformationModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }
    // getName() を override.この関数の返り値が、JavaScript に公開されるModule名になる。iOSとそろえる。
    @Override
    public String getName() {
        return "DeviceInformation";
    }
　　// @ReactMethod というアノテーションを行うことで、JavaScript側に定義したメソッドを公開すること示す。
    @ReactMethod
    public void getOSInfo(Callback callback) {
        // Androidでは、JavaScriptへの返り値を 可変長引数で渡す。
        callback.invoke("android",Build.VERSION.RELEASE);
    }

```
---
④ DeviceInformationPackage クラスの作成
---
DeviceInformationModule と同様に、DeviceInformationPackageクラスを作成

```
package com.nativemodulestest;

import com.facebook.react.ReactPackage;
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 DeviceInformationPackage implements ReactPackage {
    // テンプレート的な実装。単に作成したDeviceInformationModule new して modules に追加
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new DeviceInformationModule(reactContext));
        return modules;
    }
　　// View を使わない場合は、emptyList を返す
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}
```
---
⑤ MainApplicationを修正
---
```
// 中略
    @Override
    protected List<ReactPackage> getPackages() {
      return Arrays.<ReactPackage>asList(
          new MainReactPackage(),  
          // 直前の , と 次の行を追加
          new DeviceInformationPackage()
      );
    }
```
---
⑥ JavaScriptの実装
---
iOSで実装済み
---
---
⑦ 実行
---
* Android Studio上で Run > Run 'app' (Ctrl+R)

or

* react-native run-android


---
<img width="200" alt="スクリーンショット 2018-07-25 0.35.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/2273/d68ab581-c4ec-7412-e6be-a57176587547.png">

---
まとめ
---

だいたいおまじない。実質的に実装したのは次の部分のみ。

* iOS

```
RCT_EXPORT_METHOD(getOSInfo:(RCTResponseSenderBlock)callback) {
  callback(@[@"iOS", [UIDevice currentDevice].systemVersion]);
}
```

* Android

```
@ReactMethod
public void getOSInfo(Callback callback) {
    callback.invoke("android",Build.VERSION.RELEASE);
}
```