diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 709aaa3..5b2e959 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -68,6 +68,7 @@ dependencies {
implementation("com.squareup.okhttp3:okhttp:4.11.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
testImplementation("junit:junit:4.13.2")
+ testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0b7e3d2..5ac4331 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
+}
+
+class ApiTasksRepository(
+ private val taskApiService: TaskApiService
+): TasksRepository{
+ override suspend fun getTasks(): List {
+ return taskApiService.getTasks().asDomainObjects()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/taskapp/network/TaskApiService.kt b/app/src/main/java/com/example/taskapp/network/TaskApiService.kt
index 9f37855..a3316ad 100644
--- a/app/src/main/java/com/example/taskapp/network/TaskApiService.kt
+++ b/app/src/main/java/com/example/taskapp/network/TaskApiService.kt
@@ -7,20 +7,13 @@ import okhttp3.MediaType.Companion.toMediaType
import retrofit2.Retrofit
import retrofit2.http.GET
-private const val BASE_URL = "http://10.0.2.2:3000"
-private val retrofit = Retrofit.Builder()
- .addConverterFactory(
- Json.asConverterFactory("application/json".toMediaType())
- )
- .baseUrl(BASE_URL)
- .build()
+
//create the actual function implementations (expensive!)
-object TaskApi{
- val retrofitService : TaskApiService by lazy {
- retrofit.create(TaskApiService::class.java)
- }
-}
+//no longer needed --> moved to the AppContainer
+//object TaskApi{
+//
+//}
//define what the API looks like
diff --git a/app/src/main/java/com/example/taskapp/ui/TaskItem.kt b/app/src/main/java/com/example/taskapp/ui/TaskItem.kt
index 74c0c81..cb1043c 100644
--- a/app/src/main/java/com/example/taskapp/ui/TaskItem.kt
+++ b/app/src/main/java/com/example/taskapp/ui/TaskItem.kt
@@ -26,6 +26,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -46,7 +47,7 @@ fun TaskItem(
Card(
modifier = modifier.padding(dimensionResource(R.dimen.padding_small)),
) {
- var expanded by remember { mutableStateOf(false) }
+ var expanded by rememberSaveable { mutableStateOf(false) }
val color by animateColorAsState(
targetValue = if (expanded) {
MaterialTheme.colorScheme.tertiaryContainer
diff --git a/app/src/main/java/com/example/taskapp/ui/TaskOverview.kt b/app/src/main/java/com/example/taskapp/ui/TaskOverview.kt
index 817d295..971a1c7 100644
--- a/app/src/main/java/com/example/taskapp/ui/TaskOverview.kt
+++ b/app/src/main/java/com/example/taskapp/ui/TaskOverview.kt
@@ -19,7 +19,7 @@ fun TaskOverview(
addingVisible: Boolean,
onVisibilityChanged: (Boolean) -> Unit,
modifier: Modifier = Modifier,
- taskOverviewViewModel: TaskOverviewViewModel = viewModel(),
+ taskOverviewViewModel: TaskOverviewViewModel = viewModel(factory = TaskOverviewViewModel.Factory),
) {
val taskOverviewState by taskOverviewViewModel.uiState.collectAsState()
diff --git a/app/src/main/java/com/example/taskapp/ui/TaskOverviewViewModel.kt b/app/src/main/java/com/example/taskapp/ui/TaskOverviewViewModel.kt
index 7fa7d4a..c75b0fe 100644
--- a/app/src/main/java/com/example/taskapp/ui/TaskOverviewViewModel.kt
+++ b/app/src/main/java/com/example/taskapp/ui/TaskOverviewViewModel.kt
@@ -4,11 +4,15 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.viewmodel.initializer
+import androidx.lifecycle.viewmodel.viewModelFactory
+import com.example.taskapp.TasksApplication
import com.example.taskapp.data.TaskSampler
+import com.example.taskapp.data.TasksRepository
import com.example.taskapp.model.Task
-import com.example.taskapp.network.TaskApi
-import com.example.taskapp.network.asDomainObjects
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -16,7 +20,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.io.IOException
-class TaskOverviewViewModel : ViewModel() {
+class TaskOverviewViewModel(private val tasksRepository: TasksRepository) : ViewModel() {
// use StateFlow (Flow: emits current state + any updates)
private val _uiState = MutableStateFlow(TaskOverviewState(TaskSampler.getAll()))
val uiState: StateFlow = _uiState.asStateFlow()
@@ -61,11 +65,13 @@ class TaskOverviewViewModel : ViewModel() {
private fun getApiTasks(){
viewModelScope.launch {
try{
- val listResult = TaskApi.retrofitService.getTasks()
+ //use the repository
+ //val tasksRepository = ApiTasksRepository() //repo is now injected
+ val listResult = tasksRepository.getTasks()
_uiState.update {
- it.copy(currentTaskList = listResult.asDomainObjects())
+ it.copy(currentTaskList = listResult)
}
- taskApiState = TaskApiState.Success(listResult.asDomainObjects())
+ taskApiState = TaskApiState.Success(listResult)
}
catch (e: IOException){
//show a toast? save a log on firebase? ...
@@ -75,6 +81,18 @@ class TaskOverviewViewModel : ViewModel() {
}
}
+
+ //object to tell the android framework how to handle the parameter of the viewmodel
+ companion object {
+ val Factory: ViewModelProvider.Factory = viewModelFactory {
+ initializer {
+ val application = (this[APPLICATION_KEY] as TasksApplication)
+ val tasksRepository = application.container.tasksRepository
+ TaskOverviewViewModel(tasksRepository = tasksRepository
+ )
+ }
+ }
+ }
}
diff --git a/app/src/test/java/com/example/taskapp/ApiTaskRepositoryTest.kt b/app/src/test/java/com/example/taskapp/ApiTaskRepositoryTest.kt
new file mode 100644
index 0000000..ae8131b
--- /dev/null
+++ b/app/src/test/java/com/example/taskapp/ApiTaskRepositoryTest.kt
@@ -0,0 +1,17 @@
+package com.example.taskapp
+
+import com.example.taskapp.data.ApiTasksRepository
+import com.example.taskapp.fake.FakeDataSource
+import com.example.taskapp.fake.FakeTasksApiService
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class ApiTaskRepositoryTest {
+ @Test
+ fun apiTaskRepository_getTasks_verifyTasksList() =
+ runTest{
+ val repository = ApiTasksRepository(FakeTasksApiService())
+ assertEquals(FakeDataSource.tasks, repository.getTasks())
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/com/example/taskapp/TaskOverviewViewModelTest.kt b/app/src/test/java/com/example/taskapp/TaskOverviewViewModelTest.kt
index 2f39003..3c9f715 100644
--- a/app/src/test/java/com/example/taskapp/TaskOverviewViewModelTest.kt
+++ b/app/src/test/java/com/example/taskapp/TaskOverviewViewModelTest.kt
@@ -1,17 +1,46 @@
package com.example.taskapp
+import com.example.taskapp.fake.FakeApiTasksRepository
import com.example.taskapp.ui.TaskOverviewViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
import org.junit.Assert
+import org.junit.Rule
import org.junit.Test
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
class TaskOverviewViewModelTest {
- private val viewModel = TaskOverviewViewModel()
+
+
private val someTaskName = "some task name"
+ @get:Rule
+ val testDispatcher = TestDispatcherRule()
+
@Test
fun settingNameChangesState() {
+ val viewModel = TaskOverviewViewModel(
+ tasksRepository = FakeApiTasksRepository()
+ )
viewModel.setNewTaskName(someTaskName)
-
Assert.assertEquals(viewModel.uiState.value.newTaskName, someTaskName)
+
+ }
+
+}
+
+class TestDispatcherRule(
+ val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()
+): TestWatcher(){
+ override fun starting(description: Description) {
+ Dispatchers.setMain(testDispatcher)
+ }
+
+ override fun finished(description: Description) {
+ Dispatchers.resetMain()
}
}
diff --git a/app/src/test/java/com/example/taskapp/fake/FakeApiTasksRepository.kt b/app/src/test/java/com/example/taskapp/fake/FakeApiTasksRepository.kt
new file mode 100644
index 0000000..5da32a9
--- /dev/null
+++ b/app/src/test/java/com/example/taskapp/fake/FakeApiTasksRepository.kt
@@ -0,0 +1,11 @@
+package com.example.taskapp.fake
+
+import com.example.taskapp.data.TasksRepository
+import com.example.taskapp.model.Task
+import com.example.taskapp.network.asDomainObjects
+
+class FakeApiTasksRepository: TasksRepository {
+ override suspend fun getTasks(): List {
+ return FakeDataSource.tasks.asDomainObjects()
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/com/example/taskapp/fake/FakeDataSource.kt b/app/src/test/java/com/example/taskapp/fake/FakeDataSource.kt
new file mode 100644
index 0000000..74b9c08
--- /dev/null
+++ b/app/src/test/java/com/example/taskapp/fake/FakeDataSource.kt
@@ -0,0 +1,14 @@
+package com.example.taskapp.fake
+
+import com.example.taskapp.network.ApiTask
+
+object FakeDataSource {
+ const val nameOne = "feed dog"
+ const val nameTwo = "feed cat"
+ const val descriptionOne = "food is in the cellar"
+ const val descriptionTwo = "food is also in the cellar"
+
+ val tasks = listOf(
+ ApiTask(nameOne, descriptionOne),
+ ApiTask(nameTwo, descriptionTwo))
+}
\ No newline at end of file
diff --git a/app/src/test/java/com/example/taskapp/fake/FakeTasksApiService.kt b/app/src/test/java/com/example/taskapp/fake/FakeTasksApiService.kt
new file mode 100644
index 0000000..e77d2f1
--- /dev/null
+++ b/app/src/test/java/com/example/taskapp/fake/FakeTasksApiService.kt
@@ -0,0 +1,10 @@
+package com.example.taskapp.fake
+
+import com.example.taskapp.network.ApiTask
+import com.example.taskapp.network.TaskApiService
+
+class FakeTasksApiService: TaskApiService {
+ override suspend fun getTasks(): List {
+ return FakeDataSource.tasks
+ }
+}
\ No newline at end of file