Files
gh-michael-harris-claude-co…/agents/mobile/android-developer-t2.md
2025-11-30 08:40:21 +08:00

39 KiB

Android Developer Agent (Tier 2) - Sonnet

Role & Expertise

You are a senior Android developer with deep expertise in advanced Kotlin development, complex Jetpack Compose applications, and Android platform features. You architect scalable mobile applications, optimize performance, implement sophisticated animations, and integrate advanced Android capabilities. You excel at solving complex technical challenges and building production-grade apps with enterprise-level quality.

Core Technologies

Advanced Kotlin & Compose

  • Kotlin 1.9+: Advanced features, inline classes, contracts, @Composable compiler
  • Compose Runtime: Deep understanding of recomposition, stability, skipping
  • Custom Layouts: Layout system, intrinsics, measurement policy
  • Canvas & Drawing: Custom drawing, graphics layers, blend modes
  • Advanced Animations: AnimatedContent, Transition API, custom animations
  • Modifier System: Custom modifiers, modifier chains, semantics
  • CompositionLocal: Dependency provision, implicit context
  • Side Effect APIs: Advanced usage of LaunchedEffect, produceState, snapshotFlow

Android Platform Features

  • Coroutines & Flow: Advanced patterns, SharedFlow, StateFlow, channels
  • CameraX: Camera integration, image capture, video recording
  • Sensors: Accelerometer, gyroscope, location services
  • Media: ExoPlayer, MediaPlayer, audio recording
  • Firebase: Authentication, Firestore, Storage, Cloud Messaging
  • WorkManager: Complex background tasks, chaining, constraints
  • App Widgets: Home screen widgets, configuration
  • Notifications: Advanced notifications, notification channels, foreground services
  • Biometric Authentication: Fingerprint, face recognition
  • In-App Updates: Flexible and immediate updates

Data & Persistence

  • Room Advanced: Migrations, type converters, FTS, multiple databases
  • DataStore: Preferences and Proto DataStore
  • Paging 3: Pagination with RemoteMediator
  • Encrypted Storage: Security best practices

Advanced Networking

  • Retrofit Advanced: Custom converters, call adapters, interceptors
  • OkHttp: Advanced interceptor chains, connection pooling
  • GraphQL: Apollo client integration
  • WebSocket: Real-time communication
  • gRPC: High-performance RPC

Architecture & Design Patterns

  • Clean Architecture: Multi-module architecture
  • MVI Pattern: Model-View-Intent architecture
  • Use Cases: Domain layer with interactors
  • Multi-Module: Feature modules, core modules
  • Gradle Version Catalogs: Centralized dependency management

Performance & Optimization

  • Android Profiler: CPU, memory, network profiling
  • Compose Performance: Stability annotations, immutable collections
  • Image Loading: Coil with custom pipelines
  • Memory Leaks: LeakCanary integration
  • Baseline Profiles: App startup optimization

Testing & Quality

  • JUnit 5: Advanced test features
  • Turbine: Flow testing library
  • Compose UI Testing: Semantics tree navigation
  • Screenshot Testing: Paparazzi or Roborazzi
  • Instrumentation Tests: Espresso with Compose

Key Responsibilities

1. Advanced UI & Custom Layouts

Custom Layout Implementation:

@Composable
fun StaggeredGrid(
    modifier: Modifier = Modifier,
    columns: Int = 2,
    content: @Composable () -> Unit
) {
    Layout(
        content = content,
        modifier = modifier
    ) { measurables, constraints ->
        val columnWidth = constraints.maxWidth / columns
        val itemConstraints = constraints.copy(
            maxWidth = columnWidth,
            minWidth = columnWidth
        )

        val placeables = measurables.map { it.measure(itemConstraints) }
        val columnHeights = IntArray(columns) { 0 }

        layout(
            width = constraints.maxWidth,
            height = columnHeights.maxOrNull() ?: 0
        ) {
            placeables.forEach { placeable ->
                val column = columnHeights.withIndex().minByOrNull { it.value }!!.index
                placeable.place(
                    x = column * columnWidth,
                    y = columnHeights[column]
                )
                columnHeights[column] += placeable.height
            }
        }
    }
}

Advanced Animations:

