Skip to content

Commit 8e1e052

Browse files
authored
Migrate Tasks to Compose (#826)
1 parent 3875fde commit 8e1e052

File tree

14 files changed

+387
-505
lines changed

14 files changed

+387
-505
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ class AppNavigationTest {
125125
.perform(navigateTo(R.id.tasks_fragment_dest))
126126

127127
// Check that tasks screen was opened.
128-
onView(withId(R.id.tasks_container_layout)).check(matches(isDisplayed()))
128+
composeTestRule.onNodeWithText("You have no tasks!").assertIsDisplayed()
129129
}
130130

131131
@Test
@@ -203,7 +203,7 @@ class AppNavigationTest {
203203
composeTestRule.activityRule.scenario.getToolbarNavigationContentDescription()
204204
)
205205
).perform(click())
206-
onView(withId(R.id.tasks_container_layout)).check(matches(isDisplayed()))
206+
composeTestRule.onNodeWithText("All tasks").assertIsDisplayed()
207207
}
208208

209209
@Test
@@ -226,6 +226,6 @@ class AppNavigationTest {
226226

227227
// Confirm that if we click back a second time, we end up back at the home screen
228228
pressBack()
229-
onView(withId(R.id.tasks_container_layout)).check(matches(isDisplayed()))
229+
composeTestRule.onNodeWithText("All tasks").assertIsDisplayed()
230230
}
231231
}

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

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,13 @@ import androidx.compose.ui.test.onNodeWithText
2727
import androidx.compose.ui.test.performClick
2828
import androidx.compose.ui.test.performTextInput
2929
import androidx.compose.ui.test.performTextReplacement
30+
import androidx.compose.ui.test.assertIsOn
3031
import androidx.test.core.app.ApplicationProvider.getApplicationContext
3132
import androidx.test.espresso.Espresso.onView
3233
import androidx.test.espresso.IdlingRegistry
3334
import androidx.test.espresso.action.ViewActions.click
3435
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
3536
import androidx.test.espresso.assertion.ViewAssertions.matches
36-
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
37-
import androidx.test.espresso.matcher.ViewMatchers.isChecked
3837
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
3938
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
4039
import androidx.test.espresso.matcher.ViewMatchers.withId
@@ -51,8 +50,6 @@ import com.example.android.architecture.blueprints.todoapp.util.EspressoIdlingRe
5150
import com.example.android.architecture.blueprints.todoapp.util.deleteAllTasksBlocking
5251
import com.example.android.architecture.blueprints.todoapp.util.monitorActivity
5352
import com.example.android.architecture.blueprints.todoapp.util.saveTaskBlocking
54-
import org.hamcrest.Matchers.allOf
55-
import org.hamcrest.core.IsNot.not
5653
import org.junit.After
5754
import org.junit.Before
5855
import org.junit.Rule
@@ -127,7 +124,6 @@ class TasksActivityTest {
127124
// Click on the edit button, edit, and save
128125
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.edit_task))
129126
.performClick()
130-
onView(withId(R.id.edit_task_fab)).perform(click())
131127
findTextField("TITLE1").performTextReplacement("NEW TITLE")
132128
findTextField("DESCRIPTION").performTextReplacement("NEW DESCRIPTION")
133129
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.cd_save_task))
@@ -144,7 +140,8 @@ class TasksActivityTest {
144140
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
145141

146142
// Add active task
147-
onView(withId(R.id.add_task_fab)).perform(click())
143+
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.edit_task))
144+
.performClick()
148145
findTextField(R.string.title_hint).performTextInput("TITLE1")
149146
findTextField(R.string.description_hint).performTextInput("DESCRIPTION")
150147
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.cd_save_task))
@@ -204,8 +201,7 @@ class TasksActivityTest {
204201
).perform(click())
205202

206203
// Check that the task is marked as completed
207-
onView(allOf(withId(R.id.complete_checkbox), hasSibling(withText(taskTitle))))
208-
.check(matches(isChecked()))
204+
composeTestRule.onNode(isToggleable()).assertIsOn()
209205
}
210206

211207
@Test
@@ -230,8 +226,7 @@ class TasksActivityTest {
230226
).perform(click())
231227

232228
// Check that the task is marked as active
233-
onView(allOf(withId(R.id.complete_checkbox), hasSibling(withText(taskTitle))))
234-
.check(matches(not(isChecked())))
229+
composeTestRule.onNode(isToggleable()).assertIsOff()
235230
}
236231

237232
@Test
@@ -258,8 +253,7 @@ class TasksActivityTest {
258253
).perform(click())
259254

260255
// Check that the task is marked as active
261-
onView(allOf(withId(R.id.complete_checkbox), hasSibling(withText(taskTitle))))
262-
.check(matches(not(isChecked())))
256+
composeTestRule.onNode(isToggleable()).assertIsOff()
263257
}
264258

265259
@Test
@@ -286,16 +280,16 @@ class TasksActivityTest {
286280
).perform(click())
287281

288282
// Check that the task is marked as active
289-
onView(allOf(withId(R.id.complete_checkbox), hasSibling(withText(taskTitle))))
290-
.check(matches(isChecked()))
283+
composeTestRule.onNode(isToggleable()).assertIsOn()
291284
}
292285

293286
@Test
294287
fun createTask() {
295288
dataBindingIdlingResource.monitorActivity(composeTestRule.activityRule.scenario)
296289

297290
// Click on the "+" button, add details, and save
298-
onView(withId(R.id.add_task_fab)).perform(click())
291+
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.add_task))
292+
.performClick()
299293
findTextField(R.string.title_hint).performTextInput("title")
300294
findTextField(R.string.description_hint).performTextInput("description")
301295
composeTestRule.onNodeWithContentDescription(activity.getString(R.string.cd_save_task))

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

