Skip to content

Commit 3875fde

Browse files
authored
Migrate TaskDetail to Compose (#822)
1 parent 11aa9c1 commit 3875fde

File tree

11 files changed

+372
-326
lines changed

11 files changed

+372
-326
lines changed

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

Lines changed: 18 additions & 9 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.
@@ -18,7 +18,9 @@ package com.example.android.architecture.blueprints.todoapp.tasks
1818
import android.view.Gravity
1919
import androidx.compose.ui.test.assertIsDisplayed
2020
import androidx.compose.ui.test.junit4.createAndroidComposeRule
21+
import androidx.compose.ui.test.onNodeWithContentDescription
2122
import androidx.compose.ui.test.onNodeWithText
23+
import androidx.compose.ui.test.performClick
2224
import androidx.drawerlayout.widget.DrawerLayout
2325
import androidx.navigation.findNavController
2426
import androidx.test.core.app.ApplicationProvider.getApplicationContext
@@ -66,6 +68,7 @@ class AppNavigationTest {
6668

6769
@get:Rule
6870
val composeTestRule = createAndroidComposeRule<TasksActivity>()
71+
private val activity get() = composeTestRule.activity
6972

7073
@Before
7174
fun init() {
@@ -111,6 +114,7 @@ class AppNavigationTest {
111114

112115
// Check that statistics screen was opened.
113116
composeTestRule.onNodeWithText("You have no tasks.").assertIsDisplayed()
117+
composeTestRule.waitForIdle()
114118

115119
onView(withId(R.id.drawer_layout))
116120
.check(matches(isClosed(Gravity.START))) // Left Drawer should be closed.
@@ -153,6 +157,7 @@ class AppNavigationTest {
153157
composeTestRule.activityRule.scenario.onActivity {
154158
it.findNavController(R.id.nav_host_fragment).navigate(R.id.statistics_fragment_dest)
155159
}
160+
composeTestRule.waitForIdle()
156161

157162
// Then check that left drawer is closed at startup
158163
onView(withId(R.id.drawer_layout))
@@ -172,23 +177,25 @@ class AppNavigationTest {
172177

173178
@Test
174179
fun taskDetailScreen_doubleUIBackButton() {
180+
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
181+
175182
val task = Task("UI <- button", "Description")
176183
tasksRepository.saveTaskBlocking(task)
177-
178-
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
184+
composeTestRule.waitForIdle()
179185

180186
// Click on the task on the list
181187
onView(withText("UI <- button")).perform(click())
182188
// Click on the edit task button
183-
onView(withId(R.id.edit_task_fab)).perform(click())
189+
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.edit_task))
190+
.performClick()
184191

185192
// Confirm that if we click "<-" once, we end up back at the task details page
186193
onView(
187194
withContentDescription(
188195
composeTestRule.activityRule.scenario.getToolbarNavigationContentDescription()
189196
)
190197
).perform(click())
191-
onView(withId(R.id.task_detail_title_text)).check(matches(isDisplayed()))
198+
composeTestRule.onNodeWithText("UI <- button").assertIsDisplayed()
192199

193200
// Confirm that if we click "<-" a second time, we end up back at the home screen
194201
onView(
@@ -201,19 +208,21 @@ class AppNavigationTest {
201208

202209
@Test
203210
fun taskDetailScreen_doubleBackButton() {
211+
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
212+
204213
val task = Task("Back button", "Description")
205214
tasksRepository.saveTaskBlocking(task)
206-
207-
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
215+
composeTestRule.waitForIdle()
208216

209217
// Click on the task on the list
210218
onView(withText("Back button")).perform(click())
211219
// Click on the edit task button
212-
onView(withId(R.id.edit_task_fab)).perform(click())
220+
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.edit_task))
221+
.performClick()
213222

214223
// Confirm that if we click back once, we end up back at the task details page
215224
pressBack()
216-
onView(withId(R.id.task_detail_title_text)).check(matches(isDisplayed()))
225+
composeTestRule.onNodeWithText("Back button").assertIsDisplayed()
217226

218227
// Confirm that if we click back a second time, we end up back at the home screen
219228
pressBack()

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

Lines changed: 17 additions & 11 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.
@@ -16,10 +16,14 @@
1616
package com.example.android.architecture.blueprints.todoapp.tasks
1717

1818
import androidx.compose.ui.test.SemanticsNodeInteraction
19+
import androidx.compose.ui.test.assertIsDisplayed
20+
import androidx.compose.ui.test.assertIsOff
1921
import androidx.compose.ui.test.hasSetTextAction
2022
import androidx.compose.ui.test.hasText
23+
import androidx.compose.ui.test.isToggleable
2124
import androidx.compose.ui.test.junit4.createAndroidComposeRule
2225
import androidx.compose.ui.test.onNodeWithContentDescription
26+
import androidx.compose.ui.test.onNodeWithText
2327
import androidx.compose.ui.test.performClick
2428
import androidx.compose.ui.test.performTextInput
2529
import androidx.compose.ui.test.performTextReplacement
@@ -66,7 +70,7 @@ class TasksActivityTest {
6670

6771
@get:Rule
6872
val composeTestRule = createAndroidComposeRule<TasksActivity>()
69-
private val activity by lazy { composeTestRule.activity }
73+
private val activity get() = composeTestRule.activity
7074

7175
// An Idling Resource that waits for Data Binding to have no pending bindings
7276
private val dataBindingIdlingResource = DataBindingIdlingResource()
@@ -116,11 +120,13 @@ class TasksActivityTest {
116120

117121
// Click on the task on the list and verify that all the data is correct
118122
onView(withText("TITLE1")).perform(click())
119-
onView(withId(R.id.task_detail_title_text)).check(matches(withText("TITLE1")))
120-
onView(withId(R.id.task_detail_description_text)).check(matches(withText("DESCRIPTION")))
121-
onView(withId(R.id.task_detail_complete_checkbox)).check(matches(not(isChecked())))
123+
composeTestRule.onNodeWithText("TITLE1").assertIsDisplayed()
124+
composeTestRule.onNodeWithText("DESCRIPTION").assertIsDisplayed()
125+
composeTestRule.onNode(isToggleable()).assertIsOff()
122126

123127
// Click on the edit button, edit, and save
128+
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.edit_task))
129+
.performClick()
124130
onView(withId(R.id.edit_task_fab)).perform(click())
125131
findTextField("TITLE1").performTextReplacement("NEW TITLE")
126132
findTextField("DESCRIPTION").performTextReplacement("NEW DESCRIPTION")
@@ -188,7 +194,7 @@ class TasksActivityTest {
188194
onView(withText(taskTitle)).perform(click())
189195

190196
// Click on the checkbox in task details screen
191-
onView(withId(R.id.task_detail_complete_checkbox)).perform(click())
197+
composeTestRule.onNode(isToggleable()).performClick()
192198

193199
// Click on the navigation up button to go back to the list
194200
onView(
@@ -214,7 +220,7 @@ class TasksActivityTest {
214220
// Click on the task on the list
215221
onView(withText(taskTitle)).perform(click())
216222
// Click on the checkbox in task details screen
217-
onView(withId(R.id.task_detail_complete_checkbox)).perform(click())
223+
composeTestRule.onNode(isToggleable()).performClick()
218224

219225
// Click on the navigation up button to go back to the list
220226
onView(
@@ -240,9 +246,9 @@ class TasksActivityTest {
240246
// Click on the task on the list
241247
onView(withText(taskTitle)).perform(click())
242248
// Click on the checkbox in task details screen
243-
onView(withId(R.id.task_detail_complete_checkbox)).perform(click())
249+
composeTestRule.onNode(isToggleable()).performClick()
244250
// Click again to restore it to original state
245-
onView(withId(R.id.task_detail_complete_checkbox)).perform(click())
251+
composeTestRule.onNode(isToggleable()).performClick()
246252

247253
// Click on the navigation up button to go back to the list
248254
onView(
@@ -268,9 +274,9 @@ class TasksActivityTest {
268274
// Click on the task on the list
269275
onView(withText(taskTitle)).perform(click())
270276
// Click on the checkbox in task details screen
271-
onView(withId(R.id.task_detail_complete_checkbox)).perform(click())
277+
composeTestRule.onNode(isToggleable()).performClick()
272278
// Click again to restore it to original state
273-
onView(withId(R.id.task_detail_complete_checkbox)).perform(click())
279+
composeTestRule.onNode(isToggleable()).performClick()
274280

275281
// Click on the navigation up button to go back to the list
276282
onView(

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

Lines changed: 38 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -22,86 +22,71 @@ import android.view.MenuInflater
2222
import android.view.MenuItem
2323
import android.view.View
2424
import android.view.ViewGroup
25+
import androidx.compose.foundation.layout.fillMaxSize
26+
import androidx.compose.material.Surface
27+
import androidx.compose.ui.Modifier
28+
import androidx.compose.ui.platform.ComposeView
29+
import androidx.compose.ui.platform.ViewCompositionStrategy
2530
import androidx.fragment.app.Fragment
2631
import androidx.fragment.app.viewModels
32+
import androidx.lifecycle.lifecycleScope
2733
import androidx.navigation.fragment.findNavController
2834
import androidx.navigation.fragment.navArgs
29-
import com.example.android.architecture.blueprints.todoapp.EventObserver
3035
import com.example.android.architecture.blueprints.todoapp.R
31-
import com.example.android.architecture.blueprints.todoapp.databinding.TaskdetailFragBinding
3236
import com.example.android.architecture.blueprints.todoapp.tasks.DELETE_RESULT_OK
3337
import com.example.android.architecture.blueprints.todoapp.util.getViewModelFactory
34-
import com.example.android.architecture.blueprints.todoapp.util.setupRefreshLayout
35-
import com.example.android.architecture.blueprints.todoapp.util.setupSnackbar
36-
import com.google.android.material.snackbar.Snackbar
38+
import com.google.accompanist.appcompattheme.AppCompatTheme
39+
import kotlinx.coroutines.launch
3740

3841
/**
3942
* Main UI for the task detail screen.
4043
*/
4144
class TaskDetailFragment : Fragment() {
42-
private lateinit var viewDataBinding: TaskdetailFragBinding
4345

4446
private val args: TaskDetailFragmentArgs by navArgs()
4547

4648
private val viewModel by viewModels<TaskDetailViewModel> { getViewModelFactory() }
4749

48-
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
49-
super.onViewCreated(view, savedInstanceState)
50-
setupFab()
51-
view.setupSnackbar(this, viewModel.snackbarText, Snackbar.LENGTH_SHORT)
52-
setupNavigation()
53-
this.setupRefreshLayout(viewDataBinding.refreshLayout)
54-
}
55-
56-
private fun setupNavigation() {
57-
viewModel.deleteTaskEvent.observe(
58-
this,
59-
EventObserver {
60-
val action = TaskDetailFragmentDirections
61-
.actionTaskDetailFragmentToTasksFragment(DELETE_RESULT_OK)
62-
findNavController().navigate(action)
63-
}
64-
)
65-
viewModel.editTaskEvent.observe(
66-
this,
67-
EventObserver {
68-
val action = TaskDetailFragmentDirections
69-
.actionTaskDetailFragmentToAddEditTaskFragment(
70-
args.taskId,
71-
resources.getString(R.string.edit_task)
72-
)
73-
findNavController().navigate(action)
74-
}
75-
)
76-
}
77-
78-
private fun setupFab() {
79-
activity?.findViewById<View>(R.id.edit_task_fab)?.setOnClickListener {
80-
viewModel.editTask()
81-
}
82-
}
83-
8450
override fun onCreateView(
8551
inflater: LayoutInflater,
8652
container: ViewGroup?,
8753
savedInstanceState: Bundle?
88-
): View? {
89-
val view = inflater.inflate(R.layout.taskdetail_frag, container, false)
90-
viewDataBinding = TaskdetailFragBinding.bind(view).apply {
91-
viewmodel = viewModel
92-
}
93-
viewDataBinding.lifecycleOwner = this.viewLifecycleOwner
94-
95-
viewModel.start(args.taskId)
96-
54+
): View {
9755
setHasOptionsMenu(true)
98-
return view
56+
57+
return ComposeView(requireContext()).apply {
58+
// Dispose of the Composition when the view's LifecycleOwner is destroyed
59+
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
60+
setContent {
61+
AppCompatTheme {
62+
Surface(Modifier.fillMaxSize()) {
63+
TaskDetailScreen(
64+
taskId = args.taskId,
65+
viewModel = viewModel,
66+
onEditTask = {
67+
val action = TaskDetailFragmentDirections
68+
.actionTaskDetailFragmentToAddEditTaskFragment(
69+
args.taskId,
70+
resources.getString(R.string.edit_task)
71+
)
72+
findNavController().navigate(action)
73+
}
74+
)
75+
}
76+
}
77+
}
78+
}
9979
}
10080

10181
override fun onOptionsItemSelected(item: MenuItem): Boolean {
10282
return when (item.itemId) {
10383
R.id.menu_delete -> {
104-
viewModel.deleteTask()
84+
lifecycleScope.launch {
85+
viewModel.deleteTask()
86+
val action = TaskDetailFragmentDirections
87+
.actionTaskDetailFragmentToTasksFragment(DELETE_RESULT_OK)
88+
findNavController().navigate(action)
89+
}
10590
true
10691
}
10792
else -> false

0 commit comments

Comments
 (0)