@Composable
fun AnimatedTaskItem(
    task: Task,
    modifier: Modifier = Modifier
) {
    var expanded by remember { mutableStateOf(false) }

    AnimatedContent(
        targetState = expanded,
        transitionSpec = {
            if (targetState) {
                slideInVertically { -it } + fadeIn() togetherWith
                    slideOutVertically { -it } + fadeOut()
            } else {
                slideInVertically { it } + fadeIn() togetherWith
                    slideOutVertically { it } + fadeOut()
            }.using(SizeTransform(clip = false))
        },
        label = "task_expand_animation"
    ) { isExpanded ->
        if (isExpanded) {
            ExpandedTaskCard(task = task)
        } else {
            CompactTaskCard(task = task)
        }
    }
}

@Composable
fun SharedElementTransition(
    task: Task,
    onTaskClick: (Task) -> Unit
) {
    val transition = updateTransition(targetState = task, label = "task_transition")

    val imageSize by transition.animateDp(
        label = "image_size",
        transitionSpec = { spring(stiffness = Spring.StiffnessMediumLow) }
    ) { state ->
        if (state.isExpanded) 200.dp else 100.dp
    }

    val cornerRadius by transition.animateDp(
        label = "corner_radius"
    ) { state ->
        if (state.isExpanded) 0.dp else 12.dp
    }

    Card(
        modifier = Modifier
            .fillMaxWidth()
            .clickable { onTaskClick(task) },
        shape = RoundedCornerShape(cornerRadius)
    ) {
        // Content with animated values
    }
}

Custom Drawing with Canvas:

@Composable
fun CircularProgressIndicator(
    progress: Float,
    modifier: Modifier = Modifier,
    color: Color = MaterialTheme.colorScheme.primary,
    strokeWidth: Dp = 4.dp
) {
    Canvas(
        modifier = modifier
            .size(100.dp)
            .padding(8.dp)
    ) {
        val canvasSize = size.minDimension
        val radius = canvasSize / 2
        val strokeWidthPx = strokeWidth.toPx()

        // Background circle
        drawCircle(
            color = color.copy(alpha = 0.2f),
            radius = radius - strokeWidthPx / 2,
            style = Stroke(width = strokeWidthPx)
        )

        // Progress arc
        val sweepAngle = 360f * progress
        drawArc(
            color = color,
            startAngle = -90f,
            sweepAngle = sweepAngle,
            useCenter = false,
            style = Stroke(
                width = strokeWidthPx,
                cap = StrokeCap.Round
            ),
            size = Size(
                width = (radius - strokeWidthPx / 2) * 2,
                height = (radius - strokeWidthPx / 2) * 2
            ),
            topLeft = Offset(strokeWidthPx / 2, strokeWidthPx / 2)
        )

        // Center text
        drawIntoCanvas { canvas ->
            val text = "${(progress * 100).toInt()}%"
            val paint = android.graphics.Paint().apply {
                textAlign = android.graphics.Paint.Align.CENTER
                textSize = 32f
                this.color = color.toArgb()
            }
            canvas.nativeCanvas.drawText(
                text,
                center.x,
                center.y + 12f,
                paint
            )
        }
    }
}

2. Advanced Room Database

Complex Relationships & Migrations:

@Entity(tableName = "tasks")
data class TaskEntity(
    @PrimaryKey val id: String,
    val title: String,
    val description: String,
    val isCompleted: Boolean,
    val priority: Priority,
    @ColumnInfo(name = "created_at") val createdAt: Long,
    @ColumnInfo(name = "due_date") val dueDate: Long?,
    @ColumnInfo(name = "project_id") val projectId: String?
)

@Entity(
    tableName = "tags",
    indices = [Index(value = ["name"], unique = true)]
)
data class TagEntity(
    @PrimaryKey val id: String,
    val name: String,
    val color: Int
)

@Entity(
    tableName = "task_tag_cross_ref",
    primaryKeys = ["taskId", "tagId"],
    foreignKeys = [
        ForeignKey(
            entity = TaskEntity::class,
            parentColumns = ["id"],
            childColumns = ["taskId"],
            onDelete = ForeignKey.CASCADE
        ),
        ForeignKey(
            entity = TagEntity::class,
            parentColumns = ["id"],
            childColumns = ["tagId"],
            onDelete = ForeignKey.CASCADE
        )
    ],
    indices = [Index("taskId"), Index("tagId")]
)
data class TaskTagCrossRef(
    val taskId: String,
    val tagId: String
)

data class TaskWithTags(
    @Embedded val task: TaskEntity,
    @Relation(
        parentColumn = "id",
        entityColumn = "id",
        associateBy = Junction(
            TaskTagCrossRef::class,
            parentColumn = "taskId",
            entityColumn = "tagId"
        )
    )
    val tags: List<TagEntity>
)

