元ブログ:https://www.alexthinks.com/2021/07/making-a-mobile-app-with-clojurescript-in-2021

イントロ

今から4年以上前、私はJUXTで働き始めました。私の最初のプロジェクトでは、clojurescriptを使ってモバイルアプリを作りました。当時、これは画期的な技術的成果だと言われていましたが、正直なところあまり実感がありませんでした。使用していたExpoのバージョンがサポートされなくなり、新しいバージョンのreact nativeでは、使用していたナビゲーションライブラリがサポートされていなかったため、半年後にはアップデートを公開することすらできなくなってしまいました。 しかし、ClojureScriptのコア・メンテナが書いたKrellの記事を読んで、再び興味を持ち、もう一度やってみることにしました。

ツーリングの現状

Clojureを使ってモバイルアプリを作るためには、CLJSをJSにコンパイルしてからReact Nativeを使うという方法にかなり行き詰まっています。React Nativeは、iOSとAndroidの両方の開発を容易にすることを約束しており、JSをiOSまたはAndroidターゲット用のネイティブコードにコンパイルします。React NativeはHTMLを使用しないため、「通常の」ReactとReact Nativeの間でコードを簡単にコピー&ペーストできると考えてはいけません。(おそらくここで展開すべき)

では、現在のツールの選択肢は以下の通りです。

Krell- 非常にシンプルで、外部依存性が少ない。CLJSをReact Nativeに可能な限り「ネイティブ」に導入するために設計されており、付加的な機能はありません。

Re-Natal - figwheelを使ってCLJSをコンパイルし、React Nativeでより「clojureライク」な体験ができるようにいくつかの追加機能を加えています。しかし、Leiningenと古いバージョンのReact Nativeに依存しているため、お勧めできません。

Shadow-Cljs - 多くのCLJS開発者はすでにWebプロジェクトで使用していると思いますが、React Nativeターゲットのコードもビルドできます。生のReact Nativeと一緒に使うことも、Expoと一緒に使うこともできます。

Expo - React Nativeの上にあるレイヤーで、クロスプラットフォームのモバイル開発における不満な部分の多くを解決することを目的としています。Expoを使えば、MacもAndroid Studioも必要ありません。QRコードをスキャンするだけで、実際の携帯電話でアプリをテストすることができます。また、AppleやGoogleの承認を必要とせず、コードの更新を即座に行うことができます。しかし、このような魔法には、複雑さというコストがかかります。この記事では、ShadowとExpoの組み合わせを評価し、どのような問題に遭遇するかを見ていきます。

アプリとは?

このアプリは、1~2日でロジックを構築できるほどシンプルで、かつモチベーションが下がらない程度に面白いものにしたかったんです。最近、瞑想にハマっていて、ちょっとユニークな工夫をしていますが、また別の瞑想アプリを作ろうと思いました。

例えば、「The Wim Hof Method」と呼ばれる瞑想と呼吸法の人気セットがあります。主なエクササイズは以下の通りです。

fig

そこでWim Hofは、各ステップの音声クリップを再生し、何サイクル行ったかなどを記録するアプリを用意しました。

これは、ヨガなど他の多くのアクティビティでも同じです。例えば、「木の上に立つ」といった一連の「活動」を、画像と音声で説明するアプリを作ります。ある程度時間が経つと、次のステップに進みます。

しかし、これらのルーチンはすべて、次のようなシンプルなデータ構造に凝縮できると思います。

(def power-breath
  {:name "Wim Hof Power Breath"
   :description "Active Deep inhale, passive exhale "
   :activities [{:title "Breath in"
                 :duration 2000}
                {:title "Breath out"
                 :duration 4000}]})

(def exhale-hold-breath
  {:name "Hold your breath"
   :description "Exhale completely and retain as long as possible"
   :activities [{:title "Exhale fully"
                 :duration 2000}
                {:title "Hold your breath for as long as possible"
                 :duration nil}]})

