6
0

More than 1 year has passed since last update.

お題は不問!Qiita Engineer Festa 2023で記事投稿!

ElixirDesktopでスマホアプリを作る - LiveViewを使用してCRUDの機能を実装する

Last updated at Posted at 2023-07-18

こんにちは!
プログラミング未経験文系出身、Elixirの国に迷い込んだ?!見習いアルケミストのaliceと申します。
今回はElixirDesktop + Phoenix1.7のスマホアプリにCRUDの機能を実装したいと思います。


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

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

↓27:03~以前が前回の記事、以降が本記事のハンズオン動画です

↓「微調整」の部分以降の動画です。


■「ElixirDesktopでスマホアプリを作る」シリーズの目次
Windows11+WSL2に環境構築
|> ②Phoenix1.7のアプリを起動
|> ③LiveViewを使用してCRUDの機能を実装する
|> ④Android実機に転送する(Google Drive経由)

目的

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

実行環境

Windows 11 + WSL2 + Ubuntu 22.04
Elixir v1.14.3
Erlang v25.0.4
Phoenix v1.7.3

前提条件

本記事は、前回の記事までの実施が完了している前提で進めます。

CRUD作成

LiveViewのスキャフォールドを使用して、CRUDの機能を実装します。

todo_app
mix phx.gen.live Tasks Task tasks name:string status:boolean

上記コマンド実行後、コンソールの指示に従って以下を追加します。

todo_app/lib/todo_app_web/router.ex
   scope "/", TodoAppWeb do
     pipe_through :browser

     get "/", PageController, :home

+    live "/tasks", TaskLive.Index, :index
+    live "/tasks/new", TaskLive.Index, :new
+    live "/tasks/:id/edit", TaskLive.Index, :edit

+    live "/tasks/:id", TaskLive.Show, :show
+    live "/tasks/:id/show/edit", TaskLive.Show, :edit
   end

Migration周り

ElixirDesktopにはマイグレーションしてくれる機能がありません。
よって起動時にマイグレーションを実行する機能の実装が必要です。

migrationsディレクトリのコピーとリネーム

todo_app/priv/repo/migrationsフォルダ全体をtodo_app/lib/todo_app/にコピーします。
xxxx_create_tasks.exsxxxx_create_tasks.exにリネームします。1

※青枠の部分が実施前、赤枠の部分が実施後のmigrations周りのディレクトリです。
image.png

名前衝突を防ぐためにモジュール名をリネームします。

todo_app/lib/todo_app/migrations/xxx_create_tasks.ex
- defmodule TodoApp.Repo.Migrations.CreateTasks do
+ defmodule TodoApp.Migrations.CreateTasks do
  ...
end

起動時にマイグレーションする設定

前回の記事repo.ex内に作成したinitialize関数に、マイグレーションを実行する内容を書いていきます。

Ecto.Migrator.upはマイグレーションを実行する関数で
第1引数はRepoモジュール
第2引数はマイグレーション番号、今回はファイル名のタイムスタンプ
第3引数は先ほどコピーしたマイグレーションファイルのモジュール名
をそれぞれ指定します。

todo_app/lib/todo_app/repo.ex
defmodule TodoApp.Repo do
  ...
   def initialize() do
+    Ecto.Migrator.up(TodoApp.Repo, 20_230_712_123_236, TodoApp.Migrations.CreateTasks)
   end
end

起動時に開くページを指定

前回の記事完了時点では、起動時にPhoenix1.7のトップページ2が開かれています。
起動時の設定を変更し、エンドポイント/tasksが起動時に開かれるようにします。

todo_app/lib/todo_app.ex
defmodule TodoApp do
  ...

  @app Mix.Project.config()[:app]
  def start(:normal, []) do
    ...
    {:ok, _} =
      Supervisor.start_child(sup, {
        Desktop.Window,
        [
          app: @app,
          id: TodoAppWindow,
          title: "TodoApp",
          size: {400, 800},
-         url: "http://localhost:#{port}"
+         url: "http://localhost:#{port}/tasks"
        ]
      })
  end
end

以上がPhonenixプロジェクトの設定です。

androidでの起動

再ビルド

ブランクスクリプトではない方を走らせて、リビルドします。

shell
cd ../android-example-app/app
./run_mix_build

Android StudioでRunする

前回の記事の手順に従って、Android StudioでRunします。

無事CRUD画面が表示されました(^▽^)/

image.png

(補足)"table tasks already exits"という内容のエラーが出て変更内容がandroidに反映されない場合

"table tasks already exits"というエラーが返ってくる場合、DBのリネームが必要です。

todo_app/lib/todo_app.ex
     # DBの場所を指定
     Application.put_env(:todo_app, TodoApp.Repo,
-      database: Path.join(config_dir(), "/database.sq3")
+      database: Path.join(config_dir(), "/database2.sq3")
     )

微調整

元記事様と同様に、微調整を行います。

core_component カスタム

core_components.ex内にあるtableで表のサイズが指定されていますので、これを微調整します。

todo_app/lib/todo_app_web/components/core_components.ex
  def table(assigns) do
    assigns =
      with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do
        assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end)
      end

    ~H"""
    <div class="overflow-y-auto px-4 sm:overflow-visible sm:px-0">
-      <table class="mt-11 w-[40rem] sm:w-full">
+      <table class="mt-11 w-full"> 
    ...
    """
  end

confirm modalを追加

元記事様と同様に、削除リンクをモーダルを開くナビゲーションにしてindex.html.heexの末尾にconfirm modalを表示するようにします。

削除イベントのトリガー(発火)部分

editを参考にイベントの発火部分と、deleteモーダルのレンダリング部分を追加します。