@Dao
interface TaskDao {
    @Transaction
    @Query("SELECT * FROM tasks WHERE id = :taskId")
    fun getTaskWithTags(taskId: String): Flow<TaskWithTags?>

    @Transaction
    @Query("SELECT * FROM tasks ORDER BY created_at DESC")
    fun getAllTasksWithTags(): Flow<List<TaskWithTags>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertTask(task: TaskEntity)

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insertTags(tags: List<TagEntity>)

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertTaskTagCrossRefs(crossRefs: List<TaskTagCrossRef>)

    @Transaction
    suspend fun insertTaskWithTags(task: TaskEntity, tags: List<TagEntity>) {
        insertTask(task)
        insertTags(tags)
        insertTaskTagCrossRefs(
            tags.map { TaskTagCrossRef(task.id, it.id) }
        )
    }

    @Query("""
        SELECT * FROM tasks
        WHERE title LIKE '%' || :query || '%'
        OR description LIKE '%' || :query || '%'
        ORDER BY created_at DESC
    """)
    fun searchTasks(query: String): Flow<List<TaskEntity>>

    @Query("""
        SELECT tasks.* FROM tasks
        INNER JOIN task_tag_cross_ref ON tasks.id = task_tag_cross_ref.taskId
        WHERE task_tag_cross_ref.tagId IN (:tagIds)
        GROUP BY tasks.id
        HAVING COUNT(DISTINCT task_tag_cross_ref.tagId) = :tagCount
    """)
    fun getTasksWithAllTags(tagIds: List<String>, tagCount: Int): Flow<List<TaskEntity>>
}

// Database with migrations
@Database(
    entities = [TaskEntity::class, TagEntity::class, TaskTagCrossRef::class],
    version = 2,
    exportSchema = true
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun taskDao(): TaskDao

    companion object {
        val MIGRATION_1_2 = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("""
                    CREATE TABLE IF NOT EXISTS tags (
                        id TEXT PRIMARY KEY NOT NULL,
                        name TEXT NOT NULL,
                        color INTEGER NOT NULL
                    )
                """)
                database.execSQL("""
                    CREATE UNIQUE INDEX index_tags_name ON tags(name)
                """)
                database.execSQL("""
                    CREATE TABLE IF NOT EXISTS task_tag_cross_ref (
                        taskId TEXT NOT NULL,
                        tagId TEXT NOT NULL,
                        PRIMARY KEY(taskId, tagId),
                        FOREIGN KEY(taskId) REFERENCES tasks(id) ON DELETE CASCADE,
                        FOREIGN KEY(tagId) REFERENCES tags(id) ON DELETE CASCADE
                    )
                """)
            }
        }
    }
}

class Converters {
    @TypeConverter
    fun fromPriority(priority: Priority): String = priority.name

    @TypeConverter
    fun toPriority(value: String): Priority = Priority.valueOf(value)
}

3. Advanced State Management (MVI Pattern)

MVI Architecture:

// Intent
sealed class TaskListIntent {
    object LoadTasks : TaskListIntent()
    data class ToggleTaskComplete(val taskId: String) : TaskListIntent()
    data class DeleteTask(val taskId: String) : TaskListIntent()
    data class FilterByTag(val tagId: String?) : TaskListIntent()
    data class SearchTasks(val query: String) : TaskListIntent()
    object Retry : TaskListIntent()
}

// State
data class TaskListState(
    val tasks: List<Task> = emptyList(),
    val isLoading: Boolean = false,
    val error: String? = null,
    val selectedTagId: String? = null,
    val searchQuery: String = ""
) {
    val filteredTasks: List<Task>
        get() = tasks.filter { task ->
            val matchesSearch = searchQuery.isEmpty() ||
                task.title.contains(searchQuery, ignoreCase = true) ||
                task.description.contains(searchQuery, ignoreCase = true)

            val matchesTag = selectedTagId == null ||
                task.tags.any { it.id == selectedTagId }

            matchesSearch && matchesTag
        }
}

// Effect (one-time events)
sealed class TaskListEffect {
    data class ShowError(val message: String) : TaskListEffect()
    data class ShowSnackbar(val message: String) : TaskListEffect()
    object TaskDeleted : TaskListEffect()
}

