Get Started
API Documentation
- Web SDK
- Android
- iOS
Hawcx Android SDK
Next-generation passwordless authentication for your Android applications
Overview
Hawcx SDK provides next-generation passwordless authentication for your Android applications. With Hawcx, you can implement secure, frictionless authentication that works seamlessly across all user devices.
Passwordless Authentication
Eliminate password vulnerabilities and user friction
Multi-Device Support
Enable users to securely access their accounts across all their devices
Enterprise-Grade Security
Protect user accounts with advanced security protocols
Web Login Approval
Allow users to approve web logins from their mobile device
Biometric Integration
Leverage biometric authentication for additional security
Architecture
Quick Start
Installation
dependencies {
implementation files('libs/hawcx-3.0.0.aar')
}
dependencies {
implementation files('libs/hawcx-3.0.0.aar')
}
- Download the latest hawcx-3.0.0.aar
- Place the AAR file in your project’s
libs
directory - Add the dependency to your module’s build.gradle as shown above
Initialize SDK
import com.hawcx.internal.HawcxInitializer
class YourApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize Hawcx SDK with your API key
HawcxInitializer.getInstance().init(
context = this,
apiKey = "YOUR_API_KEY"
)
}
}
Don’t forget to register your Application class in the AndroidManifest.xml:
<application
android:name=".YourApplication"
...>
<!-- Activities and other components -->
</application>
Implement authentication using methods below:
Core Features
class AuthManager : SignUpCallback {
private val signUp = SignUp(apiKey = "YOUR_API_KEY")
fun registerUser(email: String) {
signUp.signUp(userid = email, callback = this)
}
fun verifyOTP(otp: String) {
signUp.handleVerifyOTP(otp = otp, callback = this)
}
// SignUpCallback Methods
override fun showError(signUpErrorCode: SignUpErrorCode, errorMessage: String) {
// Handle error based on signUpErrorCode
Log.e("AuthManager", "Error: $errorMessage")
}
override fun onSuccessfulSignUp() {
// Handle successful registration
Log.d("AuthManager", "Registration successful!")
}
override fun onGenerateOTPSuccess() {
// Show OTP entry UI
Log.d("AuthManager", "OTP sent to user's email")
}
}
class AuthManager : SignUpCallback {
private val signUp = SignUp(apiKey = "YOUR_API_KEY")
fun registerUser(email: String) {
signUp.signUp(userid = email, callback = this)
}
fun verifyOTP(otp: String) {
signUp.handleVerifyOTP(otp = otp, callback = this)
}
// SignUpCallback Methods
override fun showError(signUpErrorCode: SignUpErrorCode, errorMessage: String) {
// Handle error based on signUpErrorCode
Log.e("AuthManager", "Error: $errorMessage")
}
override fun onSuccessfulSignUp() {
// Handle successful registration
Log.d("AuthManager", "Registration successful!")
}
override fun onGenerateOTPSuccess() {
// Show OTP entry UI
Log.d("AuthManager", "OTP sent to user's email")
}
}
import androidx.compose.material3.*
import androidx.compose.runtime.*
import com.hawcx.sdk.*
@Composable
fun SignUpScreen(
viewModel: SignUpViewModel = viewModel()
) {
var email by remember { mutableStateOf("") }
var otp by remember { mutableStateOf("") }
var showOtpField by remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Create Account",
style = MaterialTheme.typography.headlineLarge
)
if (!showOtpField) {
// Email entry
OutlinedTextField(
value = email,
onValueChange = { email = it },
label = { Text("Email Address") },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email
),
modifier = Modifier.fillMaxWidth()
)
Button(
onClick = { viewModel.registerUser(email) },
modifier = Modifier.fillMaxWidth()
) {
Text("Sign Up")
}
} else {
// OTP Verification
Text(
text = "Enter the verification code sent to $email",
textAlign = TextAlign.Center
)
OutlinedTextField(
value = otp,
onValueChange = { otp = it },
label = { Text("6-Digit Code") },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number
),
modifier = Modifier.fillMaxWidth()
)
Button(
onClick = { viewModel.verifyOTP(otp) },
modifier = Modifier.fillMaxWidth()
) {
Text("Verify")
}
}
}
// Handle state changes
LaunchedEffect(viewModel.otpGenerated) {
showOtpField = viewModel.otpGenerated
}
// Show alerts
if (viewModel.showAlert) {
AlertDialog(
onDismissRequest = { viewModel.showAlert = false },
title = { Text(viewModel.alertTitle) },
text = { Text(viewModel.alertMessage) },
confirmButton = {
TextButton(onClick = { viewModel.showAlert = false }) {
Text("OK")
}
}
)
}
}
class SignUpViewModel : ViewModel(), SignUpCallback {
private val signUp = SignUp(apiKey = "YOUR_API_KEY")
var otpGenerated by mutableStateOf(false)
private set
var showAlert by mutableStateOf(false)
private set
var alertTitle by mutableStateOf("")
private set
var alertMessage by mutableStateOf("")
private set
fun registerUser(email: String) {
signUp.signUp(userid = email, callback = this)
}
fun verifyOTP(otp: String) {
signUp.handleVerifyOTP(otp = otp, callback = this)
}
// SignUpCallback
override fun showError(signUpErrorCode: SignUpErrorCode, errorMessage: String) {
viewModelScope.launch {
alertTitle = "Error"
alertMessage = errorMessage
showAlert = true
}
}
override fun onSuccessfulSignUp() {
viewModelScope.launch {
alertTitle = "Success"
alertMessage = "Account created successfully!"
showAlert = true
}
}
override fun onGenerateOTPSuccess() {
viewModelScope.launch {
otpGenerated = true
}
}
}
class AuthManager : SignInCallback {
private val signIn = SignIn(apiKey = "YOUR_API_KEY")
fun authenticateUser(email: String) {
signIn.signIn(userid = email, callback = this)
}
// SignInCallback Methods
override fun showError(signInErrorCode: SignInErrorCode, errorMessage: String) {
// Handle authentication error
Log.e("AuthManager", "Error: $errorMessage")
}
override fun onSuccessfulLogin(email: String) {
// User successfully authenticated
Log.d("AuthManager", "Login successful for $email")
}
override fun navigateToRegistration(email: String) {
// User doesn't exist, show registration UI
Log.d("AuthManager", "User not found, redirecting to registration")
}
override fun initiateAddDeviceRegistrationFlow(email: String) {
// Current device needs to be added to account
Log.d("AuthManager", "New device detected, initiate device registration")
}
}
class AuthManager : SignInCallback {
private val signIn = SignIn(apiKey = "YOUR_API_KEY")
fun authenticateUser(email: String) {
signIn.signIn(userid = email, callback = this)
}
// SignInCallback Methods
override fun showError(signInErrorCode: SignInErrorCode, errorMessage: String) {
// Handle authentication error
Log.e("AuthManager", "Error: $errorMessage")
}
override fun onSuccessfulLogin(email: String) {
// User successfully authenticated
Log.d("AuthManager", "Login successful for $email")
}
override fun navigateToRegistration(email: String) {
// User doesn't exist, show registration UI
Log.d("AuthManager", "User not found, redirecting to registration")
}
override fun initiateAddDeviceRegistrationFlow(email: String) {
// Current device needs to be added to account
Log.d("AuthManager", "New device detected, initiate device registration")
}
}
import androidx.compose.material3.*
import androidx.compose.runtime.*
import com.hawcx.sdk.*
@Composable
fun SignInScreen(
viewModel: SignInViewModel = viewModel(),
onNavigateToSignUp: () -> Unit,
onNavigateToAddDevice: () -> Unit,
onNavigateToHome: () -> Unit
) {
var email by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Log In",
style = MaterialTheme.typography.headlineLarge
)
OutlinedTextField(
value = email,
onValueChange = { email = it },
label = { Text("Email Address") },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email
),
modifier = Modifier.fillMaxWidth()
)
Button(
onClick = { viewModel.authenticateUser(email) },
modifier = Modifier.fillMaxWidth()
) {
Text("Sign In")
}
}
// Handle navigation
LaunchedEffect(viewModel.needsSignUp) {
if (viewModel.needsSignUp) {
onNavigateToSignUp()
viewModel.needsSignUp = false
}
}
LaunchedEffect(viewModel.needsAddDevice) {
if (viewModel.needsAddDevice) {
onNavigateToAddDevice()
viewModel.needsAddDevice = false
}
}
LaunchedEffect(viewModel.isLoggedIn) {
if (viewModel.isLoggedIn) {
onNavigateToHome()
viewModel.isLoggedIn = false
}
}
// Show alerts
if (viewModel.showAlert) {
AlertDialog(
onDismissRequest = { viewModel.showAlert = false },
title = { Text(viewModel.alertTitle) },
text = { Text(viewModel.alertMessage) },
confirmButton = {
TextButton(onClick = { viewModel.showAlert = false }) {
Text("OK")
}
}
)
}
}
class SignInViewModel : ViewModel(), SignInCallback {
private val signIn = SignIn(apiKey = "YOUR_API_KEY")
var needsSignUp by mutableStateOf(false)
private set
var needsAddDevice by mutableStateOf(false)
private set
var isLoggedIn by mutableStateOf(false)
private set
var showAlert by mutableStateOf(false)
private set
var alertTitle by mutableStateOf("")
private set
var alertMessage by mutableStateOf("")
private set
var userEmail by mutableStateOf("")
private set
fun authenticateUser(email: String) {
userEmail = email
signIn.signIn(userid = email, callback = this)
}
// SignInCallback
override fun showError(signInErrorCode: SignInErrorCode, errorMessage: String) {
viewModelScope.launch {
alertTitle = "Error"
alertMessage = errorMessage
showAlert = true
}
}
override fun onSuccessfulLogin(email: String) {
viewModelScope.launch {
isLoggedIn = true
}
}
override fun navigateToRegistration(email: String) {
viewModelScope.launch {
needsSignUp = true
}
}
override fun initiateAddDeviceRegistrationFlow(email: String) {
viewModelScope.launch {
needsAddDevice = true
}
}
}
class DeviceManager : AddDeviceCallback {
private val addDeviceManager = AddDeviceManager(apiKey = "YOUR_API_KEY")
fun addDevice(email: String) {
addDeviceManager.startAddDeviceFlow(userid = email, callback = this)
}
fun verifyOTP(otp: String) {
addDeviceManager.handleVerifyOTP(otp = otp)
}
// AddDeviceCallback Methods
override fun showError(addDeviceErrorCode: AddDeviceErrorCode, errorMessage: String) {
// Handle error
Log.e("DeviceManager", "Error: $errorMessage")
}
override fun onAddDeviceSuccess() {
// Device successfully added
Log.d("DeviceManager", "Device successfully added to account!")
}
override fun onGenerateOTPSuccess() {
// Show OTP entry UI
Log.d("DeviceManager", "OTP sent to user's email")
}
}
class DeviceManager : AddDeviceCallback {
private val addDeviceManager = AddDeviceManager(apiKey = "YOUR_API_KEY")
fun addDevice(email: String) {
addDeviceManager.startAddDeviceFlow(userid = email, callback = this)
}
fun verifyOTP(otp: String) {
addDeviceManager.handleVerifyOTP(otp = otp)
}
// AddDeviceCallback Methods
override fun showError(addDeviceErrorCode: AddDeviceErrorCode, errorMessage: String) {
// Handle error
Log.e("DeviceManager", "Error: $errorMessage")
}
override fun onAddDeviceSuccess() {
// Device successfully added
Log.d("DeviceManager", "Device successfully added to account!")
}
override fun onGenerateOTPSuccess() {
// Show OTP entry UI
Log.d("DeviceManager", "OTP sent to user's email")
}
}
import androidx.compose.material3.*
import androidx.compose.runtime.*
import com.hawcx.sdk.*
@Composable
fun AddDeviceScreen(
viewModel: AddDeviceViewModel = viewModel(),
onNavigateToHome: () -> Unit
) {
var email by remember { mutableStateOf("") }
var otp by remember { mutableStateOf("") }
var showOtpField by remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Add This Device",
style = MaterialTheme.typography.headlineLarge
)
if (!showOtpField) {
// Email entry
OutlinedTextField(
value = email,
onValueChange = { email = it },
label = { Text("Email Address") },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Email
),
modifier = Modifier.fillMaxWidth()
)
Button(
onClick = { viewModel.addDevice(email) },
modifier = Modifier.fillMaxWidth()
) {
Text("Continue")
}
} else {
// OTP Verification
Text(
text = "Enter the verification code sent to $email",
textAlign = TextAlign.Center
)
OutlinedTextField(
value = otp,
onValueChange = { otp = it },
label = { Text("6-Digit Code") },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number
),
modifier = Modifier.fillMaxWidth()
)
Button(
onClick = { viewModel.verifyOTP(otp) },
modifier = Modifier.fillMaxWidth()
) {
Text("Verify")
}
}
}
// Handle state changes
LaunchedEffect(viewModel.otpGenerated) {
showOtpField = viewModel.otpGenerated
}
LaunchedEffect(viewModel.deviceAdded) {
if (viewModel.deviceAdded) {
onNavigateToHome()
viewModel.deviceAdded = false
}
}
// Show alerts
if (viewModel.showAlert) {
AlertDialog(
onDismissRequest = { viewModel.showAlert = false },
title = { Text(viewModel.alertTitle) },
text = { Text(viewModel.alertMessage) },
confirmButton = {
TextButton(onClick = { viewModel.showAlert = false }) {
Text("OK")
}
}
)
}
}
class AddDeviceViewModel : ViewModel(), AddDeviceCallback {
private val addDeviceManager = AddDeviceManager(apiKey = "YOUR_API_KEY")
var otpGenerated by mutableStateOf(false)
private set
var deviceAdded by mutableStateOf(false)
private set
var showAlert by mutableStateOf(false)
private set
var alertTitle by mutableStateOf("")
private set
var alertMessage by mutableStateOf("")
private set
fun addDevice(email: String) {
addDeviceManager.startAddDeviceFlow(userid = email, callback = this)
}
fun verifyOTP(otp: String) {
addDeviceManager.handleVerifyOTP(otp = otp)
}
// AddDeviceCallback
override fun showError(addDeviceErrorCode: AddDeviceErrorCode, errorMessage: String) {
viewModelScope.launch {
alertTitle = "Error"
alertMessage = errorMessage
showAlert = true
}
}
override fun onAddDeviceSuccess() {
viewModelScope.launch {
alertTitle = "Success"
alertMessage = "Device successfully added to your account!"
showAlert = true
deviceAdded = true
}
}
override fun onGenerateOTPSuccess() {
viewModelScope.launch {
otpGenerated = true
}
}
}
class WebLoginManager : WebLoginCallback {
private val webLoginManager = WebLoginManager(apiKey = "YOUR_API_KEY")
fun verifyPin(pin: String, accessToken: String) {
webLoginManager.webLogin(accessToken = accessToken, pin = pin, callback = this)
}
fun approveLogin(token: String, accessToken: String) {
webLoginManager.webApprove(accessToken = accessToken, token = token, callback = this)
}
// WebLoginCallback Methods
override fun showError(webLoginErrorCode: WebLoginErrorCode, errorMessage: String) {
// Handle error
Log.e("WebLoginManager", "Error: $errorMessage")
}
override fun onSuccess() {
// Web login successfully approved
Log.d("WebLoginManager", "Web login approved!")
}
}
class WebLoginManager : WebLoginCallback {
private val webLoginManager = WebLoginManager(apiKey = "YOUR_API_KEY")
fun verifyPin(pin: String, accessToken: String) {
webLoginManager.webLogin(accessToken = accessToken, pin = pin, callback = this)
}
fun approveLogin(token: String, accessToken: String) {
webLoginManager.webApprove(accessToken = accessToken, token = token, callback = this)
}
// WebLoginCallback Methods
override fun showError(webLoginErrorCode: WebLoginErrorCode, errorMessage: String) {
// Handle error
Log.e("WebLoginManager", "Error: $errorMessage")
}
override fun onSuccess() {
// Web login successfully approved
Log.d("WebLoginManager", "Web login approved!")
}
}
import androidx.compose.material3.*
import androidx.compose.runtime.*
import com.hawcx.sdk.*
@Composable
fun WebLoginScreen(
viewModel: WebLoginViewModel = viewModel(),
onNavigateBack: () -> Unit
) {
var pin by remember { mutableStateOf("") }
var showApprovalScreen by remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
if (!showApprovalScreen) {
Text(
text = "Web Login",
style = MaterialTheme.typography.headlineLarge
)
Text(
text = "Enter the 7-digit PIN shown on the web login screen",
textAlign = TextAlign.Center
)
OutlinedTextField(
value = pin,
onValueChange = { pin = it },
label = { Text("7-Digit PIN") },
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number
),
modifier = Modifier.fillMaxWidth()
)
Button(
onClick = { viewModel.verifyPin(pin, "YOUR_ACCESS_TOKEN") },
modifier = Modifier.fillMaxWidth()
) {
Text("Verify PIN")
}
} else {
Text(
text = "Approve Login",
style = MaterialTheme.typography.headlineLarge
)
Text(
text = "Someone is trying to log in to your account. Verify it's you.",
textAlign = TextAlign.Center
)
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text("Browser: ${viewModel.sessionDetails?.browserWithVersion ?: "Unknown"}")
Text("Device: ${viewModel.sessionDetails?.deviceType ?: "Unknown"}")
Text("Location: ${viewModel.sessionDetails?.city ?: ""}, ${viewModel.sessionDetails?.country ?: ""}")
Text("IP Address: ${viewModel.sessionDetails?.ipDetails ?: "Unknown"}")
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Button(
onClick = { viewModel.denyLogin() },
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error
)
) {
Text("Deny")
}
Button(
onClick = { viewModel.approveLogin() },
modifier = Modifier.weight(1f),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
)
) {
Text("Approve")
}
}
}
}
// Handle state changes
LaunchedEffect(viewModel.showApproval) {
showApprovalScreen = viewModel.showApproval
}
LaunchedEffect(viewModel.loginProcessed) {
if (viewModel.loginProcessed) {
onNavigateBack()
viewModel.loginProcessed = false
}
}
// Show alerts
if (viewModel.showAlert) {
AlertDialog(
onDismissRequest = { viewModel.showAlert = false },
title = { Text(viewModel.alertTitle) },
text = { Text(viewModel.alertMessage) },
confirmButton = {
TextButton(onClick = { viewModel.showAlert = false }) {
Text("OK")
}
}
)
}
}
class WebLoginViewModel : ViewModel(), WebLoginCallback {
private val webLoginManager = WebLoginManager(apiKey = "YOUR_API_KEY")
var showApproval by mutableStateOf(false)
private set
var loginProcessed by mutableStateOf(false)
private set
var showAlert by mutableStateOf(false)
private set
var alertTitle by mutableStateOf("")
private set
var alertMessage by mutableStateOf("")
private set
var sessionDetails: SessionDetails? = null
private set
fun verifyPin(pin: String, accessToken: String) {
webLoginManager.webLogin(accessToken = accessToken, pin = pin, callback = this)
}
fun approveLogin() {
sessionDetails?.let { details ->
webLoginManager.webApprove(
accessToken = details.accessToken,
token = details.token,
callback = this
)
}
}
fun denyLogin() {
// Handle login denial
loginProcessed = true
}
// WebLoginCallback
override fun showError(webLoginErrorCode: WebLoginErrorCode, errorMessage: String) {
viewModelScope.launch {
alertTitle = "Error"
alertMessage = errorMessage
showAlert = true
}
}
override fun onSuccess() {
viewModelScope.launch {
loginProcessed = true
}
}
}
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
fun authenticateWithBiometrics(
activity: FragmentActivity,
username: String,
onSuccess: () -> Unit,
onError: () -> Unit
) {
val executor = ContextCompat.getMainExecutor(activity)
val biometricPrompt = BiometricPrompt(
activity,
executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
// Biometric authentication successful, proceed with Hawcx authentication
signIn.signIn(userid = username, callback = this@AuthManager)
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
// Biometric authentication failed
onError()
}
}
)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Log in to your account")
.setSubtitle("Verify your identity")
.setNegativeButtonText("Cancel")
.build()
biometricPrompt.authenticate(promptInfo)
}
class SessionManager : DevSessionCallback {
private val deviceSession = DevSession(apiKey = "YOUR_API_KEY")
fun fetchDeviceDetails() {
deviceSession.GetDeviceDetails(callback = this)
}
// DevSessionCallback Methods
override fun onSuccess() {
// Device details successfully retrieved
// Access device data from SharedPreferences
val prefs = context.getSharedPreferences("hawcx_prefs", Context.MODE_PRIVATE)
val deviceDetailsJson = prefs.getString("devDetails", null)
deviceDetailsJson?.let { json ->
try {
val devices = Gson().fromJson(json, Array<DeviceSessionInfo>::class.java)
// Process device information
Log.d("SessionManager", "Retrieved ${devices.size} devices")
} catch (e: Exception) {
Log.e("SessionManager", "Error decoding device details: ${e.message}")
}
}
}
override fun showError() {
// Error fetching device details
Log.e("SessionManager", "Error fetching device details")
}
}
class SessionManager : DevSessionCallback {
private val deviceSession = DevSession(apiKey = "YOUR_API_KEY")
fun fetchDeviceDetails() {
deviceSession.GetDeviceDetails(callback = this)
}
// DevSessionCallback Methods
override fun onSuccess() {
// Device details successfully retrieved
// Access device data from SharedPreferences
val prefs = context.getSharedPreferences("hawcx_prefs", Context.MODE_PRIVATE)
val deviceDetailsJson = prefs.getString("devDetails", null)
deviceDetailsJson?.let { json ->
try {
val devices = Gson().fromJson(json, Array<DeviceSessionInfo>::class.java)
// Process device information
Log.d("SessionManager", "Retrieved ${devices.size} devices")
} catch (e: Exception) {
Log.e("SessionManager", "Error decoding device details: ${e.message}")
}
}
}
override fun showError() {
// Error fetching device details
Log.e("SessionManager", "Error fetching device details")
}
}
import androidx.compose.material3.*
import androidx.compose.runtime.*
import com.hawcx.sdk.*
@Composable
fun DeviceSessionScreen(
viewModel: DeviceSessionViewModel = viewModel()
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Device Management",
style = MaterialTheme.typography.headlineLarge
)
if (viewModel.isLoading) {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Text(
text = "Loading device details...",
modifier = Modifier.align(Alignment.CenterHorizontally)
)
} else {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(viewModel.devices) { device ->
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = device.deviceName,
style = MaterialTheme.typography.titleMedium
)
Text(
text = "Last active: ${device.lastActiveDate}",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = "Device ID: ${device.deviceId}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
}
Button(
onClick = { viewModel.fetchDeviceDetails() },
modifier = Modifier.fillMaxWidth()
) {
Text("Refresh Device List")
}
}
// Show alerts
if (viewModel.showAlert) {
AlertDialog(
onDismissRequest = { viewModel.showAlert = false },
title = { Text(viewModel.alertTitle) },
text = { Text(viewModel.alertMessage) },
confirmButton = {
TextButton(onClick = { viewModel.showAlert = false }) {
Text("OK")
}
}
)
}
}
class DeviceSessionViewModel : ViewModel(), DevSessionCallback {
private val deviceSession = DevSession(apiKey = "YOUR_API_KEY")
var devices by mutableStateOf<List<DeviceSessionInfo>>(emptyList())
private set
var isLoading by mutableStateOf(false)
private set
var showAlert by mutableStateOf(false)
private set
var alertTitle by mutableStateOf("")
private set
var alertMessage by mutableStateOf("")
private set
fun fetchDeviceDetails() {
isLoading = true
deviceSession.GetDeviceDetails(callback = this)
}
// DevSessionCallback
override fun onSuccess() {
viewModelScope.launch {
isLoading = false
val prefs = context.getSharedPreferences("hawcx_prefs", Context.MODE_PRIVATE)
val deviceDetailsJson = prefs.getString("devDetails", null)
deviceDetailsJson?.let { json ->
try {
devices = Gson().fromJson(json, Array<DeviceSessionInfo>::class.java).toList()
} catch (e: Exception) {
alertTitle = "Error"
alertMessage = "Failed to decode device details: ${e.message}"
showAlert = true
}
}
}
}
override fun showError() {
viewModelScope.launch {
isLoading = false
alertTitle = "Error"
alertMessage = "Failed to fetch device details"
showAlert = true
}
}
}
// SignUp Error Handling
fun showError(signUpErrorCode: SignUpErrorCode, errorMessage: String) {
when (signUpErrorCode) {
SignUpErrorCode.USER_ALREADY_EXISTS -> {
// Show login option instead
Log.d("AuthManager", "User already exists. Please log in.")
}
SignUpErrorCode.VERIFY_OTP_FAILED -> {
// Show OTP retry UI
Log.d("AuthManager", "Invalid OTP. Please try again.")
}
SignUpErrorCode.GENERATE_OTP_FAILED -> {
// Show retry option
Log.d("AuthManager", "Failed to generate OTP. Please try again.")
}
SignUpErrorCode.NETWORK_ERROR -> {
// Show connectivity error
Log.d("AuthManager", "Network error. Please check your connection.")
}
SignUpErrorCode.UNKNOWN_ERROR -> {
// Show generic error
Log.d("AuthManager", "An unexpected error occurred: $errorMessage")
}
else -> {
Log.d("AuthManager", "Error: $errorMessage")
}
}
}
// SignIn Error Handling
fun showError(signInErrorCode: SignInErrorCode, errorMessage: String) {
when (signInErrorCode) {
SignInErrorCode.USER_NOT_FOUND -> {
// Show registration option
Log.d("AuthManager", "User not found. Please sign up.")
}
SignInErrorCode.NETWORK_ERROR -> {
// Show connectivity error
Log.d("AuthManager", "Network error. Please check your connection.")
}
SignInErrorCode.ADD_DEVICE_REQUIRED -> {
// Redirect to Add Device flow
Log.d("AuthManager", "This device needs to be added to your account.")
}
else -> {
Log.d("AuthManager", "Error: $errorMessage")
}
}
}
// SignUp Error Handling
fun showError(signUpErrorCode: SignUpErrorCode, errorMessage: String) {
when (signUpErrorCode) {
SignUpErrorCode.USER_ALREADY_EXISTS -> {
// Show login option instead
Log.d("AuthManager", "User already exists. Please log in.")
}
SignUpErrorCode.VERIFY_OTP_FAILED -> {
// Show OTP retry UI
Log.d("AuthManager", "Invalid OTP. Please try again.")
}
SignUpErrorCode.GENERATE_OTP_FAILED -> {
// Show retry option
Log.d("AuthManager", "Failed to generate OTP. Please try again.")
}
SignUpErrorCode.NETWORK_ERROR -> {
// Show connectivity error
Log.d("AuthManager", "Network error. Please check your connection.")
}
SignUpErrorCode.UNKNOWN_ERROR -> {
// Show generic error
Log.d("AuthManager", "An unexpected error occurred: $errorMessage")
}
else -> {
Log.d("AuthManager", "Error: $errorMessage")
}
}
}
// SignIn Error Handling
fun showError(signInErrorCode: SignInErrorCode, errorMessage: String) {
when (signInErrorCode) {
SignInErrorCode.USER_NOT_FOUND -> {
// Show registration option
Log.d("AuthManager", "User not found. Please sign up.")
}
SignInErrorCode.NETWORK_ERROR -> {
// Show connectivity error
Log.d("AuthManager", "Network error. Please check your connection.")
}
SignInErrorCode.ADD_DEVICE_REQUIRED -> {
// Redirect to Add Device flow
Log.d("AuthManager", "This device needs to be added to your account.")
}
else -> {
Log.d("AuthManager", "Error: $errorMessage")
}
}
}
import androidx.compose.material3.*
import androidx.compose.runtime.*
import com.hawcx.sdk.*
@Composable
fun ErrorHandlingScreen(
viewModel: ErrorHandlingViewModel = viewModel()
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Error Handling",
style = MaterialTheme.typography.headlineLarge
)
// SignUp Error Example
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "SignUp Errors",
style = MaterialTheme.typography.titleMedium
)
Button(
onClick = { viewModel.testSignUpError(SignUpErrorCode.USER_ALREADY_EXISTS) },
modifier = Modifier.fillMaxWidth()
) {
Text("Test User Already Exists")
}
Button(
onClick = { viewModel.testSignUpError(SignUpErrorCode.VERIFY_OTP_FAILED) },
modifier = Modifier.fillMaxWidth()
) {
Text("Test OTP Verification Failed")
}
}
}
// SignIn Error Example
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "SignIn Errors",
style = MaterialTheme.typography.titleMedium
)
Button(
onClick = { viewModel.testSignInError(SignInErrorCode.USER_NOT_FOUND) },
modifier = Modifier.fillMaxWidth()
) {
Text("Test User Not Found")
}
Button(
onClick = { viewModel.testSignInError(SignInErrorCode.ADD_DEVICE_REQUIRED) },
modifier = Modifier.fillMaxWidth()
) {
Text("Test Add Device Required")
}
}
}
}
// Show alerts
if (viewModel.showAlert) {
AlertDialog(
onDismissRequest = { viewModel.showAlert = false },
title = { Text(viewModel.alertTitle) },
text = { Text(viewModel.alertMessage) },
confirmButton = {
TextButton(onClick = { viewModel.showAlert = false }) {
Text("OK")
}
}
)
}
}
class ErrorHandlingViewModel : ViewModel() {
var showAlert by mutableStateOf(false)
private set
var alertTitle by mutableStateOf("")
private set
var alertMessage by mutableStateOf("")
private set
fun testSignUpError(errorCode: SignUpErrorCode) {
when (errorCode) {
SignUpErrorCode.USER_ALREADY_EXISTS -> {
alertTitle = "User Already Exists"
alertMessage = "This email is already registered. Please log in instead."
}
SignUpErrorCode.VERIFY_OTP_FAILED -> {
alertTitle = "Invalid OTP"
alertMessage = "The verification code is incorrect. Please try again."
}
SignUpErrorCode.GENERATE_OTP_FAILED -> {
alertTitle = "OTP Generation Failed"
alertMessage = "We couldn't send a verification code. Please try again."
}
SignUpErrorCode.NETWORK_ERROR -> {
alertTitle = "Network Error"
alertMessage = "Please check your internet connection and try again."
}
SignUpErrorCode.UNKNOWN_ERROR -> {
alertTitle = "Unknown Error"
alertMessage = "An unexpected error occurred. Please try again later."
}
else -> {
alertTitle = "Error"
alertMessage = "An error occurred during sign up."
}
}
showAlert = true
}
fun testSignInError(errorCode: SignInErrorCode) {
when (errorCode) {
SignInErrorCode.USER_NOT_FOUND -> {
alertTitle = "User Not Found"
alertMessage = "This email is not registered. Please sign up first."
}
SignInErrorCode.NETWORK_ERROR -> {
alertTitle = "Network Error"
alertMessage = "Please check your internet connection and try again."
}
SignInErrorCode.ADD_DEVICE_REQUIRED -> {
alertTitle = "Add Device Required"
alertMessage = "This device needs to be added to your account."
}
else -> {
alertTitle = "Error"
alertMessage = "An error occurred during sign in."
}
}
showAlert = true
}
}
Troubleshooting
Solution: Ensure your API key is correct and check if you need to add the INTERNET permission in your AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
Solution:
- Ensure you’re using the most recent OTP (codes typically expire after 5-10 minutes)
- Verify there are no leading/trailing spaces in the OTP input
- Check if the OTP has exactly 6 digits
Solution:
-
Add the necessary permission in AndroidManifest.xml:
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
-
Check if the device supports biometrics:
val biometricManager = BiometricManager.from(context) val canAuthenticate = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
Error Codes
Error Code | Description |
---|---|
USER_ALREADY_EXISTS | User is already registered in the system |
VERIFY_OTP_FAILED | Invalid or expired OTP |
GENERATE_OTP_FAILED | Failed to generate or send OTP |
NETWORK_ERROR | Connection or server error |
UNKNOWN_ERROR | Unspecified error |
Error Code | Description |
---|---|
USER_ALREADY_EXISTS | User is already registered in the system |
VERIFY_OTP_FAILED | Invalid or expired OTP |
GENERATE_OTP_FAILED | Failed to generate or send OTP |
NETWORK_ERROR | Connection or server error |
UNKNOWN_ERROR | Unspecified error |
Error Code | Description |
---|---|
USER_NOT_FOUND | User is not registered in the system |
NETWORK_ERROR | Connection or server error |
ADD_DEVICE_REQUIRED | Current device not registered to account |
UNKNOWN_ERROR | Unspecified error |
Error Code | Description |
---|---|
VERIFY_OTP_FAILED | Invalid or expired OTP |
GENERATE_OTP_FAILED | Failed to generate or send OTP |
NETWORK_ERROR | Connection or server error |
UNKNOWN_ERROR | Unspecified error |
Try it out!
SDK
Check out our Android SDK and try it yourself!
Example App
Check out our example app on GitHub for a complete implementation.