Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Kino LiveView Native

notebooks/example.livemd

Kino LiveView Native

notebook_path = __ENV__.file |> String.split("#") |> hd()

Mix.install(
  [
    {:kino_live_view_native, path: Path.join([Path.dirname(notebook_path), "../"])}
    # {:kino_live_view_native,github: "liveview-native/kino_live_view_native"}
  ],
  config: [
    kino_live_view_native: [
      qr_enabled: true
    ],
    server: [
      {ServerWeb.Endpoint,
       [
         server: true,
         url: [host: "localhost"],
         adapter: Phoenix.Endpoint.Cowboy2Adapter,
         render_errors: [
           formats: [html: ServerWeb.ErrorHTML, json: ServerWeb.ErrorJSON],
           layout: false
         ],
         pubsub_server: Server.PubSub,
         live_view: [signing_salt: "JSgdVVL6"],
         http: [ip: {0, 0, 0, 0}, port: 4000],
         secret_key_base: String.duplicate("a", 64),
         live_reload: [
           patterns: [
             ~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg|styles)$",
             ~r/#{notebook_path}$/
           ]
         ],
         code_reloader: true
       ]}
    ],
    kino: [
      group_leader: Process.group_leader()
    ],
    phoenix: [
      template_engines: [neex: LiveViewNative.Engine]
    ],
    phoenix_template: [format_encoders: [swiftui: Phoenix.HTML.Engine]],
    mime: [
      types: %{"text/swiftui" => ["swiftui"], "text/styles" => ["styles"]}
    ],
    live_view_native: [plugins: [LiveViewNative.SwiftUI]],
    live_view_native_stylesheet: [
      content: [
        swiftui: [
          "lib/**/*swiftui*",
          notebook_path
        ]
      ],
      output: "priv/static/assets",
      attribute_parsers: [
        style: [
          livemd: &Server.AttributeParsers.Style.parse/2
        ]
      ]
    ],
    # Ensures that app.js compiles to avoid the switch to longpolling
    # when a LiveView doesn't exist yet
    esbuild: [
      version: "0.17.11",
      server_web: [
        args:
          ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
        cd: Path.expand("../assets", __DIR__),
        env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
      ]
    ]
  ],
  force: true
)

Setup

To use the Server project we need to install it. You can include the notebook dependencies as seen in the above Notebook dependencies and setup section.

By default, the server starts on port 4000. You can change this configuration if you want the server to run on a different port.

Basic LiveView Native Example

This is the basic "LiveView Native: LiveView" smart cell. It is available at http://localhost:4000. You can also inspect the native template code at http://localhost:4000/?_lvn[format]=swiftui

require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]

defmodule ServerWeb.ExampleLive.SwiftUI do
  use ServerNative, [:render_component, format: :swiftui]

  def render(assigns) do
    ~LVN"""
    Hello, world!
    """
  end
end

defmodule ServerWeb.ExampleLive do
  use ServerWeb, :live_view
  use ServerNative, :live_view

  @impl true
  def render(assigns),
    do: ~H"""
    

Hello, world!

"""
def handle_event("clicked", _params, socket) do IO.inspect("CLICKED") {:noreply, socket} end end |> Server.SmartCells.LiveViewNative.register("/") import Server.Livebook, only: [] import Kernel :ok

Render Components

This is the "LiveView Native: Render Component". If a LiveView is already defined, you can separately define the Render component to reduce code boilerplate.

require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]

defmodule ServerWeb.ExampleLive.SwiftUI do
  use ServerNative, [:render_component, format: :swiftui]

  def render(assigns) do
    ~LVN"""
    Hello, from LiveView Native!
    """
  end
end
|> Server.SmartCells.RenderComponent.register()

import Server.Livebook, only: []
import Kernel
:ok

Live Reloading

Server uses automatic code reloading, so anytime you change this file or evaluate one of the LiveView Native smart cells, the server will hot reload the page.

Evaluate the cell below that changes the text, and you should see the application reload in your simulator.

require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]

defmodule ServerWeb.HomeLive.SwiftUI do
  use LiveViewNative.Component,
    format: :swiftui

  def render(assigns) do
    ~LVN"""
    Hello, Live Reload!
    """
  end
end

defmodule ServerWeb.HomeLive do
  use ServerWeb, :live_view

  use LiveViewNative.LiveView,
    formats: [:swiftui],
    layouts: [
      swiftui: {ServerWeb.Layouts.SwiftUI, :app}
    ]

  @impl true
  def render(assigns) do
    ~H"""
    

Hello Live Reload!

"""
end end |> Server.SmartCells.LiveViewNative.register("/") import Server.Livebook, only: [] import Kernel :ok