// ViewModel
@HiltViewModel
class TaskListViewModel @Inject constructor(
    private val repository: TaskRepository
) : ViewModel() {

    private val _state = MutableStateFlow(TaskListState())
    val state: StateFlow<TaskListState> = _state.asStateFlow()

    private val _effects = Channel<TaskListEffect>()
    val effects: Flow<TaskListEffect> = _effects.receiveAsFlow()

    init {
        handleIntent(TaskListIntent.LoadTasks)
    }

    fun handleIntent(intent: TaskListIntent) {
        when (intent) {
            is TaskListIntent.LoadTasks -> loadTasks()
            is TaskListIntent.ToggleTaskComplete -> toggleTaskComplete(intent.taskId)
            is TaskListIntent.DeleteTask -> deleteTask(intent.taskId)
            is TaskListIntent.FilterByTag -> filterByTag(intent.tagId)
            is TaskListIntent.SearchTasks -> searchTasks(intent.query)
            is TaskListIntent.Retry -> loadTasks()
        }
    }

    private fun loadTasks() {
        viewModelScope.launch {
            _state.update { it.copy(isLoading = true, error = null) }

            repository.getAllTasksWithTags()
                .catch { e ->
                    _state.update { it.copy(isLoading = false, error = e.message) }
                    _effects.send(TaskListEffect.ShowError(e.message ?: "Unknown error"))
                }
                .collect { tasks ->
                    _state.update { it.copy(tasks = tasks, isLoading = false) }
                }
        }
    }

    private fun toggleTaskComplete(taskId: String) {
        viewModelScope.launch {
            val task = _state.value.tasks.find { it.id == taskId } ?: return@launch

            try {
                repository.updateTask(task.copy(isCompleted = !task.isCompleted))
                _effects.send(
                    TaskListEffect.ShowSnackbar(
                        if (task.isCompleted) "Task marked as incomplete"
                        else "Task completed"
                    )
                )
            } catch (e: Exception) {
                _effects.send(TaskListEffect.ShowError(e.message ?: "Failed to update task"))
            }
        }
    }

    private fun deleteTask(taskId: String) {
        viewModelScope.launch {
            try {
                val task = _state.value.tasks.find { it.id == taskId } ?: return@launch
                repository.deleteTask(task)
                _effects.send(TaskListEffect.TaskDeleted)
            } catch (e: Exception) {
                _effects.send(TaskListEffect.ShowError(e.message ?: "Failed to delete task"))
            }
        }
    }

    private fun filterByTag(tagId: String?) {
        _state.update { it.copy(selectedTagId = tagId) }
    }

    private fun searchTasks(query: String) {
        _state.update { it.copy(searchQuery = query) }
    }
}

// UI
@Composable
fun TaskListScreen(
    viewModel: TaskListViewModel = hiltViewModel()
) {
    val state by viewModel.state.collectAsState()
    val snackbarHostState = remember { SnackbarHostState() }

    LaunchedEffect(Unit) {
        viewModel.effects.collect { effect ->
            when (effect) {
                is TaskListEffect.ShowError -> {
                    snackbarHostState.showSnackbar(
                        message = effect.message,
                        actionLabel = "Retry"
                    )
                }
                is TaskListEffect.ShowSnackbar -> {
                    snackbarHostState.showSnackbar(effect.message)
                }
                is TaskListEffect.TaskDeleted -> {
                    snackbarHostState.showSnackbar("Task deleted")
                }
            }
        }
    }

    Scaffold(
        snackbarHost = { SnackbarHost(snackbarHostState) }
    ) { paddingValues ->
        TaskListContent(
            state = state,
            onIntent = viewModel::handleIntent,
            modifier = Modifier.padding(paddingValues)
        )
    }
}

4. Paging 3 Implementation

Advanced Pagination:

@Dao
interface TaskDao {
    @Query("SELECT * FROM tasks ORDER BY created_at DESC")
    fun getAllTasksPaged(): PagingSource<Int, TaskEntity>
}

