ElixirDektopのアプリを mix phx.newから自分で作る

# はじめに
この記事は Elixirアドベントカレンダーのシリーズ４の４日目の記事です

ElixirDekstopのアプリを mix phx.newで0から作って、デスクトップアプリとしてPhoenixのデフォルトページを表示するところまで行います

# 環境
macOS 14 Sonoma
Elixir 1.15.7-opt-26
Erlang 26.2

iOS、Androidのサンプルに合わせて Erlangのバージョンは 25.0.4がいいのですが
Desktopアプリを起動するときに使うwxwidgetのパスをビルド時に解消できずデスクトップアプリが起動できないため、その問題を解消している Erlang 26.2を使用します

# Erlang,Elixirをインストール

Erlangのインストール必要なライブラリを以下を参考にインストールします
https://github.com/asdf-vm/asdf-erlang

```
brew install git openssl npm wxwidgets openjdk　autoconf libxslt fop
```

必要なライブラリのインストールが完了したらErlangとElixirをインストールします

```
asdf install erlang 26.2
asdf install elixir 1.15.7-opt-26
```
インストールしたらグローバルで使用するバージョンとして指定します


```
asdf global erlang 26.2
asdf global elixir 1.15.7-otp-26
```

プロジェクト内だけ指定したい場合は phx.newしたあとにフォルダ内で`asdf current`とすることで .tool-versionsが作成されます

# Phoenix Frameworkのセットアップ

以下のコマンドでパッケーマネージャのセットアップを行います
```
mix local.hex
```

以下のコマンドでジェネレーターのphx_newをセットアップを行います

```
mix archive.install hex phx_new
```

# Phoenix Projectの作成
セットアップが完了したのでPhoenix Projectを作成していきます
今回はスマホアプリでよくあるようなAPIサーバーを立ててデータのやり取りを粉う形式ではなく、
アプリ単体で完結できるスタンドアローンなアプリケーションを作成してきます

またDBはデフォルトのpostgresqlではなく Sqlite3を使用します

以下のようなアプリを作成していきます
- 本棚アプリ bookshelf
- 本棚のCRUD
- 書籍データをGoogle Books APIから取得して本棚に追加

データベースを指定するときは `--database`オプションをつけます

```
mix phx.new bookshelf --database sqlite3
```

プロジェクトの作成が完了したら、ログに従ってプロジェクトフォルダに移動してDBを作成しましょう

```
cd bookshelf
mix ecto.create
```

次に以下のコマンドでウェブサーバーを起動させます

```
mix phx.server
```

localhost:4000にアクセスすると問題なく起動しているのが確認できました

![スクリーンショット 2023-11-28 23.20.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/4e96affd-c136-0ba7-9676-7eefcfb7d1d4.png)


# ライブラリの追加
次はデスクトップアプリとして動くようにしていきます
以下の3つのライブラリを追加します

```mix.exs
  defp deps do
    [
      ...
      {:exqlite, github: "elixir-desktop/exqlite", override: true},
      {:desktop, "~> 1.5"},
      {:wx, "~> 1.1", hex: :bridge, targets: [:android, :ios]}
    ]
  end
```

追加したら`mix deps.get`を実行しましょう

# config修正

configファイルをElixirDesktopに合わせて変更します
Endpointの設定を以下のように変更します

```diff_elixir:config/config.exs
...
config :bookshelf, BookshelfWeb.Endpoint,
-  url: [host: "localhost"],
+  http: [ip: {127, 0, 0, 1}, port: 10_000 + :rand.uniform(45_000)],
  adapter: Phoenix.Endpoint.Cowboy2Adapter,
  render_errors: [
    formats: [html: BookshelfWeb.ErrorHTML, json: BookshelfWeb.ErrorJSON],
    layout: false
  ],
  pubsub_server: Bookshelf.PubSub,
- live_view: [signing_salt: "3YOiiVQi"]
+ live_view: [signing_salt: "3YOiiVQi"],
+ secret_key_base: :crypto.strong_rand_bytes(32),
+ server: true
...
```

portの設定はiOSのバックグラウンドモードからの復帰時に必要です
`server: true`とすることで、スマホ内で起動したPhoenixアプリケーションをWebViewから閲覧ができます


`config/runtime.exs`があるとエラーになるので`runtime_disable.exs`にリネームします

# Endpoint設定
EndpointのモジュールをPhoenixからDesktopに変更します
ブラウザではないのでセッション管理をcookieから EralangビルドインインメモリDBのetsに変更します

```diff_elixir:lib/bookshelf_web/endpoint.ex
defmodule BookshelfWeb.Endpoint do
-  use Phoenix.Endpoint, otp_app: :bookshelf
+  use Desktop.Endpoint, otp_app: :bookshelf

  @session_options [
-    store: :cookie,
+    store: :ets,
     key: "_bookshelf_key",
+    table: :session
-    signing_salt: "YBmbGVCM",
-    same_site: "Lax"    
  ]
  ... 
end
```

# アプリケーション起動時の設定
アプリケーション起動時に実行される、プロセス監視を行うsuperviserの設定を行います

```lib/bookshelf.ex
defmodule Bookshelf do
  use Application

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

  @app Mix.Project.config()[:app]
  def start(:normal, []) do
    # configフォルダを掘る
    File.mkdir_p!(config_dir())

    # Databaseのパスをセット
    Application.put_env(:bookshelf, Bookshelf.Repo,
      database: Path.join(config_dir(), "/database.sq3")
    )

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

    children = [
      Bookshelf.Repo,
      {Phoenix.PubSub, name: Bookshelf.PubSub},
      BookshelfWeb.Endpoint
    ]

    opts = [strategy: :one_for_one, name: Bookshelf.Supervisor]
    # メインのsuperviser起動
    {:ok, sup} = Supervisor.start_link(children, opts)


    # phoenixサーバーが起動中のポート番号を取得
    port = :ranch.get_port(BookshelfWeb.Endpoint.HTTP)
    
    # メインのsuperviserの配下にElixirDesktopのsuperviserを追加
    {:ok, _} =
      Supervisor.start_child(sup, {
        Desktop.Window,
        [
          app: @app,
          id: BookshelfWindow,
          title: "bookshelf",
          size: {400, 800},
          url: "http://localhost:#{port}"
        ]
      })
  end

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


アプリケーション設定ファイルを差し替えます

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

# 動作確認
デスクトップアプリとして起動する準備が整ったので、以下のコマンドを実行します

```
iex -S mix 
```
無事デスクトップアプリとして起動できました

![スクリーンショット 2023-11-29 13.11.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/640dbdf6-f7b2-a6e6-4535-b123e016561c.png)


# 最後に
今回はmix phx.newで通常のPhoenix Projectを作成してデスクトップアプリとして起動するところまでを行いました、
通常のwebアプリケーションに少し手を加えるだけでデスクトップアプリ化できることがわかったかと思われます

次はiOSプロジェクトを０から作っていきます

本記事は以上になりますありがとうございました