(def inhale-hold-breath
  {:name "Hold your breath"
   :description "Inhale deeply"
   :activities [{:title "Inhale deeply"
                 :duration 3000}
                {:title "Hold your breath"
                 :duration 15000}
                {:title "Exhale"
                 :duration 3000}]})

(def wim-hof-round
  {:name "Wim Hof Method"
   :description "30 cycles of power breaths and breath holding"
   :activities [{:title "Power Breaths"
                 :cycle-count 30
                 :activity power-breath}
                exhale-hold-breath
                inhale-hold-breath
                {:title "Meditation"
                 :duration nil}]})

ご覧のように、これは非常に柔軟な構造になっています。期間がある場合は、タイマーで残り時間を表示することができますし、期間がない場合は、ユーザーがボタンをクリックして先に進むことができるようになっています。ステップごとに音声や画像を表示させることも可能です。このアプリに必要なのは

  • 各アクティビティを回転させ、どのステップが「現在のアクティビティ」なのかを記録する。
  • 各「現在のアクティビティ」マップをレンダリングする(期間、タイトル、説明などをレンダリングするコンポーネントを持つ

これらのロジックをすべて実行する基本的なモバイルアプリを1時間以内にセットアップすることができました。録画したライブストリームは以下の通りで、コミットはこちらです。

このビデオの最初に出てくるスタート地点を設定するために。

ここから始めることができます。expo webのホットリロードを無効にしたり、インデントを自分の好みに合わせて整理したり、不要なコードを削除したりと、初期の修正を行いました。また、誰にでもお勧めできるclj-kondoのセットアップを追加しました。詳細はこちらで私の最初のコミットを確認してください。

次のエピソードでは、より良いUIを追加する予定です。Twitchでフォローしていただければ、私がストリーミングしたときにアップデートされます。youtubeの動画やこちらのページにコメントをお寄せください。

Krellの国への寄り道

UIを作る前に、2つの重要な機能を実現するために何が必要かを調べました。HealthKitとの統合(マインドフルネスやフィットネスの分数を保存するため)と、バックグラウンドオーディオ(ユーザーが携帯電話をロックするとアプリが停止するため、音楽アプリのように動作して「再生中」の画面を表示するのが望ましい)です。

しかし、この2つは、Expoから退出しないとできないようでした。これに加えて、shadow+expoの設定でエラーが見えなくなるという、小さいながらも厄介な問題があったので、代わりにKrellで今あるものを作ってみることにしました。

ExpoなしでのReact Native

krellを使い始めるには、まず適切なReact Native環境を構築する必要があります。私はこのガイドに従いました。十分に簡単なプロセスですが、私のようにモバイル開発をしたことがない場合は、iOS/Androidのみにするにしても、ダウンロードするものがたくさんあるので、時間がかかります。両方でテストしたい場合は、セットアップにかかる時間が2倍になりますが、これは1回だけでよく、リアクトネイティブのプロジェクトを始めるたびにする必要はありません。

expo ejectを使おうとして30分ほど費やしましたが、xcodeから193個のエラーが出たので、その価値はないと判断し、npx ignite-cli new KalmKrellを使ってボイラープレートをセットアップして新しいレポを作りました。npx react-native init AwesomeProjectだけを動かそうとして1時間ほど費やして失敗した後、アプリがないよりは半分のアプリの方がマシだと判断して、今は諦めました。expoの依存関係は極力使わないようにして、もしexpoなしでreact nativeをセットアップする方法がわかったときに、コードを書き直す必要がないようにすることにしました。

私が抱えていた問題

もしあなたがコード内でミスをした場合、ブラウザからの警告は受け取れず、ページを更新すると代わりにコンパイルされたバンドルの前のバージョンが表示されます。このため、shadow-cljs ターミナルを常に表示して、コンパイルエラーを確認できるようにする必要があります。

shadow-cljs が Build completed と言っているにもかかわらず、古いバンドルが更新されないことが何度もありました。私が見つけた唯一の解決策は、shadow と expo の両方のプロセスを再起動することでした。これは本当に生産性を低下させるもので、確実に再現することは困難でした (そのため、おそらく修正することも困難です)。