elm-native-uiでiosアプリを動かしてみた

# やってみた 
- elm-native-uiを使ってelmでネイティブアプリhelloをしてみる
- elm-native-uiがどういう理屈でreact-nativeで動くのか少し覗く

# elm-native-ui
https://github.com/ohanhi/elm-native-ui

- elmでスマホアプリが作れるもの
- react-nativeを利用している
- まだWIP
- elm-packageでダウンロードできない

## 引っかかりそうなところ(ぼくがひっかかった)
Sierraの人 -> https://github.com/facebook/react-native/issues/9309

## elmでiosのアプリを作ってみる

手順は

- react-nativeのプロジェクトをつくる
-  プロジェクト内にelmの環境を作る
-  githubからelm-native-uiをclone、プロジェクトにソースコードをコピー
-  elm-package.jsonを編集
-  viewにelm-native-uiを使っていつものelmアプリケーションを書く
-  jsファイルにコンパイルする
-  index.ios.jsへコンパイルしたファイルをrequireしてメソッドを実行するコードを記述

### react-nativeプロジェクトの作成
react-native cliのダウンロード
> npm install -g react-native-cli

react-nativeプロジェクトの作成
コマンドを実行したディレクトリにelmNativeAppディレクトリが作成されるはず
その中でnpmインストール

> react-native init elmNativeApp
> ~/elmNativeApp npm i

### elmの環境をelmNativeAppディレクトリ以下に作る
>~/elmNativeApp elm-package install

### elm-native-uiパッケージのダウンロード

- elmのパッケージ「elm-native-ui」を利用するが、これはelm-packageでダウンロードしてこられない。なのでgithubから「elm-native-ui」をクローンしてくる
- ダウンロードしてきたプロジェクトのsrcディレクトリを丸ごとelmNativeAppへ移動させる

>git clone https://github.com/ohanhi/elm-native-ui.git
>cp -r elm-native-ui/src elmNativeApp/elm-native-ui


### elm-native-uiを自分のソースコードからimportできるように設定する
elm-package.jsonを編集

- source-directoriesに先ほどコピーしてきたelm-native-uiのディレクトリを追加
- repositoryをhttps://github.com/elm-native-ui/elm-native-ui.git に変更
 - elmでpublishされてないモジュールを扱うための作法を省略するため(ほんらいこうはしない)
 
 - お作法 
 - http://faq.elm-community.org/#how-do-i-install-an-elm-package-that-has-not-been-published-to-packageselm-langorg-for-use-in-my-project

```
{
　"version": "1.0.0",
  "summary": "..",
  "repository": "https://github.com/elm-native-ui/elm-native-ui.git",
  "license": "BSD3",
  "source-directories": [
    ".",
    "./elm-native-ui"
  ],
  "exposed-modules": [],
  "dependencies": {
    "elm-lang/core": "5.0.0 <= v < 6.0.0",
    "elm-lang/html": "2.0.0 <= v < 3.0.0"
  },
  "native-modules": true,
  "elm-version": "0.18.0 <= v < 0.19.0"
}
```
### アプリケーションになるコードを書いて行く
ここではApp.elmという名前でプロジェクトルート直下に作る

```App.elm
module App exposing (..)

import NativeUi
import NativeUi.Elements as Elements exposing (..)
import NativeUi.Style as Style


-- MODEL


type alias Model =
    String


initialModel : Model
initialModel =
    "Hello"



-- UPDATE


type Msg = NoMsg

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    model ! []



-- VIEW


view : Model -> NativeUi.Node Msg
view model =
    Elements.view
        [ NativeUi.style [ Style.alignItems "center" ] ]
        [ Elements.view
            [ NativeUi.style
                [ Style.height 64
                , Style.width 64
                , Style.marginBottom 30
                ]
            ]
            []
        , text
            []
            [ NativeUi.string model ]
        ]



-- PROGRAM


main : Program Never Model Msg
main =
    NativeUi.program
        { init = initialModel ! []
        , view = view
        , update = update
        , subscriptions = \_ -> Sub.none
        }

```
#### ポイント
- viewの定義、webでは`view : Model -> Html Msg`という定義になるがnativeでは`view : Model -> NativeUi.Node Msg`になる
- mainに定義にはNativeUi.programを使うことになる
- `import NativeUi`でNativeのviewを定義するためのライブラリをimportする
 - NativeUi.Events, NativeUi.Style 等でUIのイベントハンドラやスタイルを定義するためのライブラリを扱える

### elmのソースコードをJavaScriptへコンパイルする
> elm-make App.elm --output App.js

