diff --git a/ground/src/main/java/com/google/android/ground/model/job/Job.kt b/ground/src/main/java/com/google/android/ground/model/job/Job.kt
index 36b6b43824..1e341bbc4a 100644
--- a/ground/src/main/java/com/google/android/ground/model/job/Job.kt
+++ b/ground/src/main/java/com/google/android/ground/model/job/Job.kt
@@ -54,6 +54,9 @@ data class Job(
/** Returns true if the job has one or more tasks. */
fun hasTasks() = tasks.values.isNotEmpty()
+
+ /** Returns whether the job has non-LOI tasks. */
+ fun hasNonLoiTasks() = tasks.values.count { !it.isAddLoiTask } > 0
}
fun Job.getDefaultColor(): Int =
diff --git a/ground/src/main/java/com/google/android/ground/ui/datacollection/DataCollectionViewModel.kt b/ground/src/main/java/com/google/android/ground/ui/datacollection/DataCollectionViewModel.kt
index 7759c783c1..c3442ec9f9 100644
--- a/ground/src/main/java/com/google/android/ground/ui/datacollection/DataCollectionViewModel.kt
+++ b/ground/src/main/java/com/google/android/ground/ui/datacollection/DataCollectionViewModel.kt
@@ -312,6 +312,14 @@ internal constructor(
return currentIndex to size
}
+ /** Returns the index of the task ID, or -1 if null or not found. */
+ private fun getIndexOfTask(taskId: String?) =
+ if (taskId == null) {
+ -1
+ } else {
+ tasks.indexOfFirst { it.id == taskId }
+ }
+
/**
* Retrieves the current task sequence given the inputs and conditions set on the tasks. Setting a
* start ID will always generate a sequence with the start ID as the first element, and if
@@ -321,17 +329,16 @@ internal constructor(
if (tasks.isEmpty()) {
error("Can't generate sequence for empty task list")
}
-
- val task = tasks.filter { it.id == (startId ?: tasks[0].id) }
-
- // TODO(#2539): Cleanup once https://github.com/google/ground-android/issues/2539 is resolved.
- if (task.isEmpty()) {
- error(
- "Unable to find a task with id startId=$startId, firstTaskId=${tasks[0].id}, allTasks=${tasks.map { it.id }}"
- )
- }
-
- val startIndex = tasks.indexOf(task.first())
+ val startIndex =
+ getIndexOfTask(startId).let {
+ if (it < 0) {
+ // Default to 0 if startId is not found or is null.
+ if (startId != null) Timber.w("startId, $startId, was not found. Defaulting to 0")
+ 0
+ } else {
+ it
+ }
+ }
return if (reversed) {
tasks.subList(0, startIndex + 1).reversed()
} else {
diff --git a/ground/src/main/java/com/google/android/ground/ui/home/mapcontainer/HomeScreenMapContainerFragment.kt b/ground/src/main/java/com/google/android/ground/ui/home/mapcontainer/HomeScreenMapContainerFragment.kt
index ac3035014e..96351ac877 100644
--- a/ground/src/main/java/com/google/android/ground/ui/home/mapcontainer/HomeScreenMapContainerFragment.kt
+++ b/ground/src/main/java/com/google/android/ground/ui/home/mapcontainer/HomeScreenMapContainerFragment.kt
@@ -79,7 +79,7 @@ class HomeScreenMapContainerFragment : AbstractMapContainerFragment() {
val canUserSubmitData = userRepository.canUserSubmitData()
// Handle collect button clicks
- adapter.setCollectDataListener { onCollectData(canUserSubmitData, it) }
+ adapter.setCollectDataListener { onCollectData(canUserSubmitData, hasValidTasks(it), it) }
// Bind data for cards
mapContainerViewModel.getMapCardUiData().launchWhenStartedAndCollect { (mapCards, loiCount) ->
@@ -90,15 +90,32 @@ class HomeScreenMapContainerFragment : AbstractMapContainerFragment() {
map.featureClicks.launchWhenStartedAndCollect { mapContainerViewModel.onFeatureClicked(it) }
}
+ private fun hasValidTasks(cardUiData: MapCardUiData) =
+ when (cardUiData) {
+ // LOI tasks are filtered out of the tasks list for pre-defined tasks.
+ is MapCardUiData.LoiCardUiData ->
+ cardUiData.loi.job.tasks.values.count { !it.isAddLoiTask } > 0
+ is MapCardUiData.AddLoiCardUiData -> cardUiData.job.tasks.values.isNotEmpty()
+ }
+
/** Invoked when user clicks on the map cards to collect data. */
- private fun onCollectData(canUserSubmitData: Boolean, cardUiData: MapCardUiData) {
- if (canUserSubmitData) {
- navigateToDataCollectionFragment(cardUiData)
- } else {
+ private fun onCollectData(
+ canUserSubmitData: Boolean,
+ hasTasks: Boolean,
+ cardUiData: MapCardUiData,
+ ) {
+ if (!canUserSubmitData) {
// Skip data collection screen if the user can't submit any data
// TODO(#1667): Revisit UX for displaying view only mode
ephemeralPopups.ErrorPopup().show(getString(R.string.collect_data_viewer_error))
+ return
+ }
+ if (!hasTasks) {
+ // NOTE(#2539): The DataCollectionFragment will crash if there are no tasks.
+ ephemeralPopups.ErrorPopup().show(getString(R.string.no_tasks_error))
+ return
}
+ navigateToDataCollectionFragment(cardUiData)
}
/** Updates the given [TextView] with the submission count for the given [LocationOfInterest]. */
diff --git a/ground/src/main/java/com/google/android/ground/ui/home/mapcontainer/cards/MapCardAdapter.kt b/ground/src/main/java/com/google/android/ground/ui/home/mapcontainer/cards/MapCardAdapter.kt
index 53254cb806..f7a8479270 100644
--- a/ground/src/main/java/com/google/android/ground/ui/home/mapcontainer/cards/MapCardAdapter.kt
+++ b/ground/src/main/java/com/google/android/ground/ui/home/mapcontainer/cards/MapCardAdapter.kt
@@ -145,8 +145,9 @@ class MapCardAdapter(
with(binding) {
loiName.text = loiHelper.getDisplayLoiName(loi)
jobName.text = loiHelper.getJobName(loi)
+ // NOTE(#2539): The DataCollectionFragment will crash if there are no non-LOI tasks.
collectData.visibility =
- if (canUserSubmitData && loi.job.hasTasks()) View.VISIBLE else View.GONE
+ if (canUserSubmitData && loi.job.hasNonLoiTasks()) View.VISIBLE else View.GONE
updateSubmissionCount(loi, submissions)
}
}
diff --git a/ground/src/main/res/values/strings.xml b/ground/src/main/res/values/strings.xml
index bbc116f916..bd818d2d5d 100644
--- a/ground/src/main/res/values/strings.xml
+++ b/ground/src/main/res/values/strings.xml
@@ -90,6 +90,7 @@
Drag your map until the center pin is on the desired location
Current location:
Loading…
+ This job has no more tasks to complete
Can’t collect data as user is a VIEWER
Offline map imagery
Hide or show downloaded imagery