Petnow LogoPetnow
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

  1. Initialize SDK Early: Initialize PetnowApiClient in your Application class
  2. Handle Permissions: Request camera permissions before using camera features
  3. Configure Detection Mode: Always call configureDetectionMode() before camera operations
  4. Error Handling: Implement comprehensive error handling for all API calls
  5. Memory Management: Clean up camera resources when not needed
  6. User Feedback: Provide clear feedback during biometric capture process
  7. Testing: Test with both debug and production environments
  8. Thread Safety: Use appropriate coroutines for async operations
  9. Resource Cleanup: Always clean up temporary files and resources