todo_app/lib/todo_app_web/live/task_live/index.html.heex
...
<.table
  id="tasks"
  rows={@streams.tasks}
  row_click={fn {_id, task} -> JS.navigate(~p"/tasks/#{task}") end}
>
  <:col :let={{_id, task}} label="Name"><%= task.name %></:col>
  <:col :let={{_id, task}} label="Status"><%= task.status %></:col>
  <:action :let={{_id, task}}>
    <div class="sr-only">
      <.link navigate={~p"/tasks/#{task}"}>Show</.link>
    </div>
    <.link patch={~p"/tasks/#{task}/edit"}>Edit</.link>
  </:action>
-  <:action :let={{id, task}}>
+  <:action :let={{_id, task}}> #使用しない変数idを_で潰しておく。
    <.link
-      phx-click={JS.push("delete", value: %{id: task.id}) |> hide("##{id}")}
-      data-confirm="Are you sure?"
+      patch={~p"/tasks/#{task}/delete"} #"delete"のリンクを押下した際に部分的にページを更新する。
    >
      Delete
    </.link>
  </:action>
</.table>

...
+ <.modal
+   :if={@live_action in [:delete]} #変数live_actionがdeleteのときに表示されるモーダル。
+   id="task-delete-modal" #idは一意の値。
+   show
+   on_cancel={JS.navigate(~p"/tasks")} #モーダルのキャンセルボタンが押下されたときは~p"/tasks"に遷移する。
+ >
+   <p class="m-8">Are you sure?</p>
+   <.button phx-click={JS.push("delete", value: %{id: @task.id})}>Delete</.button> #ボタンのレンダリング、およびそのボタンが押下された時に、valueとして渡されたtask.idの内容を削除するイベントをトリガーする。
+ </.modal>
  • patch={~p"/tasks/#{task}/delete"}の部分が増えたので、新しいルーティングが必要になります。(「削除イベントを実行させるルーティング」参照)

  • :if={@live_action in [:delete]}の部分が増えたため、index.ex内handle_params関数のパターンマッチにヒットするようなapply_action関数が必要になります(「削除イベントのハンドラ部分」参照)

  • phx-click={JS.push("delete", value: %{id: @task.id}の部分が増えたので、トリガーしたイベントの処理内容を書く関数が必要になります。(「削除イベントのハンドラ部分」参照)

削除イベントのハンドラ部分

こちらもeditを参考にapply_action部分と、トリガーしたイベントの処理内容を追加します。

todo_app/lib/todo_app_web/live/task_live/index.ex
 defmodule TodoAppWeb.TaskLive.Index do
   use TodoAppWeb, :live_view

   alias TodoApp.Tasks
   alias TodoApp.Tasks.Task

  ...

   def handle_params(params, _url, socket) do
      {:noreply, apply_action(socket, socket.assigns.live_action, params)}
   end

  ...
   defp apply_action(socket, :index, _params) do
     socket
     |> assign(:page_title, "Listing Tasks")
     |> assign(:task, nil)
   end

+  defp apply_action(socket, :delete, %{"id" => id}) do
+    socket
+    |> assign(:page_title, "Delete Task")
+    |> assign(:task, Tasks.get_task!(id))
+  end

   @impl true
   def handle_info({TodoAppWeb.TaskLive.FormComponent, {:saved, task}}, socket) do
     {:noreply, stream_insert(socket, :tasks, task)}
   end

   @impl true
   def handle_event("delete", %{"id" => id}, socket) do 
     task = Tasks.get_task!(id)
     {:ok, _} = Tasks.delete_task(task)

+    {
+      :noreply,
+      socket
+      |> put_flash(:info, "Task deleted successfully") # flashを追加
+      |> push_navigate(to: ~p"/tasks") # モーダルを閉じる
+      |> stream_delete(:tasks, task) # 要素が削除されたことをstream上に反映させる
+    }
   end
 end

削除イベントを実行させるルーティング

routeにdelete actionを追加します

todo_app/router.ex
   scope "/", TodoAppWeb do
     pipe_through :browser

     get "/", PageController, :home

     live "/tasks", TaskLive.Index, :index
     live "/tasks/new", TaskLive.Index, :new
     live "/tasks/:id/edit", TaskLive.Index, :edit
+    live "/tasks/:id/delete", TaskLive.Index, :delete # 追加

     live "/tasks/:id", TaskLive.Show, :show
     live "/tasks/:id/show/edit", TaskLive.Show, :edit
   end

androidでの起動(微調整後)

再ビルド

ブランクスクリプトではない方を走らせて、リビルドします

shell
cd ../android-example-app/app
./run_mix_build

Android StudioでRunする

前回の記事の手順に従って、Android StudioでRunします。

DELETEの機能がついて、CRUDの機能が一通り実装されました(^▽^)/
image.png

image.png

~Elixirの国のご案内~

↓Elixirって何ぞや?と思ったらこちらもどぞ。Elixirは先端のアレコレをだいたい全部できちゃいます:laughing::sparkles::sparkles:

↓ゼロからElixirを始めるなら「エリクサーチ」がおすすめ!私もエンジニア未経験から学習中です。

We Are The Alchemists, my friends!:bouquet:3
Elixirコミュニティは本当に優しくて温かい人たちばかり!
私が挫折せずにいられるのもこの恵まれた環境のおかげです。
まずは気軽にコミュニティを訪れてみてください。4

  1. このxxxx_create_tasks.exsおよびxxxx_create_tasks.exをマイグレーションファイルと呼びます。

  2. トップページ = PhoenixのロゴとPeace of mind from prototype to production.と書かれたページ

  3. @torifukukaiouさんのAwesomeな名言をお借りしました。Elixirコミュニティを一言で表すと、これに尽きます。

  4. @kn339264さんの素敵なスライドをお借りしました。Elixirコミュニティはいろんな形で活動中!

6
0
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0