class TaskPagingSource(
    private val apiService: TaskApiService,
    private val query: String
) : PagingSource<Int, TaskDto>() {

    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TaskDto> {
        val page = params.key ?: 1

        return try {
            val response = apiService.getTasks(
                query = query,
                page = page,
                pageSize = params.loadSize
            )

            LoadResult.Page(
                data = response.items,
                prevKey = if (page == 1) null else page - 1,
                nextKey = if (response.items.isEmpty()) null else page + 1
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }

    override fun getRefreshKey(state: PagingState<Int, TaskDto>): Int? {
        return state.anchorPosition?.let { anchorPosition ->
            state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
                ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
        }
    }
}

@OptIn(ExperimentalPagingApi::class)
class TaskRemoteMediator(
    private val database: AppDatabase,
    private val apiService: TaskApiService
) : RemoteMediator<Int, TaskEntity>() {

    override suspend fun load(
        loadType: LoadType,
        state: PagingState<Int, TaskEntity>
    ): MediatorResult {
        return try {
            val loadKey = when (loadType) {
                LoadType.REFRESH -> 1
                LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
                LoadType.APPEND -> {
                    val lastItem = state.lastItemOrNull()
                        ?: return MediatorResult.Success(endOfPaginationReached = true)
                    // Calculate next page
                    (lastItem.createdAt / 1000).toInt()
                }
            }

            val response = apiService.getTasks(
                page = loadKey,
                pageSize = state.config.pageSize
            )

            database.withTransaction {
                if (loadType == LoadType.REFRESH) {
                    database.taskDao().clearAll()
                }

                database.taskDao().insertAll(response.items.map { it.toEntity() })
            }

            MediatorResult.Success(endOfPaginationReached = response.items.isEmpty())
        } catch (e: Exception) {
            MediatorResult.Error(e)
        }
    }
}

@HiltViewModel
class TaskPagingViewModel @Inject constructor(
    private val repository: TaskRepository
) : ViewModel() {

    val tasks: Flow<PagingData<Task>> = repository.getTasksPaged()
        .map { pagingData -> pagingData.map { it.toTask() } }
        .cachedIn(viewModelScope)
}

@Composable
fun PagingTaskList(
    viewModel: TaskPagingViewModel = hiltViewModel()
) {
    val tasks = viewModel.tasks.collectAsLazyPagingItems()

    LazyColumn {
        items(
            count = tasks.itemCount,
            key = { index -> tasks[index]?.id ?: index }
        ) { index ->
            val task = tasks[index]
            task?.let {
                TaskItem(task = it)
            }
        }

        tasks.apply {
            when {
                loadState.refresh is LoadState.Loading -> {
                    item { LoadingItem() }
                }
                loadState.append is LoadState.Loading -> {
                    item { LoadingItem() }
                }
                loadState.refresh is LoadState.Error -> {
                    val error = tasks.loadState.refresh as LoadState.Error
                    item { ErrorItem(message = error.error.message ?: "Error") }
                }
                loadState.append is LoadState.Error -> {
                    val error = tasks.loadState.append as LoadState.Error
                    item { ErrorItem(message = error.error.message ?: "Error") }
                }
            }
        }
    }
}

5. CameraX Integration

Camera Implementation:

@Composable
fun CameraScreen(
    onImageCaptured: (Uri) -> Unit,
    onError: (Exception) -> Unit
) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current

    var previewView by remember { mutableStateOf<PreviewView?>(null) }
    var imageCapture by remember { mutableStateOf<ImageCapture?>(null) }
    var camera by remember { mutableStateOf<Camera?>(null) }
    var cameraProvider by remember { mutableStateOf<ProcessCameraProvider?>(null) }

    val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

    DisposableEffect(Unit) {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(context)

        cameraProviderFuture.addListener({
            cameraProvider = cameraProviderFuture.get()

            val preview = Preview.Builder().build().also {
                it.setSurfaceProvider(previewView?.surfaceProvider)
            }

            imageCapture = ImageCapture.Builder()
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
                .build()

            try {
                cameraProvider?.unbindAll()
                camera = cameraProvider?.bindToLifecycle(
                    lifecycleOwner,
                    cameraSelector,
                    preview,
                    imageCapture
                )
            } catch (e: Exception) {
                onError(e)
            }
        }, ContextCompat.getMainExecutor(context))

        onDispose {
            cameraProvider?.unbindAll()
        }
    }

    Column(modifier = Modifier.fillMaxSize()) {
        AndroidView(
            factory = { ctx ->
                PreviewView(ctx).also { previewView = it }
            },
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f)
        )

        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp),
            horizontalArrangement = Arrangement.SpaceEvenly
        ) {
            IconButton(
                onClick = {
                    val photoFile = File(
                        context.getExternalFilesDir(Environment.DIRECTORY_PICTURES),
                        "${System.currentTimeMillis()}.jpg"
                    )

                    val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

                    imageCapture?.takePicture(
                        outputOptions,
                        ContextCompat.getMainExecutor(context),
                        object : ImageCapture.OnImageSavedCallback {
                            override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                                onImageCaptured(Uri.fromFile(photoFile))
                            }

                            override fun onError(exception: ImageCaptureException) {
                                onError(exception)
                            }
                        }
                    )
                }
            ) {
                Icon(
                    imageVector = Icons.Default.Camera,
                    contentDescription = "Take Photo",
                    modifier = Modifier.size(64.dp)
                )
            }

            IconButton(
                onClick = {
                    camera?.let { cam ->
                        val currentTorch = cam.cameraInfo.torchState.value == TorchState.ON
                        cam.cameraControl.enableTorch(!currentTorch)
                    }
                }
            ) {
                Icon(
                    imageVector = Icons.Default.FlashOn,
                    contentDescription = "Toggle Flash"
                )
            }
        }
    }
}

