30
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ElmAdvent Calendar 2016

Day 18

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

Last updated at Posted at 2016-12-17

やってみた

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

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を編集

{
 "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

elmのコードでネイティブアプリが動いた!嬉しい!
もうちょっとうごくもの

なんで動いてんだ!

作ったコードを見つめる

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)の結果が返ってくる。
makeComponentAppRegistry.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でネイティブアプリまで作れるなんて楽しいですね!!!!

30
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?