androidkotlinwebviewkotlin-multiplatform

Kotlin Multiplatform: How to extract the JSON value loaded in the WebView?


I am loading a Google SAML Authentication URL to authenticate the user using WebView. When the user successfully logs in entering his credentials, I get this JSON response in the WebView:

{
  "status": 1,
  "msg": "user Authenticated !!",
  "user": {
    "userFirstName": "John",
    "userLastName": "Doe",
    "userEmail": "john@example.in",
    "userType": "manager",
    "manager": true,
    "employee": false,
    "teamLead": false
  },
  "token": "eyJhbGciOiJI................."
}

I want to extract this information from the WebView and store it locally.

To implement the WebView in the Kotlin Multiplatform, I am using this library:

Medium Post: https://medium.com/@kevinnzou/web-everywhere-introducing-our-multiplatform-compose-webview-library-f9b1264b370

GitHub Repo: https://github.com/KevinnZou/compose-webview-multiplatform

WebView Implementation:

@Composable
internal fun LoginWebView(navHostController: NavHostController? = null) {
    val state = rememberWebViewState(url = SAML_DEV)
    val navigator = rememberWebViewNavigator()
    val jsBridge = rememberWebViewJsBridge()

    jsBridge.register(LoginMessageHandler())
    state.webSettings.apply {
        isJavaScriptEnabled = true
    }

    DisposableEffect(Unit) {
        state.webSettings.apply {
            logSeverity = KLogSeverity.Verbose
        }

        onDispose { }
    }


    MaterialTheme {
        Column {
            TopAppBar(
                title = { Text(text = "Login") },
                navigationIcon = {
                    IconButton(onClick = {
                        if (navigator.canGoBack) {
                            navigator.navigateBack()
                        } else {
                            navHostController?.popBackStack()
                        }
                    }) {
                        Icon(
                            imageVector = Icons.AutoMirrored.Filled.ArrowBack,
                            contentDescription = "Back",
                        )
                    }
                },
            )

            val loadingState = state.loadingState
            if (loadingState is LoadingState.Loading) {
                LinearProgressIndicator(
                    color = MaterialTheme.colorScheme.onPrimary,
                    modifier = Modifier
                        .height(6.dp)
                        .fillMaxWidth(),
                )
            }

            WebView(
                state = state,
                modifier = Modifier.fillMaxSize(),
                navigator = navigator,
                webViewJsBridge = jsBridge,
            )

            LaunchedEffect(state, navigator) {
                snapshotFlow { state.loadingState }
                    .filter { it is LoadingState.Finished }
                    .collect {
                        navigator.evaluateJavaScript(
                            """
                                try {
                                        // Select the <pre> element in the DOM
                                        const preElement = document.querySelector('pre');
                                        if (preElement) {
                                            // Extract the JSON content from the <pre> element
                                            const jsonResponse = preElement.innerText;
                                            console.log("Extracted JSON response:", jsonResponse);
                                            // Send the response to the native side using the bridge
                                            window.JsBridge.postMessage('login', jsonResponse);
                                        } else {
                                            console.error("<pre> element not found");
                                        }
                                    } catch (e) {
                                        console.error('Error extracting JSON response:', e);
                                    }
                            """.trimIndent()
                        )
                    }
            }

        }
    }
}

class LoginMessageHandler : IJsMessageHandler {
    override fun handle(
        message: JsMessage,
        navigator: WebViewNavigator?,
        callback: (String) -> Unit
    ) {
        println("LoginMessageHandler handle: $message")

        Logger.i {
            "Greet Handler Get Message: $message"
        }
        //val param = processParams<User>(message)
        callback(message.params)
    }

    override fun methodName(): String {
        return "login"
    }

}


Solution

  • According to the documentation, for your native code to be called you'll have to: window.kmpJsBridge.callNative but you have: window.JsBridge.postMessage which is wrong.

    See the part of the documentation