Android SDK
Sample Code
Common Android snippets for Petnow SDK usage.
Complete Pet Registration Example
Here's a complete example of implementing pet registration with the Petnow Android SDK:
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.lifecycleScope
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import io.petnow.PetnowApiClient
import io.petnow.model.*
import kotlinx.coroutines.launch
import java.io.File
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialize Petnow SDK
PetnowApiClient.init(
key = "YOUR_API_KEY",
isDebugMode = true,
onSuccess = {
Log.d("PetnowSDK", "SDK initialized successfully")
},
onError = { exception ->
Log.e("PetnowSDK", "SDK initialization failed", exception)
}
)
setContent {
PetRegistrationApp()
}
}
}
@Composable
fun PetRegistrationApp() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "petInfo") {
composable("petInfo") {
PetInfoScreen(
onNext = { petInfo ->
navController.navigate("camera/${petInfo.species}/${petInfo.name}/${petInfo.breed}")
}
)
}
composable("camera/{species}/{name}/{breed}") { backStackEntry ->
val species = backStackEntry.arguments?.getString("species") ?: "DOG"
val name = backStackEntry.arguments?.getString("name") ?: ""
val breed = backStackEntry.arguments?.getString("breed") ?: ""
CameraScreen(
species = PetSpecies.valueOf(species),
petName = name,
petBreed = breed,
onBack = { navController.popBackStack() },
onComplete = { fingerprintIds, appearanceIds ->
navController.navigate("result/$name/$breed/${fingerprintIds.joinToString(",")}/${appearanceIds.joinToString(",")}")
}
)
}
composable("result/{name}/{breed}/{fingerprintIds}/{appearanceIds}") { backStackEntry ->
val name = backStackEntry.arguments?.getString("name") ?: ""
val breed = backStackEntry.arguments?.getString("breed") ?: ""
val fingerprintIds = backStackEntry.arguments?.getString("fingerprintIds")?.split(",") ?: emptyList()
val appearanceIds = backStackEntry.arguments?.getString("appearanceIds")?.split(",") ?: emptyList()
RegistrationResultScreen(
petName = name,
petBreed = breed,
fingerprintIds = fingerprintIds,
appearanceIds = appearanceIds,
onFinish = {
navController.navigate("petInfo") {
popUpTo("petInfo") { inclusive = true }
}
}
)
}
}
}
data class PetInfo(
val name: String,
val breed: String,
val species: String
)
@Composable
fun PetInfoScreen(onNext: (PetInfo) -> Unit) {
var name by remember { mutableStateOf("") }
var breed by remember { mutableStateOf("") }
var species by remember { mutableStateOf("DOG") }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
TextField(
value = name,
onValueChange = { name = it },
label = { Text("Pet Name") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
TextField(
value = breed,
onValueChange = { breed = it },
label = { Text("Breed") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
RadioButton(
selected = species == "DOG",
onClick = { species = "DOG" }
)
Text("Dog")
Spacer(modifier = Modifier.width(16.dp))
RadioButton(
selected = species == "CAT",
onClick = { species = "CAT" }
)
Text("Cat")
}
Spacer(modifier = Modifier.height(32.dp))
Button(
onClick = {
if (name.isNotEmpty() && breed.isNotEmpty()) {
onNext(PetInfo(name, breed, species))
}
},
enabled = name.isNotEmpty() && breed.isNotEmpty()
) {
Text("Next: Capture Biometric Data")
}
}
}
@Composable
fun CameraScreen(
species: PetSpecies,
petName: String,
petBreed: String,
onBack: () -> Unit,
onComplete: (List<String>, List<String>) -> Unit
) {
val context = LocalContext.current
var isProcessing by remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
text = "Capture biometric data for $petName",
style = MaterialTheme.typography.h6
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Use the PetnowCameraFragment to capture biometric data. " +
"The fragment will handle camera permissions and detection automatically."
)
Spacer(modifier = Modifier.weight(1f))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Button(onClick = onBack) {
Text("Back")
}
Button(
onClick = {
// Simulate successful capture
// In real implementation, this would come from CameraFragment callbacks
isProcessing = true
// Simulate upload and registration
performPetRegistration(
context = context,
species = species,
petName = petName,
petBreed = petBreed,
onComplete = { fingerprintIds, appearanceIds ->
isProcessing = false
onComplete(fingerprintIds, appearanceIds)
}
)
},
enabled = !isProcessing
) {
if (isProcessing) {
CircularProgressIndicator(modifier = Modifier.size(20.dp))
Spacer(modifier = Modifier.width(8.dp))
Text("Processing...")
} else {
Text("Complete Registration")
}
}
}
}
}
@Composable
fun RegistrationResultScreen(
petName: String,
petBreed: String,
fingerprintIds: List<String>,
appearanceIds: List<String>,
onFinish: () -> Unit
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Registration Successful!",
style = MaterialTheme.typography.h4,
color = MaterialTheme.colors.primary
)
Spacer(modifier = Modifier.height(16.dp))
Text("Pet: $petName ($petBreed)")
Text("Fingerprints: ${fingerprintIds.size}")
Text("Appearances: ${appearanceIds.size}")
Spacer(modifier = Modifier.height(32.dp))
Button(onClick = onFinish) {
Text("Register Another Pet")
}
}
}
// Helper function for pet registration
private fun performPetRegistration(
context: android.content.Context,
species: PetSpecies,
petName: String,
petBreed: String,
onComplete: (List<String>, List<String>) -> Unit
) {
// Configure detection mode
PetnowApiClient.configureDetectionMode(
purpose = DetectionPurpose.PET_PROFILE_REGISTRATION,
species = species
)
// Simulate fingerprint upload
val fingerprintIds = mutableListOf<String>()
val appearanceIds = mutableListOf<String>()
// In real implementation, you would upload actual captured images
// For this example, we'll simulate the process
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
// Simulate successful uploads
fingerprintIds.add("fingerprint_001")
appearanceIds.add("appearance_001")
// Register pet
val metadata = """
{
"name": "$petName",
"breed": "$petBreed",
"registration_date": "${java.util.Date().toString()}"
}
""".trimIndent()
PetnowApiClient.registerPetProfile(
fingerPrints = fingerprintIds,
appearances = appearanceIds,
metadata = metadata,
onSuccess = { petId ->
Log.d("PetRegistration", "Pet registered with ID: $petId")
onComplete(fingerprintIds, appearanceIds)
},
onError = { exception ->
Log.e("PetRegistration", "Registration failed", exception)
// Handle error - show user feedback
}
)
}, 2000) // Simulate 2 second processing time
}Custom Camera Fragment Implementation
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import io.petnow.PetnowCameraFragment
import io.petnow.PetnowApiClient
import io.petnow.model.*
import kotlinx.coroutines.launch
import java.io.File
class CustomCameraFragment : PetnowCameraFragment(), PetnowCameraDetectionListener {
private var capturedFingerprints = mutableListOf<File>()
private var capturedAppearances = mutableListOf<File>()
private var onCaptureComplete: ((List<File>, List<File>) -> Unit)? = null
companion object {
fun newInstance(
species: PetSpecies,
purpose: DetectionPurpose,
onComplete: (List<File>, List<File>) -> Unit
): CustomCameraFragment {
return CustomCameraFragment().apply {
onCaptureComplete = onComplete
// Configure detection mode before creating fragment
PetnowApiClient.configureDetectionMode(
purpose = purpose,
species = species
)
}
}
}
override fun onAttach(context: android.content.Context) {
super.onAttach(context)
setPetnowCameraDetectionListener(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Add custom layout overlay
addCustomLayout(R.layout.fragment_custom_camera)
}
override fun onAddedCustomLayout(view: View) {
super.onAddedCustomLayout(view)
// Initialize custom UI elements
val statusText = view.findViewById<android.widget.TextView>(R.id.status_text)
val progressBar = view.findViewById<android.widget.ProgressBar>(R.id.progress_bar)
val captureButton = view.findViewById<android.widget.Button>(R.id.capture_button)
statusText.text = "Position your pet in the camera frame"
captureButton.setOnClickListener {
// Manually trigger detection if needed
resumeDetection()
}
}
override fun onDetectionStatus(primaryDetectionStatus: PetnowDetectionStatus) {
val statusText = view?.findViewById<android.widget.TextView>(R.id.status_text)
val captureButton = view?.findViewById<android.widget.Button>(R.id.capture_button)
when (primaryDetectionStatus) {
PetnowDetectionStatus.Detected -> {
statusText?.text = "Perfect! Biometric data captured successfully"
captureButton?.isEnabled = true
}
PetnowDetectionStatus.NoObject -> {
statusText?.text = "No pet detected. Please position your pet in frame"
captureButton?.isEnabled = false
}
PetnowDetectionStatus.TooClose -> {
statusText?.text = "Too close! Please move camera back"
captureButton?.isEnabled = false
}
PetnowDetectionStatus.TooFarAway -> {
statusText?.text = "Too far! Please move camera closer"
captureButton?.isEnabled = false
}
else -> {
statusText?.text = "Position your pet for best results"
captureButton?.isEnabled = false
}
}
}
override fun onDetectionProgress(progress: Int) {
val progressBar = view?.findViewById<android.widget.ProgressBar>(R.id.progress_bar)
val statusText = view?.findViewById<android.widget.TextView>(R.id.status_text)
progressBar?.progress = progress
if (progress >= 100) {
statusText?.text = "Detection complete!"
}
}
override fun onDetectionFinished(result: DetectionCaptureResult) {
when (result) {
is DetectionCaptureResult.Success -> {
capturedFingerprints.addAll(result.noseImageFiles)
capturedAppearances.addAll(result.faceImageFiles)
Log.d("CustomCameraFragment",
"Captured ${result.noseImageFiles.size} fingerprints and ${result.faceImageFiles.size} appearances")
// Notify parent activity/fragment
onCaptureComplete?.invoke(capturedFingerprints, capturedAppearances)
// Navigate back or show success message
parentFragmentManager.popBackStack()
}
is DetectionCaptureResult.Fail -> {
Log.e("CustomCameraFragment", "Detection failed")
// Show error message to user
}
}
}
override fun onDestroyView() {
super.onDestroyView()
capturedFingerprints.clear()
capturedAppearances.clear()
onCaptureComplete = null
}
}Error Handling and Best Practices
import io.petnow.PetnowApiClient
import io.petnow.PetnowApiException
// Comprehensive error handling
suspend fun performPetOperation() {
try {
// Configure detection mode
PetnowApiClient.configureDetectionMode(
purpose = DetectionPurpose.PET_PROFILE_REGISTRATION,
species = PetSpecies.DOG
)
// Your pet operation here
PetnowApiClient.getPetnowPets(
onSuccess = { pets ->
Log.d("PetOperation", "Found ${pets.size} pets")
},
onError = { exception ->
handleApiError(exception)
}
)
} catch (e: PetnowApiException) {
when (e) {
is PetnowApiException.NotInitializedException -> {
Log.e("PetOperation", "SDK not initialized")
// Show initialization error to user
}
is PetnowApiException.InvalidApiKeyException -> {
Log.e("PetOperation", "Invalid API key")
// Prompt user to check API key
}
is PetnowApiException.ClientException -> {
Log.e("PetOperation", "Client error: ${e.statusCode}")
// Handle client-side errors (4xx)
}
is PetnowApiException.ServerException -> {
Log.e("PetOperation", "Server error: ${e.statusCode}")
// Handle server-side errors (5xx)
}
is PetnowApiException.ValidationException -> {
Log.e("PetOperation", "Validation error: ${e.message}")
// Handle validation errors
}
}
} catch (e: Exception) {
Log.e("PetOperation", "Unexpected error", e)
// Handle unexpected errors
}
}
private fun handleApiError(exception: Exception) {
when (exception) {
is PetnowApiException -> {
// Handle specific Petnow API errors
Log.e("ApiError", "Petnow API error", exception)
}
else -> {
// Handle network or other errors
Log.e("ApiError", "Network or other error", exception)
}
}
}Best Practices
- Initialize SDK Early: Initialize PetnowApiClient in your Application class
- Handle Permissions: Request camera permissions before using camera features
- Configure Detection Mode: Always call
configureDetectionMode()before camera operations - Error Handling: Implement comprehensive error handling for all API calls
- Memory Management: Clean up camera resources when not needed
- User Feedback: Provide clear feedback during biometric capture process
- Testing: Test with both debug and production environments
- Thread Safety: Use appropriate coroutines for async operations
- Resource Cleanup: Always clean up temporary files and resources