Integration
Android Integration
Add Reqflow to your Android app using WebView for seamless feedback collection.
Prerequisites
- Android API 21+ — Minimum deployment target (Lollipop)
- Android Studio — Arctic Fox or later recommended
- Reqflow app — Created in the dashboard
Add Internet Permission
Add the internet permission to your AndroidManifest.xml:
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<!-- ... -->
</manifest>Basic Implementation
Create a new Activity for the feedback screen:
FeedbackActivity.kt
import android.os.Bundle
import android.provider.Settings
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
import java.net.URLEncoder
class FeedbackActivity : AppCompatActivity() {
// Your app's slug from the Reqflow dashboard
private val appId = "your-app-id"
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
webView = WebView(this).apply {
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
webViewClient = WebViewClient()
}
setContentView(webView)
supportActionBar?.title = "Feedback"
loadFeedbackPage()
}
private fun loadFeedbackPage() {
// Get user info from your auth system
val userId = getUserId()
val userName = getUserName()
val userEmail = getUserEmail()
val url = buildString {
append("https://reqflow.com/vote/$appId")
append("?user_id=${URLEncoder.encode(userId, "UTF-8")}")
append("&user_name=${URLEncoder.encode(userName, "UTF-8")}")
userEmail?.let {
append("&user_email=${URLEncoder.encode(it, "UTF-8")}")
}
}
webView.loadUrl(url)
}
private fun getUserId(): String {
// Option 1: Use Android ID for anonymous users
return Settings.Secure.getString(
contentResolver,
Settings.Secure.ANDROID_ID
)
// Option 2: Use your auth system's user ID
// return AuthManager.currentUser?.id ?: UUID.randomUUID().toString()
}
private fun getUserName(): String {
// Return the user's display name from your auth system
// return AuthManager.currentUser?.name ?: "Anonymous"
return "User"
}
private fun getUserEmail(): String? {
// Optional: Return user's email if available
// return AuthManager.currentUser?.email
return null
}
override fun onBackPressed() {
if (webView.canGoBack()) {
webView.goBack()
} else {
super.onBackPressed()
}
}
}Jetpack Compose Implementation
If you're using Jetpack Compose, use the Accompanist WebView library:
build.gradle
// Add to build.gradle
dependencies {
implementation "com.google.accompanist:accompanist-webview:0.30.1"
}FeedbackScreen.kt
import android.provider.Settings
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import com.google.accompanist.web.WebView
import com.google.accompanist.web.rememberWebViewState
import java.net.URLEncoder
@Composable
fun FeedbackScreen(
appId: String,
userId: String,
userName: String,
userEmail: String? = null
) {
val url = buildString {
append("https://reqflow.com/vote/$appId")
append("?user_id=${URLEncoder.encode(userId, "UTF-8")}")
append("&user_name=${URLEncoder.encode(userName, "UTF-8")}")
userEmail?.let {
append("&user_email=${URLEncoder.encode(it, "UTF-8")}")
}
}
val state = rememberWebViewState(url)
WebView(
state = state,
onCreated = { webView ->
webView.settings.javaScriptEnabled = true
}
)
}
// Usage
@Composable
fun SettingsScreen() {
val context = LocalContext.current
val androidId = Settings.Secure.getString(
context.contentResolver,
Settings.Secure.ANDROID_ID
)
FeedbackScreen(
appId = "your-app-id",
userId = androidId,
userName = "John Doe",
userEmail = "john@example.com"
)
}Opening Feedback from a Button
Add a button in your app to open the feedback screen:
// In your Activity or Fragment
binding.feedbackButton.setOnClickListener {
val intent = Intent(this, FeedbackActivity::class.java)
startActivity(intent)
}
// Or using Navigation Component
findNavController().navigate(R.id.feedbackFragment)Consistent User ID
Use
ANDROID_ID or your auth system's user ID. Don't generate a new UUID each time, or users will lose their votes.Secure Authentication (Optional)
For enhanced security, you can add HMAC signature verification to prevent user impersonation. The signature should be generated by your backend and passed to the app.
SignatureHelper.kt
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
/**
* Generate HMAC signature for secure authentication (optional)
*/
object SignatureHelper {
fun generateSignature(userId: String, secret: String): Pair<String, String> {
val timestamp = (System.currentTimeMillis() / 1000).toString()
val payload = "$userId:$timestamp"
val mac = Mac.getInstance("HmacSHA256")
mac.init(SecretKeySpec(secret.toByteArray(Charsets.UTF_8), "HmacSHA256"))
val signature = mac.doFinal(payload.toByteArray(Charsets.UTF_8))
.joinToString("") { "%02x".format(it) }
return Pair(signature, timestamp)
}
}
// Usage
fun loadFeedbackPageWithSignature() {
val userId = getUserId()
val secret = getSecretFromBackend() // Fetch from your backend
val (signature, timestamp) = SignatureHelper.generateSignature(userId, secret)
val url = buildString {
append("https://reqflow.com/vote/$appId")
append("?user_id=${URLEncoder.encode(userId, "UTF-8")}")
append("&user_name=${URLEncoder.encode(getUserName(), "UTF-8")}")
append("&signature=${URLEncoder.encode(signature, "UTF-8")}")
append("×tamp=$timestamp")
}
webView.loadUrl(url)
}Security Note
Don't hardcode the secret in your app. Fetch it from your backend or use Android Keystore for secure storage. The signature feature is optional and only needed if you enable it on your Reqflow server.
Best Practices
- Handle Back Button — Override onBackPressed to handle WebView navigation history
- Enable JavaScript — Make sure javaScriptEnabled is set to true
- Dark Mode — Reqflow automatically adapts to the system theme
- Loading State — Consider adding a ProgressBar while the WebView loads