【Tauri Android】戻るボタン押下イベントを制御する

# はじめに
## Tauriとは

[Tauri](https://v2.tauri.app/)とは、デスクトップアプリ(Windows, macOS, Linux)およびモバイルアプリ(iOS, Android)[^1]を開発することができるフレームワークです。フロントエンドはHTML, CSS, TypeScriptで書くことができ、WebViewベースで構築されます。ローカルシステムとのやりとりはRustで書きます。いわゆるクロスプラットフォームと呼ばれるもので、ワンソースで複数のOS向けにアプリを開発できます。

[^1]: Tauri 2.0で正式にiOS, Androidアプリが開発できるようになりました。https://v2.tauri.app/blog/tauri-20/

## この記事は何?

Tauriで開発するAndroidアプリにおいて、戻るボタン押下イベントを検知し、フロントエンド側(TypeScript)で制御する方法を紹介します。

`npm run tauri android dev` コマンドによって自動で生成された `WryActivity` を見ると、WebViewの `canGoBack()` , `goBack()` メソッドにより戻る操作を制御していることがわかります。

```kotlin:WryActivity
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
  if (keyCode == KeyEvent.KEYCODE_BACK && mWebView.canGoBack()) {
    mWebView.goBack()
    return true
  }
  return super.onKeyDown(keyCode, event)
}
```

Webページのヒストリーに則った画面遷移をしているならば、このままで良さそうですが、ヒストリーを利用していないSPAなどでは、戻るボタン押下でいきなりアプリが終了するといった挙動になってしまいます。

戻るボタン押下イベントを制御するやり方は、公式サポートには無さそうなので、自前で実装します。

# 戻るボタン押下イベントの制御
## MainActivityの修正

自動生成された `src-tauri/gen/android/app/src/main/java/path/to/MainActivity.kt` に以下のコードを追記します。

```diff_kotlin:MainActivity.kt
+ import android.webkit.WebView

class MainActivity : TauriActivity() {
+  private var webView: WebView? = null

+  override fun onWebViewCreate(webView: WebView) {
+    this.webView = webView
+    super.onWebViewCreate(webView)
+  }

+  @Deprecated("Deprecated in Java")
+  override fun onBackPressed() {
+    webView?.run {
+      evaluateJavascript(
+        """
+        try {
+          window.onAndroidBackPressed()
+        } catch (_) {
+          true
+        }
+        """.trimIndent()
+      ) { result ->
+        if (result == "true") {
+          super.onBackPressed();
+        }
+      }
+    } ?: super.onBackPressed()
+  }
}
```

このコードは、戻るボタンが押されたとき、 `evaluateJavascript` によって

```javascript:javascript
window.onAndroidBackPressed()
```

を実行します。 `onAndroidBackPressed` 関数がtrueを返す場合はデフォルトの戻る操作を行い、falseを返す場合はデフォルトの戻る操作をキャンセルします。

## フロントエンド側の修正

以下の `AndroidBackPressedController.ts` ファイルを追加します。本稿では `src` ディレクトリ直下に置いています。

```ts:src/AndroidBackPressedController.ts
export type OnAndroidBackPressedListener = () => boolean;

export default class AndroidBackPressedController {
  private listener?: OnAndroidBackPressedListener;

  constructor() {
    // @ts-ignore
    window.onAndroidBackPressed = () => {
      // trueを返すとデフォルトの動作、falseを返すとデフォルトの動作をキャンセル
      return this.listener?.() ?? true;
    };
  }

  setListener(listener: OnAndroidBackPressedListener) {
    this.listener = listener;
  }

  removeListener() {
    this.listener = undefined;
  }
}
```

上記の `AndroidBackPressedController` クラスのインスタンスをAppコンポーネント内で生成し、リスナーを登録します。 `AndroidBackPressedController` インスタンスが生成されるタイミングで `onAndroidBackPressed` 関数がセットされます。この関数は、戻るボタンが押された際にKotlinから `evaluateJavascript` を通して実行されます。

```src/App.tsx
function App() {

  ...

  new AndroidBackPressedController().setListener(() => {
    // TODO: 戻るボタンが押された時の処理
    return false;
  });

  ...
}
```

リスナーに登録した関数内で戻るボタンが押された時の処理を実装します。falseを戻り値として返すことで、デフォルトの戻る操作はキャンセルされます。

# おわりに

今回は、Tauriで開発するAndroidアプリにおいて、戻るボタン押下イベントをフロントエンド側で制御する方法を紹介しました。SPAなどのモダンなWebアプリをTauriでAndroidアプリ化する際に、この方法が参考になれば幸いです。