LiveViewNative Flutter超入門~PhoenixサーバーからFlutterのUIをレンダリング~

# はじめに
本記事はPhoenix LiveViewからHTMLではなく、
SwiftUIのレンダリングするLiveViewNativeFluterの解説と
実際にPhoenixプロジェクトを作成してiOSアプリでHello Worldを表示するところまでを解説します

# LiveViewNativeについて
LiveViewNativeについては以下の記事に記載してあるので省略します

https://qiita.com/the_haigo/items/8ddbbda9d6be69b2609e#liveviewnative%E3%81%A8%E3%81%AF

https://qiita.com/the_haigo/items/1936319bad327d14ad7b

# LiveViewNativeFlutterとは？

LiveViewNative SwiftUI,Jetpackとは違いDockyardではなくAlexさんという方が個人で開発しているLiveViewからFlutterのフロント側の処理を記述するライブラリになります

https://live-view-flutter-pad.alex-min.fr/

https://github.com/alex-min

https://github.com/alex-min/live_view_native_flutter_client

https://github.com/alex-min/live_view_native_flutter

![スクリーンショット 2025-02-05 13.06.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/1d8c10fd-8284-c937-b7f0-82f81d3cf433.png)

# 環境構築

Elixir,Phoenixの他にFlutterSDKのインストールが必要となります

大体以下のサイトで問題なくできるかと思います


Mac
本家

https://docs.flutter.dev/get-started/install/macos/mobile-ios

Zenn Book日本語

https://zenn.dev/heyhey1028/books/flutter-basics/viewer/getting_started_mac

Windows Android
本家

https://docs.flutter.dev/get-started/install/windows/mobile

Zenn Book

https://zenn.dev/heyhey1028/books/flutter-basics/viewer/getting_started_windows

Linux Android

https://docs.flutter.dev/get-started/install/linux/android

# デモを動かす

以下に沿って行っていく

https://github.com/alex-min/live_view_native_flutter_client?tab=readme-ov-file#getting-started

## Phoenix Serverの構築

```
git clone git@github.com:alex-min/live_view_flutter_demo.git
```

`mix setup`を実行すると`liveview_native_flutter`のパスが間違ってるとなるのでgithubを参照するようにします

リポジトリを指定しても最新版が来なかったので最新のコミットハッシュで指定します

```diff_elixir:mix.exs
  defp deps do
    [
      {:phoenix, "~> 1.7.18"},
      ...
      {:live_view_native, "~> 0.4.0-rc.0"},
      {:live_view_native_stylesheet, "~> 0.3.2"},
      {:live_view_native_swiftui, "~> 0.4.0-rc.0"},
      {:live_view_native_live_form, "~> 0.4.0-rc.0"},
-     {:live_view_native_flutter, path: "../plugin"}
+     {:live_view_native_flutter,
+      github: "alex-min/live_view_native_flutter",
+      ref: "2328cb7566da5e35e01333e86c615f770ce3ddcc"}
    ]
  end
```

再度`mix setup`を実行するとliveview周りでエラーになるのでliveviewのバージョンを`1.0.1`で固定します

```
** (CaseClauseError) no case clause matching: :text
    (phoenix_live_view 1.0.4) lib/phoenix_live_view/tokenizer.ex:117: Phoenix.LiveView.Tokenizer.tokenize/5
    lib/live_view_native/tag_engine.ex:33: LiveViewNative.TagEngine.handle_text/3
    (eex 1.17.2) lib/eex/compiler.ex:317: EEx.Compiler.generate_buffer/4
    expanding macro: LiveViewNative.Component.sigil_LVN/2
    lib/live_view_native/component.ex:315: LiveViewNative.Component.csrf_token/1
could not compile dependency :live_view_native, "mix compile" failed. Errors may have been logged above. You can recompile this dependency with "mix deps.compile live_view_native --force", update it with "mix deps.update live_view_native" or clean it with "mix deps.clean live_view_native"
```

`mix setup`が完了したなら以下のコマンドでサーバーを起動します

```
mix phx.server
```

## Flutterアプリの起動

以下のコマンドでサンプルを取得します

```
git clone git@github.com:alex-min/live_view_native_flutter_client.git
```

完了したら`example`まで移動して

```
cd live_view_native_flutter_client/example
```

以下のコマンドでアプリが立ち上がります

```
flutter run
```

レイアウトが壊れているので横に倒します