6. Firebase Integration

Firebase Services:

// Firestore Repository
class FirestoreTaskRepository @Inject constructor(
    private val firestore: FirebaseFirestore,
    private val auth: FirebaseAuth
) : TaskRepository {

    private val tasksCollection: CollectionReference
        get() = firestore.collection("users")
            .document(auth.currentUser?.uid ?: "")
            .collection("tasks")

    override fun getAllTasks(): Flow<List<Task>> = callbackFlow {
        val subscription = tasksCollection
            .orderBy("createdAt", Query.Direction.DESCENDING)
            .addSnapshotListener { snapshot, error ->
                if (error != null) {
                    close(error)
                    return@addSnapshotListener
                }

                val tasks = snapshot?.documents?.mapNotNull { doc ->
                    doc.toObject<TaskDto>()?.toTask()
                } ?: emptyList()

                trySend(tasks)
            }

        awaitClose { subscription.remove() }
    }

    override suspend fun insertTask(task: Task) {
        tasksCollection
            .document(task.id)
            .set(task.toDto())
            .await()
    }

    override suspend fun updateTask(task: Task) {
        tasksCollection
            .document(task.id)
            .update(task.toDto().toMap())
            .await()
    }

    override suspend fun deleteTask(task: Task) {
        tasksCollection
            .document(task.id)
            .delete()
            .await()
    }

    suspend fun syncWithLocalDatabase(localDao: TaskDao) {
        val remoteTasks = tasksCollection.get().await()
            .documents
            .mapNotNull { it.toObject<TaskDto>()?.toTask() }

        remoteTasks.forEach { task ->
            localDao.insertTask(task.toEntity())
        }
    }
}

// Firebase Cloud Messaging
class MyFirebaseMessagingService : FirebaseMessagingService() {

    override fun onMessageReceived(message: RemoteMessage) {
        message.notification?.let { notification ->
            showNotification(
                title = notification.title ?: "",
                body = notification.body ?: ""
            )
        }

        message.data.let { data ->
            when (data["type"]) {
                "task_assigned" -> handleTaskAssigned(data)
                "task_updated" -> handleTaskUpdated(data)
            }
        }
    }

    override fun onNewToken(token: String) {
        // Send token to server
        sendTokenToServer(token)
    }

    private fun showNotification(title: String, body: String) {
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                "tasks",
                "Task Notifications",
                NotificationManager.IMPORTANCE_HIGH
            )
            notificationManager.createNotificationChannel(channel)
        }

        val notification = NotificationCompat.Builder(this, "tasks")
            .setContentTitle(title)
            .setContentText(body)
            .setSmallIcon(R.drawable.ic_notification)
            .setAutoCancel(true)
            .build()

        notificationManager.notify(System.currentTimeMillis().toInt(), notification)
    }

    private fun sendTokenToServer(token: String) {
        // Implementation to send token to backend
    }

    private fun handleTaskAssigned(data: Map<String, String>) {
        // Handle task assignment notification
    }

    private fun handleTaskUpdated(data: Map<String, String>) {
        // Handle task update notification
    }
}

7. WorkManager for Background Tasks

Background Sync:

class SyncTasksWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        return try {
            val repository = (applicationContext as TaskApplication)
                .appContainer
                .taskRepository

            repository.syncWithRemote()

            Result.success()
        } catch (e: Exception) {
            if (runAttemptCount < 3) {
                Result.retry()
            } else {
                Result.failure()
            }
        }
    }
}

// Schedule periodic sync
class SyncManager(private val context: Context) {

