メモ書きなので雑。
シナリオ
- Xamarin.Forms + WebView(HTML, JavaScript な SPA) のガワネイティブアプリである
- Android アプリの BACK キーの有効無効を、WebView 内のページ状態によって切り替えたい
- データ編集中(未保存)なので Back キーで history.back やアプリ終了されたら困る、とか
流れ
1. [Forms側] Action<TaskCompletionSource<bool>> OnRequestIsEnableBackKey { get; set; }
を生やす
2. [Forms側] WebViewEx(WebViewを拡張したもの)に Task<bool> IsEnableBackKeyAsync()
も生やす。実装は次のように。
public Task<bool> IsEnableBackKeyAsync()
{
var comp = new TaskCompletionSource<bool>();
if (OnRequestIsEnableBackKey != null)
{
OnRequestIsEnableBackKey.Invoke(comp);
}
else
{
comp.SetResult(false);
}
return comp.Task;
}
3. [Android側] WebViewRenderer を拡張した MyWebViewRenderer を作る。
4. Xamarin.Forms の WebView で JavaScript 連携を行う(with iOS/Android共通化) - Qiita を参考に JavaScriptHandler も作る。コンストラクタで Control に加え e.NewElement as WebViewEx
も渡す。引数の名前は WebViewEx outer
とする。AddJavascriptInterface の第2引数は GawaApp
とでもしておく。
5.JavaScriptHandler のコンストラクタで OnRequestIsEnableBackKey を受信するコードを書く。
private TaskCompletionSource<bool> isEnableBackKeyComp = null;
public JavaScriptHandler(Android.Webkit.WebView webView, WebViewEx outer)
{
outer.OnRequestIsEnableBackKey = comp =>
{
isEnableBackKeyComp = comp;
// メインスレッドから呼ばないとエラー
webView.Post(() =>
{
webView.EvaluateJavascript("window.requestIsEnableBackKey()", null);
// webView.LoadUrl("javascript:window.requestIsEnableBackKey();"); ←これでもOKっぽい
});
};
}
ここまでの処理で、Forms 側で await webViewEx.IsEnableBackKeyAsync()
が呼び出されたら、JavaScript の window.requestIsEnableBackKey()
関数が呼び出される。
6.JavaScriptHandler 内に onResultCanExitApp を生やす。JavaScript側 から結果が通知されるメソッドである。次のように。
[Export]
[Android.Webkit.JavascriptInterface]
public void onResultIsEnableBackKey(bool value)
{
isEnableBackKeyComp?.SetResult(value);
isEnableBackKeyComp = null; // one shot
}
JavaScript 側で GawaApp.onResultIsEnableBackKey(true or false)
を呼び出すと、このメソッドがコールバックされる。
TaskCompletionSource である isEnableBackKeyComp に値を設定すれば、Forms 側の await webViewEx.IsEnableBackKeyAsync()
の結果が返される。
7.JavaScript 側はきっとこんな感じ
window.requestIsEnableBackKey = () => {
// 何かの処理
GawaApp.onResultIsEnableBackKey(true or false);
}
途中まで実装したけど、面倒になって(JavaScript→C# に単方向の方がシンプルでいいやと思って)やめちゃったので、アイデアだけ残しておきます。