[2023年12月版]ElixirDesktopでスマホアプリを作る - Phoenix1.7のアプリを起動(iex -S mixまで)

こんにちは！
プログラミング未経験文系出身、Elixirの国に迷い込んだ？！見習いアルケミストのaliceと申します。
今回はElixirDesktop Phoenix1.7のアプリをWindows11 + WSL2で動かす手順をまとめなおします。


# 目的
Phonenix+LiveViewでCRUD機能のあるアプリを実装してエミュレータやスマホ実機で動かしたい。

## 実行環境
:::note warn
[android-example-appを動かす環境](https://qiita.com/Alicesky2127/items/ad463d50cfe81b2f9400)とElixir, Erlangのバージョンが違います。ご注意ください。[^0]
:::

Windows 11 ＋ WSL2 ＋ Ubuntu 22.04
Elixir v1.15.7-otp-26 
Erlang v26.1.2 
Phoenix v1.7.3

# 今回やりたいこと
Phoenix1.7をElixirDesktopの環境で動かしたい。
まずはデスクトップ(PC)での動作確認をしたい。


# 前提条件
## Elixirのインストール
以下のいずれかが未実施の場合は[前回の記事](https://qiita.com/Alicesky2127/items/ad463d50cfe81b2f9400)を参照して環境構築を行ってください。
- Android Studioのインストール
- NDK（※Native Development Kitの略、Android で C や C++ のコードを使用できるようにするツールセット)のインストール
- git、npm、asdf のインストール


## asdfを使用してのErlangおよびElixirのインストールおよび設定
### インストール
```bash:bash
asdf install erlang 26.1.2 
asdf install elixir 1.15.7-otp-26 
```

### 環境変数に設定-グローバル変数
```bash:bash
asdf global erlang 26.1.2 
asdf global elixir 1.15.7-otp-26 
```

#### (余談)環境変数に設定-ローカル変数
ローカル変数に入れるとそのディレクトリ配下でのみ適用されます。
グローバル変数に入れるかローカル変数に入れるかはお好みで。
```bash:bash
asdf local erlang 26.1.2 
asdf local elixir 1.15.7-otp-26 
```


## Phoenixのインストール
最新のPhoenixをインストールします。
(私は既にインストール済のためスキップしました)
```bash:shell
mix archive.install hex phx_new
```


## SQLite3のインストール
apt経由でSQLite3をインストールします。[^2]
```bash:shell
sudo apt install sqlite3
```


# rootプロジェクトの作成
まず、Phoenixのプロジェクトとandroid-studioのプロジェクトの2つが必要なので、両方のプロジェクトをいれるディレクトリを作成します。

```bash:shell
mkdir todo_root
cd todo_root
```

# Phoenixプロジェクトの作成
次に、Phoenixのプロジェクトを作ります。
DBはsqlite3を使うので --database sqlite3のオプションをつけます。

```bash:shell
mix phx.new todo_app --database sqlite3
cd todo_app
mix ecto.create #todo_app_dev.dbが作成される
```

## ライブラリの追加
```diff_elixir:todo_app/mix.exs
  defp deps do
    [
      ...
+      {:exqlite, github: "elixir-desktop/exqlite", override: true},
+      {:desktop, "~> 1.5"},
+      {:wx, "~> 1.1", hex: :bridge, targets: [:android, :ios]}
    ]
  end
```

- exqliteとは、ElixirDesktopからSqlite3を操作するライブラリ
- desktopとは、ElixirDesktopフレームワーク本体
- wxとは、ElixirDesktopをスマートフォン上で動かすためのライブラリ

追加後、以下を実行します。
```bash:todo_app
mix deps.get
```

## config修正
```diff_elixir:todo_app/config/config.exs
 config :todo_app, TodoAppWeb.Endpoint,
-  url: [host: "localhost"],
+  http: [ip: {127, 0, 0, 1}, port: 10_000 + :rand.uniform(45_000)],
   render_errors: [
     formats: [html: TodoAppWeb.ErrorHTML, json: TodoAppWeb.ErrorJSON],
     layout: false
   ],
   pubsub_server: TodoApp.PubSub,
-  live_view: [signing_salt: "J82JhPeQ"]
+  live_view: [signing_salt: "J82JhPeQ"],
+  secret_key_base: :crypto.strong_rand_bytes(32),
+  server: true
```

※signing_saltの値は人によって異なります。

- secret_key_baseとは、secureな文字列を作る際のベースになるもの。
- server: trueとは、アプリケーションが起動しているサーバーのIPアドレス + ポート番号への外部からのアクセスを受け付けられるようになる設定。[^3]


## runtime.exsの無効化
アプリの実行時に`todo_app/config/runtime.exs`があるとエラーになるそうです。
よって、`runtime_disable.exs`にリネームしておきます。


## Endpoint設定
EndpointのモジュールをPhoenixからDesktopに変更します。
ElixirDesktopではcookieでのセッション管理を使えないのでetsに変更します。

```diff_elixir:todo_app/lib/todo_app_web/endpoint.ex
 defmodule TodoAppWeb.Endpoint do
-  use Phoenix.Endpoint, otp_app: :todo_app
+  use Desktop.Endpoint, otp_app: :todo_app

   @session_options [
-    store: :cookie,
+    store: :ets,
     key: "_todo_app_key",
-    signing_salt: "GvxD48aX",
-    same_site: "Lax"
+    table: :session
   ]
```


## アプリケーション起動時の設定
`todo_app.ex`にどのような手順でPhoenixアプリケーションを起動していくかの設定をします。
`todo_app.ex`にはデフォルトではドキュメントしか書かれていないので、以下を追加します。

```diff_elixir:todo_app/lib/todo_app.ex
 defmodule TodoApp do
+  use Application

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

+  @app Mix.Project.config()[:app]
+  def start(:normal, []) do 
+    # ~/.config/todo_appフォルダを作成する。その中に設定ファイルを放り込んでいく
+    File.mkdir_p!(config_dir())

+    # DBの場所を指定
+    Application.put_env(:todo_app, TodoApp.Repo,
+      database: Path.join(config_dir(), "/database.sq3")
+    )

+    # session用のETS(インメモリDB)を起動
+    :session = :ets.new(:session, [:named_table, :public, read_concurrency: true])

+    children = [
+      TodoApp.Repo,
+      {Phoenix.PubSub, name: TodoApp.PubSub},
+      TodoAppWeb.Endpoint
+    ]

+   opts = [strategy: :one_for_one, name: TodoApp.Supervisor]

+    # Phoenixのsuperviserをメインとして起動
+    {:ok, sup} = Supervisor.start_link(children, opts)

+    # DBの初期化とマイグレーション実行
+    TodoApp.Repo.initialize()

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

+    # ElixirDesktopのsuperviserを子プロセスとして起動
+    {:ok, _} =
+      Supervisor.start_child(sup, {
+        Desktop.Window,
+        [
+          app: @app,
+          id: TodoAppWindow,
+          title: "TodoApp",
+          size: {400, 800},
+          url: "http://localhost:#{port}"
+        ]
+      })
+  end

+  def config_change(changed, _new, removed) do
+    TodoAppWeb.Endpoint.config_change(changed, removed)
+    :ok
+  end
end
```

- `Desktop.OS.home()`はユーザーのホームディレクトリを返す関数[^4]
- superviserとは、アプリが落ちたときに自動的にプロセスキルして再起動してくれるもの。堅牢なシステムを作る際に有用。

### repo.exの変更
`todo_app.ex`から呼び出される関数の設定をします。
（ひとまず空の関数を置いておきます）

```diff_elixir:todo_app/lib/todo_app/repo.ex
defmodule TodoApp.Repo do
  ...
+  def initialize() do
+  end
end
```

### mix.exsの変更
`mix.exs`で呼び出される起動時の設定を変更します。
\# アプリケーション起動時の設定 で設定した`todo_app.ex`に差し替えます。

```diff_elixir:todo_app/mix.exs
  def application do
    [
-      mod: {TodoApp.Application, []},
+      mod: {TodoApp, []},
      extra_applications: [:logger, :runtime_tools]
    ]
  end
```

以上がPhonenixプロジェクトの設定です。

## iex -S mixでデスクトップ上での起動を確認
```bash:shell
iex -S mix
```

localhost:4000番ポートでデスクトップアプリの起動が確認できました(^▽^)/
※あとはこれがモバイルアプリとしてスマホで見ることができればPhoenix1.7のアプリが起動できたと言い切れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677280/9371be68-6708-f3cc-925a-2297a37cadaa.png)


