ElixirDesktopとPhoenixを同一のProjectで開発する際の構成

# はじめに
本記事はElixirDesktopとPhoenixを１つのPhoenixプロジェクトとして開発する際に
どのような構成にすると良いか模索した備忘録になります

# 環境構築
asdf でできるようになりましたので、以下の手順で該当のElixirとErlangをインストールしておいてください

https://github.com/elixir-desktop/ios-example-app#how-to-build--run


# 構成
色々模索した結果以下の構成にしてMIX_TARGETで読み込む設定ファイルを切り替えるのがお手軽でよさそうでした
top project
└ Phoenix -> スマホ、Web共通のコード
└ Andoroid -> ビルド+WebView設定
└ iOS -> ビルド+WebView設定

UmbrellaでWebはWeb,スマホはスマホでプロジェクトを分けてDB部分を分けてビルド時に必要なものだけを読み込むでも良いですが、Umbrella自体があまり情報が多くないのとソースコード管理や、phx.gen周りが面倒なので、途中で断念しました。

Umbrellaに関してはこちらが参考になるかもしれません

https://github.com/dashbitco/bytepack_archive

https://dev.to/gumi/mix-otp-07-1p5k

https://elixirschool.com/ja/lessons/advanced/umbrella_projects

# プロジェクトの作成

早速作成していきます、今回は行きたい場所を管理するアプリを作成していてそちらの作り直しの過程をサンプルとして実装していきます

```
mkdir trarecord_project
cd trarecord_project
git init
mix phx.new trarecord
cd trarecord
mix ecto.create
```

# Desktopを組み込む
基本こちらに沿って作成していきます

https://qiita.com/the_haigo/items/0fd907d24fdde1905aa2

## ライブラリの追加
mix targetでdesktop,ios,androidを指定したときだけ追加するように targetsを指定します

```mix.exs
defmodule Trarecord.MixProject do
  ...
  defp deps do
    [
       ...
      {:desktop, "~> 1.4", targets: [:android, :ios, :desktop]},
      {:wx, "~> 1.0.10", hex: :bridge, targets: [:android, :ios]}
    ]
  end
...
end
```

## Applicationファイルを作成
Application起動時に行うことを記述するdesktop用のファイルを作成します
phoenixはapplication.ex
desktopはtrarecord.ex
を使うようにします

```trarecord/lib/trarecord.ex
defmodule Trarecord do
  @app Mix.Project.config()[:app]
  def start(:normal, []) do
    {:ok, sup} = Supervisor.start_link([Trarecord.Repo], name: __MODULE__, strategy: :one_for_one)
    {:ok, _} = Supervisor.start_child(sup, TrarecordWeb.DesktopSup)

    {:ok, _} =
      Supervisor.start_child(sup, {
        Desktop.Window,
        [
          app: @app,
          id: TrerecordWindow,
          title: "TrerecordApp",
          size: {600, 500},
          url: &TrarecordWeb.Endpoint.url/0
        ]
      })
  end
end
```

session,pubsub周りのsuperviserを記述する desktop_sup.exを作成します

```trarecord/lib/trarecord_web/desktop_sup.ex
defmodule TrarecordWeb.DesktopSup do
  use Supervisor

  @moduledoc """
    Supervisor for the WebApp
  """

  def start_link([]) do
    Supervisor.start_link(__MODULE__, [], name: __MODULE__)
  end

  def init([]) do
    children = [
      {Phoenix.PubSub, name: Trarecord.PubSub},
      TrarecordWeb.Endpoint
    ]

    :session = :ets.new(:session, [:named_table, :public, read_concurrency: true])
    Supervisor.init(children, strategy: :one_for_one, max_restarts: 1000)
  end
end
```

Desktop用のEndpointファイルを作成します、同じファイル名を使用して MIX_TARGETで切り替えたいので、実行するdefmoduleをif文で分けます
Dektopの方は記事にあるようにendpointをPhoenixのからDesktopのものに変えて、sessionをetsで管理するようにします

