🐻‍❄️

[Jetpack Compose]WebViewで特定のリンクをクリックしたらネイティブの画面に遷移させる

2023/02/05に公開

はじめに

AndroidアプリでWebViewを使ったアプリを実装していると、特定の画面はネイティブ画面で実装したいという、要望が出てくることがあります。このような要望が出てきたときにJetpack Composeでどのようにすれば実現できるのか調べ、サンプルを実装したのでこちらの記事にまとめます。

仕様

今回作成したサンプルアプリの仕様を以下にまとめます。

  • アプリが起動したらHOMEページ(Yahoo! JAPAN)をWebViewに表示する
  • 表示されたHOMEのWebページ(Yahoo! JAPAN)で、MAILのWebページ(Yahoo! Mail)へのリンクをクリックしたら、MAILのネイティブ画面に遷移させる

準備

Accompanist WebViewのセットアップする

Jetpack ComposeでWebViewを使うときはAccompanistのWebViewを利用するのがベターなようでした。なので今回はAccompanistのWebViewの依存関係をbuild.graldeに追加して使っていきたいきます。

dependencies {
    implementation "com.google.accompanist:accompanist-webview:0.28.0"
    ︙ 省略
}

WebページのURLを定義する

HOMEのWebページとMAILのWebページを識別するには、予めページの識別に必要となるURLを定義しておく必要があります。なので以下のような感じでHOMEのWebページをUrls.home、MAILのWebページをUrls.mailとして定義しておきます。

object Urls {
    val home = "https://m.yahoo.co.jp/"
    val mail = "https://mail.yahoo.co.jp/"
}

実装

実装は以下の順番で進めていくことにします。

  1. HOMEのWebページを作成する
  2. MAILのネイティブ画面を作成する
  3. HOMEのWebページとMAILのネイティブ画面の切り替えを実装する

HOMEのWebページを作成する

以下のようにWebViewStateとWebViewを宣言して、WebViewでHOMEのWebページを表示できるようにします。

@Composable
private fun HomeScreen() {
    val webViewState = rememberWebViewState(url = MainActivity.Urls.home)
    WebView(state = webViewState)
}

次にonNavigateMailを追加、またAccompanistWebViewClientのshouldOverrideUrlLoadingoverrideし、MAILのWebページの読み込みが開始されたら、MAILのネイティブ画面への遷移イベントを発行するようにします。

@Composable
private fun HomeScreen(onNavigateMail: () -> Unit) {
    val webViewState = rememberWebViewState(url = MainActivity.Urls.home)
    val client = object : AccompanistWebViewClient() {
        override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { 
            if (request?.url.toString() == MainActivity.Urls.mail) {
                onNavigateMail()
                return true
            }
            return false
        }
    }
    WebView(state = webViewState, client = client)
}

AccompanistWebViewClientshouldOverrideUrlLoadingについてですがWebViewで新しいページを読み込む際に新しいページの読み込みを続けてもよいか判定するメソッドになります。

説明
- 新しいページの読み込みを開始する前に呼び出されるメソッドで読み込みを継続するかキャンセルするか制御できる。

引数
- WebView : 新しいページを読み込むWebView
- WebResourceRequest : 読み込む新しいページの詳細情報

戻り値
- Boolean : 新しいページの読み込みをキャンセルするかどうか(trueならば読み込みをキャンセル、falseならば読み込みを継続する)
/**
    * Give the host application a chance to take control when a URL is about to be loaded in the
    * current WebView. If a WebViewClient is not provided, by default WebView will ask Activity
    * Manager to choose the proper handler for the URL. If a WebViewClient is provided, returning
    * {@code true} causes the current WebView to abort loading the URL, while returning
    * {@code false} causes the WebView to continue loading the URL as usual.
    *
    * <p class="note"><b>Note:</b> Do not call {@link WebView#loadUrl(String)} with the request's
    * URL and then return {@code true}. This unnecessarily cancels the current load and starts a
    * new load with the same URL. The correct way to continue loading a given URL is to simply
    * return {@code false}, without calling {@link WebView#loadUrl(String)}.
    *
    * <p class="note"><b>Note:</b> This method is not called for POST requests.
    *
    * <p class="note"><b>Note:</b> This method may be called for subframes and with non-HTTP(S)
    * schemes; calling {@link WebView#loadUrl(String)} with such a URL will fail.
    *
    * @param view The WebView that is initiating the callback.
    * @param request Object containing the details of the request.
    * @return {@code true} to cancel the current load, otherwise return {@code false}.
    */
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
    return shouldOverrideUrlLoading(view, request.getUrl().toString());
}