![スクリーンショット 2025-02-04 19.13.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/ff37cd71-93d7-0b79-f919-2158799e26f4.png)

無事デモが動きました

# 自分で作ってみる

# Phoenix Server


## プロジェクト作成

```
mix phx.new my_flutter --no-ecto
```

## ライブラリの追加

必要なライブラリとバージョンの固定を行います

```diff_elixir:mix.exs
  defp deps do
    [
      {:phoenix, "~> 1.7.18"},
      {:phoenix_html, "~> 4.1"},
      {:phoenix_live_reload, "~> 1.2", only: :dev},
-     {:phoenix_live_view, "~> 1.0.0"},
+     {:phoenix_live_view, "1.0.1"},
      {:floki, ">= 0.30.0", only: :test},
      {:phoenix_live_dashboard, "~> 0.8.3"},
      {:esbuild, "~> 0.8", runtime: Mix.env() == :dev},
      {:tailwind, "~> 0.2", runtime: Mix.env() == :dev},
      {:heroicons,
       github: "tailwindlabs/heroicons",
       tag: "v2.1.1",
       sparse: "optimized",
       app: false,
       compile: false,
       depth: 1},
      {:swoosh, "~> 1.5"},
      {:finch, "~> 0.13"},
      {:telemetry_metrics, "~> 1.0"},
      {:telemetry_poller, "~> 1.0"},
      {:gettext, "~> 0.26"},
      {:jason, "~> 1.2"},
      {:dns_cluster, "~> 0.1.1"},
-     {:bandit, "~> 1.5"}
+     {:bandit, "~> 1.5"},
+     {:live_view_native, "~> 0.4.0-rc.0"},
+     {:live_view_native_stylesheet, "~> 0.3.2"},
+     {:live_view_native_swiftui, "~> 0.4.0-rc.0"},
+     {:live_view_native_live_form, "~> 0.4.0-rc.0"},
+     {:live_view_native_flutter,
+      github: "alex-min/live_view_native_flutter",
+      ref: "2328cb7566da5e35e01333e86c615f770ce3ddcc"}
    ]
  end
```

```
mix deps.get
```

## セットアップ

```
mix lvn.setup.config
```

```
mix lvn.setup.gen
```

```
rm -rf native
```

## LiveViewページの作成
実際に表示するliveviewのページを作っていきます
通常のLiveViewとLiveViewNativeFlutterを切り替える起点のhome_live.exを作成します

ついでに render関数を追加して通常のブラウザでも表示できるようにします

```lib/my_flutter_web/live/home_live.ex
defmodule MyFlutterWeb.HomeLive do
  use MyFlutterWeb, :live_view
  use MyFlutterNative, :live_view

  def render(assigns) do
    ~H"""
    <h1>Hellow Web</h1>
    """
  end
end
```

## LiveViewNativeページの作成

flutterでのアクセスの場合はこちらが表示されます

実装済みコンポーネントの一覧はこちら
Expandedが本家のContainerのオプション的な扱いから独立コンポーネントとして使うみたいなのでちょっとハマった

https://github.com/alex-min/live_view_native_flutter_client/tree/master/lib/live_view/ui/components

```lib/my_flutter_web/live/home_live.flutter.ex
defmodule MyFlutterWeb.HomeLive.Flutter do
  use MyFlutterNative, [:render_component, format: :flutter]

  def render(assigns) do
    ~LVN"""
    <Expanded>
      <Center>
        <Text>Hello Flutter</Text>
      </Center>
    </Expanded>
    """
  end
end
```

## routerへの追加

routerへは、home_liveだけを表示するだけでアーキテクチャごとに表示するliveviewを切り替えてくれます

```lib/my_flutter_web/router.ex
  scope "/", MyFlutterWeb do
    pipe_through :browser

-   get "/", PageController, :home
+   live "/", HomeLive
  end
```

# Flutter

Flutterのでもアプリはライブラリ読み込んでlocalhostを表示する処理のみなのでデモと同じく以下のコマンドで実行します

```
flutter run
```

![スクリーンショット 2025-02-05 14.51.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/311becea-e5f9-dc40-d44b-90e258004058.png)

## 作ってみる

```
flutter create my_flutter_app
```

ライブラリの追加

```diff_yaml:pubspec.yaml:L30
dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.8
+ liveview_flutter:
+   git:
+     url: git@github.com:alex-min/live_view_native_flutter_client.git
+     ref: master
```

以下のコマンドで取得します

