Implementing Friendly Captcha in Jetpack Compose: A Ready-to-Use Solution
A Step-by-Step Guide to Adding Friendly Captcha in Your Android App
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 updatesnextButtonEnabled
. - 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! 😊🎉