Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Android Studio
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild

# Gradle files
gradle-app.setting
!gradle-wrapper.jar
.gradletasknamecache

# Compiled class files
*.class

# Log files
*.log

# Package Files
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# Virtual machine crash logs
hs_err_pid*

# Android specific
bin/
gen/
out/
release/

# Android Profiling
*.hprof

# Dependency directories
/node_modules
/jspm_packages

# Editor directories and files
.idea/
.vscode/
*.swp
*.swo

# Environment files
.env

# Secrets
*.key
secrets.json

# Build output
/build
/dist
/target
31 changes: 31 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-kapt")
}

android {
compileSdk = 33
defaultConfig {
applicationId = "com.example.todoapp"
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
}

dependencies {
// Room dependencies
val roomVersion = "2.5.1"
implementation("androidx.room:room-runtime:$roomVersion")
implementation("androidx.room:room-ktx:$roomVersion")
kapt("androidx.room:room-compiler:$roomVersion")

// Testing dependencies
testImplementation("androidx.room:room-testing:$roomVersion")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}
80 changes: 80 additions & 0 deletions app/src/main/java/com/example/todoapp/data/dao/TodoItemDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.example.todoapp.data.dao

import androidx.room.*
import com.example.todoapp.data.model.TodoItem
import kotlinx.coroutines.flow.Flow

@Dao
interface TodoItemDao {
/**
* Inserts a new todo item into the database.
* @param todoItem The todo item to insert
* @return The ID of the newly inserted item
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(todoItem: TodoItem): Long

/**
* Inserts multiple todo items into the database.
* @param todoItems The list of todo items to insert
* @return List of inserted item IDs
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(todoItems: List<TodoItem>): List<Long>

/**
* Updates an existing todo item.
* @param todoItem The todo item with updated information
*/
@Update
suspend fun update(todoItem: TodoItem)

/**
* Deletes a specific todo item.
* @param todoItem The todo item to delete
*/
@Delete
suspend fun delete(todoItem: TodoItem)

/**
* Retrieves a todo item by its ID.
* @param id The ID of the todo item
* @return The todo item or null if not found
*/
@Query("SELECT * FROM todo_items WHERE id = :id")
suspend fun getById(id: Long): TodoItem?

/**
* Retrieves all todo items, sorted by creation date.
* @return A Flow of todo items
*/
@Query("SELECT * FROM todo_items ORDER BY createdAt DESC")
fun getAllTodoItems(): Flow<List<TodoItem>>

/**
* Retrieves completed todo items.
* @return A Flow of completed todo items
*/
@Query("SELECT * FROM todo_items WHERE isCompleted = 1 ORDER BY updatedAt DESC")
fun getCompletedTodoItems(): Flow<List<TodoItem>>

/**
* Retrieves active (not completed) todo items.
* @return A Flow of active todo items
*/
@Query("SELECT * FROM todo_items WHERE isCompleted = 0 ORDER BY createdAt DESC")
fun getActiveTodoItems(): Flow<List<TodoItem>>

/**
* Deletes all todo items from the database.
*/
@Query("DELETE FROM todo_items")
suspend fun deleteAll()

/**
* Counts the total number of todo items.
* @return The total number of todo items
*/
@Query("SELECT COUNT(*) FROM todo_items")
suspend fun count(): Int
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.example.todoapp.data.database

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.example.todoapp.data.dao.TodoItemDao
import com.example.todoapp.data.model.TodoItem

@Database(entities = [TodoItem::class], version = 1, exportSchema = false)
abstract class TodoDatabase : RoomDatabase() {
abstract fun todoItemDao(): TodoItemDao

companion object {
@Volatile
private var INSTANCE: TodoDatabase? = null

fun getDatabase(context: Context): TodoDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
TodoDatabase::class.java,
"todo_database"
).build()
INSTANCE = instance
instance
}
}
}
}
15 changes: 15 additions & 0 deletions app/src/main/java/com/example/todoapp/data/model/TodoItem.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.todoapp.data.model

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "todo_items")
data class TodoItem(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val title: String,
val description: String? = null,
val isCompleted: Boolean = false,
val createdAt: Long = System.currentTimeMillis(),
val updatedAt: Long = System.currentTimeMillis()
)
104 changes: 104 additions & 0 deletions app/src/test/java/com/example/todoapp/data/dao/TodoItemDaoTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.example.todoapp.data.dao

import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.example.todoapp.data.model.TodoItem
import com.example.todoapp.data.database.TodoDatabase
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 org.junit.runner.RunWith
import java.io.IOException

@RunWith(AndroidJUnit4::class)
class TodoItemDaoTest {
private lateinit var todoItemDao: TodoItemDao
private lateinit var database: TodoDatabase

@Before
fun createDatabase() {
val context = ApplicationProvider.getApplicationContext<Context>()
database = Room.inMemoryDatabaseBuilder(context, TodoDatabase::class.java)
.allowMainThreadQueries()
.build()
todoItemDao = database.todoItemDao()
}

@After
@Throws(IOException::class)
fun closeDatabase() {
database.close()
}

@Test
@Throws(Exception::class)
fun insertAndGetTodoItem() = runBlocking {
val todoItem = TodoItem(title = "Test Todo", description = "Test Description")
val id = todoItemDao.insert(todoItem)
val retrievedItem = todoItemDao.getById(id)

assertNotNull(retrievedItem)
assertEquals("Test Todo", retrievedItem?.title)
assertEquals("Test Description", retrievedItem?.description)
}

@Test
@Throws(Exception::class)
fun updateTodoItem() = runBlocking {
val todoItem = TodoItem(title = "Original Title")
val id = todoItemDao.insert(todoItem)

val updatedItem = todoItem.copy(id = id, title = "Updated Title")
todoItemDao.update(updatedItem)

val retrievedItem = todoItemDao.getById(id)
assertEquals("Updated Title", retrievedItem?.title)
}

@Test
@Throws(Exception::class)
fun deleteTodoItem() = runBlocking {
val todoItem = TodoItem(title = "Item to Delete")
val id = todoItemDao.insert(todoItem)

val retrievedItem = todoItemDao.getById(id)
assertNotNull(retrievedItem)

todoItemDao.delete(retrievedItem!!)
val deletedItem = todoItemDao.getById(id)
assertNull(deletedItem)
}

@Test
@Throws(Exception::class)
fun getAllTodoItems() = runBlocking {
val todoItems = listOf(
TodoItem(title = "Item 1"),
TodoItem(title = "Item 2"),
TodoItem(title = "Item 3")
)
todoItemDao.insertAll(todoItems)

val allItems = todoItemDao.getAllTodoItems().first()
assertEquals(3, allItems.size)
}

@Test
@Throws(Exception::class)
fun getCompletedTodoItems() = runBlocking {
val todoItems = listOf(
TodoItem(title = "Completed 1", isCompleted = true),
TodoItem(title = "Completed 2", isCompleted = true),
TodoItem(title = "Active", isCompleted = false)
)
todoItemDao.insertAll(todoItems)

val completedItems = todoItemDao.getCompletedTodoItems().first()
assertEquals(2, completedItems.size)
}
}