mix phx.newしたプロジェクトをElixirDesktop Android app化する方法(最小の変更)

# Elixir Desktop化の困った状態

Elixir Desktop化する方法が[本家のドキュメント](https://github.com/elixir-desktop/desktop/blob/main/guides/your_first_app.md)に含まれていました（以下、your_first_app.mdと呼びます）。

このドキュメント通りにすればよいとおもったんですが、これだけでは、うまくいきませんでした。

- サンプルの[todo app](https://github.com/elixir-desktop/android-example-app)（以下、todo appと呼びます）は、動作はするが、your_first_app.mdとは異なる点が多い（Applicationがtodo_app.exに含まれている等、Phx.newで作成したものとDesktop化に関係のない違いがある。

- your_first_app.mdは、変更の要が書かれているが、実際には、この変更だけでは、動作しない。SQLite3の起動などは含まれてない。

という困った状況でした。

こんな中、[ElixirDesktop Androidでphx.newで作成したプロジェクトを動かす](https://qiita.com/the_haigo/items/0fd907d24fdde1905aa2)がブレークスルーとなり、your_first_app.mdに不足している点を明らかにできました。
勉強も兼ねて、phx.newの素の状態にできるだけ近い状態でDesktop化してみます。

この調査は、Androidアプリ化を前提にしてます。
DBはSQLite3を使用して、起動する設定を含めてみます。

# 変更点

変更点の詳細は、最後のdiff結果を参照してください。

|ファイル |your_first_app.md記載箇所|追加した内容、コメント|
|--------|----------------|-----|
|mix.exs| (1) desktop追加|wx、ecto_sqlite3も追加した|
|application.ex|(2) Desktop.Window追加<br>(5)identify_default_locale追加|SQLite3の初期化の処理追加が必要だった|
|endpint.ex|(3) Desktop.Endpointに変更 |ドキュメント通り|
|config.exs|(4) http: [ip: {127, 0, 0, 1}, port: 0]とserver: true |secret_key_base: :crypto.strong_rand_bytes(64)を追加|
|prod.exs|　　　　　|host: "example.com"行削除。Desktop化とは関係なくProd環境設定として必要な変更|
|runtime.exs|         |ファイル削除。Androidで、App起動時に環境変数を読まない設定にするため必要|
|repo.ex|             |adapterをSQLite3、DB初期化のSQLを記載|
|build.sh|　　　　　　　|ビルドしてapp.zip.xz作成するスクリプト(おまけ)|

application.ex,repo.exについて、補足説明しておきます。

# application.ex

### SQLite3の起動
SQLite3の起動前の準備、起動、起動後のDB初期化の処理を追加します。

まず参考にした、todo appの処理をみてみます。
todo appでは、application.exは存在しておらず、todo_app.exで同様の処理をおこなっています。

```Elixir:todo_app.ex
  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(TodoWeb.Gettext)
    File.mkdir_p!(config_dir())　# SQLite3の初期ファイル用ディレクトリ作成

    Application.put_env(:todo_app, TodoApp.Repo,
      database: Path.join(config_dir(), "/database.sq3")
    ) # SQLite3の初期ファイル作成

    {:ok, sup} = Supervisor.start_link([TodoApp.Repo], name: __MODULE__, strategy: :one_for_one)# ←-Repo起動定
    TodoApp.Repo.initialize()# DB初期化

　　# 以下、そのほかのモジュールの起動

    {:ok, _} = Supervisor.start_child(sup, TodoWeb.Sup)

    {:ok, _} =
      Supervisor.start_child(sup, {
        Desktop.Window,
        [
          app: @app,
          id: TodoWindow,
          title: "TodoApp",
          size: {600, 500},
          icon: "icon.png",
          menubar: TodoApp.MenuBar,
          icon_menu: TodoApp.Menu,
          url: &TodoWeb.Endpoint.url/0
        ]
      })
  end
```

Supervisor.start_link()では、ElixirDesktop.Repoだけ先に起動して、ElixirDesktop.Repo.initialize()でDBを初期化し、その後、Supervisor.start_child()で、他のモジュールを起動しています。

この仕組みを、application.exに移植します。同様の記述をしてみたんですが、なぜか、Supervisor.start_childでエラーが発生しうまくいきませんでした。
DynamicSupervisorのstart_childを使った[例](https://elixirforum.com/t/how-to-add-a-children-to-supervisor-start-link-dynamically/24329/6)を参考に、DynamicSupervisorを使って、同様の処理を行っています。
### etsについて
todo appでは、sessionにetsを使う設定をしています。これは行った好ましいものなのか、必要ないものなのか判断つきませんでした。今回は、phx.newからの最小限の変更ということで含めていません。


```Elixir:application.ex
  def config_dir() do
    Path.join([Desktop.OS.home(), ".config", "elixir_desktop"])
  end

  def setup_sqlite3_env() do
    File.mkdir_p!(config_dir())
    Application.put_env(:elixir_desktop, ElixirDesktop.Repo,
      database: Path.join(config_dir(), "/database.sq3")
    )
  end

  @impl true
  def start(_type, _args) do
    Desktop.identify_default_locale(ElixirDesktopWeb.Gettext)
    opts = [strategy: :one_for_one, name: ElixirDesktop.Supervisor]
    Supervisor.start_link([{DynamicSupervisor, strategy: :one_for_one, name: ElixirDesktop.DynamicSupervisor}], opts)

    setup_sqlite3_env()
    DynamicSupervisor.start_child(ElixirDesktop.DynamicSupervisor, ElixirDesktop.Repo)
    ElixirDesktop.Repo.initialize()

    children = [
      # Start the Telemetry supervisor
      ElixirDesktopWeb.Telemetry,
      # Start the PubSub system
      {Phoenix.PubSub, name: ElixirDesktop.PubSub},
      # Start the Endpoint (http/https)
      ElixirDesktopWeb.Endpoint,
      # Start a worker by calling: ElixirDesktop.Worker.start_link(arg)
      # {ElixirDesktop.Worker, arg}
      {
        # After your other children
        # Starting Desktop.Windows
        Desktop.Window,
        [
          app: :elixir_desktop,
          id: ElixirDesktopWindow,
          url: &ElixirDesktopWeb.Endpoint.url/0
        ]
      }
    ]
    children
     |> Enum.map(&DynamicSupervisor.start_child(ElixirDesktop.DynamicSupervisor, &1))
     |> Enum.at(-1)
  end
```

### 参考 Phx.new直後のapplication.ex
```Elixir:application.ex
def start(_type, _args) do
    children = [
      # Start the Ecto repository
      ElixirDesktop.Repo,
      # Start the Telemetry supervisor
      ElixirDesktopWeb.Telemetry,
      # Start the PubSub system
      {Phoenix.PubSub, name: ElixirDesktop.PubSub},
      # Start the Endpoint (http/https)
      ElixirDesktopWeb.Endpoint
      # Start a worker by calling: ElixirDesktop.Worker.start_link(arg)
      # {ElixirDesktop.Worker, arg}
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: ElixirDesktop.Supervisor]
    Supervisor.start_link(children, opts)
  end
```

# repo.ex

アプリを起動した時、SQLiteはまっさらな状態で、mix ecto.createにあたる処理を行う必要があります。
CREATE TABLEのSQL文を実行するようにしています。
この例では、todo appと同じものを記載しています。実際のアプリに合わせて変更する必要があります。

- Desktop化しない状態で、mix ecto.createを実行
- `sqlite3 database.db .dump`を実行してDBの内容をSQLで表示。Create TABLEの行を、repo.exに転記する。

このような方法で作成できます。
 
```Elixir:repo.ex
defmodule ElixirDesktop.Repo do
  use Ecto.Repo, otp_app: :elixir_desktop, adapter: Ecto.Adapters.SQLite3

  def initialize() do
    Ecto.Adapters.SQL.query!(__MODULE__, """
        CREATE TABLE IF NOT EXISTS todos (
          id INTEGER PRIMARY KEY,
          text TEXT,
          status TEXT
        )
    """)
  end
end
```

# build.sh

Desktop化の変更点とは関係ありませんが、おまけです。

appのビルドは、run_mixスクリプトを切り出してきて以下のbuild.shで行ってます。

```
set -e
. ~/projects/24.beta/activate
export MIX_ENV=prod
export MIX_TARGET=android
mix assets.deploy
mix release --overwrite
cd _build/android_prod/rel/elixir_desktop/
zip -0r ../../../../app.zip lib/ releases/ --exclude "*.so"
xz -9ef ../../../../app.zip
```

# 変更点のdiff

変更点の全体のdiffです


```diff
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000..d3acc05
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,9 @@
+set -e
+. ~/projects/24.beta/activate
+export MIX_ENV=prod
:...skipping...
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000..d3acc05
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,9 @@
+set -e
+. ~/projects/24.beta/activate
+export MIX_ENV=prod
+export MIX_TARGET=android
+mix assets.deploy
+mix release --overwrite
+cd _build/android_prod/rel/elixir_desktop/
+zip -0r ../../../../app.zip lib/ releases/ --exclude "*.so"
+xz -9ef ../../../../app.zip
\ No newline at end of file
diff --git a/config/config.exs b/config/config.exs
index 69fc678..b7faf31 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -12,7 +12,9 @@ config :elixir_desktop,
 
 # Configures the endpoint
 config :elixir_desktop, ElixirDesktopWeb.Endpoint,
-  url: [host: "localhost"],
+  http: [ip: {127, 0, 0, 1}, port: 0],
+  secret_key_base: :crypto.strong_rand_bytes(64),
+  server: true,
   render_errors: [view: ElixirDesktopWeb.ErrorView, accepts: ~w(html json), layout: false],
   pubsub_server: ElixirDesktop.PubSub,
   live_view: [signing_salt: "WznVGqh4"]
diff --git a/config/prod.exs b/config/prod.exs
index 33f2ea5..6216540 100644
--- a/config/prod.exs
+++ b/config/prod.exs
@@ -10,7 +10,6 @@ import Config
 # which you should run after static files are built and
 # before starting your production server.
 config :elixir_desktop, ElixirDesktopWeb.Endpoint,
-  url: [host: "example.com", port: 80],
   cache_static_manifest: "priv/static/cache_manifest.json"
 
 # Do not print debug messages in production
diff --git a/config/runtime.exs b/config/runtime.exs
deleted file mode 100644
index 14c36d5..0000000
--- a/config/runtime.exs
+++ /dev/null
@@ -1,73 +0,0 @@
-import Config
-
-# config/runtime.exs is executed for all environments, including
-# during releases. It is executed after compilation and before the
-# system starts, so it is typically used to load production configuration
-# and secrets from environment variables or elsewhere. Do not define
-# any compile-time configuration in here, as it won't be applied.
-# The block below contains prod specific runtime configuration.
-if config_env() == :prod do
-  database_url =
-    System.get_env("DATABASE_URL") ||
-      raise """
-      environment variable DATABASE_URL is missing.
-      For example: ecto://USER:PASS@HOST/DATABASE
-      """
-
-  config :elixir_desktop, ElixirDesktop.Repo,
-    # ssl: true,
-    # socket_options: [:inet6],
-    url: database_url,
-    pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
-
-  # The secret key base is used to sign/encrypt cookies and other secrets.
-  # A default value is used in config/dev.exs and config/test.exs but you
-  # want to use a different value for prod and you most likely don't want
-  # to check this value into version control, so we use an environment
-  # variable instead.
-  secret_key_base =
-    System.get_env("SECRET_KEY_BASE") ||
-      raise """
-      environment variable SECRET_KEY_BASE is missing.
-      You can generate one by calling: mix phx.gen.secret
-      """
-
-  config :elixir_desktop, ElixirDesktopWeb.Endpoint,
-    http: [
-      # Enable IPv6 and bind on all interfaces.
-      # Set it to  {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.
-      # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html
-      # for details about using IPv6 vs IPv4 and loopback vs public addresses.
-      ip: {0, 0, 0, 0, 0, 0, 0, 0},
-      port: String.to_integer(System.get_env("PORT") || "4000")
-    ],
-    secret_key_base: secret_key_base
-
-  # ## Using releases
-  #
-  # If you are doing OTP releases, you need to instruct Phoenix
-  # to start each relevant endpoint:
-  #
-  #     config :elixir_desktop, ElixirDesktopWeb.Endpoint, server: true
-  #
-  # Then you can assemble a release by calling `mix release`.
-  # See `mix help release` for more information.
-
-  # ## Configuring the mailer
-  #
-  # In production you need to configure the mailer to use a different adapter.
-  # Also, you may need to configure the Swoosh API client of your choice if you
-  # are not using SMTP. Here is an example of the configuration:
-  #
-  #     config :elixir_desktop, ElixirDesktop.Mailer,
-  #       adapter: Swoosh.Adapters.Mailgun,
-  #       api_key: System.get_env("MAILGUN_API_KEY"),
-  #       domain: System.get_env("MAILGUN_DOMAIN")
-  #
-  # For this example you need include a HTTP client required by Swoosh API client.
-  # Swoosh supports Hackney and Finch out of the box:
-  #
-  #     config :swoosh, :api_client, Swoosh.ApiClient.Hackney
-  #
-  # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details.
-end
diff --git a/lib/elixir_desktop/application.ex b/lib/elixir_desktop/application.ex
index 00e8209..dc5bf5a 100644
--- a/lib/elixir_desktop/application.ex
+++ b/lib/elixir_desktop/application.ex
@@ -5,25 +5,50 @@ defmodule ElixirDesktop.Application do
 
   use Application
 
+  def config_dir() do
+    Path.join([Desktop.OS.home(), ".config", "elixir_desktop"])
+  end
+
+  def setup_sqlite3_env() do
+    File.mkdir_p!(config_dir())
+    Application.put_env(:elixir_desktop, ElixirDesktop.Repo,
+      database: Path.join(config_dir(), "/database.sq3")
+    )
+  end
+
   @impl true
   def start(_type, _args) do
+    Desktop.identify_default_locale(ElixirDesktopWeb.Gettext)
+    opts = [strategy: :one_for_one, name: ElixirDesktop.Supervisor]
+    Supervisor.start_link([{DynamicSupervisor, strategy: :one_for_one, name: ElixirDesktop.DynamicSupervisor}], opts)
+
+    setup_sqlite3_env()
+    DynamicSupervisor.start_child(ElixirDesktop.DynamicSupervisor, ElixirDesktop.Repo)
+    ElixirDesktop.Repo.initialize()
+
     children = [
-      # Start the Ecto repository
-      ElixirDesktop.Repo,
       # Start the Telemetry supervisor
       ElixirDesktopWeb.Telemetry,
       # Start the PubSub system
       {Phoenix.PubSub, name: ElixirDesktop.PubSub},
       # Start the Endpoint (http/https)
-      ElixirDesktopWeb.Endpoint
+      ElixirDesktopWeb.Endpoint,
       # Start a worker by calling: ElixirDesktop.Worker.start_link(arg)
       # {ElixirDesktop.Worker, arg}
+      {
+        # After your other children
+        # Starting Desktop.Windows
+        Desktop.Window,
+        [
+          app: :elixir_desktop,
+          id: ElixirDesktopWindow,
+          url: &ElixirDesktopWeb.Endpoint.url/0
+        ]
+      }
     ]
-
-    # See https://hexdocs.pm/elixir/Supervisor.html
-    # for other strategies and supported options
-    opts = [strategy: :one_for_one, name: ElixirDesktop.Supervisor]
-    Supervisor.start_link(children, opts)
+    children
+     |> Enum.map(&DynamicSupervisor.start_child(ElixirDesktop.DynamicSupervisor, &1))
+     |> Enum.at(-1)
   end
 
   # Tell Phoenix to update the endpoint configuration
diff --git a/lib/elixir_desktop/repo.ex b/lib/elixir_desktop/repo.ex
index 4134b77..cb80bc6 100644
--- a/lib/elixir_desktop/repo.ex
+++ b/lib/elixir_desktop/repo.ex
@@ -1,5 +1,13 @@
 defmodule ElixirDesktop.Repo do
-  use Ecto.Repo,
-    otp_app: :elixir_desktop,
-    adapter: Ecto.Adapters.Postgres
+  use Ecto.Repo, otp_app: :elixir_desktop, adapter: Ecto.Adapters.SQLite3
+
+  def initialize() do
+    Ecto.Adapters.SQL.query!(__MODULE__, """
+        CREATE TABLE IF NOT EXISTS todos (
+          id INTEGER PRIMARY KEY,
+          text TEXT,
+          status TEXT
+        )
+    """)
+  end
 end
diff --git a/lib/elixir_desktop_web/endpoint.ex b/lib/elixir_desktop_web/endpoint.ex
index b1d4315..df9cbdc 100644
--- a/lib/elixir_desktop_web/endpoint.ex
+++ b/lib/elixir_desktop_web/endpoint.ex
@@ -1,5 +1,5 @@
 defmodule ElixirDesktopWeb.Endpoint do
-  use Phoenix.Endpoint, otp_app: :elixir_desktop
+  use Desktop.Endpoint, otp_app: :elixir_desktop
 
   # The session will be stored in the cookie and signed,
   # this means its contents can be read but not tampered with.
diff --git a/mix.exs b/mix.exs
index 2ebfa13..5b7f1be 100644
--- a/mix.exs
+++ b/mix.exs
@@ -33,10 +33,9 @@ defmodule ElixirDesktop.MixProject do
   # Type `mix help deps` for examples and options.
   defp deps do
     [
+      {:ecto_sqlite3, "~> 0.7"},
       {:phoenix, "~> 1.6.2"},
       {:phoenix_ecto, "~> 4.4"},
-      {:ecto_sql, "~> 3.6"},
-      {:postgrex, ">= 0.0.0"},
       {:phoenix_html, "~> 3.0"},
       {:phoenix_live_reload, "~> 1.2", only: :dev},
       {:phoenix_live_view, "~> 0.16.0"},
@@ -48,7 +47,9 @@ defmodule ElixirDesktop.MixProject do
       {:telemetry_poller, "~> 1.0"},
       {:gettext, "~> 0.18"},
       {:jason, "~> 1.2"},
-      {:plug_cowboy, "~> 2.5"}
+      {:plug_cowboy, "~> 2.5"},
+      {:desktop, "~> 1.4"},
+      {:wx, "~> 1.0.10", hex: :bridge, targets: [:android, :ios]}
     ]
   end
 

```

参考サイト
https://qiita.com/the_haigo/items/0fd907d24fdde1905aa2
https://github.com/elixir-desktop/desktop/blob/main/guides/your_first_app.md
https://elixirforum.com/t/how-to-add-a-children-to-supervisor-start-link-dynamically/24329/6