LiveView Native Documentation

repository·main·Indexed 21 days ago

https://github.com/liveview-native/live_view_native

A platform for building native applications using Elixir and Phoenix LiveView. It allows developers to share business logic and state management between web and native clients by transforming LiveView templates into native UI components using the ~LVN sigil (NEEx). It supports multiple UI frameworks including SwiftUI for Apple devices and Jetpack Compose for Android.

Tokens
8.8K
Snippets
27
Records
41
Agent score
76%

What's inside LiveView Native

  1. What is LiveView Native?

    main
    LiveView Native is a platform that allows you to build native applications using Elixir and Phoenix LiveView. It enables a single LiveView to serve both web and non-web clients by transforming platform-specific template code into native UIs. This allows developers to share business logic and state management between web browsers and native mobile/desktop applications.
  2. How LiveView Native works

    main

    LiveView Native extends Phoenix LiveView to serve platform-specific server-rendered markup to native clients. It operates by delegating requests from a standard LiveView to specialized markup processors based on query parameters.

    The Request Flow

    1. Querying: The client sends a request with specific query parameters to denote the platform and device type.
    2. Delegation: LiveView Native matches the request to the appropriate LiveView route and delegates rendering to a platform-specific module.
    3. Processing: The platform-specific module uses the ~LVN sigil to return native UI representations (NEEx templates) instead of HTML.
    4. Client Execution: A native client (like SwiftUI or Jetpack Compose) receives the data and renders the native UI components.
  3. NEEx vs HEEx templates

    main

    LiveView Native uses NEEx (Native+EEx) via the ~LVN sigil. While similar to HEEx, there are critical differences for native rendering:

    • Tag Casing: Tag name casing is preserved. For example, <Text> will not be converted to <text> as it would be in HTML.
    • Explicit Booleans: Boolean attributes are not permitted in their shorthand form (e.g., <Text on>). Because native clients handle truthy/falsy values differently, you must be explicit: <Text on={true}>.
    • Special Attributes: NEEx supports all standard HEEx special attributes plus LiveView Native specific ones defined in LiveViewNative.Component.sigil_LVN/2.
  4. Install LiveView Native in a Phoenix Application

    main

    To use LiveView Native, you must have an existing Phoenix application. Add live_view_native to your mix.exs dependencies. Depending on your target platform, you may also want to include specialized libraries for stylesheets, SwiftUI, or live forms.

    After updating your dependencies, run the setup command to complete the installation process.

    # In mix.exs
    {:live_view_native, "~> 0.4.0-rc.1"},
    {:live_view_native_stylesheet, "~> 0.4.0-rc.1"},
    {:live_view_native_swiftui, "~> 0.4.0-rc.1"},
    {:live_view_native_live_form, "~> 0.4.0-rc.1"}
    $ mix lvn.setup
  5. Implement platform-specific rendering with LiveView Native

    main

    To support multiple platforms, you should separate your logic into a base LiveView for event handling and specialized modules for rendering.

    1. Base LiveView: Use MyAppNative, :live_view to handle mount and events. This module acts as the entry point for all platforms.
    2. Platform Modules: Create modules for specific formats (e.g., :swift_ui or :jetpack). These modules use a two-arity render/2 function to match on client information and metadata.
    3. NEEx Templates: Use the ~LVN sigil to define templates. These are similar to HEEx but optimized for native UI.
    # lib/my_app_web/live/hello_live.ex
    defmodule MyAppWeb.HelloLive do
      use MyAppWeb, :live_view
      use MyAppNative, :live_view
    
      def mount(_params, _session, socket) do
        {:ok, socket}
      end
    end
    
    # lib/my_app_web/live/hello_live_swiftui.ex
    defmodule MyAppWeb.HelloLive.SwiftUI do
      use MyAppNative, :live_view
    
      def render(assigns, %{"target" => "watchos"}) do
        ~LVN"""
        <VStack>
          <Text>Hello WatchOS!</Text>
        </VStack>
        """
      end
    
      def render(assigns, _interface) do
        ~LVN"""
        <VStack>
          <Text>Hello SwiftUI!</Text>
        </VStack>
        """
      end
    end
  6. How to define a LiveView for Native Clients

    main

    To make a LiveView compatible with native clients, use MyAppNative, :live_view. To provide platform-specific rendering (e.g., for SwiftUI or specific watchOS targets), you can use MyAppNative, [:render_component, format: :swiftui] and implement a render/2 function that pattern matches on the interface or target.

    LiveView Native uses the ~LVN sigil to define native UI templates.

    # Standard LiveView with Native support
    defmodule MyAppWeb.HelloLive do
      use MyAppWeb, :live_view
      use MyAppNative, :live_view
    end
    
    # Platform-specific rendering for SwiftUI
    defmodule MyAppWeb.HelloLive.SwiftUI do
      use MyAppNative, [:render_component, format: :swiftui]
    
      def render(assigns, %{"target" => "watchos"}) do
        ~LVN"""
        <VStack>
          <Text>
            Hello WatchOS!
          </Text>
        </VStack>
        """
      end
    
      def render(assigns, _interface) do
        ~LVN"""
        <VStack>
          <Text>
            Hello SwiftUI!
          </Text>
        </VStack>
        """
      end
    end
  7. Use the ~LVN sigil for native templates

    main

    The ~LVN sigil is used to write composable markup directly inside Elixir source files. It is similar to Phoenix's ~H sigil but optimized for LiveView Native clients.

    Key Differences from HTML/XML:

    • Casing: ~LVN does not enforce lowercase tag names. <Text> is a valid and common tag.
    • Special Attributes: Supports standard HEEx attributes plus the :interface- prefix.

    The :interface- attribute

    You can conditionally render elements based on the client interface map using the :interface-<key> syntax. This is a shorthand for checking get_in(assigns, [:_interface, "<key>"]).

    <Text :interface-target="mobile">This is a phone</Text>
    <Text :interface-target="watch">This is a watch</Text>
    <Text :interface-target="mobile">This is a phone</Text>
  8. How LiveView Native Components work

    main

    LiveView Native Components differ from standard Phoenix Components in their function arity. While Phoenix components typically use a single-argument function (component/1), LiveView Native components use a two-argument function: component(assigns, interface).

    • assigns: The standard map of data for the component.
    • interface: A map containing metadata about the connecting native client. This allows you to tailor UI based on the device or client version.

    The Interface Map

    Each native client can send an interface map containing:

    • "target": The specific device target (e.g., ios, ipados, watchos, macos, tvos).
    • "version": The application version set in the native client build.
    • "client_version": The version of the client library connecting to the server.
    def render(assigns, %{"target" => "watchos"}) do
      ~LVN"""
      <Text>Hello, from WatchOS!</Text>
      """
    end
  9. Handle multiple targets for the same template

    main

    LiveView Native supports multiple targets for a single template name using a + suffix in the filename. When using embed_templates/2, the renderer generates specialized functions for each target and a fallback function for the general case. The fallback is always sorted to appear last, allowing for greedy matching.

    Example File Pattern:

    • home_live.swiftui+watchos.neex
    • home_live.swiftui+tvos.neex
    • home_live.swiftui.neex (Fallback)

    Generated Function Signatures:

    def render(assigns, %{"target" => "watchos"} = interface)
    def render(assigns, %{"target" => "tvos"} = interface)
    def render(assigns, interface) # Fallback
  10. Define a LiveView Native Render Component

    main

    To create a format-specific render component (e.g., for SwiftUI), use LiveViewNative.Component with the :format and :as options. The :as option allows you to specify the name of the injected rendering function (defaulting to render).

    defmodule MyAppWeb.HomeLive.SwiftUI do
      use LiveViewNative.Component,
        format: :swiftui,
        as: :render
    end

    If you have a file at swiftui/home_live.swiftui.neex, the use macro will automatically embed it as render/2.

    defmodule MyAppWeb.HomeLive.SwiftUI do
      use LiveViewNative.Component,
        format: :swiftui,
        as: :render
    end
  11. Enable LiveView Native in a Phoenix LiveView

    main

    To delegate rendering requests from a native client to format-specific components, use the LiveViewNative.LiveView macro within your Phoenix LiveView module.

    Key Responsibilities:

    • Event Handling: All event handling logic should remain in the original LiveView module.
    • Rendering: All rendering concerns for a specific format (e.g., SwiftUI, Jetpack) are delegated to the components identified in the :layouts and :formats options.

    When a native client requests a specific format, LiveView Native will use the configured dispatch_to function to find the appropriate module and call its render/1 function.

    defmodule MyAppWeb.HomeLive do
      use MyAppWeb, :live_view
      use LiveViewNative.LiveView,
        formats: [:swiftui, :jetpack],
        layouts: [
          swiftui: {MyAppWeb.Layouts.SwiftUI, :app},
          jetpack: {MyAppWeb.Layouts.Jetpack, :app}
        ],
        dispatch_to: &Module.concat/2
    end
  12. Configure LiveView Native within a Phoenix application

    main

    Use the mix lvn.setup.config command to automatically configure your Phoenix LiveView application to support LiveView Native. This task patches several key Phoenix configuration files to integrate LiveView Native plugins, MIME types, format encoders, template engines, and live reload patterns.

    Important Requirements:

    • This command must be invoked from within your *_web application root directory (not the umbrella root if using an umbrella project).
    • After running this, you should typically run mix lvn.setup.gen to generate the necessary files.
    mix lvn.setup.config