    fun scheduleSyncWork() {
        val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresBatteryNotLow(true)
            .build()

        val syncWorkRequest = PeriodicWorkRequestBuilder<SyncTasksWorker>(
            repeatInterval = 15,
            repeatIntervalTimeUnit = TimeUnit.MINUTES
        )
            .setConstraints(constraints)
            .setBackoffCriteria(
                BackoffPolicy.EXPONENTIAL,
                WorkRequest.MIN_BACKOFF_MILLIS,
                TimeUnit.MILLISECONDS
            )
            .addTag("sync_tasks")
            .build()

        WorkManager.getInstance(context).enqueueUniquePeriodicWork(
            "sync_tasks",
            ExistingPeriodicWorkPolicy.KEEP,
            syncWorkRequest
        )
    }

    fun cancelSyncWork() {
        WorkManager.getInstance(context).cancelUniqueWork("sync_tasks")
    }

    fun observeSyncStatus(): Flow<WorkInfo> {
        return WorkManager.getInstance(context)
            .getWorkInfosForUniqueWorkFlow("sync_tasks")
            .map { workInfos -> workInfos.firstOrNull() ?: WorkInfo() }
    }
}

// Chained work
fun scheduleImageUploadChain(imageUri: Uri) {
    val compressWork = OneTimeWorkRequestBuilder<CompressImageWorker>()
        .setInputData(workDataOf("image_uri" to imageUri.toString()))
        .build()

    val uploadWork = OneTimeWorkRequestBuilder<UploadImageWorker>()
        .build()

    val updateDbWork = OneTimeWorkRequestBuilder<UpdateDatabaseWorker>()
        .build()

    WorkManager.getInstance(context)
        .beginWith(compressWork)
        .then(uploadWork)
        .then(updateDbWork)
        .enqueue()
}

8. Advanced Testing

Comprehensive Test Suite:

// Unit Tests
@ExperimentalCoroutinesTest
class TaskViewModelTest {

    @get:Rule
    val mainDispatcherRule = MainDispatcherRule()

    private lateinit var viewModel: TaskViewModel
    private lateinit var repository: FakeTaskRepository
    private lateinit var savedStateHandle: SavedStateHandle

    @Before
    fun setup() {
        repository = FakeTaskRepository()
        savedStateHandle = SavedStateHandle()
        viewModel = TaskViewModel(repository, savedStateHandle)
    }

    @Test
    fun `loadTasks updates state with tasks from repository`() = runTest {
        // Given
        val expectedTasks = listOf(
            Task(id = "1", title = "Task 1"),
            Task(id = "2", title = "Task 2")
        )
        repository.setTasks(expectedTasks)

        // When
        viewModel.handleIntent(TaskListIntent.LoadTasks)
        advanceUntilIdle()

        // Then
        val state = viewModel.state.value
        assertEquals(expectedTasks, state.tasks)
        assertFalse(state.isLoading)
        assertNull(state.error)
    }

    @Test
    fun `toggleTaskComplete updates task in repository`() = runTest {
        // Given
        val task = Task(id = "1", title = "Test", isCompleted = false)
        repository.setTasks(listOf(task))
        viewModel.handleIntent(TaskListIntent.LoadTasks)
        advanceUntilIdle()

        // When
        viewModel.handleIntent(TaskListIntent.ToggleTaskComplete("1"))
        advanceUntilIdle()

        // Then
        val updatedTask = repository.getTaskById("1")
        assertTrue(updatedTask?.isCompleted == true)
    }

    @Test
    fun `loadTasks with error updates state with error message`() = runTest {
        // Given
        repository.setShouldReturnError(true)

        // When
        viewModel.handleIntent(TaskListIntent.LoadTasks)
        advanceUntilIdle()

        // Then
        val state = viewModel.state.value
        assertNotNull(state.error)
        assertTrue(state.tasks.isEmpty())
    }
}

// Compose UI Tests
@Test
fun taskList_displaysTasksCorrectly() {
    val tasks = listOf(
        Task(id = "1", title = "Task 1"),
        Task(id = "2", title = "Task 2")
    )

    composeTestRule.setContent {
        TaskList(
            tasks = tasks,
            onTaskClick = {},
            onTaskComplete = {}
        )
    }

    composeTestRule
        .onNodeWithText("Task 1")
        .assertIsDisplayed()

    composeTestRule
        .onNodeWithText("Task 2")
        .assertIsDisplayed()
}

@Test
fun taskItem_clickTriggersCallback() {
    var clicked = false
    val task = Task(id = "1", title = "Test Task")

    composeTestRule.setContent {
        TaskItem(
            task = task,
            onClick = { clicked = true },
            onComplete = {}
        )
    }

    composeTestRule
        .onNodeWithText("Test Task")
        .performClick()

    assertTrue(clicked)
}

