Skip to content

Commit

Permalink
fix: [ANDROAPP-6182] crash when rotating scheduling dialog (#3677)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmmateos authored Jun 14, 2024
1 parent e202ed5 commit afe0b88
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.core.app.ActivityOptionsCompat
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.map
import androidx.recyclerview.widget.DividerItemDecoration
Expand Down Expand Up @@ -53,7 +54,9 @@ import org.dhis2.usescases.teiDashboard.dashboardfragments.teidata.teievents.Eve
import org.dhis2.usescases.teiDashboard.dashboardfragments.teidata.teievents.EventCatComboOptionSelector
import org.dhis2.usescases.teiDashboard.dashboardfragments.teidata.teievents.ui.mapper.TEIEventCardMapper
import org.dhis2.usescases.teiDashboard.dialogs.scheduling.SchedulingDialog
import org.dhis2.usescases.teiDashboard.dialogs.scheduling.SchedulingDialog.Companion.PROGRAM_STAGE_UID
import org.dhis2.usescases.teiDashboard.dialogs.scheduling.SchedulingDialog.Companion.SCHEDULING_DIALOG
import org.dhis2.usescases.teiDashboard.dialogs.scheduling.SchedulingDialog.Companion.SCHEDULING_DIALOG_RESULT
import org.dhis2.usescases.teiDashboard.ui.TeiDetailDashboard
import org.dhis2.usescases.teiDashboard.ui.mapper.InfoBarMapper
import org.dhis2.usescases.teiDashboard.ui.mapper.TeiDashboardCardMapper
Expand Down Expand Up @@ -159,6 +162,16 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View {
setEvents(it)
showLoadingProgress(false)
}

setFragmentResultListener(SCHEDULING_DIALOG_RESULT) { _, bundle ->
showToast(
resourceManager.formatWithEventLabel(
R.string.event_label_created,
bundle.getString(PROGRAM_STAGE_UID),
),
)
presenter.fetchEvents()
}
}.root
}

