Blog Infos
Author
Published
Topics
, , , ,
Published

A Step-by-Step Guide to Adding Friendly Captcha in Your Android App

Photo by Aaron Burden on Unsplash
Introduction

CAPTCHAs are widely used to prevent spam and ensure that users interacting with an application are human. Friendly Captcha is an alternative to traditional CAPTCHAs that offers a privacy-friendly, user-friendly, and automated challenge without requiring users to solve puzzles.

This article walks you through the integration of Friendly Captcha in a Jetpack Compose Android app using a ready-to-use composable component.

Step 1: Add Dependencies

Ensure you have WebView support enabled in your project:

In AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

In build.gradle.kts (Module: app)

dependencies {
    implementation "androidx.compose.ui:ui:1.5.1"
    implementation "androidx.compose.material:material:1.5.1"
    implementation "androidx.compose.ui:ui-tooling-preview:1.5.1"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1"
    implementation "androidx.activity:activity-compose:1.7.2"
    implementation "androidx.webkit:webkit:1.6.0"
}

 

Step 2: Add Friendly Captcha API Key
<string name="friendly_captcha_api_key">YOUR_SITE_KEY_HERE</string>

 

Replace YOUR_SITE_KEY_HERE with the actual API key from your Friendly Captcha account.

Step 3: Implement the CaptchaComponent

This component is responsible for:

  • Embedding the CAPTCHA inside a WebView.
  • Handling the challenge completion using JavaScript callbacks.
  • Enabling the Next button when the challenge is solved.

 

@Composable
fun CaptchaComponent(
    modifier: Modifier = Modifier,
    onNextButtonEnabled: (enabled: Boolean) -> Unit,
) {
    var captchaResponse by remember { mutableStateOf("") }

    val buttonEnabled by remember {
        derivedStateOf { captchaResponse.isNotEmpty() }
    }

    LaunchedEffect(buttonEnabled) {
        onNextButtonEnabled(buttonEnabled)
    }

    FriendlyCaptchaComponent(modifier) { response ->
        captchaResponse = response
    }
}

 

What Happens Here?

  • We use remember { mutableStateOf("") } to track the CAPTCHA response.
  • The button state is updated dynamically with derivedStateOf { captchaResponse.isNotEmpty() }.
  • When the CAPTCHA is solved, the Next button becomes enabled.

The WebView loads the CAPTCHA widget using Friendly Captcha’s official JavaScript SDK.

@SuppressLint("SetJavaScriptEnabled")
@Composable
fun FriendlyCaptchaComponent(
    modifier: Modifier = Modifier,
    onCaptchaSolved: (response: String) -> Unit
) {
    val context = LocalContext.current

    val captchaHtml = """
        <!DOCTYPE html>
        <html>
            <head>
                <meta charset="UTF-8" />
                <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
                <script type="module"
                        src="https://cdn.jsdelivr.net/npm/friendly-challenge@0.9.13/widget.module.min.js"
                        async defer></script>
                <script nomodule
                        src="https://cdn.jsdelivr.net/npm/friendly-challenge@0.9.13/widget.min.js"
                        async defer></script>
                <script>
                    function onReturn(solution) {
                        Android.onCaptchaSolved(solution);
                    }
                </script>
                <style>
                    .captcha-container {
                        display: flex;
                        justify-content: center;
                        align-items: center;
                        width: 100%;
                        max-width: 100%;
                    }
                    .frc-captcha {
                        width: 100%;
                        max-width: 100%;
                    }
                </style>
            </head>
            <body>
                <div class="captcha-container">
                    <div class="frc-captcha" 
                         data-sitekey="${context.getString(R.string.friendly_captcha_api_key)}" 
                         data-callback="onReturn"></div>
                </div>
            </body>
        </html>
    """.trimIndent()

    AndroidView(
        modifier = modifier.fillMaxWidth(),
        factory = { ctx ->
            WebView(ctx).apply {
                settings.javaScriptEnabled = true
                settings.domStorageEnabled = true
                webChromeClient = WebChromeClient()
                webViewClient = WebViewClient()

                addJavascriptInterface(object {
                    @JavascriptInterface
                    fun onCaptchaSolved(response: String) {
                        onCaptchaSolved(response)
                    }
                }, "Android")

                loadDataWithBaseURL(null, captchaHtml, "text/html", "UTF-8", null)
            }
        }
    )
}

What Happens Here?

  • The HTML & JavaScript loads the Friendly Captcha challenge.
  • The data-sitekey dynamically fetches the API key from strings.xml.
  • The JavaScript interface (Android.onCaptchaSolved(response)) sends the response back to the app.
  • Once solved, the Next button is enabled.

Now, place the CaptchaComponent in your UI and connect it to the Next button.

Step 5: Integrate CAPTCHA Component into Your UI
var nextButtonEnabled by remember { mutableStateOf(false) }

Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
    Text(
        text = "Solve the CAPTCHA to continue",
        style = MaterialTheme.typography.h6,
        modifier = Modifier.padding(bottom = 10.dp)
    )

    CaptchaComponent(
        onNextButtonEnabled = { enabled ->
            nextButtonEnabled = enabled
        }
    )

    Spacer(modifier = Modifier.height(16.dp))

    Button(
        onClick = { /* Navigate to the next step */ },
        enabled = nextButtonEnabled
    ) {
        Text("Next")
    }
}

What Happens Here?

  • The Next button is initially disabled.
  • Once the CAPTCHA is solved, the onNextButtonEnabled callback updates nextButtonEnabled.
  • The Next button becomes clickable only after CAPTCHA verification.

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Kobweb:Creating websites in Kotlin leveraging Compose HTML

Kobweb is a Kotlin web framework that aims to make web development enjoyable by building on top of Compose HTML and drawing inspiration from Jetpack Compose.
Watch Video

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kobweb

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author o ...

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kob ...

Jobs

No results found.

Conclusion

You’ve successfully integrated Friendly Captcha into your Jetpack Compose Android app!

This solution is:

  • Easy to integrate
  • Privacy-friendly (No tracking)
  • Works seamlessly with Jetpack Compose

I hope this article has given you a clear and practical way to integrate Friendly Captcha into your Jetpack Compose application. By following these steps, you can enhance your app’s security without compromising user experience.

If you found this guide useful, give it a clap 👏

Happy coding, and may your apps stay bot-free! 😊🎉

This article is previously published on proandroiddev.com.

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
In the world of Jetpack Compose, where designing reusable and customizable UI components is…
READ MORE
blog
Hi, today I come to you with a quick tip on how to update…
READ MORE
Menu