From 30058566bd60625439d1d0cadadeed41b54ae311 Mon Sep 17 00:00:00 2001 From: Akshay Nandwana Date: Fri, 11 Oct 2024 04:48:23 +0530 Subject: [PATCH] [CaptureLocation & DropPin task] Fix crashes on config change (#2765) * Get view model from parent fragment * Revert variable rename for cleaner diff * Replace uiState with navigationRequests * Reinit view model on config change * Remove debugging log * Move arg key into const * Log error * Fix failing check * Revert changes to manifest * Fix tests * Add TODOs * Revert formatting change * copied same to other map frag * viewmodel check * Remove final CR * Remove final CR * test try * nit fix * updated * nit fix * moved to base --------- Co-authored-by: Gino Miceli Co-authored-by: Gino Miceli <228050+gino-m@users.noreply.github.com> --- .../common/AbstractMapFragmentWithControls.kt | 13 ++++++++++ .../DataCollectionViewPagerAdapter.kt | 6 +++-- .../location/CaptureLocationTaskFragment.kt | 25 +++++++++++-------- .../CaptureLocationTaskMapFragment.kt | 16 ++++++------ .../tasks/point/DropPinTaskFragment.kt | 15 +++++++---- .../tasks/point/DropPinTaskMapFragment.kt | 16 ++++++------ .../tasks/polygon/DrawAreaTaskFragment.kt | 3 ++- .../tasks/polygon/DrawAreaTaskMapFragment.kt | 11 +------- 8 files changed, 63 insertions(+), 42 deletions(-) diff --git a/ground/src/main/java/com/google/android/ground/ui/common/AbstractMapFragmentWithControls.kt b/ground/src/main/java/com/google/android/ground/ui/common/AbstractMapFragmentWithControls.kt index cba083913d..290b65234f 100644 --- a/ground/src/main/java/com/google/android/ground/ui/common/AbstractMapFragmentWithControls.kt +++ b/ground/src/main/java/com/google/android/ground/ui/common/AbstractMapFragmentWithControls.kt @@ -20,12 +20,14 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.StringRes +import androidx.hilt.navigation.fragment.hiltNavGraphViewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.google.android.ground.R import com.google.android.ground.databinding.MapTaskFragBinding import com.google.android.ground.model.submission.CaptureLocationTaskData.Companion.toCaptureLocationResult +import com.google.android.ground.ui.datacollection.DataCollectionViewModel import com.google.android.ground.ui.map.CameraPosition import com.google.android.ground.ui.map.MapFragment import com.google.android.ground.util.toDmsFormat @@ -42,6 +44,13 @@ abstract class AbstractMapFragmentWithControls : AbstractMapContainerFragment() protected lateinit var binding: MapTaskFragBinding + protected val dataCollectionViewModel: DataCollectionViewModel by + hiltNavGraphViewModels(R.id.data_collection) + + protected val taskId: String by lazy { + arguments?.getString(TASK_ID_FRAGMENT_ARG_KEY) ?: error("null taskId fragment arg") + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -112,4 +121,8 @@ abstract class AbstractMapFragmentWithControls : AbstractMapContainerFragment() } updateLocationInfoCard(R.string.map_location, position.coordinates.toDmsFormat()) } + + companion object { + const val TASK_ID_FRAGMENT_ARG_KEY = "taskId" + } } diff --git a/ground/src/main/java/com/google/android/ground/ui/datacollection/DataCollectionViewPagerAdapter.kt b/ground/src/main/java/com/google/android/ground/ui/datacollection/DataCollectionViewPagerAdapter.kt index a8de0b3a47..9fe557baa4 100644 --- a/ground/src/main/java/com/google/android/ground/ui/datacollection/DataCollectionViewPagerAdapter.kt +++ b/ground/src/main/java/com/google/android/ground/ui/datacollection/DataCollectionViewPagerAdapter.kt @@ -38,6 +38,8 @@ class DataCollectionViewPagerAdapter @AssistedInject constructor( private val drawAreaTaskFragmentProvider: Provider, + private val captureLocationTaskFragmentProvider: Provider, + private val dropPinTaskFragmentProvider: Provider, @Assisted fragment: Fragment, @Assisted val tasks: List, ) : FragmentStateAdapter(fragment) { @@ -51,12 +53,12 @@ constructor( Task.Type.TEXT -> TextTaskFragment() Task.Type.MULTIPLE_CHOICE -> MultipleChoiceTaskFragment() Task.Type.PHOTO -> PhotoTaskFragment() - Task.Type.DROP_PIN -> DropPinTaskFragment() + Task.Type.DROP_PIN -> dropPinTaskFragmentProvider.get() Task.Type.DRAW_AREA -> drawAreaTaskFragmentProvider.get() Task.Type.NUMBER -> NumberTaskFragment() Task.Type.DATE -> DateTaskFragment() Task.Type.TIME -> TimeTaskFragment() - Task.Type.CAPTURE_LOCATION -> CaptureLocationTaskFragment() + Task.Type.CAPTURE_LOCATION -> captureLocationTaskFragmentProvider.get() Task.Type.UNKNOWN -> throw UnsupportedOperationException("Unsupported task type: ${task.type}") } diff --git a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/location/CaptureLocationTaskFragment.kt b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/location/CaptureLocationTaskFragment.kt index 8c05dc0c51..8fa5904d70 100644 --- a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/location/CaptureLocationTaskFragment.kt +++ b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/location/CaptureLocationTaskFragment.kt @@ -15,24 +15,27 @@ */ package com.google.android.ground.ui.datacollection.tasks.location +import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.widget.LinearLayout import com.google.android.ground.R import com.google.android.ground.model.submission.isNotNullOrEmpty import com.google.android.ground.model.submission.isNullOrEmpty +import com.google.android.ground.ui.common.AbstractMapFragmentWithControls.Companion.TASK_ID_FRAGMENT_ARG_KEY import com.google.android.ground.ui.datacollection.components.ButtonAction import com.google.android.ground.ui.datacollection.components.TaskView import com.google.android.ground.ui.datacollection.components.TaskViewFactory import com.google.android.ground.ui.datacollection.tasks.AbstractTaskFragment -import com.google.android.ground.ui.map.MapFragment import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject +import javax.inject.Provider @AndroidEntryPoint -class CaptureLocationTaskFragment : AbstractTaskFragment() { - - @Inject lateinit var map: MapFragment +class CaptureLocationTaskFragment @Inject constructor() : + AbstractTaskFragment() { + @Inject + lateinit var captureLocationTaskMapFragmentProvider: Provider override fun onCreateTaskView(inflater: LayoutInflater): TaskView = TaskViewFactory.createWithCombinedHeader(inflater, R.drawable.outline_pin_drop) @@ -41,20 +44,22 @@ class CaptureLocationTaskFragment : AbstractTaskFragment() { - - @Inject lateinit var map: MapFragment +class DropPinTaskFragment @Inject constructor() : AbstractTaskFragment() { + @Inject lateinit var dropPinTaskMapFragmentProvider: Provider override fun onCreateTaskView(inflater: LayoutInflater): TaskView = TaskViewFactory.createWithCombinedHeader(inflater, R.drawable.outline_pin_drop) @@ -47,9 +48,13 @@ class DropPinTaskFragment : AbstractTaskFragment() { // NOTE(#2493): Multiplying by a random prime to allow for some mathematical "uniqueness". // Otherwise, the sequentially generated ID might conflict with an ID produced by Google Maps. val rowLayout = LinearLayout(requireContext()).apply { id = View.generateViewId() * 11617 } + val fragment = dropPinTaskMapFragmentProvider.get() + val args = Bundle() + args.putString(TASK_ID_FRAGMENT_ARG_KEY, taskId) + fragment.arguments = args parentFragmentManager .beginTransaction() - .add(rowLayout.id, DropPinTaskMapFragment.newInstance(viewModel, map), "Drop a pin fragment") + .add(rowLayout.id, fragment, "Drop a pin fragment") .commit() return rowLayout } diff --git a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/point/DropPinTaskMapFragment.kt b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/point/DropPinTaskMapFragment.kt index 748970ec75..d36a69e184 100644 --- a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/point/DropPinTaskMapFragment.kt +++ b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/point/DropPinTaskMapFragment.kt @@ -21,12 +21,18 @@ import com.google.android.ground.ui.common.BaseMapViewModel import com.google.android.ground.ui.map.CameraPosition import com.google.android.ground.ui.map.MapFragment import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject @AndroidEntryPoint -class DropPinTaskMapFragment(private val viewModel: DropPinTaskViewModel) : - AbstractMapFragmentWithControls() { +class DropPinTaskMapFragment @Inject constructor() : AbstractMapFragmentWithControls() { private lateinit var mapViewModel: BaseMapViewModel + private val viewModel: DropPinTaskViewModel by lazy { + // Access to this viewModel is lazy for testing. This is because the NavHostController could + // not be initialized before the Fragment under test is created, leading to + // hiltNavGraphViewModels() to fail when called on launch. + dataCollectionViewModel.getTaskViewModel(taskId) as DropPinTaskViewModel + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -36,6 +42,7 @@ class DropPinTaskMapFragment(private val viewModel: DropPinTaskViewModel) : override fun getMapViewModel(): BaseMapViewModel = mapViewModel override fun onMapReady(map: MapFragment) { + super.onMapReady(map) viewModel.features.observe(this) { map.setFeatures(it) } } @@ -43,9 +50,4 @@ class DropPinTaskMapFragment(private val viewModel: DropPinTaskViewModel) : super.onMapCameraMoved(position) viewModel.updateCameraPosition(position) } - - companion object { - fun newInstance(viewModel: DropPinTaskViewModel, map: MapFragment) = - DropPinTaskMapFragment(viewModel).apply { this.map = map } - } } diff --git a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/polygon/DrawAreaTaskFragment.kt b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/polygon/DrawAreaTaskFragment.kt index f2f9a2d0c3..acb700ae00 100644 --- a/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/polygon/DrawAreaTaskFragment.kt +++ b/ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/polygon/DrawAreaTaskFragment.kt @@ -27,6 +27,7 @@ import androidx.lifecycle.lifecycleScope import com.google.android.ground.R import com.google.android.ground.model.geometry.LineString import com.google.android.ground.model.geometry.LineString.Companion.lineStringOf +import com.google.android.ground.ui.common.AbstractMapFragmentWithControls.Companion.TASK_ID_FRAGMENT_ARG_KEY import com.google.android.ground.ui.datacollection.components.ButtonAction import com.google.android.ground.ui.datacollection.components.InstructionsDialog import com.google.android.ground.ui.datacollection.components.TaskButton @@ -60,7 +61,7 @@ class DrawAreaTaskFragment @Inject constructor() : AbstractTaskFragment