Rails: Hotwire Nativeをデバッグする(2)定番のチェック項目(翻訳)
Hotwire Nativeの仕組みをしっかり理解しておけば、デバッグの問題にもずっと取り組みやすくなります。ただしHotwire Nativeを正しく理解するには、やはりHotwire Nativeでネイティブアプリをいくつか作ってみるのが一番です。今回は実際に触って動かせるリポジトリを作成しましょう。
私はこれまでに10個以上のHotwire Nativeアプリを作ってきました(個人的に作ったアプリ、顧客向けの新規アプリや既存アプリも含めて)。経験を積むうちに、いくつかのパターンが見えてきました。
私のLTでは自分のためのチェックリストをまとめたスライドを1枚用意しましたが、詳しく説明する時間がなかったので、今回はそこを補うことにします。
🔗 1: TurboJSはインストールされているか?
これはHotwire Nativeに初めて取り組む人にありがちで、TurboJSが依存関係であることに気づいていないケースが多々あります。TurboJSは、Hotwire iOSもHotwire Androidでも必須であり、それぞれ独自のアダプタでネイティブ機能をトリガーしています。
アプリにTurboJSがインストールされているかどうかをチェックするには、ブラウザのDevConsoleでTurboと入力します。
以下のように表示される場合、Turboが正常にインストールされていません。
Uncaught ReferenceError: Turbo is not defined
at<anonymous>:1:1
以下のようにオブジェクトがコンソールに出力されていれば準備完了です。
🔗 2: ログの種類をひと通り押さえているか?
ひねった問いかけですが、問題が起きたときにどのログをチェックできるかを知っておく必要があります。Hotwire Nativeでは以下のようにさまざまなログが使えます。
- サーバーログ
- Webコンソール(DevTools)のログ
- iOSのログ
- Androidのログ
ログのフォーマットはそれぞれ異なります。ログを収集するテクニックについて詳しくは今後の記事に譲りますが、まずは基本的なものを見ていきましょう。
🔗 サーバーログ
アプリを起動して操作すると、サーバーログが出力されます(ログを有効にしている場合)。
LaravelとRailsは、重要なログ機能を最初から備えています。その他のフレームワークではHotwire Nativeを使ったことがないので、コメントできません。
サーバーログの良い点は、パス設定にシンタックスエラーがあるかどうか、あるいは正しいエンドポイントに接続されているかがわかることです(恥ずかしながら、私はこのレベルのチェックをいつもやっています)。
🔗 ログを有効にする
Hotwire Native iOSとHotwire Native Androidは、どちらもデフォルトでログが有効になっています。
ログの見方について詳しくは今後の記事で解説しますが、iOSとAndroidでは以下のようにするだけでログを有効にできます。
# iOS
Hotwire.config.debugLoggingEnabled = true
# Android
Hotwire.config.debugLoggingEnabled = BuildConfig.DEBUG
Hotwire.config.webViewDebuggingEnabled = BuildConfig.DEBUG
🔗 Webコンソールのログ
これについては今後の記事で詳しく説明しますが、現在実行中のHotwire NativeアプリのWebViewに以下の方法で接続できます。
🔗 iOS
最初にHotwire NativeアプリをXcodeのシミュレータで動かす必要があります。
次にSafariを開いて、メニューの「開発 > シミュレータ」を実行します。
なお、この「シミュレータ」オプションは、WebView設定をinspect可能にしている場合にしか表示されません。
🔗 Android
Webコンソールを開くには、Chromeブラウザのアドレスバーにchrome://inspect'と入力します。ここから、レンダリングされた任意のWebViewに接続できます。
🔗 3: デバッガを使うと何が起きるかを理解しているか?
Hotwire Nativeアプリの開発では、以下の4種類のデバッガを使えます。
- Railsのデバッガ:
pryやbinding.irb`など - JavaScriptデバッガ
- iOSのブレークポイント
- Androidのブレークポイント
これによって、プログラムが中断された場所からコード実行を再開できるようになります。これは、関数に渡される値をチェックしたい場合や、欲しいオブジェクトが存在するかどうかをチェックしたい場合に特に便利です。
私はログを調べて原因を究明するのが好きなプログラマーですが、デバッガーの強力さを否定するつもりはありません。
🔗 4: ブリッジコンポーネント: メッセージは送信されているか?
ブリッジコンポーネントには可動部品がたくさんあるため、慣れないうちは特に混乱する可能性があります。
特にブリッジコンポーネントのデバッグでは、最初に「メッセージが送信されているかどうか」を確認するのが定番です。
私がJoe Masilottiのブリッジコンポーネントに送信したプルリクをご覧ください(#14)。
import { BridgeComponent } from "@hotwired/hotwire-native-bridge"
export default class extends BridgeComponent {
static component = "haptic"
vibrate() {
const element = this.bridgeElement
const feedback = element.bridgeAttribute("feedback") || "success"
this.send("vibrate", {feedback})
}
}
私がこれを構築したとき、JavaScriptを書いた直後には真っ先にメッセージが確実に送信されているかどうかを確認しました。方法自体はいたって簡単で、コンポーネントを登録してからiOSやAndroidのログを読むだけです。
メッセージが送信されていることを確信できたら、次の段階に進めます。
🔗 5: ブリッジコンポーネント: 返信は受信されているか?
プルリク#14で触覚(haptic)フィードバックを追加した話の続きです。次に行うべきステップは、メッセージが確実に受信されているかどうかを確かめることです。
ログで確認することについては既に申し上げましたが、他にもできることがあります。
class HapticComponent(
name: String,
private val bridgeDelegate: BridgeDelegate<HotwireDestination>
) : BridgeComponent<HotwireDestination>(name, bridgeDelegate) {
private val fragment: Fragment
get() = bridgeDelegate.destination.fragment
override fun onReceive(message: Message) {
when (message.event) {
"vibrate" -> Log.w("HapticComponent", "received event")
else -> Log.w(
"HapticComponent", "Unknown event for message: $message"
)
}
}
}
これで、Android Studioのタグフィルタでtag:HapticComponentのようにタグを指定してログをフィルタできるようになりました。これでHapticComponentのタグだけが表示されるようになります。
iOSでも同様にフィルタを追加できます。
final class HapticComponent: BridgeComponent {
override class var name: String { "haptic" }
override func onReceive(message: Message) {
guard let event = Event(rawValue: message.event) else { return }
switch event {
case .vibrate:
print("HapticComponent")
}
}
}
private extension HapticComponent {
enum Event: String {
case vibrate
}
}
Xcodeへのログ出力はAndroidほど高度ではありませんが、今後の記事では、さらに便利なテクニックを紹介する予定です。
🔗 6: フラグメントやブリッジコンポーネントは登録したか?
ここ2か月間に、私とペアを組んでいた少なくとも3人がこれでハマりました。コンポーネントやフラグメントを書いたまではよかったのですが、Hotwireの設定オブジェクトに登録するのをコロっと忘れていたのです。
iOSの場合、ブリッジコンポーネントは以下のように登録します。
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Hotwire.registerBridgeComponents([
ButtonComponent.self
])
return true
}
}
Androidの場合、フラグメントとブリッジコンポーネントを「両方」とも登録する必要があります。さもないと利用できません。
Hotwire.registerBridgeComponents(
BridgeComponentFactory("button", ::ButtonComponent)
)
Hotwire.registerFragmentDestinations(
HotwireWebFragment::class,
NumbersFragment::class
)
🔗 7: パス設定は正しく行われているか?
JSONファイルで単純な構文ミスをやらかしたばかりに、何もかも台無しになる可能性があります。
ブラウザでリモートパスの設定を確認して、JSONが正常であることを必ず検証しましょう。ここで述べたように、期待されるパス設定オブジェクトに準拠していることを検証しましょう。
🔗 まとめ
経験を積むに連れて、確認すべき点が次々に浮かび上がってくるようになります。そして、以前に時間を溶かす羽目になった小さな設定ミスを確認せずにいられなくなるようになるでしょう。
本記事によって、私がこれまで溶かし続けてきた多くの時間を皆さんが溶かさずに済むことを願っています。
Happy hacking.


概要
元サイトの許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。
従来Turbo NativeとStradaと呼ばれていたものは、現在はHotwire Nativeに統合されました。
参考: Hotwire Native: Hotwire Native is a web-first framework for building native mobile apps.