```trarecord/lib/trarecord_web/endpoint.ex
if Mix.target() in [:desktop, :android, :ios] do
  defmodule TrarecordWeb.Endpoint do
    use Desktop.Endpoint, otp_app: :trarecord

    @session_options [
      store: :ets,
      key: "_trarecord_key",
      table: :session
    ]
  # 以下endpoint.exのコピー
  end
else
  # もとのendpoint.exをコピー
end
```

applicationファイルができたのでmix.exsでtargetで切り替えるようにします

```mix.exs
defmodule Trarecord.MixProject do
  use Mix.Project
  ...
  def application do
    application(Mix.target())
  end

  def application(target) when target in [:android, :ios, :desktop] do
    [
      mod: {Trarecord, []},
      extra_applications: [:logger, :runtime_tools]
    ]
  end

  def application(_) do
    [
      mod: {Trarecord.Application, []},
      extra_applications: [:logger, :runtime_tools]
    ]
  end

  ...
end
```

## configの切り替え
こちらもtargetで切り替えるようにしていきます

endpointの設定を削除してtargetで読み込むファイルを切り替えるようにします

```diff_elixir:trarecord/config/config.exs

# 以下追加
if Mix.target() in [:desktop, :android, :ios] do
  import_config "desktop.exs"
else
  import_config "web.exs"  
end

import_config "#{config_env()}.exs"
```
desktop用のEndtpointの設定を読み込む

```trarecord/config/desktop.exs
import Config

# Configures the endpoint
config :trarecord, TrarecordWeb.Endpoint,
  http: [ip: {127, 0, 0, 1}, port: 0], # desktop全体で設定
  render_errors: [view: TrarecordWeb.ErrorView, accepts: ~w(html json), layout: false],
  pubsub_server: Trarecord.PubSub, # すべての環境で行うなら Redisにしたほうが良い
  live_view: [signing_salt: "nt9pr/6W"],
  secret_key_base: "cVZaz98CBZNGr4dk7XhVqYpW49We6OKdzORLS20peE0ZK8Qv3ex6uOLujx/mXS80",
  server: true # server trueを設定

```
PhoenixのEdnpointで使用する設定ファイルを作成
```trarecord/config/web.exs
import Config

if config_env() == :dev do
  config :trarecord, TrarecordWeb.Endpoint,
    # Binding to loopback ipv4 address prevents access from other machines.
    # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
    http: [ip: {127, 0, 0, 1}, port: 4000]
end
```
releaseビルドするのでprodにローカル環境のiOS,AndroidからPostgreSQLに接続する情報をいれます

iosはloopbackアドレスで行けましたが、Androidは10.0.2.2にする必要があります

https://araramistudio.jimdo.com/2018/01/11/android%E3%81%AE%E3%82%A8%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%82%BF%E3%83%BC%E3%81%8B%E3%82%89%E8%87%AA%E8%BA%AB%E3%81%AEpc-localhost-%E3%81%B8%E6%8E%A5%E7%B6%9A/

```trarecord/config/prod.exs
import Config

if Mix.target() == :ios do
    username: "postgres",
    password: "postgres",
    hostname: "127.0.0.1",
    database: "trarecord_dev",
    stacktrace: true,
    show_sensitive_data_on_connection_error: true,
    pool_size: 10
end

if Mix.target() == :android do
  config :trarecord, Trarecord.Repo,
    username: "postgres",
    password: "postgres",
    hostname: "10.0.2.2",
    database: "trarecord_dev",
    stacktrace: true,
    show_sensitive_data_on_connection_error: true,
    pool_size: 10
end
```

runtime.exsがあるとエラーになるのは変わっていないようなのでとりあえずファイル名を変えておきます
Phonixをデプロイする時は runtime.exsに戻す運用にしましょう
runtime.exs -> runtime_disable.exs

## 各OSビルド用のプロジェクトフォルダを作成する
ios-example-appとandroid-example-appをクローンしてきます

