ElixirDesktopを使ってみた(仮

この記事では、elixir-desktopを使用してAndroidアプリを作成する方法について説明します。
少し前に[github.com/elixir-desktop/android-example-app](https://github.com/elixir-desktop/android-example-app)のsampleを実行することができたのですが、自分で作成したDesktopプロジェクトをAndroidで実行したいなと思いました。AndroidStudioを使ったことがなくAndoridアプリ化することに難儀したのですが、他の方のqiita記事をじっくり~~ことこと~~見させて頂いて、ようやく理解することができました。ぜひご覧下さい。


**私の環境**
Manjaro Linux 22.1.2

# 開発環境構築
android-example-appの[How to build & run](https://github.com/elixir-desktop/android-example-app#how-to-build--run)という項目に従って、asdfをインストールします。
すでにerlangとlixirをインストールしている場合、パッケージマネージャのコマンドからアンインストールを行ってください。
erlangとelixirの最新バージョンもインストールして普段は最新バージョンを、elixir-dektopのプロジェクトを置くディレクトリに指定されたバージョンを使うようにします。
```
erlangとelixirをインストール
asdf install erlang 25.0.4
asdf install elixir 1.13.4-otp-25

最新のバージョン(5/24現在の)もインストールします。
asdf install erlang 25.3.1
asdf install elixir 1.14.4-otp-25

インストールできているか確認
asdf list

asdf plugin-add erlang https://github.com/asdf-vm/asdf-erlang.git
asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git

いつも使用するバージョンを指定(最新バージョン)
asdf global erlang 25.3.1
asdf global elixir 1.14.4-otp-25

elixir-desktop用のディレクトリを作って移動。このディレクトリ(以下)で使用するバージョンを指定する。
asdf local erang 25.0.4
asdf local elixir 1.13.4-otp-25

現在のバージョンを確認
asdf current
```
erlangとelixirのインストールが終わったら、phoenixをインストールします。
```
mix archive.install hex phx_new
```
# Desktopプロジェクトの作成
上の項目で作成したディレクトリ(以後desktop_dirと呼ぶことにします)でphoenixプロジェクトを作成していきます。
今回はno-ectoを指定し、ここではプロジェクト名をdesktop_tutoにします。
```
mix phx.new desktop_tuto --no-ecto
```

この時点ではディレクトリ構成は以下のようになっているはずです。
```
.
├── .tool-versions
└── desktop_tuto
```

デスクトップ化するには以下の手順を行います
1. mix.exsの変更
2. endpoint.exの変更
3. config.exの変更
4. runtime.exの削除
5. スーパーバイザの設定

### mix.exsの変更
以下の変更を行ってください
```diff_elixir:desktop_dir/desktop_tuto/mix.exs
defmodule DesktopTuto.MixProject do
  use Mix.Project
  ...
  def application do
    [
-     mod: {DesktopTuto.Application, []},
+     mod: {DesktopTuto, []},
      extra_applications: [:logger, :runtime_tools]
    ]
  end
  ...
  defp deps do
    [
      ...
+     {:desktop, "~> 1.5"},
+     {:wx, "~> 1.1", hex: :bridge, targets: [:android, :ios]}
    ]
  end
```

### endpoint.exsの変更

```diff_elixir:desktop_dir/desktop_tuto/lib/desktop_tuto_web/endpoint.ex
defmodule DesktopTutoWeb.Endpoint do
- use Phoenix.Endpoint, otp_app: :desktop_tuto
+ use Desktop.Endpoint, otp_app: :desktop_tuto
  ...
  @session_options [
-   store: :cookie,
+   store: :ets,
    key: "_desktop_tuto_key",
-   signing_salt: ..., #`mix phx.new`毎に値が異なりますので「...」で省略しています
+   table: :session
  ]
```
### config.exsの変更
```diff_elixir:desktop_dir/desktop_tuto/config/config.exs
import Config
...
config :desktop_tuto, DesktopTutoWeb.Endpoint,
- url: [host: "localhost"],
+ http: [ip: {127, 0, 0, 1}, port: 10_000 + :rand.uniform(45_000)],
  render_errors: [view: DesktopTutoWeb.ErrorView, accepts: ~w(html json), layout: false],
  pubsub_server: DesktopTuto.PubSub,
  live_view: [signing_salt: ...], #`mix phx.new`毎に値が異なりますので省略
+ secret_key_base: :crypto.strong_rand_bytes(32),
+ server: true
```
### runtime.exsの削除
runtime.exsがありますと起動できないようなので、リネームか削除を行ってください。
### スーパーバイザの設定
以下を貼り付けてください
```diff_elixir:desktop_dir/desktop_tuto/lib/desktop_tuto.ex
defmodule DesktopTuto do
  use Application
  require Logger

  def config_dir() do
    Path.join([Desktop.OS.home(), ".config", "todo"])
  end

  @app Mix.Project.config()[:app]

  def start(:normal, []) do
    Desktop.identify_default_locale(DesktopTutoWeb.Gettext)
    File.mkdir_p!(config_dir())

    # session用のETSを起動
    :session = :ets.new(:session, [:named_table, :public, read_concurrency: true])

    children = [
      {Phoenix.PubSub, name: DesktopTuto.PubSub},
      DesktopTutoWeb.Endpoint
    ]

    opts = [strategy: :one_for_one, name: DesktopTuto.Supervisor]

    {:ok, sup} = Supervisor.start_link(children, opts)

    # phoenixサーバーの起動中のポート番号を取得
    port = :ranch.get_port(DesktopTutoWeb.Endpoint.HTTP)

    {:ok, _} =
      Supervisor.start_child(sup, {
        Desktop.Window,
        [
          app: @app,
          id: DesktopTutoWindow,
          title: "DesktopTutoApp",
          size: {600, 500},
          # アイコンとして表示される画像(priv/)
          # icon: "icon.png",
          url: "http://localhost:#{port}"
        ]
      })
  end

  def config_change(changed, _new, removed) do
    DesktopTutoWeb.Endpoint.config_change(changed, removed)
    :ok
  end
end
```
ここまでを行ったあと`mix phx.server`で起動してみてください。以下のようになると思います。
（アイコンにたぬきのイラストを置きましたので左上とタスクバーのアイコンがたぬきになってますが気にしないでください。なにもいじっていないと歯車が表示されていると思います。)
![起動直後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3455723/3f275da8-bc03-7847-4d03-be3bc2c0486a.png)

# Androidでの実行
@the_haigoさんの記事[^1]を参考に
```
git clone https://github.com/elixir-desktop/android-example-app.git android
```
androidディレクトリのappにDesktopプロジェクト(desktop_tuto)をコピーしてください。
次にapp/run_mixを書き換えます。さんの記事[^1]を参考にrun_mixを変更します。
```diff_shell:android/app/run_mix.sh
#!/bin/bash
set -e

BASE=`pwd`
APP_FILE="$BASE/src/main/assets/app.zip"
export MIX_ENV=prod
export MIX_TARGET=android

- if [ ! -d "elixir-app" ]; then
-   git clone https://github.com/elixir-desktop/desktop-example-app.git elixir-app
- fi

# using the right runtime versions
- if [ ! -f "elixir-app/.tool-versions" ]; then
-   cp .tool-versions elixir-app/
- fi

- cd elixir-app
+ cd desktop_tuto
if [ ! -d "deps/desktop" ]; then
  mix local.hex --force
  mix local.rebar
  mix deps.get
fi

# まだライブラリは未使用なので
# if [ ! -d "assets/node_modules" ]; then
#   cd assets && npm i && cd ..
# fi

if [ -f "$APP_FILE" ]; then
  rm "$APP_FILE"
fi

mix assets.deploy && \
  mix release --overwrite && \
- cd "_build/${MIX_TARGET}_${MIX_ENV}/rel/todo_app" && \
+ cd "_build/${MIX_TARGET}_${MIX_ENV}/rel/desktop_tuto" && \

  zip -9r "$APP_FILE" lib/ releases/ --exclude "*.so"
```
このあとはappディレクトリに`cd`してから`run_mix`を実行しましょう
実行後はandroid-studioでfile->open->androidディレクトリ選択したあとに実行ボタンを押します。
すると次のような画面が出るかと思います。
![実行後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3455723/f8d12e07-ab58-9572-542d-08c5f205810b.png)

# 終わりとあとがき
このあと機能を実装していく場合、はいつも通りにliveviewを触る感覚でコードを記述していきます

最初にelixirでスマホアプリを開発できると聞いたときは胸が踊りました。
「何ですと！？」って感じです。スマホアプリのボタン押して画面が変わるみたいな処理は、
なんかのボタン押してhandle_eventでpush_navigateが〜って感じで。
phoenix勉強続けて本当に良かったです。学習した知識をもとにもっと実用的なものを作りたいですね。
誤字脱字、間違いがございましたら教えてください。

[^1]: https://qiita.com/the_haigo/items/fa3906e45cf805ca38fa#android-emunlator%E3%81%A7%E5%8B%95%E3%81%8B%E3%81%99