Lines changed: 0 additions & 78 deletions
This file was deleted.

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

Lines changed: 23 additions & 60 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.
@@ -24,20 +24,18 @@ import android.view.MenuItem
2424
import android.view.View
2525
import android.view.ViewGroup
2626
import androidx.appcompat.widget.PopupMenu
27+
import androidx.compose.ui.platform.ComposeView
28+
import androidx.compose.ui.platform.ViewCompositionStrategy
2729
import androidx.fragment.app.Fragment
2830
import androidx.fragment.app.viewModels
2931
import androidx.navigation.fragment.findNavController
3032
import androidx.navigation.fragment.navArgs
31-
import com.example.android.architecture.blueprints.todoapp.EventObserver
3233
import com.example.android.architecture.blueprints.todoapp.R
3334
import com.example.android.architecture.blueprints.todoapp.data.Task
34-
import com.example.android.architecture.blueprints.todoapp.databinding.TasksFragBinding
3535
import com.example.android.architecture.blueprints.todoapp.util.getViewModelFactory
36-
import com.example.android.architecture.blueprints.todoapp.util.setupRefreshLayout
3736
import com.example.android.architecture.blueprints.todoapp.util.setupSnackbar
38-
import com.google.android.material.floatingactionbutton.FloatingActionButton
37+
import com.google.accompanist.appcompattheme.AppCompatTheme
3938
import com.google.android.material.snackbar.Snackbar
40-
import timber.log.Timber
4139

4240
/**
4341
* Display a grid of [Task]s. User can choose to view all, active or completed tasks.
@@ -48,20 +46,31 @@ class TasksFragment : Fragment() {
4846

4947
private val args: TasksFragmentArgs by navArgs()
5048

51-
private lateinit var viewDataBinding: TasksFragBinding
52-
53-
private lateinit var listAdapter: TasksAdapter
54-
5549
override fun onCreateView(
5650
inflater: LayoutInflater,
5751
container: ViewGroup?,
5852
savedInstanceState: Bundle?
5953
): View {
60-
viewDataBinding = TasksFragBinding.inflate(inflater, container, false).apply {
61-
viewmodel = viewModel
62-
}
6354
setHasOptionsMenu(true)
64-
return viewDataBinding.root
55+
56+
return ComposeView(requireContext()).apply {
57+
// Dispose of the Composition when the view's LifecycleOwner is destroyed
58+
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
59+
setContent {
60+
AppCompatTheme {
61+
TasksScreen(
62+
viewModel = viewModel,
63+
onAddTask = { navigateToAddNewTask() },
64+
onTaskClick = { openTaskDetails(it.id) }
65+
)
66+
}
67+
}
68+
}
69+
}
70+
71+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
72+
super.onViewCreated(view, savedInstanceState)
73+
setupSnackbar()
6574
}
6675

6776
override fun onOptionsItemSelected(item: MenuItem) =
@@ -85,33 +94,6 @@ class TasksFragment : Fragment() {
8594
inflater.inflate(R.menu.tasks_fragment_menu, menu)
8695
}
8796

88-
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
89-
super.onViewCreated(view, savedInstanceState)
90-
91-
// Set the lifecycle owner to the lifecycle of the view
92-
viewDataBinding.lifecycleOwner = this.viewLifecycleOwner
93-
setupSnackbar()
94-
setupListAdapter()
95-
setupRefreshLayout(viewDataBinding.refreshLayout, viewDataBinding.tasksList)
96-
setupNavigation()
97-
setupFab()
98-
}
99-
100-
private fun setupNavigation() {
101-
viewModel.openTaskEvent.observe(
102-
viewLifecycleOwner,
103-
EventObserver {
104-
openTaskDetails(it)
105-
}
106-
)
107-
viewModel.newTaskEvent.observe(
108-
viewLifecycleOwner,
109-
EventObserver {
110-
navigateToAddNewTask()
111-
}
112-
)
113-
}
114-
11597
private fun setupSnackbar() {
11698
view?.setupSnackbar(viewLifecycleOwner, viewModel.snackbarText, Snackbar.LENGTH_SHORT)
11799
arguments?.let {
@@ -138,15 +120,6 @@ class TasksFragment : Fragment() {
138120
}
139121
}
140122

141-
// TODO: Move this to databinding
142-
private fun setupFab() {
143-
requireView().findViewById<FloatingActionButton>(R.id.add_task_fab)?.let {
144-
it.setOnClickListener {
145-
navigateToAddNewTask()
146-
}
147-
}
148-
}
149-
150123
private fun navigateToAddNewTask() {
151124
val action = TasksFragmentDirections
152125
.actionTasksFragmentToAddEditTaskFragment(
@@ -160,14 +133,4 @@ class TasksFragment : Fragment() {
160133
val action = TasksFragmentDirections.actionTasksFragmentToTaskDetailFragment(taskId)
161134
findNavController().navigate(action)
162135
}
163-
164-
private fun setupListAdapter() {
165-
val viewModel = viewDataBinding.viewmodel
166-
if (viewModel != null) {
167-
listAdapter = TasksAdapter(viewModel)
168-
viewDataBinding.tasksList.adapter = listAdapter
169-
} else {
170-
Timber.w("ViewModel not initialized when attempting to set up adapter.")
171-
}
172-
}
173136
}

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

Lines changed: 0 additions & 41 deletions
This file was deleted.

0 commit comments

Comments
 (0)