今回はこのshouldOverrideUrlLoadingoverrideすることで、MAILのWebページの読み込みをキャンセルし、その後にMAILのネイティブ画面の遷移させるようにして、仕様を通りの動作をするようにしています。

override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { 
    // 新しく読み込むページの情報が、MAILのWebページと一致したら、MAILのネイティブ画面へ遷移させて、読み込みをキャンセルさせる
    if (request?.url.toString() == MainActivity.Urls.mail) {
        onNavigateMail()
        return true
    }

    // MAILのWebページと一致しない場合には、そのまま読み込みを継続する
    return false
}

MAILのネイティブ画面を作成する

MAILのネイティブ画面ですが今回はサンプルなのでLazyColumnを追加しておきSAMPLE0, SAMPLE1...というカードのリストを表示するようにしておきます。あとonBackButtonも追加しておき、BACKボタンがクリックされたら、HOMEのWebページへの遷移イベントを発行できるようにもしておきます。

@Composable
private fun MailScreen(onBack: () -> Unit) {
    Box {
        LazyColumn(
            modifier = Modifier
                .fillMaxSize()
                .padding(8.dp),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            items(10) { count ->
                Card {
                    Box(
                        modifier = Modifier
                            .padding(8.dp)
                            .fillMaxWidth()
                            .height(50.dp)
                    ) {
                        Text(
                            text = "SAMPLE MAIL $count",
                            textAlign = TextAlign.Center,
                            modifier = Modifier.align(Alignment.Center)
                        )
                    }
                }
            }

            item {
                Button(onClick = onBack, modifier = Modifier.fillMaxWidth()) {
                    Text(text = "BACK")
                }
            }
        }
    }
}

画面を切り替えるようにする

最後に作成したHOMEのWebページとMAILのネイティブ画面を切り替えられるようにします。今回は以下のようなScreenStateを定義し、各画面からのイベントに応じてScreenStateを切り替えるようにして、画面切り替えを実現します。

sealed class ScreenState {
    object Home : ScreenState()
    object Mail : ScreenState()
}
class MainActivity : ComponentActivity() {
    object Urls {
        val home = "https://m.yahoo.co.jp/"
        val mail = "https://mail.yahoo.co.jp/"
    }

    sealed class ScreenState {
        object Home : ScreenState()
        object Mail : ScreenState()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            var screenState by remember {
                mutableStateOf<ScreenState>(ScreenState.Home)
            }

            when (screenState) {
                ScreenState.Home -> {
                    HomeScreen(
                        onNavigateMail = {
                            screenState = ScreenState.Mail
                        }
                    )
                }

                is ScreenState.Mail -> {
                    MailScreen(
                        onBack = {
                            screenState = ScreenState.Home
                        }
                    )
                }
            }
        }
    }
}

動作確認

ここまで実装したら完成です、HOMEのWebページでMAILのWebページのリンクをクリックすると、作成したMAILのネイティブ画面に遷移します。MAILのネイティブ画面でBACKボタンを押すとHOMEのWebページにも戻ることができます。

今回作ったアプリのソースコード

今回作ったアプリのソースコードは以下にありますので、興味ある方は見てみてください。

https://github.com/kaleidot725-android/compose-accompanist-webview/tree/main/samples/routing

Discussion