Compose for Web でアドレスバーのURLとCompose Navigationのrouteを同期させてみる試み
最近の日課が KotlinJSとお戯れすることになっているてべすてんです。
これ is なに?
Compose for Web を使うことで Webの知識なしに Webアプリを作ることができます。
しかし そのままでは ブラウザのアドレスバーは考慮されず、画面遷移してもURLが変わることはありません。これでは リロードすると最初の画面に戻ってしまったり、特定の画面のURLをコピーするといったことができません。
そこでCompose for Web で SPA のような動きをするアプリができないか?といったチャレンジの記録になります。
今回はブラウザに標準で用意されている History API を使って ブラウザのURLを変更することで、ブラウザのアドレスバーのURLとアプリの画面を同期させてみました。
サンプル
ポイント:アドレスバーが画面遷移に応じて同期されている
(JSでビルドしてあるため、パフォーマンスは若干劣りますが、Safariでも動くはずです!)
使用技術
- Kotlin: 2.0.21
- Compose Multiplatform
- Navigation Compose: 2.8.0-alpha10
- History API
- Vercel
仕組み
Navigation Compose の画面遷移 と アドレスバー との同期には 以下の4つを考慮する必要があります。
- Navigation Composeの画面遷移に応じてアドレスバーのURLを書き換える。( Navigation Comopose -> アドレスバー )
- アドレスバーの変化に合わせてNavigation Composeを画面遷移させる。( アドレスバー -> Navigation Compose )
- どのページに来ても index.html に遷移させる
- 最初のURLに合わせて
startDestination
を変更する。
サンプルのリポジトリでは NavController.bindBrowserUrl()
と言う拡張関数を中心に
1. Navigation Comopose -> アドレスバー の同期
bindBrowserUrl 関数内では NavController の BackStackEntry を監視し、 変更があった際に window.history.pushState()
と言う関数を呼び出しています。
window.history.pushState()
はサーバにリクエストを送ることなく アドレスバーのURLのみを変更できる History API の関数です。
このようにすることで Navigation Compose の画面遷移に合わせてアドレスバーのURLを書き換える Navigation Comopose -> アドレスバー の同期ができました。
2. アドレスバー -> Navigation Compose の同期
続いてアドレスバーのURLが変更された時に Navigation Composeに反映する方法です。
これには History API の popstate イベントを使用します。
addEventListener("popstate", { TODO() })
のように書くことで アドレスバーのURLの変更を検知できます。
あとはこれに応じてNavControllerで遷移させる処理を書けば アドレスバー -> Navigation Compose の同期も完成です。
index.html
に遷移させる
3. どのページに来ても 今回のようなSPAでは 1つのHTMLファイルで全てのページを処理します。なので例え存在しないパスに来たとしても 必ず index.html
を返すようにする必要があります。
これにはHTTPサーバの設定が必要です。
今回のサンプルは元々 ホスティングサービスである Vercel にデプロイすることを念頭に置いていたため、Vercelの rewrite 機能を使ってみることにします。
プロジェクトルートに vercel.json を配置することで簡単に設定することができます。 どのページに来てもとは言いましたが JSファイルまでindex.htmlに飛ばされてしまうとComposeのコードが実行されないため これはrewriteの対象外にする必要がある 点には注意すると以下のようになるでしょう。
-
"buildCommand"
はデプロイする際に実行されるコマンドです。通常はnpm build
などを設定するかと思いますが、 kotlin jsでは./gradlew :composeApp:jsBrowserDistribution
と言うコマンドで本番用のHTMLやJSを出力するためこのように設定しています。 -
"devCommand"
はvercel dev
でテストをする際に実行するコマンドです。Vercelにデプロイした際の挙動をローカル環境でテストすることができます。kotlin jsでは./gradlew :composeApp:jsRun
と言うコマンドで開発を行うためこのように設定しています。-
vercel
コマンドの実行にはVercel CLIの事前インストールが必要です。npmがインストールされている場合npm i -g vercel
で簡単にインストールできます。 - とは言うものの、rewriteの設定は
vercel dev
では反映されないのか 確認できませんでした。rewriteの設定を検証するにはvercel deploy
コマンドで実際にデプロイしなければいけなさそうです。
-
より詳しい情報は Vercelのドキュメント を参照してください。
startDestination
を変更する
4. 最初のURLに合わせて 上記の設定をすることで どんなURLでアクセスされても index.html
をレスポンスすることができるようになります。
あとは URLに合わせて NavHostの startDestination
を書き換えましょう。
完成!
これでCompose for WebでSPAのようなアプリができるようになるはずですー! ヤッタネ!😎
良いCMPライフを!
Discussion