Development, Testing, and Sample App
This guide covers development best practices, testing strategies, and information about the Entrupy Android SDK sample application.
Development Environment Setup
Prerequisites
- Android Studio: Latest stable version (recommended: Android Studio Hedgehog or newer)
- Android SDK: API level 24+ (Android 7.0 Nougat)
- Gradle: Version 7.0+ (recommended: 8.0+)
- Kotlin: Version 1.5+ (recommended: 1.9+)
- Java: Version 8+ (if using Java instead of Kotlin)
Project Configuration
1. Add Repository
Ensure Maven Central is included in your project's repository configuration:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
2. Add Dependencies
// app/build.gradle.kts
dependencies {
implementation("com.entrupy:sdk:1.0.0")
// Optional: For testing
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}
3. Configure Permissions
The SDK automatically handles permission requests, but ensure your app's manifest includes:
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-feature android:name="android.hardware.camera.any" />
Testing Strategies
1. Unit Testing
Test your SDK integration logic independently:
// Example unit test
class EntrupySDKTest {
@Test
fun testUserAuthorization() {
// Mock SDK responses
val mockSdk = mock<EntrupySdk>()
whenever(mockSdk.generateAuthorizationRequest()).thenReturn("test_request")
// Test authorization flow
val authRequest = mockSdk.generateAuthorizationRequest()
assertEquals("test_request", authRequest)
}
@Test
fun testMetadataValidation() {
val validMetadata = mapOf(
"brand" to "Nike",
"style_name" to "Air Jordan 1",
"customer_item_id" to "test_123"
)
assertTrue(isValidMetadata(validMetadata))
val invalidMetadata = mapOf(
"brand" to "Nike"
// Missing required fields
)
assertFalse(isValidMetadata(invalidMetadata))
}
}
2. Integration Testing
Test the complete flow with your backend:
// Example integration test
@RunWith(AndroidJUnit4::class)
class EntrupyIntegrationTest {
@Test
fun testCompleteAuthenticationFlow() {
// 1. Initialize SDK
Entrupy.init(ApplicationProvider.getApplicationContext(), "test_api_key")
// 2. Authorize user
val authRequest = EntrupySdk.getInstance().generateAuthorizationRequest()
val signedRequest = callBackendForAuthorization(authRequest)
// 3. Login to SDK
var loginSuccess = false
EntrupySdk.getInstance().login(signedRequest, object : EntrupyLoginCallback {
override fun onSuccess(expirationTimestamp: Long) {
loginSuccess = true
}
override fun onFailure(errorCode: EntrupyErrorCode, message: String) {
fail("Login failed: $message")
}
})
// 4. Verify authorization
assertTrue(EntrupySdk.getInstance().isAuthorizationValid())
// 5. Test capture flow (if needed)
// Note: This would require a real device or emulator with camera
}
}
3. UI Testing
Test the user interface components:
@RunWith(AndroidJUnit4::class)
class EntrupyUITest {
@get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class.java)
@Test
fun testCaptureFlowLaunch() {
// Navigate to capture button
onView(withId(R.id.btn_start_capture))
.perform(click())
// Verify capture flow is launched
// Note: This would require custom assertions based on your UI
}
@Test
fun testPermissionHandling() {
// Test permission denial flow
// This requires special setup to simulate permission denial
}
}
4. Mock Backend for Testing
Create a mock backend for development and testing:
// Mock backend implementation
class MockEntrupyBackend {
fun authorizeUser(sdkRequest: String): String {
// Return a mock signed request
return "mock_signed_request_${System.currentTimeMillis()}"
}
fun getItemStatus(customerItemId: String): ItemStatus {
return ItemStatus(
customerItemId = customerItemId,
status = "completed",
result = mapOf("authenticity" to "authentic"),
eta = null
)
}
}
Sample Application
The Entrupy Android SDK includes a comprehensive sample application that demonstrates:
Features Demonstrated
-
Complete Integration Flow
- SDK initialization
- User authorization
- Capture flow integration
- Result handling
-
Best Practices
- Error handling
- Session management
- UI/UX patterns
- Backend integration
-
Advanced Features
- Custom UI theming
- Multi-language support
- Offline handling
- Analytics integration
Sample App Structure
sample-app/
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/com/entrupy/sample/
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── AuthActivity.kt
│ │ │ │ ├── CaptureActivity.kt
│ │ │ │ ├── ResultActivity.kt
│ │ │ │ ├── network/
│ │ │ │ │ ├── ApiService.kt
│ │ │ │ │ └── RetrofitClient.kt
│ │ │ │ └── utils/
│ │ │ │ ├── EntrupyManager.kt
│ │ │ │ └── SessionManager.kt
│ │ │ └── res/
│ │ │ ├── layout/
│ │ │ ├── values/
│ │ │ └── drawable/
│ │ └── test/
│ └── build.gradle.kts
├── backend/
│ ├── server.js
│ ├── package.json
│ └── README.md
└── README.md
Running the Sample App
-
Clone the Repository
git clone https://github.com/entrupy/android-sdk-sample.git
cd android-sdk-sample -
Setup Backend
cd backend
npm install
npm start -
Configure Sample App
- Update
ApiService.kt
with your backend URL - Add your Entrupy API key to
local.properties
- Update
-
Run the App
cd ..
./gradlew assembleDebug
Key Sample App Components
EntrupyManager.kt
class EntrupyManager private constructor() {
companion object {
@Volatile
private var INSTANCE: EntrupyManager? = null
fun getInstance(): EntrupyManager {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: EntrupyManager().also { INSTANCE = it }
}
}
}
fun initialize(context: Context) {
Entrupy.init(context, BuildConfig.ENTRUPY_API_KEY)
}
fun authorizeUser(callback: (Boolean) -> Unit) {
// Implementation of authorization flow
}
fun startCapture(activity: Activity, metadata: Map<String, String>) {
// Implementation of capture flow
}
}
SessionManager.kt
class SessionManager(context: Context) {
private val sharedPrefs = context.getSharedPreferences("entrupy_session", Context.MODE_PRIVATE)
fun saveTokenExpiry(expiryTimestamp: Long) {
sharedPrefs.edit()
.putLong("token_expiry", expiryTimestamp)
.apply()
}
fun isTokenValid(): Boolean {
val expiry = sharedPrefs.getLong("token_expiry", 0L)
return System.currentTimeMillis() < (expiry - 60_000) // 1-minute buffer
}
fun clearSession() {
sharedPrefs.edit().clear().apply()
EntrupySdk.getInstance().logout()
}
}
Development Best Practices
1. Error Handling
// Centralized error handling
object ErrorHandler {
fun handleSDKError(errorCode: EntrupyErrorCode, message: String) {
when (errorCode) {
EntrupyErrorCode.AUTHORIZATION_EXPIRED -> {
// Re-authorize user
reauthorizeUser()
}
EntrupyErrorCode.NETWORK_ERROR -> {
// Show network error
showNetworkError()
}
else -> {
// Log and show generic error
Log.e("Entrupy", "Error: $errorCode - $message")
showGenericError()
}
}
}
}
2. Logging
// Structured logging
object EntrupyLogger {
private const val TAG = "EntrupySDK"
fun logEvent(event: String, properties: Map<String, Any> = emptyMap()) {
Log.d(TAG, "Event: $event, Properties: $properties")
// Send to analytics service
}
fun logError(error: Throwable, context: String = "") {
Log.e(TAG, "Error in $context", error)
// Send to crash reporting service
}
}
3. Configuration Management
// Environment-specific configuration
object Config {
const val API_BASE_URL = "https://api.entrupy.com"
const val WEBHOOK_URL = "https://your-backend.com/webhooks"
// Environment-specific settings
object Environment {
const val PRODUCTION = "production"
const val STAGING = "staging"
const val DEVELOPMENT = "development"
}
val currentEnvironment = if (BuildConfig.DEBUG) {
Environment.DEVELOPMENT
} else {
Environment.PRODUCTION
}
}
Debugging Tips
1. Enable Debug Logging
// Enable detailed logging in debug builds
if (BuildConfig.DEBUG) {
EntrupySdk.getInstance().setDebugMode(true)
}
2. Network Inspection
Use tools like Charles Proxy or Flipper to inspect network requests:
// Add network interceptor for debugging
OkHttpClient.Builder()
.addInterceptor { chain ->
val request = chain.request()
Log.d("Network", "Request: ${request.url}")
val response = chain.proceed(request)
Log.d("Network", "Response: ${response.code}")
response
}
.build()
3. UI Inspection
Use Android Studio's Layout Inspector to debug UI issues:
// Add debug information to views
if (BuildConfig.DEBUG) {
view.setTag(R.id.debug_tag, "entrupy_capture_button")
}