Expand Down Expand Up @@ -374,18 +387,9 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View {
override fun displayScheduleEvent() {
val model = dashboardViewModel.dashboardModel.value
if (model is DashboardEnrollmentModel) {
SchedulingDialog(
SchedulingDialog.newInstance(
enrollment = model.currentEnrollment,
programStages = presenter.filterAvailableStages(model.programStages),
onScheduled = { programStageUid ->
showToast(
resourceManager.formatWithEventLabel(
R.string.event_label_created,
programStageUid,
),
)
presenter.fetchEvents()
},
).show(parentFragmentManager, SCHEDULING_DIALOG)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import android.view.ViewGroup
import android.widget.DatePicker
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.viewModels
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.dhis2.bindings.app
Expand All @@ -20,15 +22,26 @@ import org.hisp.dhis.android.core.program.ProgramStage
import java.util.Date
import javax.inject.Inject

class SchedulingDialog(
val enrollment: Enrollment,
val programStages: List<ProgramStage>,
val onScheduled: (String) -> Unit,
) : BottomSheetDialogFragment() {
class SchedulingDialog : BottomSheetDialogFragment() {
companion object {
const val SCHEDULING_DIALOG = "SCHEDULING_DIALOG"
const val SCHEDULING_DIALOG_RESULT = "SCHEDULING_DIALOG_RESULT"
const val PROGRAM_STAGE_UID = "PROGRAM_STAGE_UID"

fun newInstance(
enrollment: Enrollment,
programStages: List<ProgramStage>,
): SchedulingDialog {
return SchedulingDialog().apply {
this.enrollment = enrollment
this.programStages = programStages
}
}
}

var enrollment: Enrollment? = null
var programStages: List<ProgramStage>? = null

@Inject
lateinit var factory: SchedulingViewModelFactory
val viewModel: SchedulingViewModel by viewModels { factory }
Expand All @@ -40,19 +53,26 @@ class SchedulingDialog(

override fun onAttach(context: Context) {
super.onAttach(context)
app().userComponent()?.plus(
SchedulingModule(
enrollment,
programStages,
),
)?.inject(this)
app().userComponent()?.plus(SchedulingModule())?.inject(this)
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
enrollment?.let {
viewModel.enrollment = it
}
programStages?.let {
viewModel.programStages = it
viewModel.setInitialProgramStage(it.first())
}
viewModel.onEventScheduled = {
setFragmentResult(SCHEDULING_DIALOG_RESULT, bundleOf(PROGRAM_STAGE_UID to it))
dismiss()
}

viewModel.showCalendar = {
showCalendarDialog()
}
Expand All @@ -61,20 +81,15 @@ class SchedulingDialog(
showPeriodDialog()
}

viewModel.onEventScheduled = {
dismiss()
onScheduled(viewModel.programStage.value.uid())
}

return ComposeView(requireContext()).apply {
setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed,
)
setContent {
SchedulingDialogUi(
viewModel = viewModel,
programStages = programStages,
orgUnitUid = enrollment.organisationUnit(),
programStages = viewModel.programStages,
orgUnitUid = viewModel.enrollment.organisationUnit(),
onDismiss = { dismiss() },
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ fun buttonTitle(scheduleNew: Boolean): String = when (scheduleNew) {
fun ProvideScheduleNewEventForm(
programStages: List<ProgramStage>,
viewModel: SchedulingViewModel,
selectedProgramStage: ProgramStage,
selectedProgramStage: ProgramStage?,
date: EventDate,
catCombo: EventCatCombo,
orgUnitUid: String?,
Expand All @@ -136,7 +136,7 @@ fun ProvideScheduleNewEventForm(
title = stringResource(id = R.string.program_stage),
state = InputShellState.UNFOCUSED,
dropdownItems = programStages.map { DropdownItem(it.displayName().orEmpty()) },
selectedItem = DropdownItem(selectedProgramStage.displayName().orEmpty()),
selectedItem = DropdownItem(selectedProgramStage?.displayName().orEmpty()),
onResetButtonClicked = {},
onItemSelected = { item ->
programStages.find { it.displayName() == item.label }
Expand All @@ -145,7 +145,7 @@ fun ProvideScheduleNewEventForm(
)
}

if (willShowCalendar(selectedProgramStage.periodType())) {
if (willShowCalendar(selectedProgramStage?.periodType())) {
ProvideInputDate(
EventInputDateUiModel(
eventDate = date,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,19 @@ import org.dhis2.commons.di.dagger.PerFragment
import org.dhis2.commons.resources.DhisPeriodUtils
import org.dhis2.commons.resources.ResourceManager
import org.hisp.dhis.android.core.D2
import org.hisp.dhis.android.core.enrollment.Enrollment
import org.hisp.dhis.android.core.program.ProgramStage

@Module
class SchedulingModule(
val enrollment: Enrollment,
val programStages: List<ProgramStage>,
) {
class SchedulingModule {
@Provides
@PerFragment
fun provideSchedulingViewModelFactory(
d2: D2,
resourceManager: ResourceManager,
periodUtils: DhisPeriodUtils,
): SchedulingViewModelFactory =
SchedulingViewModelFactory(enrollment, programStages, d2, resourceManager, periodUtils)
SchedulingViewModelFactory(
d2,
resourceManager,
periodUtils,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ import java.util.Date
import java.util.Locale

class SchedulingViewModel(
val enrollment: Enrollment,
val programStages: List<ProgramStage>,
val d2: D2,
val resourceManager: ResourceManager,
val periodUtils: DhisPeriodUtils,
Expand All @@ -37,29 +35,28 @@ class SchedulingViewModel(
lateinit var configureEventReportDate: ConfigureEventReportDate
lateinit var configureEventCatCombo: ConfigureEventCatCombo

private val _programStage: MutableStateFlow<ProgramStage> = MutableStateFlow(programStages.first())
val programStage: StateFlow<ProgramStage> get() = _programStage
lateinit var enrollment: Enrollment
lateinit var programStages: List<ProgramStage>

private val _programStage: MutableStateFlow<ProgramStage?> = MutableStateFlow(null)
val programStage: StateFlow<ProgramStage?> get() = _programStage

var showCalendar: (() -> Unit)? = null
var showPeriods: (() -> Unit)? = null
var onEventScheduled: (() -> Unit)? = null
var onEventScheduled: ((String) -> Unit)? = null

private val _eventDate: MutableStateFlow<EventDate> = MutableStateFlow(EventDate())
val eventDate: StateFlow<EventDate> get() = _eventDate

private val _eventCatCombo: MutableStateFlow<EventCatCombo> = MutableStateFlow(EventCatCombo())
val eventCatCombo: StateFlow<EventCatCombo> get() = _eventCatCombo

init {
loadConfiguration()
}

private fun loadConfiguration() {
repository = EventDetailsRepository(
d2 = d2,
programUid = enrollment.program().orEmpty(),
eventUid = null,
programStageUid = programStage.value.uid(),
programStageUid = programStage.value?.uid(),
fieldFactory = null,
eventCreationType = EventCreationType.SCHEDULE,
onError = resourceManager::parseD2Error,
Expand All @@ -68,14 +65,14 @@ class SchedulingViewModel(
creationType = EventCreationType.SCHEDULE,
resourceProvider = EventDetailResourcesProvider(
enrollment.program().orEmpty(),
programStage.value.uid(),
programStage.value?.uid(),
resourceManager,
),
repository = repository,
periodType = programStage.value.periodType(),
periodType = programStage.value?.periodType(),
periodUtils = periodUtils,
enrollmentId = enrollment.uid(),
scheduleInterval = programStage.value.standardInterval() ?: 0,
scheduleInterval = programStage.value?.standardInterval() ?: 0,
)
configureEventCatCombo = ConfigureEventCatCombo(
repository = repository,
Expand Down Expand Up @@ -142,7 +139,7 @@ class SchedulingViewModel(
}

fun showPeriodDialog() {
programStage.value.periodType()?.let {
programStage.value?.periodType()?.let {
showPeriods?.invoke()
}
}
Expand Down Expand Up @@ -171,10 +168,15 @@ class SchedulingViewModel(
).flowOn(Dispatchers.IO)
.collect {
if (it != null) {
onEventScheduled?.invoke()
onEventScheduled?.invoke(programStage.value?.uid() ?: "")
}
}
}
}
}

fun setInitialProgramStage(programStage: ProgramStage) {
_programStage.value = programStage
loadConfiguration()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,16 @@ import androidx.lifecycle.ViewModelProvider
import org.dhis2.commons.resources.DhisPeriodUtils
import org.dhis2.commons.resources.ResourceManager
import org.hisp.dhis.android.core.D2
import org.hisp.dhis.android.core.enrollment.Enrollment
import org.hisp.dhis.android.core.program.ProgramStage

@Suppress("UNCHECKED_CAST")
class SchedulingViewModelFactory(
private val enrollment: Enrollment,
private val programStages: List<ProgramStage>,
private val d2: D2,
private val resourceManager: ResourceManager,
private val periodUtils: DhisPeriodUtils,
) : ViewModelProvider.Factory {

override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SchedulingViewModel(
enrollment,
programStages,
d2,
resourceManager,
periodUtils,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.setMain
import org.hisp.dhis.android.core.arch.helpers.DateUtils
import org.hisp.dhis.android.core.enrollment.Enrollment
import org.hisp.dhis.android.core.program.ProgramStage
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.spy
Expand All @@ -15,9 +13,6 @@ import org.mockito.kotlin.verify

class SchedulingViewModelTest {

private val enrollment = Enrollment.builder().uid("enrollmentUid").build()
private val programStage = ProgramStage.builder().uid("programStage").build()

private lateinit var schedulingViewModel: SchedulingViewModel

private val testingDispatcher = StandardTestDispatcher()
Expand All @@ -27,8 +22,6 @@ class SchedulingViewModelTest {
fun setUp() {
Dispatchers.setMain(testingDispatcher)
schedulingViewModel = SchedulingViewModel(
enrollment = enrollment,
programStages = listOf(programStage),
d2 = mock(),
resourceManager = mock(),
periodUtils = mock(),
Expand Down

0 comments on commit afe0b88

Please sign in to comment.