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.
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 updatesnextButtonEnabled
. - The Next button becomes clickable only after CAPTCHA verification.
Job Offers
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.