既存アプリに ReactNative を組み込む

## ゴール

Xcode, AndroidStudio で適当にさらのネイティブアプリを作成し、任意のアクションによって ReactNative モジュール立ち上げる。
タイトルと乖離があるけど、これができれば既存のアプリを組み込むのも普通にできる。

## 前提

- iOS それなりにわかる
    - 言語は Swift 
- Android それなりにわかる
    - 言語は Kotlin
- ReactNative それなりにわかる

## バージョン情報

```bash
$ react-native -v
react-native-cli: 2.0.1
react-native: 0.55.4
```

## 手順

### RN プロジェクト作成

`react-native-cli` で react-native プロジェクトを作成

```bash
$ react-native init rn_integrated_native # or your project name
```

プロジェクトディレクトリに移動

```bash
$ cd rn_integrated_native
```

`react-native-cli` で生成されたネイティブモジュールを削除

```bash
$ rm -rf ios android
```

### iOS プロジェクトの作成

Xcode でプロジェクトを作成して、先ほど作った RN プロジェクトのルートに保存

```
$ tree -L 2 -I 'node_modules'
.
├── App.js
├── app.json
├── index.js
├── package.json
├── rn_integrated_native
│   ├── rn_integrated_native
│   └── rn_integrated_native.xcodeproj
└── yarn.lock

3 directories, 5 files
```

保存した iOS プロジェクトのディレクトリ名を `ios` に変更

```bash
$ mv rn_integrated_native ios
$ $ tree -L 2 -I 'node_modules'
.
├── App.js
├── app.json
├── index.js
├── ios
│   ├── rn_integrated_native
│   ├── rn_integrated_native.xcodeproj
├── package.json
└── yarn.lock
```

以上の手順は Android にいても同様

### iOS プロジェクトに RN を組み込む

`cocoapods` はある

```bash
$ pod --version
1.5.0
```

以下は特に断りのない限り `ios` ディレクトリで作業

```bash
$ pod init
```

下記のコードを `Podfile` にペースト

```ruby
platform :ios, '10.0'

plugin 'cocoapods-fix-react-native'

target 'rn_integrated_native' do
  use_frameworks!

  pod 'React', :path => '../node_modules/react-native', :subspecs => [
    'Core',
    'CxxBridge',
    'DevSupport',
    'RCTActionSheet',
    'RCTAnimation',
    'RCTBlob',
    'RCTGeolocation',
    'RCTImage',
    'RCTNetwork',
    'RCTSettings',
    'RCTText',
    'RCTVibration',
    'RCTWebSocket',
  ]
  pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga'

  pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
  pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
  pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
end
```

気づいた人もいるかと思うが、`cocoapods-fix-react-native` という gem を使う
`cocoapods` を使って react-native の依存を解決しようとした時にエラーが出るという issue があり、それを解決してくれるらしい
しかも、RN のバージョンを見てそれにあったパッチを当ててくれるから、たぶんこれを使っておけば間違いない

- https://github.com/facebook/react-native/issues/17893
- https://github.com/orta/cocoapods-fix-react-native

グローバルに入れたくないので `bundler` で gem を管理する

```bash
$ cat<<EOF > Gemfile
source "https://rubygems.org"

gem "cocoapods-fix-react-native"
EOF
$ bundle install
$ pod install
```

これで必要なモジュールは全て入ったので、react-native を乗せた ViewController を立ち上げるまでを見ていく

`UIViewController` の extension として、RN を起動するメソッドを追加しておけば、あとは好きなところで呼び出せば良い

```swift:UIViewController+ReactNative.swift
import UIKit
import React

extension UIViewController {
    func launchReactNativeApp(bundleRoot: String = "index", module: String = "rn_integrated_native", initialProps: NSDictionary? = nil) {
        guard let jsCodeLocation = RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: bundleRoot, fallbackResource: nil) else {
            fatalError("Could not find react-native bundle file!")
        }
        
        let rootView = RCTRootView(
            bundleURL: jsCodeLocation,
            moduleName: module,
            initialProperties: initialProps as [NSObject : AnyObject]?
        )
        let vc = UIViewController()
        vc.view = rootView
        self.present(vc, animated: true, completion: nil)
    }
}
```

```swift
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func onPressButton(_ sender: Any) {
        self.launchReactNativeApp()
    }
}

```

iOS には ATS があるので、`localhost` を例外に追加する

```xml:Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSExceptionDomains</key>
        <dict>
            <key>localhost</key>
            <dict> <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
                <true/>
            </dict>
        </dict>
    </dict>
// ...
```

以上で、iOS に関しては当初の目的を達成できた。

### Android

疲れたので今度書く