Skip to content
This repository was archived by the owner on Apr 2, 2021. It is now read-only.

Commit 5d6a358

Browse files
committed
Pausing for the day (2021-03-30)
1 parent 482d0fc commit 5d6a358

File tree

17 files changed

+473
-73
lines changed

17 files changed

+473
-73
lines changed

app/build.gradle

+11-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ android {
3232
kotlinOptions {
3333
jvmTarget = JavaVersion.VERSION_1_8.toString()
3434
}
35+
packagingOptions {
36+
exclude 'META-INF/AL2.0'
37+
exclude 'META-INF/LGPL2.1'
38+
jniLibs {
39+
useLegacyPackaging true
40+
}
41+
}
3542
}
3643

3744
dependencies {
@@ -50,7 +57,6 @@ dependencies {
5057

5158
// Architecture Components
5259
implementation "androidx.room:room-runtime:$roomVersion"
53-
kapt "androidx.room:room-compiler:$roomVersion"
5460
implementation "androidx.room:room-ktx:$roomVersion"
5561
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
5662
kapt "androidx.lifecycle:lifecycle-common-java8:$archLifecycleVersion"
@@ -71,6 +77,10 @@ dependencies {
7177
// AndroidX Test - Instrumented testing
7278
androidTestImplementation "androidx.test.ext:junit-ktx:${androidXTestExtKotlinRunnerVersion}"
7379
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
80+
debugImplementation "androidx.fragment:fragment-testing:$fragmentVersion"
81+
implementation "androidx.test:core-ktx:$androidXTestCoreVersion"
82+
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
83+
7484

7585
// Kotlin
7686
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package com.example.android.architecture.blueprints.todoapp.data.source
2+
3+
import androidx.annotation.VisibleForTesting
4+
import androidx.lifecycle.LiveData
5+
import androidx.lifecycle.MutableLiveData
6+
import androidx.lifecycle.map
7+
8+
import com.example.android.architecture.blueprints.todoapp.data.Result
9+
import com.example.android.architecture.blueprints.todoapp.data.Result.Error
10+
import com.example.android.architecture.blueprints.todoapp.data.Result.Success
11+
import com.example.android.architecture.blueprints.todoapp.data.Task
12+
13+
import kotlinx.coroutines.runBlocking
14+
15+
class FakeAndroidTestRepository: TasksRepository {
16+
17+
var tasksServiceData: LinkedHashMap<String, Task> = LinkedHashMap()
18+
private var shouldReturnError: Boolean = false
19+
private val observableTasks = MutableLiveData<Result<List<Task>>>()
20+
21+
fun setReturnError(value: Boolean) {
22+
shouldReturnError = value
23+
}
24+
25+
override suspend fun refreshTasks() {
26+
observableTasks.value = getTasks()
27+
}
28+
29+
override suspend fun refreshTask(taskId: String) {
30+
refreshTasks()
31+
}
32+
33+
override fun observeTasks(): LiveData<Result<List<Task>>> {
34+
runBlocking { refreshTasks() }
35+
return observableTasks
36+
}
37+
38+
override fun observeTask(taskId: String): LiveData<Result<Task>> {
39+
runBlocking { refreshTasks() }
40+
return observableTasks.map { tasks ->
41+
when (tasks) {
42+
is Result.Loading -> Result.Loading
43+
is Error -> Error(tasks.exception)
44+
is Success -> {
45+
val task = tasks.data.firstOrNull() { it.id == taskId }
46+
?: return@map Error(Exception("Not found"))
47+
Success(task)
48+
}
49+
}
50+
}
51+
}
52+
53+
override suspend fun getTask(taskId: String, forceUpdate: Boolean): Result<Task> {
54+
if (shouldReturnError) {
55+
return Error(Exception("Test Exception"))
56+
}
57+
tasksServiceData[taskId]?.let { return Success(it) }
58+
return Error(Exception("Could not find task"))
59+
}
60+
61+
override suspend fun getTasks(forceUpdate: Boolean): Result<List<Task>> {
62+
if (shouldReturnError) return Error(Exception("Test Exception"))
63+
return Success(tasksServiceData.values.toList())
64+
}
65+
66+
override suspend fun saveTask(task: Task) {
67+
tasksServiceData[task.id] = task
68+
}
69+
70+
override suspend fun completeTask(task: Task) {
71+
val completeTask = Task(task.title, task.description, true, task.id)
72+
tasksServiceData[task.id] = completeTask
73+
}
74+
75+
override suspend fun completeTask(taskId: String) {
76+
throw NotImplementedError()
77+
}
78+
79+
override suspend fun activateTask(task: Task) {
80+
val activeTask = Task(task.title, task.description, false, task.id)
81+
tasksServiceData[task.id] = activeTask
82+
}
83+
84+
override suspend fun activateTask(taskId: String) {
85+
throw NotImplementedError()
86+
}
87+
88+
override suspend fun clearCompletedTasks() {
89+
tasksServiceData = tasksServiceData.filterValues {
90+
!it.isCompleted
91+
} as LinkedHashMap<String, Task>
92+
}
93+
94+
override suspend fun deleteTask(taskId: String) {
95+
tasksServiceData.remove(taskId)
96+
refreshTasks()
97+
}
98+
99+
override suspend fun deleteAllTasks() {
100+
tasksServiceData.clear()
101+
refreshTasks()
102+
}
103+
104+
fun addTasks(vararg tasks: Task) {
105+
for (task in tasks) tasksServiceData[task.id] = task
106+
runBlocking { refreshTasks() }
107+
}
108+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.example.android.architecture.blueprints.todoapp.taskdetail
2+
3+
import androidx.fragment.app.testing.launchFragmentInContainer
4+
import androidx.test.espresso.Espresso.onView
5+
import androidx.test.espresso.assertion.ViewAssertions.matches
6+
import androidx.test.espresso.matcher.ViewMatchers.isChecked
7+
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
8+
import androidx.test.espresso.matcher.ViewMatchers.withId
9+
import androidx.test.espresso.matcher.ViewMatchers.withText
10+
11+
import androidx.test.ext.junit.runners.AndroidJUnit4
12+
import androidx.test.filters.MediumTest
13+
14+
import com.example.android.architecture.blueprints.todoapp.R
15+
import com.example.android.architecture.blueprints.todoapp.ServiceLocator
16+
import com.example.android.architecture.blueprints.todoapp.data.Task
17+
import com.example.android.architecture.blueprints.todoapp.data.source.FakeAndroidTestRepository
18+
import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository
19+
20+
import kotlinx.coroutines.ExperimentalCoroutinesApi
21+
import kotlinx.coroutines.test.runBlockingTest
22+
23+
import org.hamcrest.core.IsNot.not
24+
25+
import org.junit.After
26+
import org.junit.Before
27+
import org.junit.Test
28+
import org.junit.runner.RunWith
29+
30+
@MediumTest
31+
@RunWith(AndroidJUnit4::class)
32+
@ExperimentalCoroutinesApi
33+
class TaskDetailFragmentTest {
34+
private lateinit var tasksRepository: TasksRepository
35+
36+
@Before
37+
fun initRepository() {
38+
tasksRepository = FakeAndroidTestRepository()
39+
ServiceLocator.tasksRepository = tasksRepository
40+
}
41+
42+
@Test
43+
fun activeTaskDetails_DisplayedInUi() = runBlockingTest {
44+
// Given add active, incomplete task to the DB.
45+
val activeTask = Task("Active Task", "AndroidX Rox0rs", false)
46+
tasksRepository.saveTask(activeTask)
47+
48+
// When details fragment launched to display task
49+
val bundle = TaskDetailFragmentArgs(activeTask.id).toBundle()
50+
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)
51+
52+
// Then Task details are displayed on the screen.
53+
// Make sure that the title and description are both shown and correct.
54+
onView(withId(R.id.task_detail_title_text))
55+
.check(matches(isDisplayed()))
56+
.check(matches(withText("Active Task")))
57+
onView(withId(R.id.task_detail_description_text))
58+
.check(matches(isDisplayed()))
59+
.check(matches(withText("AndroidX Rox0rs")))
60+
// And make sure the "active" checkbox is unchecked.
61+
onView(withId(R.id.task_detail_complete_checkbox))
62+
.check(matches(isDisplayed()))
63+
.check(matches(not(isChecked())))
64+
}
65+
66+
@Test
67+
fun completedTaskDetails_DisplayedInUi() = runBlockingTest {
68+
// Given - Add completed task to the DB
69+
val completedTitle = "Completed Task"
70+
val completedDescription = "This task is all done. Done done donesky. Yup!"
71+
val completedTask = Task(completedTitle, completedDescription, true)
72+
tasksRepository.saveTask(completedTask)
73+
74+
// When - Details fragment launched to display task
75+
val bundle = TaskDetailFragmentArgs(completedTask.id).toBundle()
76+
launchFragmentInContainer<TaskDetailFragment>(bundle, R.style.AppTheme)
77+
78+
// Then - Task details are displayed on the screen.
79+
// Make sure that the title and description are both shown and correct.
80+
onView(withId(R.id.task_detail_title_text))
81+
.check(matches(isDisplayed()))
82+
.check(matches(withText(completedTitle)))
83+
onView(withId(R.id.task_detail_description_text))
84+
.check(matches(isDisplayed()))
85+
.check(matches(withText(completedDescription)))
86+
onView(withId(R.id.task_detail_complete_checkbox))
87+
.check(matches(isDisplayed()))
88+
.check(matches(isChecked()))
89+
}
90+
91+
@After
92+
fun cleanupDb() {
93+
runBlockingTest {
94+
ServiceLocator.resetRepository()
95+
}
96+
}
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.example.android.architecture.blueprints.todoapp
2+
3+
import android.content.Context
4+
import androidx.annotation.VisibleForTesting
5+
6+
import androidx.room.Room
7+
8+
import com.example.android.architecture.blueprints.todoapp.data.source.DefaultTasksRepository
9+
import com.example.android.architecture.blueprints.todoapp.data.source.TasksDataSource
10+
import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository
11+
import com.example.android.architecture.blueprints.todoapp.data.source.local.TasksLocalDataSource
12+
import com.example.android.architecture.blueprints.todoapp.data.source.local.ToDoDatabase
13+
import com.example.android.architecture.blueprints.todoapp.data.source.remote.TasksRemoteDataSource
14+
15+
import kotlinx.coroutines.runBlocking
16+
17+
object ServiceLocator {
18+
private var database: ToDoDatabase? = null
19+
@Volatile
20+
var tasksRepository: TasksRepository? = null
21+
@VisibleForTesting set
22+
23+
private val lock = Any()
24+
25+
@VisibleForTesting
26+
fun resetRepository() {
27+
synchronized(lock) {
28+
runBlocking{ TasksRemoteDataSource.deleteAllTasks() }
29+
}
30+
database?.apply {
31+
clearAllTables()
32+
close()
33+
}
34+
database = null
35+
tasksRepository = null
36+
}
37+
38+
fun provideTasksRepository(context: Context) : TasksRepository {
39+
synchronized(this) {
40+
return tasksRepository ?: createTasksRepository(context)
41+
}
42+
}
43+
44+
private fun createTasksRepository(context: Context): TasksRepository {
45+
val newRepository= DefaultTasksRepository(TasksRemoteDataSource, createTaskLocalDataSource(context))
46+
tasksRepository = newRepository
47+
return newRepository
48+
}
49+
50+
private fun createTaskLocalDataSource(context: Context): TasksDataSource {
51+
val database = database ?: createDataBase(context)
52+
return TasksLocalDataSource(database.taskDao())
53+
}
54+
55+
private fun createDataBase(context: Context): ToDoDatabase {
56+
val result = Room.databaseBuilder(
57+
context.applicationContext,
58+
ToDoDatabase::class.java,
59+
"Tasks.db"
60+
).build()
61+
database = result
62+
return result
63+
}
64+
}

app/src/main/java/com/example/android/architecture/blueprints/todoapp/TodoApplication.kt

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.example.android.architecture.blueprints.todoapp
1818

1919
import android.app.Application
2020
import androidx.databinding.library.BuildConfig
21+
import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository
2122

2223
import timber.log.Timber
2324
import timber.log.Timber.DebugTree
@@ -30,6 +31,9 @@ import timber.log.Timber.DebugTree
3031
*/
3132
class TodoApplication : Application() {
3233

34+
val tasksRepository: TasksRepository
35+
get() = ServiceLocator.provideTasksRepository(this)
36+
3337
override fun onCreate() {
3438
super.onCreate()
3539
if (BuildConfig.DEBUG) Timber.plant(DebugTree())

app/src/main/java/com/example/android/architecture/blueprints/todoapp/addedittask/AddEditTaskViewModel.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import android.app.Application
2020
import androidx.lifecycle.*
2121
import com.example.android.architecture.blueprints.todoapp.Event
2222
import com.example.android.architecture.blueprints.todoapp.R
23+
import com.example.android.architecture.blueprints.todoapp.TodoApplication
2324
import com.example.android.architecture.blueprints.todoapp.data.Result.Success
2425
import com.example.android.architecture.blueprints.todoapp.data.Task
2526
import com.example.android.architecture.blueprints.todoapp.data.source.DefaultTasksRepository
@@ -32,7 +33,7 @@ class AddEditTaskViewModel(application: Application) : AndroidViewModel(applicat
3233

3334
// Note, for testing and architecture purposes, it's bad practice to construct the repository
3435
// here. We'll show you how to fix this during the codelab
35-
private val tasksRepository = DefaultTasksRepository.getRepository(application)
36+
private val tasksRepository = (application as TodoApplication).tasksRepository
3637

3738
// Two-way databinding, exposing MutableLiveData
3839
val title = MutableLiveData<String>()

app/src/main/java/com/example/android/architecture/blueprints/todoapp/data/Result.kt

-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ sealed class Result<out R> {
3333
is Success<*> -> "Success[data=$data]"
3434
is Error -> "Error[exception=$exception]"
3535
is Loading -> "Loading"
36-
else -> "Error[Uncaught Exception]"
3736
}
3837
}
3938
}

0 commit comments

Comments
 (0)