# ～Elixirの国のご案内～
↓Elixirって何ぞや？と思ったらこちらもどぞ。Elixirは先端のアレコレをだいたい全部できちゃいます:laughing::sparkles::sparkles:

https://qiita.com/piacerex/items/09876caa1e17169ec5e1

↓ゼロからElixirを始めるなら「エリクサーチ」がおすすめ！私もエンジニア未経験から学習中です。

https://elixir-lang.info/topics/entry_column


↓[We Are The Alchemists, my friends!:bouquet:](https://qiita.com/torifukukaiou/items/6bcd5bbb80df9e594e9e "Awesome-san's Awesome article")[^10]
Elixirコミュニティは本当に優しくて温かい人たちばかり！
私が挫折せずにいられるのもこの恵まれた環境のおかげです。
まずは気軽にコミュニティを訪れてみてください。[^11]


https://speakerdeck.com/elijo/elixirkomiyunitei-falsebu-kifang-guo-nei-onrainbian

[^0]:前回の記事はandroid-example-appを動かす環境構築を目的としており、android-example-appのREADMEにてElixir, Erlangをバージョン指定されていたためそちらに合わせていました。

[^2]: sqlite3の導入。参考にさせていただきました。 https://qiita.com/t-yamanashi/items/ef4c5bcab4a130105760

[^3]: 外部とはローカルホスト（同一マシン）以外を指す。

[^4]: https://github.com/elixir-desktop/desktop/blob/5927dc42d97305c43cad523b905153b687434219/lib/desktop/os.ex#L21

[^5]: run_mixとは
ダウンロードしたリポジトリ内のrun_mixはビルドスクリプトといって、
1.GitHubからexampleをcloneしてくる
2.ライブラリのインストール等を行う
3.リリース(prod環境ビルド)に必要な処理を行う
4.Zipで圧縮
5.Android側が読み込むようにしてある場所にコピー
以上の処理をまとめて走らせてくれるシェルスクリプトのこと。
https://qiita.com/Alicesky2127/items/5f9a1c6496b1c083720f#run_mix%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B
※なお、今回は1.についてはコメントアウトして実施しないようにしている。

[^10]: @torifukukaiouさんのAwesomeな名言をお借りしました。Elixirコミュニティを一言で表すと、これに尽きます。


[^11]: @kn339264さんの素敵なスライドをお借りしました。Elixirコミュニティはいろんな形で活動中！