diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6d8001d --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Android +*.iml +.gradle/ +build/ +local.properties +.idea/ +.DS_Store +captures/ +.externalNativeBuild +.cxx + +# Kotlin +*.class +*.kotlin_module + +# Dependency directories +/node_modules +/app/build + +# Logging +*.log + +# Testing +/app/reports +/app/test-results + +# Sensitive information +*.env +*.keystore + +# System files +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/app/src/main/java/com/todoapp/data/dao/TodoDao.kt b/app/src/main/java/com/todoapp/data/dao/TodoDao.kt new file mode 100644 index 0000000..003f768 --- /dev/null +++ b/app/src/main/java/com/todoapp/data/dao/TodoDao.kt @@ -0,0 +1,31 @@ +package com.todoapp.data.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import com.todoapp.data.entity.TodoItem +import kotlinx.coroutines.flow.Flow + +/** + * Data Access Object for Todo items. + * Defines database operations for TodoItem. + */ +@Dao +interface TodoDao { + /** + * Retrieve all todo items. + * + * @return Flow of list of TodoItems + */ + @Query("SELECT * FROM todo_items") + fun getAllTodoItems(): Flow> + + /** + * Insert a new todo item. + * + * @param todoItem TodoItem to be inserted + * @return Long ID of the inserted item + */ + @Insert + suspend fun insertTodoItem(todoItem: TodoItem): Long +} \ No newline at end of file diff --git a/app/src/main/java/com/todoapp/data/database/TodoDatabase.kt b/app/src/main/java/com/todoapp/data/database/TodoDatabase.kt new file mode 100644 index 0000000..9ce4b3c --- /dev/null +++ b/app/src/main/java/com/todoapp/data/database/TodoDatabase.kt @@ -0,0 +1,56 @@ +package com.todoapp.data.database + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import com.todoapp.data.dao.TodoDao +import com.todoapp.data.entity.TodoItem + +/** + * Room Database for storing Todo items. + * This is a singleton database instance to ensure only one connection is maintained. + */ +@Database(entities = [TodoItem::class], version = 1, exportSchema = false) +abstract class TodoDatabase : RoomDatabase() { + // Abstract method to get the TodoDao + abstract fun todoDao(): TodoDao + + companion object { + // Volatile ensures the instance is always up-to-date across threads + @Volatile + private var INSTANCE: TodoDatabase? = null + + /** + * Get or create the database instance. + * Uses synchronized block to ensure thread safety during instance creation. + * + * @param context Application context + * @return TodoDatabase instance + */ + fun getDatabase(context: Context): TodoDatabase { + // If instance already exists, return it + return INSTANCE ?: synchronized(this) { + // Double-checked locking pattern + INSTANCE ?: buildDatabase(context).also { INSTANCE = it } + } + } + + /** + * Create the Room database instance. + * + * @param context Application context + * @return Created TodoDatabase + */ + private fun buildDatabase(context: Context): TodoDatabase { + return Room.databaseBuilder( + context.applicationContext, + TodoDatabase::class.java, + "todo_database" + ) + // Optional: Add migration strategy if needed in future + .fallbackToDestructiveMigration() // Simplified for this example + .build() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/todoapp/data/entity/TodoItem.kt b/app/src/main/java/com/todoapp/data/entity/TodoItem.kt new file mode 100644 index 0000000..cc7ded2 --- /dev/null +++ b/app/src/main/java/com/todoapp/data/entity/TodoItem.kt @@ -0,0 +1,23 @@ +package com.todoapp.data.entity + +import androidx.room.Entity +import androidx.room.PrimaryKey + +/** + * Represents a Todo item in the database. + * + * @property id Unique identifier for the todo item + * @property title Title of the todo item + * @property description Optional description of the todo item + * @property isCompleted Flag indicating whether the todo item is completed + * @property createdAt Timestamp of item creation + */ +@Entity(tableName = "todo_items") +data class TodoItem( + @PrimaryKey(autoGenerate = true) + val id: Int = 0, + val title: String, + val description: String? = null, + val isCompleted: Boolean = false, + val createdAt: Long = System.currentTimeMillis() +) \ No newline at end of file diff --git a/app/src/test/java/com/todoapp/data/TodoDatabaseTest.kt b/app/src/test/java/com/todoapp/data/TodoDatabaseTest.kt new file mode 100644 index 0000000..1352c44 --- /dev/null +++ b/app/src/test/java/com/todoapp/data/TodoDatabaseTest.kt @@ -0,0 +1,55 @@ +package com.todoapp.data + +import android.content.Context +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import com.todoapp.data.database.TodoDatabase +import com.todoapp.data.entity.TodoItem +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import java.io.IOException + +class TodoDatabaseTest { + private lateinit var database: TodoDatabase + + @Before + fun createDatabase() { + val context = ApplicationProvider.getApplicationContext() + database = Room.inMemoryDatabaseBuilder( + context, TodoDatabase::class.java + ).build() + } + + @After + @Throws(IOException::class) + fun closeDatabase() { + database.close() + } + + @Test + @Throws(Exception::class) + fun insertAndRetrieveTodoItem() = runBlocking { + val todoItem = TodoItem( + title = "Test Todo", + description = "Test Description", + isCompleted = false + ) + + // Insert todo item + val insertedId = database.todoDao().insertTodoItem(todoItem) + assertTrue(insertedId > 0) + + // Retrieve todo items + val todoItems = database.todoDao().getAllTodoItems().first() + + // Verify + assertEquals(1, todoItems.size) + assertEquals("Test Todo", todoItems[0].title) + assertEquals("Test Description", todoItems[0].description) + assertFalse(todoItems[0].isCompleted) + } +} \ No newline at end of file