elm-make でコンパイルしますが出力はjsファイルで！
App.jsが生成されていることでしょう

### react-nativeのindex.ios.jsからelmの生成したjsファイルを呼び出す
`react-native init elmNativeApp` した時点で生成されているindex.ios.jsを編集する。

```index.ios.js
const { AppRegistry } = require('react-native');
const Elm = require('./App');
const elmApp = Elm.App.start();
AppRegistry.registerComponent('elmNativeSample', () => elmApp);
```

### 実行
> npm run start
> react-native run-ios

![スクリーンショット 2016-12-06 23.18.51.png](https://qiita-image-store.s3.amazonaws.com/0/153296/03f0b700-f895-29fc-7cff-3e0d8cafbc3d.png)

elmのコードでネイティブアプリが動いた！嬉しい！
もうちょっとうごくもの
<blockquote class="twitter-tweet" data-lang="ja"><p lang="und" dir="ltr"><a href="https://t.co/AVPbtQiFJp">pic.twitter.com/AVPbtQiFJp</a></p>&mdash; ス (@4245Ryomt) <a href="https://twitter.com/4245Ryomt/status/810141212711800834">2016年12月17日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

## なんで動いてんだ！
### 作ったコードを見つめる
react-nativeでは
`AppRegistry.registerComponent`の第二引数の関数が返すReactComponentが描画される。

```index.ios.js
const { AppRegistry } = require('react-native');
const Elm = require('./App');
const elmApp = Elm.App.start();
AppRegistry.registerComponent('elmNativeSample', () => elmApp);
```

つまり`Elm.App.start();`はReactComponentを返しているんだろう！
mainに渡されているNativeUi.programを見に行けば動く理屈がわかるはず！

```App.elm
main : Program Never Model Msg
main =
    NativeUi.program
        { init = initialModel ! []
        , view = view
        , update = update
        , subscriptions = \_ -> Sub.none
        }
```

### ソースコードをなぞる

`NativeUi.program`の定義を求めてソースコードへ、programの定義はNativeモジュールのprogramという関数だった。

```elm-native-ui/src/NativeUi.elm
program :
    { view : model -> Node msg
    , update : msg -> model -> ( model, Cmd msg )
    , subscriptions : model -> Sub msg
    , init : ( model, Cmd msg )
    }
    -> Program Never model msg
program stuff =
    Native.NativeUi.program stuff

```

### NativeUiのNativeモジュールへ
NativeUiのNativeモジュールディレクトリのNativeUiディレクト内のNativeUi.jsを見る。
`function start()`これを実行すると`makeComponent(impl)`の結果が返ってくる。
`makeComponent`が`AppRegistry.registerComponent`に渡されるReactComponentを返すに違いない！

```src/Native/NativeUi/NativeUi.js
  function program(impl) {
    return function(flagDecoder) {
      return function(object, moduleName, debugMetadata) {
        object.start = function start() {
          return makeComponent(impl);
        };
      };
    };
  }
```

makeComponentの定義を見ると中でReact.createClassが！！
componentDidMountの中見ると
`_elm_lang$core$Native_Platform.initialize(...)`
elm-nativeで作るReactComponentのツリーの最上位ではhtmlを描画しないelmが回っていた

```src/Native/NativeUi/NativeUi.js
function makeComponent(impl) {
    return React.createClass({
      getInitialState: function getInitialState() {
        return {};
      },

      componentDidMount: function componentDidMount() {
        this.eventNode = { tagger: function() {}, parent: undefined };

        this._app = _elm_lang$core$Native_Platform.initialize(
          impl.init,
          impl.update,
          impl.subscriptions,
          this.renderer
        );
      },

      renderer: function renderer(onMessage, initialModel) {
        this.eventNode.tagger = onMessage;
        this.updateModel(initialModel);
        return this.updateModel;
      },

      updateModel: function updateModel(model) {
        this.setState({ model: model });
      },

      render: function render() {
        // There won't be a model to render right away so we'll check that it
        // exists before trying to call the view function
        return typeof this.state.model !== 'undefined' ?
          renderTree(impl.view(this.state.model), this.eventNode, 0) :
          null;
      }
    });
  }

```

## まとめ

- elmのコードでiosアプリを動かすのはとても簡単！viewにライブラリを使ってコンパイルするだけ！
- elm-native-uiの動く理屈はちょっとソースコードを追えばわかった。ReactComponentを返す関数を作るわけだった。

 ***elmでネイティブアプリまで作れるなんて楽しいですね！！！！***