Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: [ANDROAPP-6182] crash when rotating scheduling dialog #3677

Merged
merged 2 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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