```
flutter pub get
```

`main.dart`を次のように書き換えます

```lib/main.dart
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:liveview_flutter/liveview_flutter.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final LiveView view = LiveView();

  @override
  initState() {
    Future.microtask(boot);
    super.initState();
  }

  void boot() async {
    if (kIsWeb) {
      view.connectToDocs();
      return;
    }

    await view.connect(
      Platform.isAndroid
          ?
          // android emulator
          'http://10.0.2.2:4000'
          // computer
          : 'http://localhost:4000/',
    );
  }

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return view.rootView;
  }
}
```

保存したら以下を実行します

```
flutter run
```

# Androidで起動

iOS Simulatorを起動していると自動でそちらが起動しますが
Android Emulatorを同時に起動しているとどちらで起動しますかと聞かれるので

該当する番号を入力しましょう

```
sdk gphone64 arm64 (mobile)     • emulator-5554                        • android-arm64  • Android 15 (API 35) (emulator)
iPhone 16 Pro (mobile)          • 1C65BA1D-E1D5-4B98-8D57-0BEFFBF0C2C3 • ios            • com.apple.CoreSimulator.SimRuntime.iOS-18-2 (simulator)
```

起動時にエラーになった場合はgradleのバージョン違いであることが多いので以下のように変更することで起動できます

```
FAILURE: Build failed with an exception.

* Where:
Build file '/Users/shou/phoenix/elixir_mobile/my_flutter_app/android/app/build.gradle' line: 2

* What went wrong:
An exception occurred applying plugin request [id: 'com.android.application']
> Failed to apply plugin 'com.android.internal.version-check'.
   > Minimum supported Gradle version is 8.6. Current version is 8.4. If using the gradle wrapper, try editing the distributionUrl in /Users/shou/phoenix/elixir_mobile/my_flutter_app/android/gradle/wrapper/gradle-wrapper.properties to gradle-8.6-all.zip
```



```diff_yaml:android/gradle/wrapper/gradle-wrapper.properties
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
- distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
+ distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
```

```diff_gradle:android/settings.gradle
plugins {
    id "dev.flutter.flutter-plugin-loader" version "1.0.0"
-   id "com.android.application" version "8.1.0" apply false    
+   id "com.android.application" version "8.3.0" apply false
    id "org.jetbrains.kotlin.android" version "1.8.22" apply false
}
```

再度 flutter runを実行して無事起動できました

![スクリーンショット 2025-02-05 15.29.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/64198/f780776d-6d83-3afa-3230-a6ada41f228c.png)

# 完成度

API等のカバー具合ですが基本的なコンポーネント、ナビゲーション、ホットリロード等は完成していて
phx-hookを使ったliveviewのようなJS(この場合はDart)Hook、アニメーション、ドキュメント、セッション周りはまだのようです


https://github.com/alex-min/live_view_native_flutter_client/tree/master?tab=readme-ov-file#what-is-there-already

- Some basic components are partially supported (Container, TextButton, Icon, AppBar, BottomNavigationBar ...)
- Modern Flutter Navigation with router 2.0 (switch pages, transitions go back). Although transitions aren't customizable yet.
- Basic styling (padding, margin and background)
- Basic forms (validation & submit)
- Dynamic attributes & replacement
- Conditional component rendering
- Material Icons
- Server-side themes with JSON, also you can switch & save theme on the server side
- Live reloading
- Responsive navigation
- Basic Images support
- Live Components
- Confirm modals
- Plugins to add your own custom components


https://github.com/alex-min/live_view_native_flutter_client/tree/master?tab=readme-ov-file#what-is-missing

- Documentation
- A full API support of all the components
- Hooks similar as web hooks
- Animations
- Local storage
- Better Image support & Video
- Responsive navigation & desktop support (like windows title)
- Sessions & Session storage events

# 最後に

今回はLiveViewNativeFlutterのデモを動かし、実際に自分で作ってみるところまでやってみました
気になる方は以下もぜひ触ってみてください

LiveViewnative SwiftUI

https://github.com/liveview-native/liveview-client-swiftui

https://dockyard.com/blog/2024/09/20/create-your-first-liveview-native-app-part-1

LiveViewnative Jetpack

https://github.com/liveview-native/liveview-client-jetpack

https://www.youtube.com/watch?v=Pddi3zQP5cM

LiveViewnative Iced

https://github.com/liveview-native/liveview-client-iced

本記事は以上になりますありがとうございました