Skip to content

Commit 653a563

Browse files
authored
Migrate Statistics to Compose (#821)
1 parent 99b9bbf commit 653a563

File tree

13 files changed

+595
-457
lines changed

13 files changed

+595
-457
lines changed

app/build.gradle

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ android {
7777
}
7878
}
7979

80+
buildFeatures {
81+
compose true
82+
}
83+
8084
dataBinding {
8185
enabled = true
8286
enabledForTests = true
@@ -95,6 +99,10 @@ android {
9599
exclude 'META-INF/AL2.0'
96100
exclude 'META-INF/LGPL2.1'
97101
}
102+
103+
composeOptions {
104+
kotlinCompilerExtensionVersion "$composeVersion"
105+
}
98106
}
99107

100108
/*
@@ -125,6 +133,18 @@ dependencies {
125133
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
126134
implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
127135

136+
// Jetpack Compose
137+
implementation "androidx.activity:activity-compose:$activityComposeVersion"
138+
implementation "androidx.compose.material:material:$composeVersion"
139+
implementation "androidx.compose.animation:animation:$composeVersion"
140+
implementation "androidx.compose.ui:ui-tooling-preview:$composeVersion"
141+
implementation "androidx.compose.runtime:runtime-livedata:$composeVersion"
142+
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$archLifecycleVersion"
143+
implementation "com.google.accompanist:accompanist-appcompat-theme:$accompanistVersion"
144+
implementation "com.google.accompanist:accompanist-swiperefresh:$accompanistVersion"
145+
debugImplementation "androidx.compose.ui:ui-tooling:$composeVersion"
146+
debugImplementation "androidx.compose.ui:ui-test-manifest:$composeVersion"
147+
128148
// Dependencies for local unit tests
129149
testImplementation "junit:junit:$junitVersion"
130150
testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
@@ -137,10 +157,12 @@ dependencies {
137157
testImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
138158
testImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
139159
testImplementation "com.google.truth:truth:$truthVersion"
160+
testImplementation "androidx.compose.ui:ui-test-junit4:$composeVersion"
140161

141162
// Dependencies for Android unit tests
142163
androidTestImplementation "junit:junit:$junitVersion"
143164
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion"
165+
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$composeVersion"
144166

145167
// AndroidX Test - JVM testing
146168
testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"

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

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
package com.example.android.architecture.blueprints.todoapp.tasks
1717

1818
import android.view.Gravity
19+
import androidx.compose.ui.test.assertIsDisplayed
20+
import androidx.compose.ui.test.junit4.createAndroidComposeRule
21+
import androidx.compose.ui.test.onNodeWithText
1922
import androidx.drawerlayout.widget.DrawerLayout
2023
import androidx.navigation.findNavController
21-
import androidx.test.core.app.ActivityScenario
2224
import androidx.test.core.app.ApplicationProvider.getApplicationContext
2325
import androidx.test.espresso.Espresso.onView
2426
import androidx.test.espresso.Espresso.pressBack
@@ -45,15 +47,13 @@ import com.example.android.architecture.blueprints.todoapp.util.monitorActivity
4547
import com.example.android.architecture.blueprints.todoapp.util.saveTaskBlocking
4648
import org.junit.After
4749
import org.junit.Before
50+
import org.junit.Rule
4851
import org.junit.Test
4952
import org.junit.runner.RunWith
5053

5154
/**
5255
* Tests for the [DrawerLayout] layout component in [TasksActivity] which manages
5356
* navigation within the app.
54-
*
55-
* UI tests usually use [ActivityTestRule] but there's no API to perform an action before
56-
* each test. The workaround is to use `ActivityScenario.launch()` and `ActivityScenario.close()`.
5757
*/
5858
@RunWith(AndroidJUnit4::class)
5959
@LargeTest
@@ -64,6 +64,9 @@ class AppNavigationTest {
6464
// An Idling Resource that waits for Data Binding to have no pending bindings
6565
private val dataBindingIdlingResource = DataBindingIdlingResource()
6666

67+
@get:Rule
68+
val composeTestRule = createAndroidComposeRule<TasksActivity>()
69+
6770
@Before
6871
fun init() {
6972
tasksRepository = ServiceLocator.provideTasksRepository(getApplicationContext())
@@ -96,8 +99,7 @@ class AppNavigationTest {
9699
@Test
97100
fun drawerNavigationFromTasksToStatistics() {
98101
// start up Tasks screen
99-
val activityScenario = ActivityScenario.launch(TasksActivity::class.java)
100-
dataBindingIdlingResource.monitorActivity(activityScenario)
102+
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
101103

102104
onView(withId(R.id.drawer_layout))
103105
.check(matches(isClosed(Gravity.START))) // Left Drawer should be closed.
@@ -108,7 +110,7 @@ class AppNavigationTest {
108110
.perform(navigateTo(R.id.statistics_fragment_dest))
109111

110112
// Check that statistics screen was opened.
111-
onView(withId(R.id.statistics_layout)).check(matches(isDisplayed()))
113+
composeTestRule.onNodeWithText("You have no tasks.").assertIsDisplayed()
112114

113115
onView(withId(R.id.drawer_layout))
114116
.check(matches(isClosed(Gravity.START))) // Left Drawer should be closed.
@@ -120,15 +122,12 @@ class AppNavigationTest {
120122

121123
// Check that tasks screen was opened.
122124
onView(withId(R.id.tasks_container_layout)).check(matches(isDisplayed()))
123-
// When using ActivityScenario.launch, always call close()
124-
activityScenario.close()
125125
}
126126

127127
@Test
128128
fun tasksScreen_clickOnAndroidHomeIcon_OpensNavigation() {
129129
// start up Tasks screen
130-
val activityScenario = ActivityScenario.launch(TasksActivity::class.java)
131-
dataBindingIdlingResource.monitorActivity(activityScenario)
130+
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
132131

133132
// Check that left drawer is closed at startup
134133
onView(withId(R.id.drawer_layout))
@@ -137,26 +136,21 @@ class AppNavigationTest {
137136
// Open Drawer
138137
onView(
139138
withContentDescription(
140-
activityScenario
141-
.getToolbarNavigationContentDescription()
139+
composeTestRule.activityRule.scenario.getToolbarNavigationContentDescription()
142140
)
143141
).perform(click())
144142

145143
// Check if drawer is open
146144
onView(withId(R.id.drawer_layout))
147145
.check(matches(isOpen(Gravity.START))) // Left drawer is open open.
148-
// When using ActivityScenario.launch, always call close()
149-
activityScenario.close()
150146
}
151147

152148
@Test
153149
fun statsScreen_clickOnAndroidHomeIcon_OpensNavigation() {
154-
// start up Tasks screen
155-
val activityScenario = ActivityScenario.launch(TasksActivity::class.java)
156-
dataBindingIdlingResource.monitorActivity(activityScenario)
150+
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
157151

158152
// When the user navigates to the stats screen
159-
activityScenario.onActivity {
153+
composeTestRule.activityRule.scenario.onActivity {
160154
it.findNavController(R.id.nav_host_fragment).navigate(R.id.statistics_fragment_dest)
161155
}
162156

@@ -167,26 +161,21 @@ class AppNavigationTest {
167161
// When the drawer is opened
168162
onView(
169163
withContentDescription(
170-
activityScenario
171-
.getToolbarNavigationContentDescription()
164+
composeTestRule.activityRule.scenario.getToolbarNavigationContentDescription()
172165
)
173166
).perform(click())
174167

175168
// Then check that the drawer is open
176169
onView(withId(R.id.drawer_layout))
177170
.check(matches(isOpen(Gravity.START))) // Left drawer is open open.
178-
// When using ActivityScenario.launch, always call close()
179-
activityScenario.close()
180171
}
181172

182173
@Test
183174
fun taskDetailScreen_doubleUIBackButton() {
184175
val task = Task("UI <- button", "Description")
185176
tasksRepository.saveTaskBlocking(task)
186177

187-
// start up Tasks screen
188-
val activityScenario = ActivityScenario.launch(TasksActivity::class.java)
189-
dataBindingIdlingResource.monitorActivity(activityScenario)
178+
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
190179

191180
// Click on the task on the list
192181
onView(withText("UI <- button")).perform(click())
@@ -196,32 +185,26 @@ class AppNavigationTest {
196185
// Confirm that if we click "<-" once, we end up back at the task details page
197186
onView(
198187
withContentDescription(
199-
activityScenario
200-
.getToolbarNavigationContentDescription()
188+
composeTestRule.activityRule.scenario.getToolbarNavigationContentDescription()
201189
)
202190
).perform(click())
203191
onView(withId(R.id.task_detail_title_text)).check(matches(isDisplayed()))
204192

205193
// Confirm that if we click "<-" a second time, we end up back at the home screen
206194
onView(
207195
withContentDescription(
208-
activityScenario
209-
.getToolbarNavigationContentDescription()
196+
composeTestRule.activityRule.scenario.getToolbarNavigationContentDescription()
210197
)
211198
).perform(click())
212199
onView(withId(R.id.tasks_container_layout)).check(matches(isDisplayed()))
213-
// When using ActivityScenario.launch, always call close()
214-
activityScenario.close()
215200
}
216201

217202
@Test
218203
fun taskDetailScreen_doubleBackButton() {
219204
val task = Task("Back button", "Description")
220205
tasksRepository.saveTaskBlocking(task)
221206

222-
// start up Tasks screen
223-
val activityScenario = ActivityScenario.launch(TasksActivity::class.java)
224-
dataBindingIdlingResource.monitorActivity(activityScenario)
207+
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
225208

226209
// Click on the task on the list
227210
onView(withText("Back button")).perform(click())
@@ -235,7 +218,5 @@ class AppNavigationTest {
235218
// Confirm that if we click back a second time, we end up back at the home screen
236219
pressBack()
237220
onView(withId(R.id.tasks_container_layout)).check(matches(isDisplayed()))
238-
// When using ActivityScenario.launch, always call close()
239-
activityScenario.close()
240221
}
241222
}
Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2019 The Android Open Source Project
2+
* Copyright (C) 2022 The Android Open Source Project
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,39 +19,34 @@ import android.os.Bundle
1919
import android.view.LayoutInflater
2020
import android.view.View
2121
import android.view.ViewGroup
22-
import androidx.databinding.DataBindingUtil
22+
import androidx.compose.foundation.layout.fillMaxSize
23+
import androidx.compose.material.Surface
24+
import androidx.compose.ui.Modifier
25+
import androidx.compose.ui.platform.ComposeView
26+
import androidx.compose.ui.platform.ViewCompositionStrategy
2327
import androidx.fragment.app.Fragment
24-
import androidx.fragment.app.viewModels
25-
import com.example.android.architecture.blueprints.todoapp.R
26-
import com.example.android.architecture.blueprints.todoapp.databinding.StatisticsFragBinding
27-
import com.example.android.architecture.blueprints.todoapp.util.getViewModelFactory
28-
import com.example.android.architecture.blueprints.todoapp.util.setupRefreshLayout
28+
import com.google.accompanist.appcompattheme.AppCompatTheme
2929

3030
/**
3131
* Main UI for the statistics screen.
3232
*/
3333
class StatisticsFragment : Fragment() {
3434

35-
private lateinit var viewDataBinding: StatisticsFragBinding
36-
37-
private val viewModel by viewModels<StatisticsViewModel> { getViewModelFactory() }
38-
3935
override fun onCreateView(
4036
inflater: LayoutInflater,
4137
container: ViewGroup?,
4238
savedInstanceState: Bundle?
43-
): View? {
44-
viewDataBinding = DataBindingUtil.inflate(
45-
inflater, R.layout.statistics_frag, container,
46-
false
47-
)
48-
return viewDataBinding.root
49-
}
50-
51-
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
52-
super.onViewCreated(view, savedInstanceState)
53-
viewDataBinding.viewmodel = viewModel
54-
viewDataBinding.lifecycleOwner = this.viewLifecycleOwner
55-
this.setupRefreshLayout(viewDataBinding.refreshLayout)
39+
): View {
40+
return ComposeView(requireContext()).apply {
41+
// Dispose of the Composition when the view's LifecycleOwner is destroyed
42+
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
43+
setContent {
44+
AppCompatTheme {
45+
Surface(Modifier.fillMaxSize()) {
46+
StatisticsScreen()
47+
}
48+
}
49+
}
50+
}
5651
}
5752
}

0 commit comments

Comments
 (0)