### Hilt Application and Activity Setup - Kotlin Source: https://context7.com/android/architecture-samples/llms.txt Sets up the main application class and activity using Hilt annotations. `@HiltAndroidApp` is used for the application class to initiate Hilt's code generation, and `@AndroidEntryPoint` is used for the activity to enable dependency injection. ```kotlin @HiltAndroidApp class TodoApplication : Application() // Activity setup: @AndroidEntryPoint class TodoActivity : ComponentActivity() ``` -------------------------------- ### Hilt Database Module Setup (Kotlin) Source: https://context7.com/android/architecture-samples/llms.txt Provides a Hilt module for setting up the Room database and the Task DAO in an Android application. It uses Hilt's dependency injection to provide the `ToDoDatabase` instance and the `TaskDao` to other parts of the application. Dependencies include Hilt, Room, and Application Context. ```kotlin // Database setup with Hilt: @Module @InstallIn(SingletonComponent::class) object DatabaseModule { @Singleton @Provides fun provideDataBase(@ApplicationContext context: Context): ToDoDatabase { return Room.databaseBuilder( context.applicationContext, ToDoDatabase::class.java, "Tasks.db" ).build() } @Provides fun provideTaskDao(database: ToDoDatabase): TaskDao = database.taskDao() } ``` -------------------------------- ### ViewModel Hilt Injection Example - Kotlin Source: https://context7.com/android/architecture-samples/llms.txt Demonstrates injecting dependencies into a ViewModel using Hilt. The example shows how to inject `TaskRepository`, `IoDispatcher`, and `ApplicationScope` into `MyViewModel` for use in coroutine operations. ```kotlin @HiltViewModel class MyViewModel @Inject constructor( private val repository: TaskRepository, @IoDispatcher private val ioDispatcher: CoroutineDispatcher, @ApplicationScope private val appScope: CoroutineScope ) : ViewModel() ``` -------------------------------- ### Implement TaskRepository Interface in Kotlin Source: https://context7.com/android/architecture-samples/llms.txt Defines the `TaskRepository` interface, serving as the single entry point for all task data operations. It abstracts data sources and provides both Flow-based reactive streams and suspend function operations for managing tasks. Includes example usage within a ViewModel demonstrating reactive UI state and asynchronous operations. ```kotlin package com.example.android.architecture.blueprints.todoapp.data import kotlinx.coroutines.flow.Flow interface TaskRepository { // Reactive streams for continuous observation fun getTasksStream(): Flow> fun getTaskStream(taskId: String): Flow // One-shot operations with optional forced updates suspend fun getTasks(forceUpdate: Boolean = false): List suspend fun getTask(taskId: String, forceUpdate: Boolean = false): Task? // Refresh operations suspend fun refresh() suspend fun refreshTask(taskId: String) // CRUD operations suspend fun createTask(title: String, description: String): String suspend fun updateTask(taskId: String, title: String, description: String) suspend fun completeTask(taskId: String) suspend fun activateTask(taskId: String) suspend fun clearCompletedTasks() suspend fun deleteAllTasks() suspend fun deleteTask(taskId: String) } // Usage Example in ViewModel: // @HiltViewModel // class ExampleViewModel @Inject constructor( // private val taskRepository: TaskRepository // ) : ViewModel() { // // Reactive UI state from Flow // val tasks: StateFlow> = taskRepository // .getTasksStream() // .stateIn( // scope = viewModelScope, // started = WhileUiSubscribed, // initialValue = emptyList() // ) // // One-shot operation // fun createNewTask(title: String, description: String) { // viewModelScope.launch { // try { // val taskId = taskRepository.createTask(title, description) // println("Created task with ID: $taskId") // } catch (e: Exception) { // println("Failed to create task: ${e.message}") // } // } // } // // Mark task complete // fun toggleTask(taskId: String, completed: Boolean) { // viewModelScope.launch { // if (completed) { // taskRepository.completeTask(taskId) // } else { // taskRepository.activateTask(taskId) // } // } // } // } ``` -------------------------------- ### MainCoroutineRule: JUnit Rule for Coroutine Testing (Kotlin) Source: https://context7.com/android/architecture-samples/llms.txt A JUnit TestWatcher rule that replaces the main dispatcher with a TestDispatcher for testing coroutines. It ensures the main dispatcher is set before tests start and reset afterward. Dependencies include Kotlin Coroutines testing utilities. ```kotlin package com.example.android.architecture.blueprints.todoapp.testing import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.* import org.junit.rules.TestWatcher import org.junit.runner.Description @ExperimentalCoroutinesApi class MainCoroutineRule( val testDispatcher: TestDispatcher = UnconfinedTestDispatcher() ) : TestWatcher() { override fun starting(description: Description) { super.starting(description) Dispatchers.setMain(testDispatcher) } override fun finished(description: Description) { super.finished(description) Dispatchers.resetMain() } } // Usage in test class: @ExperimentalCoroutinesApi class TasksViewModelTest { @get:Rule val mainCoroutineRule = MainCoroutineRule() private lateinit var fakeRepository: FakeTaskRepository private lateinit var viewModel: TasksViewModel @Before fun setupViewModel() { fakeRepository = FakeTaskRepository() viewModel = TasksViewModel( taskRepository = fakeRepository, savedStateHandle = SavedStateHandle() ) } @Test fun loadTasks_setsLoadingState() = runTest { // Given - fresh ViewModel // When - loading starts viewModel.refresh() // Then - loading state is true assertThat(viewModel.uiState.value.isLoading).isTrue() // Advance until idle advanceUntilIdle() // Then - loading state is false assertThat(viewModel.uiState.value.isLoading).isFalse() } @Test fun filterTasks_showsActiveOnly() = runTest { // Given - repository with active and completed tasks val activeTask = Task("Active", "Description", false, "1") val completedTask = Task("Done", "Description", true, "2") fakeRepository.addTasks(activeTask, completedTask) // When - filtering to active tasks viewModel.setFiltering(TasksFilterType.ACTIVE_TASKS) advanceUntilIdle() // Then - only active task shown val tasks = viewModel.uiState.value.items assertThat(tasks).containsExactly(activeTask) } } ``` -------------------------------- ### Define Task Data Model in Kotlin Source: https://context7.com/android/architecture-samples/llms.txt Defines an immutable data class `Task` to represent a task in the TODO application. Includes computed properties for UI display and business logic, and demonstrates usage with an example instantiation and access to its properties. ```kotlin package com.example.android.architecture.blueprints.todoapp.data data class Task( val title: String = "", val description: String = "", val isCompleted: Boolean = false, val id: String, ) { val titleForList: get() = if (title.isNotEmpty()) title else description val isActive get() = !isCompleted val isEmpty get() = title.isEmpty() || description.isEmpty() } // Usage Example: // val task = Task( // title = "Complete project documentation", // description = "Write comprehensive docs for the TODO app", // isCompleted = false, // id = UUID.randomUUID().toString() // ) // Access computed properties // println(task.titleForList) // "Complete project documentation" // println(task.isActive) // true // println(task.isEmpty) // false ``` -------------------------------- ### Run All Unit Tests Source: https://github.com/android/architecture-samples/wiki/To-do-app-specification Command to execute all unit tests for the project. These tests focus on individual classes and vary based on the application's architecture. ```bash ./gradlew test ``` -------------------------------- ### Android Application Build Configuration (app/build.gradle.kts) Source: https://context7.com/android/architecture-samples/llms.txt Defines the build configuration for the Android application module. This includes applying necessary plugins, setting up Android-specific configurations like namespace, SDK versions, build types, and listing all project dependencies. ```kotlin // app/build.gradle.kts plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.ksp) alias(libs.plugins.hilt) alias(libs.plugins.compose.compiler) } android { namespace = "com.example.android.architecture.blueprints.todoapp" compileSdk = 35 defaultConfig { applicationId = "com.example.android.architecture.blueprints.main" minSdk = 21 targetSdk = 35 versionCode = 1 versionName = "1.0" testInstrumentationRunner = "com.example.android.architecture.blueprints.todoapp.CustomTestRunner" javaCompileOptions { annotationProcessorOptions { arguments += "room.incremental" to "true" } } } buildTypes { debug { isMinifyEnabled = false isTestCoverageEnabled = true } release { isMinifyEnabled = true isShrinkResources = true proguardFiles( getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ) } } buildFeatures { compose = true } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { jvmTarget = "17" } } dependencies { // Compose implementation(platform(libs.compose.bom)) implementation(libs.compose.ui) implementation(libs.compose.material3) implementation(libs.compose.ui.tooling.preview) implementation(libs.androidx.lifecycle.runtime.compose) // Navigation implementation(libs.navigation.compose) implementation(libs.hilt.navigation.compose) // Hilt implementation(libs.hilt.android) ksp(libs.hilt.compiler) // Room implementation(libs.room.runtime) implementation(libs.room.ktx) ksp(libs.room.compiler) // Coroutines implementation(libs.kotlinx.coroutines.android) // Testing testImplementation(libs.junit) testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.truth) testImplementation(project(":shared-test")) androidTestImplementation(libs.androidx.test.ext.junit) androidTestImplementation(libs.androidx.test.espresso.core) androidTestImplementation(libs.compose.ui.test.junit4) androidTestImplementation(project(":shared-test")) } ``` -------------------------------- ### Gradle Version Catalog Configuration (gradle/libs.versions.toml) Source: https://context7.com/android/architecture-samples/llms.txt Manages project dependencies and plugin versions centrally using Gradle's version catalog feature. This file defines versions for libraries like Compose, Room, Hilt, and Kotlin, along with their coordinates and plugin IDs. ```toml # gradle/libs.versions.toml [versions] kotlin = "2.1.10" composeBom = "2024.12.01" room = "2.6.1" hilt = "2.53.1" coroutines = "1.10.1" navigation = "2.8.5" [libraries] compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } [plugins] android-application = { id = "com.android.application", version = "8.7.3" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version = "2.1.10-1.0.29" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } ``` -------------------------------- ### Gradle Settings Configuration (settings.gradle.kts) Source: https://context7.com/android/architecture-samples/llms.txt Configures the root-level Gradle settings, including plugin management repositories and dependency resolution settings. It also defines the included modules for the project. ```kotlin // settings.gradle.kts pluginManagement { repositories { google() mavenCentral() gradlePluginPortal() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } } rootProject.name = "android-architecture-samples" include(":app") include(":shared-test") ``` -------------------------------- ### Run Mock Debug Android Tests Source: https://github.com/android/architecture-samples/wiki/To-do-app-specification Command to execute connected Android tests against the mock data source. This is useful for fast and isolated testing without network interference. ```bash ./gradlew connectedMockDebugAndroidTest ``` -------------------------------- ### Compare Git Branches using Difftool Source: https://github.com/android/architecture-samples/wiki/How-to-compare-samples This snippet demonstrates how to use the `git checkout` and `git difftool` commands to compare code between two branches. It requires Git and a directory-aware difftool like Meld to visualize the differences effectively. ```shell git checkout todo-mvp git difftool -d todo-mvp-clean ``` -------------------------------- ### Define Navigation Routes and Actions with Kotlin Source: https://context7.com/android/architecture-samples/llms.txt Defines constants for navigation routes and creates a wrapper class for navigation actions to ensure type-safe navigation within an Android application using Kotlin and Jetpack Compose. It handles navigation to tasks, statistics, task details, and add/edit task screens, including state saving and single-top launching. ```kotlin package com.example.android.architecture.blueprints.todoapp import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController // Route definitions object TodoDestinations { const val TASKS_ROUTE = "tasks?userMessage={userMessage}" const val STATISTICS_ROUTE = "statistics" const val TASK_DETAIL_ROUTE = "task/{taskId}" const val ADD_EDIT_TASK_ROUTE = "addEditTask/{title}?taskId={taskId}" } // Navigation actions wrapper class TodoNavigationActions(private val navController: NavHostController) { fun navigateToTasks(userMessage: Int = 0) { val navigatesFromDrawer = userMessage == 0 navController.navigate( if (userMessage != 0) "tasks?userMessage=$userMessage" else "tasks" ) { popUpTo(navController.graph.findStartDestination().id) { inclusive = !navigatesFromDrawer saveState = navigatesFromDrawer } launchSingleTop = true restoreState = navigatesFromDrawer } } fun navigateToStatistics() { navController.navigate(STATISTICS_ROUTE) { popUpTo(navController.graph.findStartDestination().id) { saveState = true } launchSingleTop = true restoreState = true } } fun navigateToTaskDetail(taskId: String) { navController.navigate("task/$taskId") } fun navigateToAddEditTask(title: Int, taskId: String? = null) { navController.navigate( "addEditTask/$title".let { if (taskId != null) "$it?taskId=$taskId" else it } ) } } // Usage in NavHost: @Composable fun TodoNavGraph( navController: NavHostController = rememberNavController(), startDestination: String = TASKS_ROUTE ) { val navigationActions = remember(navController) { TodoNavigationActions(navController) } NavHost( navController = navController, startDestination = startDestination ) { composable( route = TASKS_ROUTE, arguments = listOf( navArgument("userMessage") { type = NavType.IntType defaultValue = 0 } ) ) { TasksScreen( userMessage = entry.arguments?.getInt("userMessage") ?: 0, onAddTask = { navigationActions.navigateToAddEditTask(R.string.add_task) }, onTaskClick = { task -> navigationActions.navigateToTaskDetail(task.id) } ) } composable( route = TASK_DETAIL_ROUTE, arguments = listOf( navArgument("taskId") { type = NavType.StringType } ) ) { TaskDetailScreen( onEditTask = { taskId -> navigationActions.navigateToAddEditTask( R.string.edit_task, taskId ) }, onDeleteTask = { navigationActions.navigateToTasks(DELETE_RESULT_OK) } ) } } } ``` -------------------------------- ### Hilt DI Modules for Repository, Database, and Network - Kotlin Source: https://context7.com/android/architecture-samples/llms.txt Defines Hilt modules for providing dependencies such as TaskRepository, ToDoDatabase, TaskDao, and NetworkDataSource. It utilizes Room for the local database and injects necessary components like Context and CoroutineDispatchers. ```kotlin package com.example.android.architecture.blueprints.todoapp.di import android.content.Context import androidx.room.Room import com.example.android.architecture.blueprints.todoapp.data.DefaultTaskRepository import com.example.android.architecture.blueprints.todoapp.data.TaskRepository import com.example.android.architecture.blueprints.todoapp.data.source.local.TaskDao import com.example.android.architecture.blueprints.todoapp.data.source.local.ToDoDatabase import com.example.android.architecture.blueprints.todoapp.data.source.network.NetworkDataSource import com.example.android.architecture.blueprints.todoapp.data.source.network.TaskNetworkDataSource import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import javax.inject.Qualifier import javax.inject.Singleton // Qualifiers for different dispatchers and scopes @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class IoDispatcher @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class DefaultDispatcher @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class ApplicationScope // Repository binding @Module @InstallIn(SingletonComponent::class) abstract class RepositoryModule { @Singleton @Binds abstract fun bindTaskRepository( repository: DefaultTaskRepository ): TaskRepository } // Database providers @Module @InstallIn(SingletonComponent::class) object DatabaseModule { @Singleton @Provides fun provideDataBase( @ApplicationContext context: Context ): ToDoDatabase { return Room.databaseBuilder( context.applicationContext, ToDoDatabase::class.java, "Tasks.db" ).build() } @Provides fun provideTaskDao(database: ToDoDatabase): TaskDao = database.taskDao() } // Network data source binding @Module @InstallIn(SingletonComponent::class) abstract class DataSourceModule { @Singleton @Binds abstract fun bindNetworkDataSource( dataSource: TaskNetworkDataSource ): NetworkDataSource } // Coroutine dispatchers and scope @Module @InstallIn(SingletonComponent::class) object CoroutinesModule { @IoDispatcher @Provides fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO @DefaultDispatcher @Provides fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default @ApplicationScope @Singleton @Provides fun provideApplicationScope( @DefaultDispatcher dispatcher: CoroutineDispatcher ): CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher) } ``` -------------------------------- ### Task Repository Implementation (Kotlin) Source: https://context7.com/android/architecture-samples/llms.txt Demonstrates the implementation of a `TaskRepository` class in Kotlin, which uses the `TaskDao` to interact with the Room database. It shows how to expose database data as a Flow for observation in a ViewModel and how to call suspend functions for background operations like deleting completed tasks. Dependencies include `TaskDao` and Kotlin Coroutines. ```kotlin // Usage example: class TaskRepository @Inject constructor(private val taskDao: TaskDao) { // Collect Flow in ViewModel val allTasks: Flow> = taskDao.observeAll() // Suspend function call suspend fun deleteCompletedTasks() { val deletedCount = taskDao.deleteCompleted() println("Deleted $deletedCount completed tasks") } } ``` -------------------------------- ### AddEditTaskScreen UI Implementation in Kotlin (Jetpack Compose) Source: https://context7.com/android/architecture-samples/llms.txt This code snippet demonstrates the UI implementation of the AddEditTask screen using Jetpack Compose. It collects the UI state from the AddEditTaskViewModel and renders interactive elements like TextFields and Buttons. It also handles navigation callbacks upon task saving. ```kotlin // Compose Screen usage: @Composable fun AddEditTaskScreen( viewModel: AddEditTaskViewModel = hiltViewModel(), onTaskSaved: () -> Unit ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() Column { TextField( value = uiState.title, onValueChange = { viewModel.updateTitle(it) }, label = { Text("Title") } ) TextField( value = uiState.description, onValueChange = { viewModel.updateDescription(it) }, label = { Text("Description") } ) Button(onClick = { viewModel.saveTask() }) { Text("Save Task") } } LaunchedEffect(uiState.isTaskSaved) { if (uiState.isTaskSaved) { onTaskSaved() } } } ``` -------------------------------- ### DefaultTaskRepository Implementation in Kotlin Source: https://context7.com/android/architecture-samples/llms.txt The DefaultTaskRepository class implements the TaskRepository interface, providing functionality to create, retrieve, refresh, and complete tasks. It synchronizes data between a local Room database and a network data source. Long-running operations are handled on a background dispatcher, while network synchronization is performed using the application scope for fire-and-forget operations. Dependencies are managed via Hilt injection. ```kotlin package com.example.android.architecture.blueprints.todoapp.data import com.example.android.architecture.blueprints.todoapp.data.source.local.TaskDao import com.example.android.architecture.blueprints.todoapp.data.source.network.NetworkDataSource import com.example.android.architecture.blueprints.todoapp.di.ApplicationScope import com.example.android.architecture.blueprints.todoapp.di.DefaultDispatcher import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.util.UUID import javax.inject.Inject import javax.inject.Singleton @Singleton class DefaultTaskRepository @Inject constructor( private val networkDataSource: NetworkDataSource, private val localDataSource: TaskDao, @DefaultDispatcher private val dispatcher: CoroutineDispatcher, @ApplicationScope private val scope: CoroutineScope, ) : TaskRepository { override suspend fun createTask(title: String, description: String): String { // ID generation on background dispatcher val taskId = withContext(dispatcher) { UUID.randomUUID().toString() } val task = Task( title = title, description = description, id = taskId, ) localDataSource.upsert(task.toLocal()) saveTasksToNetwork() // Fire-and-forget sync return taskId } override fun getTasksStream(): Flow> { return localDataSource.observeAll().map { withContext(dispatcher) { it.toExternal() } } } override suspend fun refresh() { withContext(dispatcher) { val remoteTasks = networkDataSource.loadTasks() localDataSource.deleteAll() localDataSource.upsertAll(remoteTasks.toLocal()) } } override suspend fun completeTask(taskId: String) { localDataSource.updateCompleted(taskId = taskId, completed = true) saveTasksToNetwork() } private fun saveTasksToNetwork() { scope.launch { try { val localTasks = localDataSource.getAll() val networkTasks = withContext(dispatcher) { localTasks.toNetwork() } networkDataSource.saveTasks(networkTasks) } catch (e: Exception) { // Handle network errors (log, show UI message, etc.) } } } } // Usage with Hilt injection: // The repository is automatically provided by Hilt through RepositoryModule @Module @InstallIn(SingletonComponent::class) abstract class RepositoryModule { @Singleton @Binds abstract fun bindTaskRepository(repository: DefaultTaskRepository): TaskRepository } ``` -------------------------------- ### FakeTaskRepository for ViewModel Testing in Kotlin Source: https://context7.com/android/architecture-samples/llms.txt An in-memory implementation of the `TaskRepository` interface in Kotlin, designed for testing ViewModels. It supports simulating error conditions for comprehensive testing of ViewModel logic and error handling. Dependencies include Kotlin coroutines and Jetpack ViewModel testing utilities. ```kotlin package com.example.android.architecture.blueprints.todoapp.testing import com.example.android.architecture.blueprints.todoapp.data.Task import com.example.android.architecture.blueprints.todoapp.data.TaskRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import java.util.UUID class FakeTaskRepository : TaskRepository { private val tasks = LinkedHashMap() private val tasksFlow = MutableStateFlow(tasks.values.toList()) private var shouldThrowError = false fun setShouldThrowError(value: Boolean) { shouldThrowError = value } override fun getTasksStream(): Flow> = tasksFlow override fun getTaskStream(taskId: String): Flow { return tasksFlow.map { tasks.firstOrNull { it.id == taskId } } } override suspend fun createTask(title: String, description: String): String { if (shouldThrowError) throw Exception("Test exception") val taskId = UUID.randomUUID().toString() val task = Task( id = taskId, title = title, description = description, isCompleted = false ) tasks[taskId] = task refreshTasksFlow() return taskId } override suspend fun completeTask(taskId: String) { tasks[taskId]?.let { tasks[taskId] = it.copy(isCompleted = true) refreshTasksFlow() } } override suspend fun deleteAllTasks() { tasks.clear() refreshTasksFlow() } private fun refreshTasksFlow() { tasksFlow.update { tasks.values.toList() } } fun addTasks(vararg tasksToAdd: Task) { for (task in tasksToAdd) { tasks[task.id] = task } refreshTasksFlow() } } // Usage in tests: @Test fun loadTasks_showsTasksInUi() = runTest { // Given - fake repository with tasks val task1 = Task("Title1", "Desc1", false, "id1") val task2 = Task("Title2", "Desc2", true, "id2") fakeRepository.addTasks(task1, task2) // When - ViewModel is created val viewModel = TasksViewModel(fakeRepository, SavedStateHandle()) // Then - UI state contains tasks val uiState = viewModel.uiState.value assertThat(uiState.items).containsExactly(task1, task2) } @Test fun createTask_withError_showsErrorMessage() = runTest { // Given - repository that throws errors fakeRepository.setShouldThrowError(true) val viewModel = TasksViewModel(fakeRepository, SavedStateHandle()) // When - creating task viewModel.createTask("Title", "Description") // Then - error message shown assertThat(viewModel.uiState.value.userMessage) .isEqualTo(R.string.error_creating_task) } ``` -------------------------------- ### Room Database DAO Operations (Kotlin) Source: https://context7.com/android/architecture-samples/llms.txt Defines the Data Access Object (DAO) for Room database operations in a Kotlin Android application. It includes methods for observing all tasks, observing a task by ID using Flow for reactive queries, and performing one-shot suspend queries for retrieving, inserting, updating (Upsert), and deleting tasks. Dependencies include Room annotations and Kotlin Coroutines Flow. ```kotlin package com.example.android.architecture.blueprints.todoapp.data.source.local import androidx.room.Dao import androidx.room.Query import androidx.room.Upsert import kotlinx.coroutines.flow.Flow @Dao interface TaskDao { // Reactive queries with Flow @Query("SELECT * FROM task") fun observeAll(): Flow> @Query("SELECT * FROM task WHERE id = :taskId") fun observeById(taskId: String): Flow // One-shot suspend queries @Query("SELECT * FROM task") suspend fun getAll(): List @Query("SELECT * FROM task WHERE id = :taskId") suspend fun getById(taskId: String): LocalTask? // Insert or update @Upsert suspend fun upsert(task: LocalTask) @Upsert suspend fun upsertAll(tasks: List) // Update operations @Query("UPDATE task SET isCompleted = :completed WHERE id = :taskId") suspend fun updateCompleted(taskId: String, completed: Boolean) // Delete operations @Query("DELETE FROM task WHERE id = :taskId") suspend fun deleteById(taskId: String): Int @Query("DELETE FROM task") suspend fun deleteAll() @Query("DELETE FROM task WHERE isCompleted = 1") suspend fun deleteCompleted(): Int } ``` -------------------------------- ### Custom Flow Sharing Strategy (Kotlin) Source: https://context7.com/android/architecture-samples/llms.txt Defines a custom SharingStarted strategy named `WhileUiSubscribed` which stops upstream flows 5 seconds after the last subscriber disconnects. This is used in ViewModels to manage resource consumption and state preservation efficiently. ```kotlin package com.example.android.architecture.blueprints.todoapp.util import kotlinx.coroutines.flow.SharingStarted // Custom SharingStarted that stops upstream flows 5 seconds after last subscriber private const val STOP_TIMEOUT_MILLIS = 5_000L val WhileUiSubscribed: SharingStarted = SharingStarted.WhileSubscribed( stopTimeoutMillis = STOP_TIMEOUT_MILLIS ) // Usage in ViewModel: @HiltViewModel class TasksViewModel @Inject constructor( private val taskRepository: TaskRepository ) : ViewModel() { val uiState: StateFlow = combine( taskRepository.getTasksStream(), filterFlow, loadingFlow ) { tasks, filter, isLoading -> TasksUiState( items = tasks.filter { /* filter logic */ }, isLoading = isLoading ) } .stateIn( scope = viewModelScope, started = WhileUiSubscribed, // Stop collecting after 5s of no subscribers initialValue = TasksUiState(isLoading = true) ) } // Benefits: // - Stops upstream flows when no UI is observing (saves resources) // - 5-second timeout allows for quick config changes without restarting flows // - Preserves last emitted value in StateFlow (doesn't reset to initial value) // - Automatically restarts when UI starts observing again ``` -------------------------------- ### Kotlin TasksViewModel for UI State Management Source: https://context7.com/android/architecture-samples/llms.txt The TasksViewModel class manages the UI state for the tasks list screen. It uses Kotlin Coroutines Flow and StateFlow to combine data from a repository, handle loading and error states, and manage user messages. Dependencies include TaskRepository and SavedStateHandle. The output is a TasksUiState object representing the current UI. ```kotlin package com.example.android.architecture.blueprints.todoapp.tasks import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.android.architecture.blueprints.todoapp.data.Task import com.example.android.architecture.blueprints.todoapp.data.TaskRepository import com.example.android.architecture.blueprints.todoapp.util.Async import com.example.android.architecture.blueprints.todoapp.util.WhileUiSubscribed import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject data class TasksUiState( val items: List = emptyList(), val isLoading: Boolean = false, val filteringUiInfo: FilteringUiInfo = FilteringUiInfo(), val userMessage: Int? = null ) enum class TasksFilterType { ALL_TASKS, ACTIVE_TASKS, COMPLETED_TASKS } @HiltViewModel class TasksViewModel @Inject constructor( private val taskRepository: TaskRepository, private val savedStateHandle: SavedStateHandle ) : ViewModel() { private val _savedFilterType = savedStateHandle.getStateFlow("FILTER_KEY", ALL_TASKS) private val _isLoading = MutableStateFlow(false) private val _userMessage: MutableStateFlow = MutableStateFlow(null) private val _filteredTasksAsync = combine( taskRepository.getTasksStream(), _savedFilterType ) { tasks, filterType -> filterTasks(tasks, filterType) } .map { Async.Success(it) } .catch>>{ emit(Async.Error(R.string.loading_tasks_error)) } val uiState: StateFlow = combine( _savedFilterType.map { getFilterUiInfo(it) }, _isLoading, _userMessage, _filteredTasksAsync ) { filterUiInfo, isLoading, userMessage, tasksAsync -> when (tasksAsync) { Async.Loading -> TasksUiState(isLoading = true) is Async.Error -> TasksUiState(userMessage = tasksAsync.errorMessage) is Async.Success -> TasksUiState( items = tasksAsync.data, filteringUiInfo = filterUiInfo, isLoading = isLoading, userMessage = userMessage ) } }.stateIn( scope = viewModelScope, started = WhileUiSubscribed, initialValue = TasksUiState(isLoading = true) ) fun setFiltering(requestType: TasksFilterType) { savedStateHandle["FILTER_KEY"] = requestType } fun completeTask(task: Task, completed: Boolean) = viewModelScope.launch { if (completed) { taskRepository.completeTask(task.id) _userMessage.value = R.string.task_marked_complete } else { taskRepository.activateTask(task.id) _userMessage.value = R.string.task_marked_active } } fun clearCompletedTasks() { viewModelScope.launch { taskRepository.clearCompletedTasks() _userMessage.value = R.string.completed_tasks_cleared refresh() } } fun refresh() { _isLoading.value = true viewModelScope.launch { taskRepository.refresh() _isLoading.value = false } } private fun filterTasks( tasks: List, filteringType: TasksFilterType ): List { return when (filteringType) { ALL_TASKS -> tasks ACTIVE_TASKS -> tasks.filter { it.isActive } COMPLETED_TASKS -> tasks.filter { it.isCompleted } } } } // Compose Screen usage: @Composable fun TasksScreen( viewModel: TasksViewModel = hiltViewModel() ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() LazyColumn { items(uiState.items) { TaskItem( task = task, onCheckedChange = { viewModel.completeTask(task, it) } ) } } if (uiState.isLoading) { CircularProgressIndicator() } } ``` -------------------------------- ### Calculate Task Statistics (Kotlin) Source: https://context7.com/android/architecture-samples/llms.txt A pure Kotlin function to calculate the percentage of active and completed tasks. It handles empty task lists by returning 0% for both. This function is used within a ViewModel to provide UI state. ```kotlin package com.example.android.architecture.blueprints.todoapp.statistics import com.example.android.architecture.blueprints.todoapp.data.Task data class StatsResult( val activeTasksPercent: Float, val completedTasksPercent: Float ) internal fun getActiveAndCompletedStats(tasks: List): StatsResult { return if (tasks.isEmpty()) { StatsResult(0f, 0f) } else { val totalTasks = tasks.size val numberOfActiveTasks = tasks.count { it.isActive } StatsResult( activeTasksPercent = 100f * numberOfActiveTasks / tasks.size, completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size ) } } // Usage in ViewModel: @HiltViewModel class StatisticsViewModel @Inject constructor( private val taskRepository: TaskRepository ) : ViewModel() { val uiState: StateFlow = taskRepository .getTasksStream() .map { tasks -> val stats = getActiveAndCompletedStats(tasks) StatisticsUiState( isEmpty = tasks.isEmpty(), activeTasksPercent = stats.activeTasksPercent, completedTasksPercent = stats.completedTasksPercent, isLoading = false ) } .catch { emit(StatisticsUiState(isLoading = false)) } .stateIn( scope = viewModelScope, started = WhileUiSubscribed, initialValue = StatisticsUiState(isLoading = true) ) } // Compose UI: @Composable fun StatisticsScreen(viewModel: StatisticsViewModel = hiltViewModel()) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() Column { Text("Active: ${uiState.activeTasksPercent.toInt()}%") Text("Completed: ${uiState.completedTasksPercent.toInt()}%") LinearProgressIndicator( progress = uiState.completedTasksPercent / 100f ) } } ``` -------------------------------- ### AddEditTaskViewModel Logic in Kotlin Source: https://context7.com/android/architecture-samples/llms.txt The AddEditTaskViewModel handles the state and logic for the AddEditTask screen. It manages task data, form input, validation, and saving new or updated tasks using Kotlin Coroutines and a MutableStateFlow for UI state. It depends on TaskRepository for data operations and SavedStateHandle for task ID retrieval. ```kotlin package com.example.android.architecture.blueprints.todoapp.addedittask import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.android.architecture.blueprints.todoapp.data.TaskRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import javax.inject.Inject data class AddEditTaskUiState( val title: String = "", val description: String = "", val isTaskCompleted: Boolean = false, val isLoading: Boolean = false, val userMessage: Int? = null, val isTaskSaved: Boolean = false ) @HiltViewModel class AddEditTaskViewModel @Inject constructor( private val taskRepository: TaskRepository, savedStateHandle: SavedStateHandle ) : ViewModel() { private val taskId: String? = savedStateHandle["taskId"] private val _uiState = MutableStateFlow(AddEditTaskUiState()) val uiState: StateFlow = _uiState.asStateFlow() init { if (taskId != null) { loadTask(taskId) } } fun updateTitle(newTitle: String) { _uiState.update { it.copy(title = newTitle) } } fun updateDescription(newDescription: String) { _uiState.update { it.copy(description = newDescription) } } fun saveTask() { if (uiState.value.title.isEmpty() || uiState.value.description.isEmpty()) { _uiState.update { it.copy(userMessage = R.string.empty_task_message) } return } if (taskId == null) { createNewTask() } else { updateTask() } } private fun createNewTask() = viewModelScope.launch { taskRepository.createTask( uiState.value.title, uiState.value.description ) _uiState.update { it.copy(isTaskSaved = true) } } private fun updateTask() = viewModelScope.launch { taskRepository.updateTask( taskId!!, title = uiState.value.title, description = uiState.value.description, ) _uiState.update { it.copy(isTaskSaved = true) } } private fun loadTask(taskId: String) { _uiState.update { it.copy(isLoading = true) } viewModelScope.launch { taskRepository.getTask(taskId)?.let { task -> _uiState.update { it.copy( title = task.title, description = task.description, isTaskCompleted = task.isCompleted, isLoading = false ) } } ?: _uiState.update { it.copy(isLoading = false) } } } } ``` -------------------------------- ### Async Result Type in Kotlin for ViewModels Source: https://context7.com/android/architecture-samples/llms.txt A sealed class `Async` in Kotlin used to represent the states of asynchronous operations (Loading, Error, Success) within Android ViewModels. It ensures type-safe handling of data and provides a clear structure for UI consumption, often used with StateFlow and LiveData. Dependencies include Kotlin coroutines and Jetpack ViewModel. ```kotlin package com.example.android.architecture.blueprints.todoapp.util sealed class Async { object Loading : Async() data class Error(val errorMessage: Int) : Async() data class Success(val data: T) : Async() } // Usage example: @HiltViewModel class TaskDetailViewModel @Inject constructor( private val taskRepository: TaskRepository, savedStateHandle: SavedStateHandle ) : ViewModel() { private val taskId: String = savedStateHandle["taskId"]!! val taskAsync: StateFlow> = taskRepository .getTaskStream(taskId) .map { task -> Async.Success(task) } .catch> { emit(Async.Error(R.string.loading_task_error)) } .onStart { emit(Async.Loading) } .stateIn( scope = viewModelScope, started = WhileUiSubscribed, initialValue = Async.Loading ) } // Compose UI consumption: @Composable fun TaskDetailScreen(viewModel: TaskDetailViewModel = hiltViewModel()) { val taskAsync by viewModel.taskAsync.collectAsStateWithLifecycle() when (taskAsync) { Async.Loading -> { CircularProgressIndicator() } is Async.Error -> { Text(stringResource((taskAsync as Async.Error).errorMessage)) } is Async.Success -> { val task = (taskAsync as Async.Success).data if (task != null) { TaskDetailContent(task = task) } else { Text("Task not found") } } } } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.