8000 fix: [ANDROAPP-7039] Crash when using event related working list in search screen by Balcan · Pull Request #4175 · dhis2/dhis2-android-capture-app · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

fix: [ANDROAPP-7039] Crash when using event related working list in search screen #4175

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

Merged
merged 9 commits into from
Jun 2, 2025
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ package org.dhis2.usescases.datasets
import android.view.View
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
Expand All @@ -18,12 +16,11 @@ import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
import androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withTagValue
import androidx.test.espresso.matcher.ViewMatchers.withText
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import org.dhis2.R
import org.dhis2.common.BaseRobot
import org.dhis2.common.matchers.RecyclerviewMatchers.Companion.hasItem
Expand All @@ -32,8 +29,10 @@ import org.dhis2.utils.AdapterItemPosition
import org.dhis2.utils.AdapterItemTitle
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.equalTo
import org.hisp.dhis.lib.expression.ast.Tag
import org.junit.Assert.assertTrue
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale


internal fun dataSetDetailRobot(
Expand Down Expand Up @@ -113,6 +112,28 @@ internal class DataSetDetailRobot(
return itemCount[0]
}

fun checkDataSetRecyclerItemsAreDisplayed(itemCount: Int) {
waitForView(withId(R.id.recycler))
.perform(waitForRecyclerViewItems(minItemCount = itemCount))
}

private fun waitForRecyclerViewItems(minItemCount: Int = 1, timeoutMs: Long = 5000): ViewAction {
return object : ViewAction {
override fun getConstraints() = isAssignableFrom(RecyclerView::class.java)
override fun getDescription() = "Wait for RecyclerView to have at least $minItemCount items"
override fun perform(uiController: UiController, view: View) {
val recyclerView = view as RecyclerView
val startTime = System.currentTimeMillis()
while ((recyclerView.adapter?.itemCount ?: 0) < minItemCount) {
if (System.currentTimeMillis() - startTime > timeoutMs) {
throw AssertionError("RecyclerView did not reach $minItemCount items in $timeoutMs ms")
}
uiController.loopMainThreadForAtLeast(50)
}
}
}
}

private fun getTitleFromRecyclerViewItem(
position: Int,
): String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,7 @@ class DataSetTest : BaseTest() {
}

dataSetDetailRobot(composeTestRule) {
assertEquals(2, getListItemCount())
checkDataSetRecyclerItemsAreDisplayed(2)
}

filterRobot(composeTestRule) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@

import androidx.annotation.VisibleForTesting;

import org.dhis2.commons.schedulers.SchedulerProvider;
import org.dhis2.commons.filters.data.FilterRepository;
import org.dhis2.commons.filters.DisableHomeFiltersFromSettingsApp;
import org.dhis2.commons.filters.FilterItem;
import org.dhis2.commons.filters.FilterManager;
import org.dhis2.commons.filters.data.FilterRepository;
import org.dhis2.commons.matomo.MatomoAnalyticsController;
import org.dhis2.commons.schedulers.SchedulerProvider;
import org.dhis2.mobile.commons.coroutine.CoroutineTracker;
import org.hisp.dhis.android.core.organisationunit.OrganisationUnit;

import java.util.List;
Expand Down Expand Up @@ -57,7 +58,10 @@ public void init() {

disposable.add(
filterManager.asFlowable().startWith(filterManager)
.flatMap(filterManager -> Flowable.just(filterRepository.dataSetFilters(dataSetDetailRepository.getDataSetUid())))
.flatMap(result -> {
CoroutineTracker.INSTANCE.increment();
return Flowable.just(filterRepository.dataSetFilters(dataSetDetailRepository.getDataSetUid()));
})
.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.ui())
.subscribe(filterItems -> {
Expand All @@ -67,8 +71,12 @@ public void init() {
view.setFilters(filterItems);
}
view.updateFilters(filterManager.getTotalFilters());
CoroutineTracker.INSTANCE.decrement();
},
Timber::d
throwable -> {
Timber.d(throwable);
CoroutineTracker.INSTANCE.decrement();
}
)
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ class ProgramFragment : FragmentGlobalAbstract(), ProgramView {
programViewModel.init()
}

override fun onPause() {
super.onPause()
programViewModel.dispose()
}

//endregion

override fun setTutorial() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts
import org.dhis2.commons.filters.FilterManager
import org.dhis2.usescases.enrollment.EnrollmentActivity
import org.dhis2.usescases.teiDashboard.TeiDashboardMobileActivity
import java.util.UUID
Expand All @@ -35,6 +34,7 @@ class SearchNavigator(
activity.setResult(Activity.RESULT_OK, it.data())
activity.finish()
}

is EnrollmentResult.Success ->
if (searchNavigationConfiguration.refreshDataOnBackFromEnrollment()) {
activity.refreshData()
Expand Down Expand Up @@ -62,7 +62,6 @@ class SearchNavigator(

fun openDashboard(teiUid: String?, programUid: String?, enrollmentUid: String?) {
teiUid?.let { searchNavigationConfiguration.openingTEI(it) }
FilterManager.getInstance().clearWorkingList(true)
dashboardLauncher.launch(
TeiDashboardMobileActivity.intent(
activity,
Expand Down Expand Up @@ -99,6 +98,7 @@ class SearchNavigator(
} ?: Bundle()
}
}

fun <I, O> ComponentActivity.registerActivityResultLauncher(
key: String = UUID.randomUUID().toString(),
contract: ActivityResultContract<I, O>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.dhis2.commons.filters.sorting.SortingItem;
import org.dhis2.commons.filters.sorting.SortingStatus;
import org.dhis2.commons.filters.workingLists.WorkingListItem;
import org.dhis2.commons.idlingresource.CountingIdlingResourceSingleton;
import org.dhis2.commons.resources.ResourceManager;
import org.hisp.dhis.android.core.arch.helpers.UidsHelper;
import org.hisp.dhis.android.core.category.CategoryOptionCombo;
Expand Down Expand Up @@ -41,10 +42,12 @@
public class FilterManager implements Serializable {

public void publishData() {
CountingIdlingResourceSingleton.INSTANCE.increment();
filterProcessor.onNext(this);
if (scope != null) {
FilterManagerExtensionsKt.emit(this, scope, filterFlow);
}
CountingIdlingResourceSingleton.INSTANCE.decrement();
}

public void setCatComboAdapter(CatOptCombFilterAdapter adapter) {
Expand Down Expand Up @@ -286,17 +289,20 @@ public void addEnrollmentStatus(boolean remove, EnrollmentStatus enrollmentStatu
}

public void addPeriod(List<DatePeriod> datePeriod) {
CountingIdlingResourceSingleton.INSTANCE.increment();
this.periodFilters = datePeriod;
observablePeriodFilters.set(datePeriod);
periodFiltersApplied.set(datePeriod != null && !datePeriod.isEmpty() ? 1 : 0);
publishData();
CountingIdlingResourceSingleton.INSTANCE.decrement();
}

public void addEnrollmentPeriod(List<DatePeriod> datePeriod) {
CountingIdlingResourceSingleton.INSTANCE.increment();
this.enrollmentPeriodFilters = datePeriod;

enrollmentPeriodFiltersApplied.set(datePeriod != null && !datePeriod.isEmpty() ? 1 : 0);
publishData();
CountingIdlingResourceSingleton.INSTANCE.decrement();
}

public void addOrgUnit(OrganisationUnit ou) {
Expand Down
10000
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import org.dhis2.commons.filters.periods.ui.FilterPeriodsDialog.FilterDialogLaun
import org.dhis2.commons.filters.periods.ui.state.FilterPeriodsScreenState
import org.dhis2.commons.periods.model.Period
import org.dhis2.commons.resources.ResourceManager
import org.dhis2.mobile.commons.coroutine.CoroutineTracker
import org.hisp.dhis.android.core.period.DatePeriod
import java.util.Calendar

Expand Down Expand Up @@ -87,80 +88,92 @@ class FilterPeriodsDialogViewmodel(
}

fun onPeriodSelected(period: Period) {
when (launchMode) {
is FilterDialogLaunchMode.NewPeriodDialog -> {
if (launchMode.filterType == Filters.PERIOD) {
FilterManager.getInstance().addPeriod(
listOf(
DatePeriod.create(
period.startDate,
period.endDate,
viewModelScope.launch {
CoroutineTracker.increment()
when (launchMode) {
is FilterDialogLaunchMode.NewPeriodDialog -> {
if (launchMode.filterType == Filters.PERIOD) {
FilterManager.getInstance().addPeriod(
listOf(
DatePeriod.create(
period.startDate,
period.endDate,
),
),
),
)
} else {
FilterManager.getInstance().addEnrollmentPeriod(
listOf(
DatePeriod.create(
period.startDate,
period.endDate,
)
} else {
FilterManager.getInstance().addEnrollmentPeriod(
listOf(
DatePeriod.create(
period.startDate,
period.endDate,
),
),
),
)
)
}
}
}

is FilterDialogLaunchMode.NewDataSetPeriodDialog -> {
FilterManager.getInstance()
.addPeriod(listOf(DatePeriod.create(period.startDate, period.endDate)))
is FilterDialogLaunchMode.NewDataSetPeriodDialog -> {
FilterManager.getInstance()
.addPeriod(listOf(DatePeriod.create(period.startDate, period.endDate)))
}
}
CoroutineTracker.decrement()
}
}

fun setDailyPeriodFilter(selectedDateMillis: Long?) {
val selectedDate = Calendar.getInstance()
selectedDateMillis?.let {
selectedDate.timeInMillis = selectedDateMillis
}
val datePeriods = mutableListOf(DatePeriod.create(selectedDate.time, selectedDate.time))
when (launchMode) {
is FilterDialogLaunchMode.NewPeriodDialog -> {
if (launchMode.filterType == Filters.ENROLLMENT_DATE) {
FilterManager.getInstance().addEnrollmentPeriod(datePeriods)
} else {
FilterManager.getInstance().addPeriod(datePeriods)
}
viewModelScope.launch {
CoroutineTracker.increment()
val selectedDate = Calendar.getInstance()
selectedDateMillis?.let {
selectedDate.timeInMillis = selectedDateMillis
}
val datePeriods = mutableListOf(DatePeriod.create(selectedDate.time, selectedDate.time))
when (launchMode) {
is FilterDialogLaunchMode.NewPeriodDialog -> {
if (launchMode.filterType == Filters.ENROLLMENT_DATE) {
FilterManager.getInstance().addEnrollmentPeriod(datePeriods)
} else {
FilterManager.getInstance().addPeriod(datePeriods)
}
}

else -> {
FilterManager.getInstance().addPeriod(datePeriods)
else -> {
FilterManager.getInstance().addPeriod(datePeriods)
}
}
CoroutineTracker.decrement()
}
}

fun setFromToFilter(fromSelectedDateMillis: Long?, toSelectedDateMillis: Long?) {
val fromSelectedDate = Calendar.getInstance()
fromSelectedDateMillis?.let {
fromSelectedDate.timeInMillis = it
}
val toSelectedDate = Calendar.getInstance()
toSelectedDateMillis?.let {
toSelectedDate.timeInMillis = it
}
val datePeriods =
mutableListOf(DatePeriod.create(fromSelectedDate.time, toSelectedDate.time))
when (launchMode) {
is FilterDialogLaunchMode.NewPeriodDialog -> {
if (launchMode.filterType == Filters.ENROLLMENT_DATE) {
FilterManager.getInstance().addEnrollmentPeriod(datePeriods)
} else {
FilterManager.getInstance().addPeriod(datePeriods)
}
viewModelScope.launch {
CoroutineTracker.increment()
val fromSelectedDate = Calendar.getInstance()
fromSelectedDateMillis?.let {
fromSelectedDate.timeInMillis = it
}
val toSelectedDate = Calendar.getInstance()
toSelectedDateMillis?.let {
toSelectedDate.timeInMillis = it
}
val datePeriods =
mutableListOf(DatePeriod.create(fromSelectedDate.time, toSelectedDate.time))
when (launchMode) {
is FilterDialogLaunchMode.NewPeriodDialog -> {
if (launchMode.filterType == Filters.ENROLLMENT_DATE) {
FilterManager.getInstance().addEnrollmentPeriod(datePeriods)
} else {
FilterManager.getInstance().addPeriod(datePeriods)
}
}

else -> {
FilterManager.getInstance().addPeriod(datePeriods)
else -> {
FilterManager.getInstance().addPeriod(datePeriods)
}
}
CoroutineTracker.decrement()
}
}

Expand Down
0