Production Quality - Testing, Performance, Security, Deployment (95 hours)
Build production-ready Android apps with comprehensive testing, performance optimization, and security implementation. Use this agent to master unit/integration/UI testing, memory profiling, SSL pinning, and staged Play Store deployments.
/plugin marketplace add pluginagentmarketplace/custom-plugin-android/plugin install android-development-assistant@pluginagentmarketplace-androidsonnetBuild production-ready Android applications with comprehensive testing, performance optimization, security implementation, and professional deployment. Master the complete path from development to production monitoring.
Prerequisite: Fundamentals, Platform, Data Management, Networking & Architecture agents Duration: 95 hours | Level: Advanced Topics: 8 major areas | Code Examples: 50+ real-world patterns
Production apps require multiple testing layers for quality assurance.
@RunWith(RobolectricTestRunner::class)
class UserViewModelTest {
private val userRepository = mockk<UserRepository>()
private lateinit var viewModel: UserViewModel
@Before
fun setup() {
viewModel = UserViewModel(userRepository)
}
@Test
fun loadUsers_showsLoadingState() = runTest {
val job = launch { viewModel.loadUsers() }
advanceUntilIdle()
assertEquals(UiState.Loading, viewModel.uiState.value)
job.cancel()
}
@Test
fun loadUsers_success_updates_state() = runTest {
val users = listOf(User(1, "John"), User(2, "Jane"))
coEvery { userRepository.getUsers() } returns Result.Success(users)
viewModel.loadUsers()
advanceUntilIdle()
assertEquals(UiState.Success(users), viewModel.uiState.value)
}
@Test
fun loadUsers_error_shows_error_state() = runTest {
val exception = Exception("Network error")
coEvery { userRepository.getUsers() } returns Result.Error(exception)
viewModel.loadUsers()
advanceUntilIdle()
assertEquals(UiState.Error("Network error"), viewModel.uiState.value)
}
}
// Test repository with FakeRepository
class FakeUserRepository : UserRepository {
var shouldFail = false
private var users = listOf(User(1, "John"))
override suspend fun getUsers(): Result<List<User>> {
return if (shouldFail) {
Result.Error(Exception("Fake error"))
} else {
Result.Success(users)
}
}
}
@RunWith(AndroidJUnit4::class)
class UserDaoIntegrationTest {
private lateinit var database: AppDatabase
private lateinit var userDao: UserDao
@Before
fun setup() {
database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
AppDatabase::class.java
).allowMainThreadQueries().build()
userDao = database.userDao()
}
@After
fun cleanup() {
database.close()
}
@Test
fun insertAndRetrieve() = runBlocking {
val user = User(1, "John", "john@example.com")
userDao.insertUser(user)
val retrieved = userDao.getUser(1)
assertEquals(user, retrieved)
}
@Test
fun observeUsers_emits_changes() = runTest {
val users = listOf(
User(1, "John", "john@example.com"),
User(2, "Jane", "jane@example.com")
)
userDao.insertUsers(users)
userDao.observeAllUsers().test {
assertEquals(users, awaitItem())
cancel()
}
}
}
@RunWith(AndroidJUnit4::class)
@HiltAndroidTest
class UserListScreenTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
@Before
fun setup() {
hiltRule.inject()
}
@Test
fun displayUsers_showsList() {
composeTestRule.apply {
onNodeWithTag("user_list").assertIsDisplayed()
onNodeWithText("John").assertIsDisplayed()
}
}
@Test
fun clickUser_navigates_to_detail() {
composeTestRule.apply {
onNodeWithText("John").performClick()
onNodeWithTag("user_detail").assertIsDisplayed()
}
}
}
// Traditional Espresso
@RunWith(AndroidJUnit4::class)
class UserActivityEspressoTest {
@get:Rule
val activityRule = ActivityScenarioRule(UserActivity::class.java)
@Test
fun loadUsers_displaysInRecyclerView() {
onView(withId(R.id.user_list))
.check(matches(isDisplayed()))
onView(withText("John"))
.check(matches(isDisplayed()))
}
@Test
fun clickUser_launchesDetailActivity() {
onView(withText("John"))
.perform(click())
intended(hasComponent(DetailActivity::class.java))
}
}
CPU Profiling:
→ Record method traces
→ Identify slow functions
→ Optimize hotspots
Memory Profiling:
→ Track heap allocations
→ Detect memory leaks
→ Monitor GC collections
Network Profiling:
→ Monitor API response times
→ Track data transmission
→ Identify bandwidth issues
// ❌ WRONG: Blocks main thread (causes ANR)
class UserActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread.sleep(2000) // 2 seconds blocks main thread!
loadUsers() // This can't start until sleep finishes
}
}
// ✅ CORRECT: Use coroutines for background work
class UserActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
// Runs on IO thread, doesn't block UI
delay(2000)
loadUsers()
}
}
}
// LeakCanary automatically detects leaks in debug builds
// Just add dependency:
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.x'
// Example of memory leak
class Singleton {
var context: Context? = null // ❌ LEAK: Holds onto Activity
}
// Fix: Use WeakReference
class Singleton {
var contextRef: WeakReference<Context>? = null // ✅ Safe
}
// Better: Avoid holding Context in Singleton
class Singleton(private val appContext: Context) { // ✅ Use ApplicationContext
// Now safe because ApplicationContext != Activity
}
// Profile with Profiler or dump frames
// adb shell dumpsys gfxinfo com.example.app
// ❌ SLOW: Creating too much garbage
fun drawFrame() {
for (i in 0..1000) {
val paint = Paint() // ❌ Allocates every frame
canvas.drawText("Text", 0f, 100f, paint)
}
}
// ✅ FAST: Reuse objects
class Renderer {
private val paint = Paint().apply { /* setup */ }
fun drawFrame() {
for (i in 0..1000) {
canvas.drawText("Text", 0f, 100f, paint) // Reuse
}
}
}
// RecyclerView optimization
class OptimizedAdapter : RecyclerView.Adapter<UserViewHolder>() {
// ✅ Efficient ViewHolder reuse
override fun onCreateViewHolder(parent: ViewGroup, type: Int): UserViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.item_user, parent, false)
return UserViewHolder(itemView) // Reused by RecyclerView
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
// Update existing ViewHolder (already inflated)
holder.bind(items[position])
}
}
android {
// Enable minification
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
// Split APK by density
bundle {
density.enableSplit = true
abi.enableSplit = true
}
// Use App Bundle instead of APK
// It automatically optimizes for each device
}
1. Improper Platform Usage
2. Insecure Data Storage
3. Insecure Communication
4. Insecure Authentication
5. Insufficient Cryptography
6. Insecure Authorization
7. Client Code Quality
8. Code Tampering
9. Reverse Engineering
10. Extraneous Functionality
// ✅ Encrypt sensitive data
class SecurePreferences @Inject constructor(context: Context) {
private val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
private val prefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
fun saveToken(token: String) {
prefs.edit {
putString("auth_token", token)
apply()
}
}
fun getToken(): String? = prefs.getString("auth_token", null)
fun clearToken() {
prefs.edit {
remove("auth_token")
apply()
}
}
}
// ✅ Encrypt files
fun encryptFile(context: Context, file: File, data: ByteArray) {
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val encryptedFile = EncryptedFile.Builder(
context,
file,
masterKey,
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()
encryptedFile.openFileOutput().use { output ->
output.write(data)
}
}
// ✅ Certificate pinning for sensitive APIs
val certificatePinner = CertificatePinner.Builder()
.add(
"api.banking.com",
"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
"sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="
)
.build()
val okHttpClient = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.banking.com/")
.client(okHttpClient)
.build()
# proguard-rules.pro
# Keep custom classes used by reflection
-keep public class com.example.** { *; }
# Keep annotations
-keepattributes *Annotation*
# Keep enums
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# Remove verbose logging in production
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
public static *** i(...);
}
# Keep Hilt-generated classes
-keep,allowobfuscation class dagger.hilt.**
-keep,allowobfuscation interface dagger.hilt.**
# Keep database classes
-keep,allowobfuscation class androidx.room.** { *; }
**BEFORE UPLOADING:**
- [ ] Version code: incremented
- [ ] Version name: updated (X.Y.Z format)
- [ ] App signed with production keystore
- [ ] ProGuard/R8 enabled (minifyEnabled = true)
- [ ] All tests passing locally
- [ ] Tested on 3+ real devices
- [ ] No obvious crashes
- [ ] Privacy policy published
- [ ] Screenshots (2-8 per language)
- [ ] App description complete
- [ ] Content rating questionnaire filled
- [ ] Pricing and distribution set
**BUILD COMMAND:**
./gradlew bundleRelease # Creates aab file for Play Store
./gradlew assembleRelease # Creates apk (if needed for side-loading)
Phase 1: Internal Testing (1-2 days)
↓ (Monitor crashes, logs)
Phase 2: Closed Testing/Beta (7-14 days)
↓ (Get feedback from testers)
Phase 3: Open Testing (optional, 7+ days)
↓ (Public beta)
Phase 4: Production (gradual rollout)
├── 5% rollout (1-2 days)
├── 25% rollout (2-3 days)
├── 50% rollout (2-3 days)
└── 100% rollout (complete release)
Benefits:
✅ Catch critical bugs early
✅ Reduce damage from issues
✅ Quick rollback if needed
✅ Gradual user exposure
# 1. Clean and build
./gradlew clean bundleRelease
# 2. Sign with keystore (can be done in Android Studio)
jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 \
-keystore release-key.jks app-release.aab your-key-name
# 3. Verify signing
jarsigner -verify -verbose -certs app-release.aab
# 4. Upload to Google Play Console
# → App → Release → Production → Create release
# → Upload app bundle
# → Set staged rollout percentage
# → Review and publish
# 5. Monitor post-release
# → Check crash metrics
# → Monitor ratings
# → Check install/uninstall rates
// Add dependency
// implementation 'com.google.firebase:firebase-crashlytics-ktx'
// Enable crash reporting
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true)
// Log custom errors
try {
riskyOperation()
} catch (e: Exception) {
FirebaseCrashlytics.getInstance().recordException(e)
}
// Track custom events
FirebaseAnalytics.getInstance().logEvent("user_signup", Bundle().apply {
putString("method", "email")
})
// Set user ID for tracking
FirebaseCrashlytics.getInstance().setUserId("user123")
// Add custom keys for context
FirebaseCrashlytics.getInstance().setCustomKey("user_plan", "premium")
class PerformanceMonitor {
fun trackApiCallDuration(endpoint: String, durationMs: Long) {
// Send to analytics service
if (durationMs > 5000) {
// Alert if slow
FirebaseCrashlytics.getInstance()
.log("Slow API call: $endpoint took ${durationMs}ms")
}
}
fun trackMemoryUsage() {
val runtime = Runtime.getRuntime()
val usedMemory = runtime.totalMemory() - runtime.freeMemory()
val maxMemory = runtime.maxMemory()
val percentUsed = (usedMemory.toFloat() / maxMemory) * 100
if (percentUsed > 90) {
FirebaseCrashlytics.getInstance()
.log("High memory usage: $percentUsed%")
}
}
}
✅ Testing
✅ Performance
✅ Security
✅ Deployment
✅ Monitoring
Week 1-2 (20h): Unit & Integration Testing
Week 3 (15h): UI Testing & Performance
Week 4 (15h): Security Implementation
Week 5 (20h): Deployment & Release
Week 6 (15h): Monitoring & Production
Mastery Checkpoint:
Learning Hours: 95 hours | Level: Advanced Completion: Master Android Developer Roadmap!
| Issue | Root Cause | Solution |
|---|---|---|
| Test flaky | Async timing issue | Use runTest, advanceUntilIdle |
| Memory leak detected | Unreleased resource | Fix with LeakCanary suggestion |
| ANR crash | Main thread blocked | Move work to coroutine/IO |
| ProGuard strips class | Missing keep rule | Add -keep in proguard-rules.pro |
| Play Store rejected | Policy violation | Review rejection reason, fix issue |
□ Are tests deterministic? Check async handling
□ Is memory profiled? Use Android Profiler
□ Is APK size optimized? Check bundle analyzer
□ Is ProGuard configured? Test release build
□ Is crash reporting enabled? Verify Crashlytics
□ Is staged rollout planned? 5% → 25% → 50% → 100%
// Track performance in production
val startTime = System.currentTimeMillis()
doWork()
val duration = System.currentTimeMillis() - startTime
if (duration > 1000) {
FirebaseCrashlytics.getInstance().log("Slow operation: ${duration}ms")
}
□ Version code incremented
□ Release notes written
□ ProGuard tested
□ Crashlytics configured
□ Analytics verified
□ Screenshots updated
□ Privacy policy current
□ Staged rollout set
You are an elite AI agent architect specializing in crafting high-performance agent configurations. Your expertise lies in translating user requirements into precisely-tuned agent specifications that maximize effectiveness and reliability.