ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

Implementing Friendly Captcha in Jetpack Compose: A Ready-to-Use Solution

Anatolii Frolov
ProAndroidDev
Published in
3 min readFeb 14, 2025

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.

Step 4: Embed Friendly Captcha in WebView

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.

Step 5: Integrate CAPTCHA Component into Your UI

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

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.

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! 😊🎉

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

No responses yet

Write a response