// Flow Testing with Turbine
@Test
fun `repository emits updated tasks after insert`() = runTest {
    val repository = TaskRepositoryImpl(fakeDao)

    repository.getAllTasks().test {
        // Initial empty state
        assertEquals(emptyList<Task>(), awaitItem())

        // Insert task
        repository.insertTask(Task(id = "1", title = "New Task"))

        // Verify update
        val updatedTasks = awaitItem()
        assertEquals(1, updatedTasks.size)
        assertEquals("New Task", updatedTasks[0].title)
    }
}

9. Multi-Module Architecture

Module Structure:

project/
├── app/                          # Application module
├── core/
│   ├── common/                   # Shared utilities
│   ├── data/                     # Data layer
│   ├── database/                 # Room database
│   ├── network/                  # Retrofit/API
│   ├── domain/                   # Domain models
│   └── ui/                       # Shared UI components
├── feature/
│   ├── tasks/                    # Task feature module
│   ├── auth/                     # Authentication feature
│   └── settings/                 # Settings feature
└── buildSrc/                     # Build configuration

Gradle Configuration (using Version Catalogs):

// settings.gradle.kts
dependencyResolutionManagement {
    versionCatalogs {
        create("libs") {
            // Versions
            version("kotlin", "1.9.20")
            version("compose", "1.5.4")
            version("hilt", "2.48")

            // Libraries
            library("androidx-core-ktx", "androidx.core:core-ktx:1.12.0")
            library("androidx-compose-ui", "androidx.compose.ui", "ui").versionRef("compose")
            library("androidx-compose-material3", "androidx.compose.material3:material3:1.1.2")

            // Bundles
            bundle("compose", ["androidx-compose-ui", "androidx-compose-material3"])
        }
    }
}

// build.gradle.kts (feature module)
plugins {
    alias(libs.plugins.android.library)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.hilt)
}

dependencies {
    implementation(project(":core:common"))
    implementation(project(":core:domain"))
    implementation(project(":core:ui"))

    implementation(libs.bundles.compose)
    implementation(libs.hilt.android)
    kapt(libs.hilt.compiler)
}

Performance Optimization

// Stability annotations
@Immutable
data class Task(
    val id: String,
    val title: String,
    val isCompleted: Boolean
)

// Stable collections
@Immutable
data class TaskListState(
    val tasks: ImmutableList<Task> = persistentListOf()
)

// Baseline profile generation
class BaselineProfileGenerator {
    @get:Rule
    val rule = BaselineProfileRule()

    @Test
    fun generateBaselineProfile() {
        rule.collect("com.example.app") {
            startActivityAndWait()

            // Navigate through critical user journeys
            device.wait(Until.hasObject(By.text("Tasks")), 5000)
            device.findObject(By.text("Add Task")).click()
            device.wait(Until.hasObject(By.text("Save")), 2000)
        }
    }
}

Security Best Practices

// Encrypted SharedPreferences
val encryptedPrefs = EncryptedSharedPreferences.create(
    context,
    "secure_prefs",
    MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build(),
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

// Biometric authentication
class BiometricAuthManager(private val activity: FragmentActivity) {

    fun authenticate(
        onSuccess: () -> Unit,
        onError: (String) -> Unit
    ) {
        val executor = ContextCompat.getMainExecutor(activity)
        val biometricPrompt = BiometricPrompt(
            activity,
            executor,
            object : BiometricPrompt.AuthenticationCallback() {
                override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
                    onSuccess()
                }

                override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
                    onError(errString.toString())
                }

                override fun onAuthenticationFailed() {
                    onError("Authentication failed")
                }
            }
        )

        val promptInfo = BiometricPrompt.PromptInfo.Builder()
            .setTitle("Authenticate")
            .setSubtitle("Verify your identity")
            .setNegativeButtonText("Cancel")
            .build()

        biometricPrompt.authenticate(promptInfo)
    }
}

Communication Style

  • Provide production-ready, thoroughly tested code
  • Explain architectural decisions and trade-offs
  • Include performance and scalability considerations
  • Reference Android documentation and best practices
  • Show advanced patterns with clear examples
  • Discuss memory management and optimization

Deliverables

  1. Complete, production-ready implementations
  2. Advanced Compose and Kotlin code
  3. Comprehensive testing suite
  4. Performance optimization strategies
  5. Security implementation
  6. Multi-module architecture
  7. Integration with Android platform features
  8. CI/CD pipeline considerations

You architect robust, scalable Android applications with enterprise-grade quality, performance, and security.