Skip to content

Commit e1cad44

Browse files
authored
Migrate the Activity and NavGraph to Compose (#827)
1 parent 8e1e052 commit e1cad44

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1303
-1635
lines changed

.github/workflows/blueprints.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ name: blueprints
33
on:
44
push:
55
branches:
6-
- main
6+
- dev-compose
77
pull_request:
88
branches:
9-
- main
9+
- dev-compose
1010

1111
jobs:
1212
build:

app/build.gradle

+1-19
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
apply plugin: 'com.android.application'
22
apply plugin: 'kotlin-android'
33
apply plugin: 'kotlin-kapt'
4-
apply plugin: "androidx.navigation.safeargs.kotlin"
54

65
android {
76
compileSdkVersion rootProject.compileSdkVersion
@@ -112,14 +111,9 @@ android {
112111
dependencies {
113112

114113
// App dependencies
115-
implementation "androidx.appcompat:appcompat:$appCompatVersion"
116-
implementation "androidx.cardview:cardview:$cardVersion"
117-
implementation "com.google.android.material:material:$materialVersion"
118-
implementation "androidx.recyclerview:recyclerview:$recyclerViewVersion"
119114
implementation "androidx.annotation:annotation:$androidXAnnotations"
120115
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
121116
implementation "com.jakewharton.timber:timber:$timberVersion"
122-
implementation "androidx.legacy:legacy-support-v4:$androidXLegacySupport"
123117
implementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion"
124118
implementation "androidx.room:room-runtime:$roomVersion"
125119
kapt "androidx.room:room-compiler:$roomVersion"
@@ -130,15 +124,14 @@ dependencies {
130124
implementation "androidx.room:room-ktx:$roomVersion"
131125
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$archLifecycleVersion"
132126
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$archLifecycleVersion"
133-
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
134-
implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
135127

136128
// Jetpack Compose
137129
implementation "androidx.activity:activity-compose:$activityComposeVersion"
138130
implementation "androidx.compose.material:material:$composeVersion"
139131
implementation "androidx.compose.animation:animation:$composeVersion"
140132
implementation "androidx.compose.ui:ui-tooling-preview:$composeVersion"
141133
implementation "androidx.compose.runtime:runtime-livedata:$composeVersion"
134+
implementation "androidx.navigation:navigation-compose:$navigationVersion"
142135
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$archLifecycleVersion"
143136
implementation "com.google.accompanist:accompanist-appcompat-theme:$accompanistVersion"
144137
implementation "com.google.accompanist:accompanist-swiperefresh:$accompanistVersion"
@@ -168,10 +161,7 @@ dependencies {
168161
testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"
169162
testImplementation "androidx.test.ext:junit-ktx:$androidXTestExtKotlinRunnerVersion"
170163
testImplementation "androidx.test:rules:$androidXTestRulesVersion"
171-
// Once https://issuetracker.google.com/127986458 is fixed this can be testImplementation
172-
debugImplementation "androidx.fragment:fragment-testing:$fragmentVersion"
173164
implementation "androidx.test:core:$androidXTestCoreVersion"
174-
implementation "androidx.fragment:fragment-ktx:$fragmentVersion"
175165

176166
// AndroidX Test - Instrumented testing
177167
androidTestImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"
@@ -187,14 +177,6 @@ dependencies {
187177
androidTestImplementation "org.robolectric:annotations:$robolectricVersion"
188178
implementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion"
189179

190-
// Resolve conflicts between main and test APK:
191-
androidTestImplementation "androidx.annotation:annotation:$androidXAnnotations"
192-
androidTestImplementation "androidx.legacy:legacy-support-v4:$androidXLegacySupport"
193-
androidTestImplementation "androidx.recyclerview:recyclerview:$recyclerViewVersion"
194-
androidTestImplementation "androidx.appcompat:appcompat:$appCompatVersion"
195-
androidTestImplementation "com.google.android.material:material:$materialVersion"
196-
197180
// Kotlin
198181
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
199-
implementation "androidx.fragment:fragment-ktx:$fragmentKtxVersion"
200182
}

app/src/androidTestMock/java/com/example/android/architecture/blueprints/todoapp/tasks/AppNavigationTest.kt

+95-120
Original file line numberDiff line numberDiff line change
@@ -15,217 +15,192 @@
1515
*/
1616
package com.example.android.architecture.blueprints.todoapp.tasks
1717

18-
import android.view.Gravity
18+
import androidx.activity.ComponentActivity
19+
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
1920
import androidx.compose.ui.test.assertIsDisplayed
21+
import androidx.compose.ui.test.assertIsNotDisplayed
2022
import androidx.compose.ui.test.junit4.createAndroidComposeRule
23+
import androidx.compose.ui.test.onAllNodesWithText
2124
import androidx.compose.ui.test.onNodeWithContentDescription
2225
import androidx.compose.ui.test.onNodeWithText
2326
import androidx.compose.ui.test.performClick
24-
import androidx.drawerlayout.widget.DrawerLayout
25-
import androidx.navigation.findNavController
2627
import androidx.test.core.app.ApplicationProvider.getApplicationContext
27-
import androidx.test.espresso.Espresso.onView
2828
import androidx.test.espresso.Espresso.pressBack
29-
import androidx.test.espresso.IdlingRegistry
30-
import androidx.test.espresso.action.ViewActions.click
31-
import androidx.test.espresso.assertion.ViewAssertions.matches
32-
import androidx.test.espresso.contrib.DrawerActions.open
33-
import androidx.test.espresso.contrib.DrawerMatchers.isClosed
34-
import androidx.test.espresso.contrib.DrawerMatchers.isOpen
35-
import androidx.test.espresso.contrib.NavigationViewActions.navigateTo
36-
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
37-
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
38-
import androidx.test.espresso.matcher.ViewMatchers.withId
39-
import androidx.test.espresso.matcher.ViewMatchers.withText
4029
import androidx.test.ext.junit.runners.AndroidJUnit4
4130
import androidx.test.filters.LargeTest
31+
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
4232
import com.example.android.architecture.blueprints.todoapp.R
4333
import com.example.android.architecture.blueprints.todoapp.ServiceLocator
34+
import com.example.android.architecture.blueprints.todoapp.TodoNavGraph
4435
import com.example.android.architecture.blueprints.todoapp.data.Task
4536
import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository
46-
import com.example.android.architecture.blueprints.todoapp.util.DataBindingIdlingResource
47-
import com.example.android.architecture.blueprints.todoapp.util.EspressoIdlingResource
48-
import com.example.android.architecture.blueprints.todoapp.util.monitorActivity
4937
import com.example.android.architecture.blueprints.todoapp.util.saveTaskBlocking
38+
import com.google.accompanist.appcompattheme.AppCompatTheme
39+
import kotlinx.coroutines.Dispatchers
5040
import org.junit.After
41+
import org.junit.Assert.assertTrue
5142
import org.junit.Before
5243
import org.junit.Rule
5344
import org.junit.Test
5445
import org.junit.runner.RunWith
5546

5647
/**
57-
* Tests for the [DrawerLayout] layout component in [TasksActivity] which manages
58-
* navigation within the app.
48+
* Tests for scenarios that requires navigating within the app.
5949
*/
6050
@RunWith(AndroidJUnit4::class)
6151
@LargeTest
6252
class AppNavigationTest {
6353

6454
private lateinit var tasksRepository: TasksRepository
6555

66-
// An Idling Resource that waits for Data Binding to have no pending bindings
67-
private val dataBindingIdlingResource = DataBindingIdlingResource()
68-
6956
@get:Rule
70-
val composeTestRule = createAndroidComposeRule<TasksActivity>()
57+
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
7158
private val activity get() = composeTestRule.activity
7259

60+
// Executes tasks in the Architecture Components in the same thread
61+
@get:Rule
62+
var instantTaskExecutorRule = InstantTaskExecutorRule()
63+
7364
@Before
7465
fun init() {
75-
tasksRepository = ServiceLocator.provideTasksRepository(getApplicationContext())
66+
// Run on UI thread to make sure the same instance of the SL is used.
67+
runOnUiThread {
68+
ServiceLocator.createDataBase(getApplicationContext(), inMemory = true)
69+
ServiceLocator.ioDispatcher = Dispatchers.Unconfined
70+
tasksRepository = ServiceLocator.provideTasksRepository(getApplicationContext())
71+
}
7672
}
7773

7874
@After
7975
fun reset() {
80-
ServiceLocator.resetRepository()
81-
}
82-
83-
/**
84-
* Idling resources tell Espresso that the app is idle or busy. This is needed when operations
85-
* are not scheduled in the main Looper (for example when executed on a different thread).
86-
*/
87-
@Before
88-
fun registerIdlingResource() {
89-
IdlingRegistry.getInstance().register(EspressoIdlingResource.countingIdlingResource)
90-
IdlingRegistry.getInstance().register(dataBindingIdlingResource)
91-
}
92-
93-
/**
94-
* Unregister your Idling Resource so it can be garbage collected and does not leak any memory.
95-
*/
96-
@After
97-
fun unregisterIdlingResource() {
98-
IdlingRegistry.getInstance().unregister(EspressoIdlingResource.countingIdlingResource)
99-
IdlingRegistry.getInstance().unregister(dataBindingIdlingResource)
76+
runOnUiThread {
77+
ServiceLocator.resetRepository()
78+
ServiceLocator.resetIODispatcher()
79+
}
10080
}
10181

10282
@Test
10383
fun drawerNavigationFromTasksToStatistics() {
104-
// start up Tasks screen
105-
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
106-
107-
onView(withId(R.id.drawer_layout))
108-
.check(matches(isClosed(Gravity.START))) // Left Drawer should be closed.
109-
.perform(open()) // Open Drawer
84+
setContent()
11085

86+
openDrawer()
11187
// Start statistics screen.
112-
onView(withId(R.id.nav_view))
113-
.perform(navigateTo(R.id.statistics_fragment_dest))
114-
88+
composeTestRule.onNodeWithText(activity.getString(R.string.statistics_title)).performClick()
11589
// Check that statistics screen was opened.
116-
composeTestRule.onNodeWithText("You have no tasks.").assertIsDisplayed()
117-
composeTestRule.waitForIdle()
118-
119-
onView(withId(R.id.drawer_layout))
120-
.check(matches(isClosed(Gravity.START))) // Left Drawer should be closed.
121-
.perform(open()) // Open Drawer
90+
composeTestRule.onNodeWithText(activity.getString(R.string.statistics_no_tasks))
91+
.assertIsDisplayed()
12292

93+
openDrawer()
12394
// Start tasks screen.
124-
onView(withId(R.id.nav_view))
125-
.perform(navigateTo(R.id.tasks_fragment_dest))
126-
95+
composeTestRule.onNodeWithText(activity.getString(R.string.list_title)).performClick()
12796
// Check that tasks screen was opened.
128-
composeTestRule.onNodeWithText("You have no tasks!").assertIsDisplayed()
97+
composeTestRule.onNodeWithText(activity.getString(R.string.no_tasks_all))
98+
.assertIsDisplayed()
12999
}
130100

131101
@Test
132102
fun tasksScreen_clickOnAndroidHomeIcon_OpensNavigation() {
133-
// start up Tasks screen
134-
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
103+
setContent()
135104

136105
// Check that left drawer is closed at startup
137-
onView(withId(R.id.drawer_layout))
138-
.check(matches(isClosed(Gravity.START))) // Left Drawer should be closed.
106+
composeTestRule.onNodeWithText(activity.getString(R.string.list_title))
107+
.assertIsNotDisplayed()
108+
composeTestRule.onNodeWithText(activity.getString(R.string.statistics_title))
109+
.assertIsNotDisplayed()
139110

140-
// Open Drawer
141-
onView(
142-
withContentDescription(
143-
composeTestRule.activityRule.scenario.getToolbarNavigationContentDescription()
144-
)
145-
).perform(click())
111+
openDrawer()
146112

147113
// Check if drawer is open
148-
onView(withId(R.id.drawer_layout))
149-
.check(matches(isOpen(Gravity.START))) // Left drawer is open open.
114+
composeTestRule.onNodeWithText(activity.getString(R.string.list_title)).assertIsDisplayed()
115+
composeTestRule.onNodeWithText(activity.getString(R.string.statistics_title))
116+
.assertIsDisplayed()
150117
}
151118

152119
@Test
153120
fun statsScreen_clickOnAndroidHomeIcon_OpensNavigation() {
154-
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
121+
setContent()
155122

156123
// When the user navigates to the stats screen
157-
composeTestRule.activityRule.scenario.onActivity {
158-
it.findNavController(R.id.nav_host_fragment).navigate(R.id.statistics_fragment_dest)
159-
}
160-
composeTestRule.waitForIdle()
161-
162-
// Then check that left drawer is closed at startup
163-
onView(withId(R.id.drawer_layout))
164-
.check(matches(isClosed(Gravity.START))) // Left Drawer should be closed.
165-
166-
// When the drawer is opened
167-
onView(
168-
withContentDescription(
169-
composeTestRule.activityRule.scenario.getToolbarNavigationContentDescription()
170-
)
171-
).perform(click())
172-
173-
// Then check that the drawer is open
174-
onView(withId(R.id.drawer_layout))
175-
.check(matches(isOpen(Gravity.START))) // Left drawer is open open.
124+
openDrawer()
125+
composeTestRule.onNodeWithText(activity.getString(R.string.statistics_title)).performClick()
126+
127+
composeTestRule.onNodeWithText(activity.getString(R.string.list_title))
128+
.assertIsNotDisplayed()
129+
130+
openDrawer()
131+
132+
// Check if drawer is open
133+
composeTestRule.onNodeWithText(activity.getString(R.string.list_title)).assertIsDisplayed()
134+
assertTrue(
135+
composeTestRule.onAllNodesWithText(activity.getString(R.string.statistics_title))
136+
.fetchSemanticsNodes().isNotEmpty()
137+
)
176138
}
177139

178140
@Test
179141
fun taskDetailScreen_doubleUIBackButton() {
180-
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
181-
182-
val task = Task("UI <- button", "Description")
142+
val taskName = "UI <- button"
143+
val task = Task(taskName, "Description")
183144
tasksRepository.saveTaskBlocking(task)
184-
composeTestRule.waitForIdle()
145+
146+
setContent()
185147

186148
// Click on the task on the list
187-
onView(withText("UI <- button")).perform(click())
149+
composeTestRule.onNodeWithText(activity.getString(R.string.label_all)).assertIsDisplayed()
150+
composeTestRule.onNodeWithText(taskName).assertIsDisplayed()
151+
composeTestRule.onNodeWithText(taskName).performClick()
152+
188153
// Click on the edit task button
154+
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.edit_task))
155+
.assertIsDisplayed()
189156
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.edit_task))
190157
.performClick()
191158

192159
// Confirm that if we click "<-" once, we end up back at the task details page
193-
onView(
194-
withContentDescription(
195-
composeTestRule.activityRule.scenario.getToolbarNavigationContentDescription()
196-
)
197-
).perform(click())
198-
composeTestRule.onNodeWithText("UI <- button").assertIsDisplayed()
160+
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.menu_back))
161+
.performClick()
162+
composeTestRule.onNodeWithText(taskName).assertIsDisplayed()
199163

200164
// Confirm that if we click "<-" a second time, we end up back at the home screen
201-
onView(
202-
withContentDescription(
203-
composeTestRule.activityRule.scenario.getToolbarNavigationContentDescription()
204-
)
205-
).perform(click())
206-
composeTestRule.onNodeWithText("All tasks").assertIsDisplayed()
165+
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.menu_back))
166+
.performClick()
167+
composeTestRule.onNodeWithText(activity.getString(R.string.label_all)).assertIsDisplayed()
207168
}
208169

