ElixirDesktopでスマホアプリを作る - Phoenix1.7のアプリを起動

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

---------------------------------------------

なお、本記事は2023/7/13開催[ElixirMobile#3：DBも内蔵したElixirDesktopスマホアプリ開発ハンズオン](https://fukuokaex.connpass.com/event/283648/)のイベントレポート(前半)を兼ねています。

------ハンズオンの様子(前半)はこちらから------

↓ ~27:03までが本記事のハンズオン動画です。

https://www.youtube.com/watch?v=EFWPu1ibGfc

---------------------------------------------


■「ElixirDesktopでスマホアプリを作る」シリーズの目次
①[Windows11+WSL2に環境構築](https://qiita.com/Alicesky2127/items/5f9a1c6496b1c083720f)
|> ②[Phoenix1.7のアプリを起動](https://qiita.com/Alicesky2127/items/7650f5650069b56bc173)
|> ③[LiveViewを使用してCRUDの機能を実装する](https://qiita.com/Alicesky2127/items/ada99a6b958be18ed270)
|> ④[Android実機に転送する(Google Drive経由)](https://qiita.com/Alicesky2127/items/623628ef64a3295fd8c5)

# 目的
こちらの記事をWindows11 + WSL2の環境で動かします。

https://qiita.com/the_haigo/items/17a89b5038a7f337b102


## 実行環境
Windows 11 ＋ WSL2 ＋ Ubuntu 22.04
Elixir v1.14.3
Erlang v25.0.4
Phoenix v1.7.3


# 前提条件
## Elixirのインストール
以下のいずれかが未実施の場合は[前回の記事](https://qiita.com/Alicesky2127/items/5f9a1c6496b1c083720f#%E5%AE%9F%E8%A1%8C%E7%92%B0%E5%A2%83)を参照して環境構築を行ってください。
- Android Studioのインストール
- NDK（※Native Development Kitの略、Android で C や C++ のコードを使用できるようにするツールセット)のインストール
- git、npm、asdf のインストール
- asdfを使用してのErlangおよびElixirのインストールと設定(※バージョン指定があります)[^1]


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


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


# プロジェクトの作成
DBはsqlite3を使うので --database sqlite3のオプションをつけます。
また、Phoenixのプロジェクトとandroid-studioのプロジェクトの2つが必要なので、両方のプロジェクトをいれるディレクトリを作成します。

```bash:shell
mkdir todo_root
cd todo_root
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プロジェクトの設定です。


# androidでの起動
## android-example-appのリポジトリをクローン
```bash:shell
cd ..
git clone git@github.com:elixir-desktop/android-example-app.git
```


## run_mixの変更
run_mix[^5]内で今回使用しないスクリプトをコメントアウトします。
使用するプロジェクトを先ほど作成したtodo_appに切り替えます。
```diff_shell:android-example-app/app/run_mix
#!/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 ../../todo_app

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" && \
  zip -9r "$APP_FILE" lib/ releases/ --exclude "*.so"
```


## run_mixの実行
先にrun_mixを単体で走らせて、リリースビルドを作成しておきます。
```bash:shell
./run_mix
```


## run_mixをブランクにする
run_mixをそのままにしてAndroid StudioのRunボタン（Android Studioの画面右上にある、緑色の▷ボタン）を押すと、再びビルドが走ってしまいます。
現時点ではrun_mixを実行済みでリリースビルドが出来上がり済のため、run_mixはもう使用しません。
よって、run_mixは別名でバックアップとして取っておき、run_mixそのものはブランクにします。
```bash:shell
cp run_mix run_mix_build
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677280/b8ab8558-698c-5a64-390a-7b3f0413a3b6.png)


## Android StudioでRunする
Android Studioを起動します。
```bash:shell
~/android-studio/bin/studio.sh
```

run_mixとrun_mix_buildがあることを確認してファイルを開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677280/cc84ce25-9aa1-29c5-e46f-cebb056d8136.png)

Android Studioの画面右上にある、緑色の▷ボタンがRunボタンです。
これを押下すると、先ほど作成したリリースビルドを読み込んで、エミュレータ上でアプリが起動します。
run_mixがブランクになっていることを確認して、Runボタンを押下します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677280/d797e9d5-4a6c-fc0e-5b8f-b2a1e5cd0c79.png)

Phoenix1.7が起動しました！(^▽^)/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677280/b114a1b6-6e62-2e12-5f03-0a0a01a8d6ea.png)

## 次の記事へ
https://qiita.com/Alicesky2127/items/ada99a6b958be18ed270


# ～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


[^1]:2023/7/12時点ではErlang26系では動かなかったとのことです。https://qiita.com/the_haigo/items/17a89b5038a7f337b102#elixir%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB

[^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コミュニティはいろんな形で活動中！