```
git clone https://github.com/elixir-desktop/ios-example-app.git ios
git clone https://github.com/elixir-desktop/android-example-app.git android
```

## iOS 設定

最初にreadmeにあるように関連ライブラリをインストールします
```
cd ios
carthage update --use-xcframeworks
```

ビルドスクリプトのrun_mixをプロジェクトに合わせます

```diff_shell:ios/run_mix
#!/bin/bash
set -e

BASE=`pwd`
export MIX_ENV=prod
export MIX_TARGET=ios

# desktop-example-appは使わないので削除
- 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/.tool-versions" ]; then
  cp .tool-versions ../trarecord # projectパスを変更
fi

cd ../trarecord 

if [ ! -d "deps/desktop" ]; then
  mix deps.get
fi

# npmをまだ使用していないのでコメントアウト
# if [ ! -d "assets/node_modules" ]; then
#   cd assets && npm i && cd ..
# fi

if [ -f "$BASE/todoapp/app.zip" ]; then
  rm "$BASE/todoapp/app.zip"
fi

mix assets.deploy && \
  mix release --overwrite && \
  cd _build/ios_prod/rel/trarecord && \ # ここのパスの変更を忘れないように
  zip -9r "$BASE/todoapp/app.zip" lib/ releases/ --exclude "*.so"
```

`todoapp.xcodeproj`をダブルクリックしてxcodeを開いて
各種ライブラリが埋め込まれていないので Embed & Sign に変更します
![スクリーンショット 2022-08-31 0.54.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/ee33ffff-d3e3-42ef-95ba-056b94e1d9b3.png)

## Android設定
AndroidStudioでandroidフォルダを開いたら
ビルドするJavaのバージョンが1.8とかになっていないかを確認します

https://qiita.com/takahirom/items/5e8d7b69e873edb3dcaf

run_mixを書き換えます
階層がiOSより深いので注意が必要です
```diff_shell:android/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

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

cd ../../trarecord

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/trarecord" && \ # ここの変更を忘れないこと
  zip -9r "$APP_FILE" lib/ releases/ --exclude "*.so"
```

# 動作確認
Web
`MIX_TARGET=host iex -S mix phx.server`
![スクリーンショット 2022-08-31 1.10.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/a2754f3e-5af2-31a4-5054-7ecd9f90a111.png)

Desktop
`MIX_TARGET=desktop iex -S mix`
![スクリーンショット 2022-08-31 1.12.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/bff0e2a5-7c3b-014a-2a20-e6c70c124398.png)

iOS
![スクリーンショット 2022-08-31 1.40.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/d117907c-0448-c912-d8fc-94151a81eb5c.png)

Android
![スクリーンショット 2022-08-31 1.39.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/561cbc91-bb25-c763-5ac2-46a93e43865f.png)

すべての環境で無事動きました！

# 最後に
まだハマリポイントが１つずつ残っていますが、バージョンアップでREADMEに従えばスマホのビルドができるようになりました
次は Tailwind,DaisyUI, 実際にDBを使う辺りをやってみたいと思います

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


# 参考サイト
https://github.com/elixir-desktop/desktop
https://github.com/elixir-desktop/ios-example-app
https://github.com/elixir-desktop/android-example-app
https://qiita.com/the_haigo/items/0fd907d24fdde1905aa2
https://github.com/dashbitco/bytepack_archive
https://dev.to/gumi/mix-otp-07-1p5k
https://elixirschool.com/ja/lessons/advanced/umbrella_projects
https://araramistudio.jimdo.com/2018/01/11/android%E3%81%AE%E3%82%A8%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%82%BF%E3%83%BC%E3%81%8B%E3%82%89%E8%87%AA%E8%BA%AB%E3%81%AEpc-localhost-%E3%81%B8%E6%8E%A5%E7%B6%9A/
https://qiita.com/takahirom/items/5e8d7b69e873edb3dcaf

# リポジトリ
https://github.com/thehaigo/trarecord/tree/setup_project_with_tailwind_daisyui