209170
@Test
210171
fun taskDetailScreen_doubleBackButton() {
211-
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
212-
213-
val task = Task("Back button", "Description")
172+
val taskName = "Back button"
173+
val task = Task(taskName, "Description")
214174
tasksRepository.saveTaskBlocking(task)
215-
composeTestRule.waitForIdle()
175+
176+
setContent()
216177

217178
// Click on the task on the list
218-
onView(withText("Back button")).perform(click())
179+
composeTestRule.onNodeWithText(taskName).assertIsDisplayed()
180+
composeTestRule.onNodeWithText(taskName).performClick()
219181
// Click on the edit task button
220182
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.edit_task))
221183
.performClick()
222184

223185
// Confirm that if we click back once, we end up back at the task details page
224186
pressBack()
225-
composeTestRule.onNodeWithText("Back button").assertIsDisplayed()
187+
composeTestRule.onNodeWithText(taskName).assertIsDisplayed()
226188

227189
// Confirm that if we click back a second time, we end up back at the home screen
228190
pressBack()
229-
composeTestRule.onNodeWithText("All tasks").assertIsDisplayed()
191+
composeTestRule.onNodeWithText(activity.getString(R.string.label_all)).assertIsDisplayed()
192+
}
193+
194+
private fun setContent() {
195+
composeTestRule.setContent {
196+
AppCompatTheme {
197+
TodoNavGraph()
198+
}
199+
}
200+
}
201+
202+
private fun openDrawer() {
203+
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.open_drawer))
204+
.performClick()
230205
}
231206
}

0 commit comments

Comments
 (0)