Elixir Desktop化の困った状態
Elixir Desktop化する方法が本家のドキュメントに含まれていました(以下、your_first_app.mdと呼びます)。
このドキュメント通りにすればよいとおもったんですが、これだけでは、うまくいきませんでした。
-
サンプルのtodo app(以下、todo appと呼びます)は、動作はするが、your_first_app.mdとは異なる点が多い(Applicationがtodo_app.exに含まれている等、Phx.newで作成したものとDesktop化に関係のない違いがある。
-
your_first_app.mdは、変更の要が書かれているが、実際には、この変更だけでは、動作しない。SQLite3の起動などは含まれてない。
という困った状況でした。
こんな中、ElixirDesktop Androidでphx.newで作成したプロジェクトを動かすがブレークスルーとなり、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追加 (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で同様の処理をおこなっています。
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を使った例を参考に、DynamicSupervisorを使って、同様の処理を行っています。
etsについて
todo appでは、sessionにetsを使う設定をしています。これは行った好ましいものなのか、必要ないものなのか判断つきませんでした。今回は、phx.newからの最小限の変更ということで含めていません。
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
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に転記する。
このような方法で作成できます。
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 --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