Asset Paths

Asset paths are relative to the host URL. We have provided an example image at http://localhost:4000/images/logo.png for you to use in any examples.

require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]

defmodule ServerWeb.HomeLive.SwiftUI do
  use LiveViewNative.Component,
    format: :swiftui

  def render(assigns) do
    ~LVN"""
    
    """
  end
end

defmodule ServerWeb.HomeLive do
  use ServerWeb, :live_view

  use LiveViewNative.LiveView,
    formats: [:swiftui],
    layouts: [
      swiftui: {ServerWeb.Layouts.SwiftUI, :app}
    ]

  @impl true
  def render(assigns) do
    ~H"""
    
    """
  end
end
|> Server.SmartCells.LiveViewNative.register("/")

import Server.Livebook, only: []
import Kernel
:ok

Core Components

Core components are supported.

require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]

defmodule ServerWeb.ExampleLive do
  use ServerWeb, :live_view

  def render(assigns) do
    ~H"""
    <.button>Click
    """
  end
end
|> Server.SmartCells.LiveViewNative.register("/")

import Server.Livebook, only: []
import Kernel
:ok

Utility Styles

Kino LiveView Native supports LiveView Native utility styles. The Mix.install.2 section above configures the stylesheets to watch the current notebook for stylesheet changes.

Below, we define the background(.blue) style.

require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]

defmodule ServerWeb.ExampleLive.SwiftUI do
  use ServerNative, [:render_component, format: :swiftui]

  def render(assigns) do
    ~LVN"""
    Hello blue text!
    """
  end
end

defmodule ServerWeb.ExampleLive do
  use ServerWeb, :live_view
  use ServerNative, :live_view

  @impl true
  def render(assigns), do: ~H""
end
|> Server.SmartCells.LiveViewNative.register("/")

import Server.Livebook, only: []
import Kernel
:ok

We can use the following debug function to test if a style exists.

ServerWeb.Styles.App.SwiftUI.class("background(.blue)")

Custom Colors

Assuming your SwiftUI application has a color asset, you can use custom colors like so:

require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]

defmodule ServerWeb.ExampleLive.SwiftUI do
  use ServerNative, [:render_component, format: :swiftui]

  def render(assigns) do
    ~LVN"""
    Hello
    """
  end
end

defmodule ServerWeb.ExampleLive do
  use ServerWeb, :live_view
  use ServerNative, :live_view

  @impl true
  def render(assigns), do: ~H""
end
|> Server.SmartCells.LiveViewNative.register("/")

import Server.Livebook, only: []
import Kernel
:ok

To use double quotes within a style, you must wrap the value for the style attribute in curly brackets. Otherwise, you will experience parser issues.

Forms

LiveView Native core components supports both web components and native components, thus allowing us to share form logic.

require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]

defmodule ServerWeb.ExampleLive.SwiftUI do
  use ServerNative, [:render_component, format: :swiftui]
  import ServerWeb.CoreComponents.SwiftUI

  def render(assigns) do
    ~LVN"""
    <.simple_form for={@form} id="form" phx-submit="submit">
      <.input field={@form[:email]} type="TextField" placeholder="Email" />
      <:actions>
        <.button type="submit">
          Send password reset instructions
        
      
    
    """
  end
end

defmodule ServerWeb.ExampleLive do
  use ServerWeb, :live_view
  use ServerNative, :live_view

  @impl true
  def render(assigns),
    do: ~H"""
    <.simple_form for={@form} id="reset_password_form" phx-submit="submit">
      <.input field={@form[:email]} placeholder="Email" />
      <:actions>
        <.button type="submit">
          Send password reset instructions
        
      
    
    """

  @impl true
  def mount(_params, _session, socket) do
    {:ok, assign(socket, form: to_form(%{}, as: "user"))}
  end

  @impl true
  def handle_event("submit", params, socket) do
    IO.inspect(params)
    {:noreply, socket}
  end
end
|> Server.SmartCells.LiveViewNative.register("/")

import Server.Livebook, only: []
import Kernel
:ok

Tailwind

While tailwind is included in the project. However, it is not currently functional.

Tailwind does static analysis to find classes used in the project, and therefore does not find classes defined within Livebook files.

require Server.Livebook
import Server.Livebook
import Kernel, except: [defmodule: 2]

defmodule ServerWeb.ExampleLive do
  use ServerWeb, :live_view

  def render(assigns) do
    ~H"""
    

Not Red Text

"""
end end |> Server.SmartCells.LiveViewNative.register("/") import Server.Livebook, only: [] import Kernel :ok