From 7c32c477dab65b84bb3c01b84b4673bd204ff26d Mon Sep 17 00:00:00 2001
From: Xavier Molloy <xavi@dhis2.org>
Date: Wed, 20 Nov 2024 16:17:21 +0100
Subject: [PATCH 01/11] ci: update vName

---
 gradle/libs.versions.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 5c12127119..2908f4b037 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -2,7 +2,7 @@
 sdk = "34"
 minSdk = "21"
 vCode = "137"
-vName = "3.1.0"
+vName = "3.1.0.1"
 gradle = "8.6.1"
 kotlin = '2.0.20'
 hilt = '2.47'

From abc926588898d11c2c17b48e7ad5416c3bd173d9 Mon Sep 17 00:00:00 2001
From: Xavier Molloy <xavi@dhis2.org>
Date: Tue, 26 Nov 2024 15:27:58 +0100
Subject: [PATCH 02/11] fix: [ANDROAPP-6665] Clear filters when navigating back
 (#3895)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* fix: [ANDROAPP-6665] Clear filters when navigating back

* fix: [ANDROAPP-6665] clear filters on home

Signed-off-by: Manu Muñoz <manu.munoz@dhis2.org>

* fix: [ANDROAPP-6665] test

Signed-off-by: Manu Muñoz <manu.munoz@dhis2.org>

---------

Signed-off-by: Manu Muñoz <manu.munoz@dhis2.org>
Co-authored-by: Manu Muñoz <manu.munoz@dhis2.org>
---
 .../main/java/org/dhis2/usescases/main/MainActivity.kt |  1 -
 .../main/java/org/dhis2/usescases/main/MainModule.kt   |  5 ++---
 .../java/org/dhis2/usescases/main/MainPresenter.kt     | 10 +++-------
 .../java/org/dhis2/usescases/main/MainPresenterTest.kt |  8 +++++---
 4 files changed, 10 insertions(+), 14 deletions(-)

diff --git a/app/src/main/java/org/dhis2/usescases/main/MainActivity.kt b/app/src/main/java/org/dhis2/usescases/main/MainActivity.kt
index c7a4d6679d..4ce0756b92 100644
--- a/app/src/main/java/org/dhis2/usescases/main/MainActivity.kt
+++ b/app/src/main/java/org/dhis2/usescases/main/MainActivity.kt
@@ -219,7 +219,6 @@ class MainActivity :
     }
 
     override fun onPause() {
-        presenter.setOpeningFilterToNone()
         presenter.onDetach()
         super.onPause()
     }
diff --git a/app/src/main/java/org/dhis2/usescases/main/MainModule.kt b/app/src/main/java/org/dhis2/usescases/main/MainModule.kt
index 95077a043c..62014e272b 100644
--- a/app/src/main/java/org/dhis2/usescases/main/MainModule.kt
+++ b/app/src/main/java/org/dhis2/usescases/main/MainModule.kt
@@ -6,7 +6,6 @@ import dhis2.org.analytics.charts.Charts
 import org.dhis2.commons.di.dagger.PerActivity
 import org.dhis2.commons.featureconfig.data.FeatureConfigRepository
 import org.dhis2.commons.filters.FilterManager
-import org.dhis2.commons.filters.data.FilterRepository
 import org.dhis2.commons.matomo.MatomoAnalyticsController
 import org.dhis2.commons.prefs.PreferenceProvider
 import org.dhis2.commons.resources.ColorUtils
@@ -32,7 +31,7 @@ class MainModule(val view: MainView, private val forceToNotSynced: Boolean) {
         schedulerProvider: SchedulerProvider,
         preferences: PreferenceProvider,
         workManagerController: WorkManagerController,
-        filterRepository: FilterRepository,
+        filterManager: FilterManager,
         matomoAnalyticsController: MatomoAnalyticsController,
         userManager: UserManager,
         deleteUserData: DeleteUserData,
@@ -47,7 +46,7 @@ class MainModule(val view: MainView, private val forceToNotSynced: Boolean) {
             schedulerProvider,
             preferences,
             workManagerController,
-            filterRepository,
+            filterManager,
             matomoAnalyticsController,
             userManager,
             deleteUserData,
diff --git a/app/src/main/java/org/dhis2/usescases/main/MainPresenter.kt b/app/src/main/java/org/dhis2/usescases/main/MainPresenter.kt
index fe2bb73058..bc88aa6a72 100644
--- a/app/src/main/java/org/dhis2/usescases/main/MainPresenter.kt
+++ b/app/src/main/java/org/dhis2/usescases/main/MainPresenter.kt
@@ -15,7 +15,6 @@ import kotlinx.coroutines.launch
 import org.dhis2.BuildConfig
 import org.dhis2.commons.Constants
 import org.dhis2.commons.filters.FilterManager
-import org.dhis2.commons.filters.data.FilterRepository
 import org.dhis2.commons.matomo.Actions.Companion.BLOCK_SESSION_PIN
 import org.dhis2.commons.matomo.Actions.Companion.OPEN_ANALYTICS
 import org.dhis2.commons.matomo.Actions.Companion.QR_SCANNER
@@ -57,7 +56,7 @@ class MainPresenter(
     private val schedulerProvider: SchedulerProvider,
     private val preferences: PreferenceProvider,
     private val workManagerController: WorkManagerController,
-    private val filterRepository: FilterRepository,
+    private val filterManager: FilterManager,
     private val matomoAnalyticsController: MatomoAnalyticsController,
     private val userManager: UserManager,
     private val deleteUserData: DeleteUserData,
@@ -78,6 +77,7 @@ class MainPresenter(
     val downloadingVersion = MutableLiveData(false)
 
     fun init() {
+        filterManager.clearAllFilters()
         preferences.removeValue(Preference.CURRENT_ORG_UNIT)
         disposable.add(
             repository.user()
@@ -158,7 +158,7 @@ class MainPresenter(
             Completable.fromCallable {
                 workManagerController.cancelAllWork()
                 syncStatusController.restore()
-                FilterManager.getInstance().clearAllFilters()
+                filterManager.clearAllFilters()
                 preferences.setValue(Preference.SESSION_LOCKED, false)
                 preferences.setValue(Preference.PIN_ENABLED, false)
                 userManager.d2.dataStoreModule().localDataStore().value(PIN).blockingDeleteIfExist()
@@ -225,10 +225,6 @@ class MainPresenter(
         matomoAnalyticsController.trackEvent(HOME, SETTINGS, CLICK)
     }
 
-    fun setOpeningFilterToNone() {
-        filterRepository.collapseAllFilters()
-    }
-
     fun isPinStored() = repository.isPinStored()
 
     fun launchInitialDataSync() {
diff --git a/app/src/test/java/org/dhis2/usescases/main/MainPresenterTest.kt b/app/src/test/java/org/dhis2/usescases/main/MainPresenterTest.kt
index ee6e872bbf..83863fac1c 100644
--- a/app/src/test/java/org/dhis2/usescases/main/MainPresenterTest.kt
+++ b/app/src/test/java/org/dhis2/usescases/main/MainPresenterTest.kt
@@ -8,7 +8,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.setMain
-import org.dhis2.commons.filters.data.FilterRepository
+import org.dhis2.commons.filters.FilterManager
 import org.dhis2.commons.matomo.Categories.Companion.HOME
 import org.dhis2.commons.matomo.MatomoAnalyticsController
 import org.dhis2.commons.prefs.Preference.Companion.DEFAULT_CAT_COMBO
@@ -52,7 +52,7 @@ class MainPresenterTest {
     private val view: MainView = mock()
     private val preferences: PreferenceProvider = mock()
     private val workManagerController: WorkManagerController = mock()
-    private val filterRepository: FilterRepository = mock()
+    private val filterManager: FilterManager = mock()
     private val matomoAnalyticsController: MatomoAnalyticsController = mock()
     private val userManager: UserManager = mock()
     private val deleteUserData: DeleteUserData = mock()
@@ -82,7 +82,7 @@ class MainPresenterTest {
                 schedulers,
                 preferences,
                 workManagerController,
-                filterRepository,
+                filterManager,
                 matomoAnalyticsController,
                 userManager,
                 deleteUserData,
@@ -103,6 +103,7 @@ class MainPresenterTest {
         verify(view).renderUsername(any())
         verify(preferences).setValue(DEFAULT_CAT_COMBO, "uid")
         verify(preferences).setValue(PREF_DEFAULT_CAT_OPTION_COMBO, "uid")
+        verify(filterManager).clearAllFilters()
     }
 
     @Test
@@ -120,6 +121,7 @@ class MainPresenterTest {
         verify(workManagerController).cancelAllWork()
         verify(preferences).setValue(SESSION_LOCKED, false)
         verify(userManager.d2.dataStoreModule().localDataStore().value(PIN)).blockingDeleteIfExist()
+        verify(filterManager).clearAllFilters()
         verify(view).goToLogin(1, false)
     }
 

From a837538f14edf1c5646246cec2039ed2846b6ff9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9s=20Miguel=20Rubio?= <andres@dhis2.org>
Date: Wed, 27 Nov 2024 10:01:14 +0100
Subject: [PATCH 03/11] fix: [ANDROAPP-6691] Null pointer on show dialog
 (#3904)

Signed-off-by: andresmr <andres@dhis2.org>
---
 .../usescases/datasets/dataSetTable/DataSetTableActivity.kt | 3 +--
 .../programEventDetail/ProgramEventDetailActivity.kt        | 6 ++----
 2 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/app/src/main/java/org/dhis2/usescases/datasets/dataSetTable/DataSetTableActivity.kt b/app/src/main/java/org/dhis2/usescases/datasets/dataSetTable/DataSetTableActivity.kt
index b16f784076..07870ed8a1 100644
--- a/app/src/main/java/org/dhis2/usescases/datasets/dataSetTable/DataSetTableActivity.kt
+++ b/app/src/main/java/org/dhis2/usescases/datasets/dataSetTable/DataSetTableActivity.kt
@@ -184,9 +184,8 @@ class DataSetTableActivity : ActivityGlobalAbstract(), DataSetTableContract.View
                 }
             })
             .onNoConnectionListener {
-                val contextView = findViewById<View>(R.id.navigationBar)
                 Snackbar.make(
-                    contextView,
+                    binding.root,
                     R.string.sync_offline_check_connection,
                     Snackbar.LENGTH_SHORT,
                 ).show()
diff --git a/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailActivity.kt b/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailActivity.kt
index 642fbe283d..3fac0ea471 100644
--- a/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailActivity.kt
+++ b/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailActivity.kt
@@ -273,9 +273,8 @@ class ProgramEventDetailActivity :
                 }
             })
             .onNoConnectionListener {
-                val contextView = findViewById<View>(R.id.navigationBar)
                 Snackbar.make(
-                    contextView,
+                    binding.root,
                     R.string.sync_offline_check_connection,
                     Snackbar.LENGTH_SHORT,
                 ).show()
@@ -524,9 +523,8 @@ class ProgramEventDetailActivity :
                 }
             })
             .onNoConnectionListener {
-                val contextView = findViewById<View>(R.id.rootView)
                 Snackbar.make(
-                    contextView,
+                    binding.root,
                     R.string.sync_offline_check_connection,
                     Snackbar.LENGTH_SHORT,
                 ).show()

From b16db7afa5e1df2a60619d57ffe1a7ce303c3c81 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Tar=C3=AD?= <daniel@dhis2.org>
Date: Fri, 29 Nov 2024 13:20:33 +0100
Subject: [PATCH 04/11] update SDK version for 3.1.0.1 hotfix (#3911)

---
 gradle/libs.versions.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 2908f4b037..fe0f21ba6b 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -8,7 +8,7 @@ kotlin = '2.0.20'
 hilt = '2.47'
 jacoco = '0.8.10'
 designSystem = "0.4.0"
-dhis2sdk = "1.11.0"
+dhis2sdk = "1.11.0.1-SNAPSHOT"
 ruleEngine = "3.0.0"
 expressionParser = "1.1.0"
 appcompat = "1.6.1"

From 964ef103aeb6e5d520556471f061bd693c39fb3d Mon Sep 17 00:00:00 2001
From: Pablo <pablo@dhis2.org>
Date: Fri, 29 Nov 2024 14:07:24 +0100
Subject: [PATCH 05/11] hotfix: Handle large option sets (#3901)

* fix: [ANDROAPP-6653] Large option sets freeze app

* update design system version

* fix period selector

* fix unit tests

* remove commented code

* fix test and remove those deprecated

* fix sonarcloud warnings

* remove commented code

* fix: option set not searching

* fix integration test

* fix code smell

* fix code smell

* fix code smell
---
 .../tablefields/spinner/SpinnerViewModel.java |  16 -
 .../usescases/enrollment/EnrollmentModule.kt  |   5 +-
 .../providers/InputFieldsProvider.kt          |  41 ++-
 .../EventInitialRepositoryImpl.java           |  24 +-
 .../SearchRepositoryImplKt.kt                 |  97 +++---
 .../teiDashboard/TeiDashboardModule.kt        |   3 +-
 .../dialogs/scheduling/SchedulingDialogUi.kt  |  27 +-
 .../utils/optionset/OptionSetPresenter.kt     |  23 --
 .../form/data/DataEntryBaseRepository.kt      |  70 +++--
 .../dhis2/form/data/DataEntryRepository.kt    |  11 +
 .../dhis2/form/data/EnrollmentRepository.kt   |  43 ++-
 .../org/dhis2/form/data/EventRepository.kt    |  50 ++--
 .../org/dhis2/form/data/FormRepository.kt     |   1 +
 .../org/dhis2/form/data/FormRepositoryImpl.kt | 151 +++++++++-
 .../org/dhis2/form/data/GeometryController.kt |   1 -
 .../form/data/RuleUtilsProviderResult.kt      |   3 +
 .../data/metadata/EnrollmentConfiguration.kt  |  30 +-
 .../data/metadata/FormBaseConfiguration.kt    |  36 +++
 .../main/java/org/dhis2/form/di/Injector.kt   |   2 +-
 .../java/org/dhis2/form/model/ActionType.kt   |   1 +
 .../org/dhis2/form/model/FieldUiModelImpl.kt  |   8 +-
 .../form/model/OptionSetConfiguration.kt      | 100 ++-----
 .../form/model/OptionSetDialogViewModel.kt    |   4 +-
 .../java/org/dhis2/form/model/UiEventType.kt  |  12 -
 .../main/java/org/dhis2/form/ui/FormView.kt   | 282 +-----------------
 .../java/org/dhis2/form/ui/FormViewModel.kt   |  17 ++
 .../org/dhis2/form/ui/event/DialogDelegate.kt |  92 ------
 .../form/ui/event/RecyclerViewUiEvents.kt     |  42 ---
 .../dhis2/form/ui/event/UiEventFactoryImpl.kt | 103 -------
 .../org/dhis2/form/ui/intent/FormIntent.kt    |   6 +
 .../inputfield/CategorySelectorProvider.kt    |  32 +-
 .../provider/inputfield/CheckBoxProvider.kt   |  31 +-
 .../provider/inputfield/DropdownProvider.kt   |  38 ++-
 .../ui/provider/inputfield/FieldProvider.kt   |  11 +
 .../inputfield/MatrixInputProvider.kt         |  20 +-
 .../inputfield/MatrixSequentialUtilites.kt    |   2 +-
 .../inputfield/MultiSelectionInputProvider.kt |  31 +-
 .../inputfield/PeriodSelectorProvider.kt      |  15 +-
 .../inputfield/RadioButtonProvider.kt         |  31 +-
 .../inputfield/SequentialInputProvider.kt     |  20 +-
 .../form/data/EnrollmentRepositoryTest.kt     |   3 +
 .../org/dhis2/form/data/FieldUiModelTest.kt   |  92 ------
 .../data/FormRepositoryIntegrationTest.kt     |   3 +
 .../dhis2/form/data/GeometryControllerTest.kt |  35 ---
 .../form/integration/ProgramRulesTest.kt      |  94 +++---
 .../model/OptionSetDialogViewModelTest.kt     |  61 ----
 .../dhis2/form/ui/DataEntryIntegrationTest.kt |  58 ++--
 .../form/ui/event/UiEventFactoryImplTest.kt   |  74 -----
 gradle/libs.versions.toml                     |   2 +-
 49 files changed, 743 insertions(+), 1211 deletions(-)
 delete mode 100644 form/src/main/java/org/dhis2/form/ui/event/DialogDelegate.kt
 delete mode 100644 form/src/test/java/org/dhis2/form/data/FieldUiModelTest.kt
 delete mode 100644 form/src/test/java/org/dhis2/form/ui/event/UiEventFactoryImplTest.kt

diff --git a/app/src/main/java/org/dhis2/data/forms/dataentry/tablefields/spinner/SpinnerViewModel.java b/app/src/main/java/org/dhis2/data/forms/dataentry/tablefields/spinner/SpinnerViewModel.java
index f44f0b2198..6c558e725f 100644
--- a/app/src/main/java/org/dhis2/data/forms/dataentry/tablefields/spinner/SpinnerViewModel.java
+++ b/app/src/main/java/org/dhis2/data/forms/dataentry/tablefields/spinner/SpinnerViewModel.java
@@ -12,9 +12,6 @@
 @AutoValue
 public abstract class SpinnerViewModel extends FieldViewModel {
 
-    private List<String> optionsToHide;
-    private List<String> optionGroupsToHide;
-
     @NonNull
     public abstract String hint();
 
@@ -52,17 +49,4 @@ public FieldViewModel withWarning(@NonNull String warning) {
     public FieldViewModel withValue(String data) {
         return new AutoValue_SpinnerViewModel(uid(),label(),mandatory(),data,programStageSection(),allowFutureDate(),editable(),warning(),error(),description(),dataElement(), options(), optionsList(), storeBy(), row(), column(),categoryOptionCombo(), catCombo(),hint(),optionSet());
     }
-
-    public void setOptionsToHide(List<String> optionsToHide, List<String> optionsGroupsToHide) {
-        this.optionsToHide = optionsToHide;
-        this.optionGroupsToHide = optionsGroupsToHide;
-    }
-
-    public List<String> getOptionsToHide() {
-        return optionsToHide;
-    }
-
-    public List<String> getOptionGroupsToHide() {
-        return optionGroupsToHide;
-    }
 }
diff --git a/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentModule.kt b/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentModule.kt
index 16790d2b35..ca121488f0 100644
--- a/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentModule.kt
+++ b/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentModule.kt
@@ -84,8 +84,7 @@ class EnrollmentModule(
     @PerActivity
     fun provideEnrollmentConfiguration(
         d2: D2,
-        metadataIconProvider: MetadataIconProvider,
-    ) = EnrollmentConfiguration(d2, enrollmentUid, metadataIconProvider)
+    ) = EnrollmentConfiguration(d2, enrollmentUid)
 
     @Provides
     @PerActivity
@@ -93,12 +92,14 @@ class EnrollmentModule(
         modelFactory: FieldViewModelFactory,
         enrollmentFormLabelsProvider: EnrollmentFormLabelsProvider,
         enrollmentConfiguration: EnrollmentConfiguration,
+        metadataIconProvider: MetadataIconProvider,
     ): EnrollmentRepository {
         return EnrollmentRepository(
             fieldFactory = modelFactory,
             conf = enrollmentConfiguration,
             enrollmentMode = EnrollmentMode.valueOf(enrollmentMode.name),
             enrollmentFormLabelsProvider = enrollmentFormLabelsProvider,
+            metadataIconProvider = metadataIconProvider,
         )
     }
 
diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/providers/InputFieldsProvider.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/providers/InputFieldsProvider.kt
index 73acff526a..51e2978dc9 100644
--- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/providers/InputFieldsProvider.kt
+++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/providers/InputFieldsProvider.kt
@@ -53,10 +53,16 @@ fun ProvideInputDate(
 ) {
     if (uiModel.showField) {
         Spacer(modifier = Modifier.height(16.dp))
-        val textSelection = TextRange(if (uiModel.eventDate.dateValue != null) uiModel.eventDate.dateValue.length else 0)
+        val textSelection =
+            TextRange(if (uiModel.eventDate.dateValue != null) uiModel.eventDate.dateValue.length else 0)
         var value by remember(uiModel.eventDate.dateValue) {
             if (uiModel.eventDate.dateValue != null) {
-                mutableStateOf(TextFieldValue(formatStoredDateToUI(uiModel.eventDate.dateValue) ?: "", textSelection))
+                mutableStateOf(
+                    TextFieldValue(
+                        formatStoredDateToUI(uiModel.eventDate.dateValue) ?: "",
+                        textSelection,
+                    ),
+                )
             } else {
                 mutableStateOf(TextFieldValue())
             }
@@ -66,7 +72,10 @@ fun ProvideInputDate(
             mutableStateOf(getInputState(uiModel.detailsEnabled))
         }
         val yearRange = if (uiModel.selectableDates != null) {
-            IntRange(uiModel.selectableDates.initialDate.substring(4, 8).toInt(), uiModel.selectableDates.endDate.substring(4, 8).toInt())
+            IntRange(
+                uiModel.selectableDates.initialDate.substring(4, 8).toInt(),
+                uiModel.selectableDates.endDate.substring(4, 8).toInt(),
+            )
         } else {
             IntRange(1924, 2124)
         }
@@ -90,7 +99,10 @@ fun ProvideInputDate(
                     }
                 },
                 is24hourFormat = uiModel.is24HourFormat,
-                selectableDates = uiModel.selectableDates ?: SelectableDates("01011924", "12312124"),
+                selectableDates = uiModel.selectableDates ?: SelectableDates(
+                    "01011924",
+                    "12312124",
+                ),
                 yearRange = yearRange,
             ),
             modifier = modifier.testTag(INPUT_EVENT_INITIAL_DATE),
@@ -246,15 +258,23 @@ fun ProvideCategorySelector(
                 selectedItem = null
                 eventCatComboUiModel.onClearCatCombo(eventCatComboUiModel.category)
             },
-            onItemSelected = { newSelectedDropdownItem ->
+            onItemSelected = { _, newSelectedDropdownItem ->
                 selectedItem = newSelectedDropdownItem.label
                 eventCatComboUiModel.onOptionSelected(selectableOptions.firstOrNull { it.displayName() == newSelectedDropdownItem.label })
             },
-            dropdownItems = dropdownItems,
+            fetchItem = { index -> dropdownItems[index] },
+            itemCount = dropdownItems.size,
+            onSearchOption = { /*no-op*/ },
+            loadOptions = { /*no-op*/ },
+            useDropDown = dropdownItems.size < 15,
             isRequiredField = eventCatComboUiModel.required,
         )
     } else {
-        ProvideEmptyCategorySelector(modifier = modifier, name = eventCatComboUiModel.category.name, option = eventCatComboUiModel.noOptionsText)
+        ProvideEmptyCategorySelector(
+            modifier = modifier,
+            name = eventCatComboUiModel.category.name,
+            option = eventCatComboUiModel.noOptionsText,
+        )
     }
 }
 
@@ -316,10 +336,13 @@ fun ProvideEmptyCategorySelector(
         onResetButtonClicked = {
             selectedItem = ""
         },
-        onItemSelected = { newSelectedDropdownItem ->
+        onItemSelected = { _, newSelectedDropdownItem ->
             selectedItem = newSelectedDropdownItem.label
         },
-        dropdownItems = listOf(DropdownItem(option)),
+        fetchItem = { DropdownItem(option) },
+        itemCount = 1,
+        onSearchOption = { /*no-op*/ },
+        loadOptions = { /*no-op*/ },
         isRequiredField = false,
     )
 }
diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventInitial/EventInitialRepositoryImpl.java b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventInitial/EventInitialRepositoryImpl.java
index ab345fd56a..141d1af9ce 100644
--- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventInitial/EventInitialRepositoryImpl.java
+++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventInitial/EventInitialRepositoryImpl.java
@@ -45,6 +45,8 @@
 
 import io.reactivex.Flowable;
 import io.reactivex.Observable;
+import kotlin.Unit;
+import kotlin.jvm.functions.Function1;
 import timber.log.Timber;
 
 public class EventInitialRepositoryImpl implements EventInitialRepository {
@@ -328,22 +330,12 @@ private FieldUiModel transform(@NonNull ProgramStageDataElement stage, DataEleme
             if (!dataValueOptions.isEmpty()) {
                 dataValue = option.get(0).displayName();
             }
-            optionSetConfig = OptionSetConfiguration.Companion.config(
-                    d2.optionModule().options().byOptionSetUid().eq(optionSet).blockingCount(),
-                    () -> {
-                        List<Option> options = d2.optionModule().options().byOptionSetUid().eq(optionSet).blockingGet();
-                        HashMap<String, MetadataIconData> metadataIconMap = new HashMap<>();
-                        for (Option optionItem : options) {
-                            metadataIconMap.put(
-                                    optionItem.uid(),
-                                    metadataIconProvider.invoke(optionItem.style()));
-                        }
-
-                        return new OptionSetConfiguration.OptionConfigData(
-                                options,
-                                metadataIconMap
-                        );
-                    }
+            optionSetConfig = new OptionSetConfiguration(
+                    null,
+                    query -> null,
+                    OptionSetConfiguration.Companion.optionDataFlow(
+                            d2.optionModule().options().byOptionSetUid().eq(optionSet).getPagingData(10),
+                            option1 -> metadataIconProvider.invoke(option1.style()))
             );
         }
 
diff --git a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImplKt.kt b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImplKt.kt
index 6abcc4278d..69c37ee07a 100644
--- a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImplKt.kt
+++ b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImplKt.kt
@@ -1,7 +1,16 @@
+@file:OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
+
 package org.dhis2.usescases.searchTrackEntity
 
 import androidx.paging.PagingData
+import androidx.paging.map
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.withContext
 import org.dhis2.commons.filters.FilterManager
 import org.dhis2.commons.resources.MetadataIconProvider
@@ -309,32 +318,29 @@ class SearchRepositoryImplKt(
             d2.trackedEntityModule().trackedEntityAttributes()
                 .uid(programAttribute.trackedEntityAttribute()!!.uid())
                 .blockingGet()?.let { attribute ->
-
+                    val searchFlow = MutableStateFlow("")
                     val optionSetConfiguration = attribute.optionSet()?.let {
-                        OptionSetConfiguration.config(
-                            d2.optionModule().options()
-                                .byOptionSetUid().eq(attribute.optionSet()!!.uid())
-                                .blockingCount(),
-                        ) {
-                            val options = d2.optionModule().options()
-                                .byOptionSetUid().eq(attribute.optionSet()!!.uid())
-                                .orderBySortOrder(RepositoryScope.OrderByDirection.ASC)
-                                .blockingGet()
-
-                            val metadataIconMap =
-                                options.associate {
-                                    it.uid() to metadataIconProvider(
-                                        it.style(),
-                                        program?.style()?.color()?.toColor()
-                                            ?: SurfaceColor.Primary,
-                                    )
-                                }
-
-                            OptionSetConfiguration.OptionConfigData(
-                                options = options,
-                                metadataIconMap = metadataIconMap,
-                            )
-                        }
+                        OptionSetConfiguration(
+                            searchEmitter = searchFlow,
+                            optionFlow = searchFlow.debounce(300).flatMapLatest {
+                                d2.optionModule().options()
+                                    .byOptionSetUid().eq(attribute.optionSet()!!.uid())
+                                    .getPagingData(10)
+                                    .map { pagingData ->
+                                        pagingData.map { option ->
+                                            OptionSetConfiguration.OptionData(
+                                                option,
+                                                metadataIconProvider(
+                                                    option.style(),
+                                                    program?.style()?.color()?.toColor()
+                                                        ?: SurfaceColor.Primary,
+                                                ),
+                                            )
+                                        }
+                                    }
+                            },
+                            onSearch = { searchFlow.value = it },
+                        )
                     }
                     createField(
                         trackedEntityAttribute = attribute,
@@ -359,31 +365,26 @@ class SearchRepositoryImplKt(
             d2.trackedEntityModule().trackedEntityAttributes()
                 .uid(typeAttribute.trackedEntityAttribute()!!.uid())
                 .blockingGet()?.let { attribute ->
-
+                    val searchEmitter = MutableStateFlow("")
                     val optionSetConfiguration = attribute.optionSet()?.let {
-                        OptionSetConfiguration.config(
-                            d2.optionModule().options()
+                        OptionSetConfiguration(
+                            searchEmitter = searchEmitter,
+                            optionFlow = d2.optionModule().options()
                                 .byOptionSetUid().eq(attribute.optionSet()!!.uid())
-                                .blockingCount(),
-                        ) {
-                            val options = d2.optionModule().options()
-                                .byOptionSetUid().eq(attribute.optionSet()!!.uid())
-                                .orderBySortOrder(RepositoryScope.OrderByDirection.ASC)
-                                .blockingGet()
-
-                            val metadataIconMap =
-                                options.associate {
-                                    it.uid() to metadataIconProvider(
-                                        it.style(),
-                                        SurfaceColor.Primary,
-                                    )
-                                }
-
-                            OptionSetConfiguration.OptionConfigData(
-                                options = options,
-                                metadataIconMap = metadataIconMap,
-                            )
-                        }
+                                .getPagingData(10)
+                                .map { pagingData ->
+                                    pagingData.map { option ->
+                                        OptionSetConfiguration.OptionData(
+                                            option,
+                                            metadataIconProvider(
+                                                option.style(),
+                                                SurfaceColor.Primary,
+                                            ),
+                                        )
+                                    }
+                                },
+                            onSearch = { searchEmitter.value = it },
+                        )
                     }
 
                     createField(
diff --git a/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardModule.kt b/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardModule.kt
index 6e9afede90..c8f1ba6401 100644
--- a/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardModule.kt
+++ b/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardModule.kt
@@ -59,8 +59,7 @@ class TeiDashboardModule(
     @PerActivity
     fun provideEnrollmentConfiguration(
         d2: D2,
-        metadataIconProvider: MetadataIconProvider,
-    ) = enrollmentUid?.let { EnrollmentConfiguration(d2, it, metadataIconProvider) }
+    ) = enrollmentUid?.let { EnrollmentConfiguration(d2, it) }
 
     @Provides
     @PerActivity
diff --git a/app/src/main/java/org/dhis2/usescases/teiDashboard/dialogs/scheduling/SchedulingDialogUi.kt b/app/src/main/java/org/dhis2/usescases/teiDashboard/dialogs/scheduling/SchedulingDialogUi.kt
index 374c5d1ec7..fa5e8cca27 100644
--- a/app/src/main/java/org/dhis2/usescases/teiDashboard/dialogs/scheduling/SchedulingDialogUi.kt
+++ b/app/src/main/java/org/dhis2/usescases/teiDashboard/dialogs/scheduling/SchedulingDialogUi.kt
@@ -161,7 +161,8 @@ private fun ButtonBlock(
                 Column(
                     verticalArrangement = Arrangement.spacedBy(8.dp),
                 ) {
-                    val eventLabel = selectedProgramStage?.displayEventLabel() ?: stringResource(R.string.event)
+                    val eventLabel =
+                        selectedProgramStage?.displayEventLabel() ?: stringResource(R.string.event)
                     Button(
                         modifier = Modifier.fillMaxWidth(),
                         style = ButtonStyle.FILLED,
@@ -233,15 +234,31 @@ fun ProvideScheduleNewEventForm(
     launchMode: LaunchMode,
 ) {
     if (programStages.size > 1 && launchMode !is LaunchMode.EnterEvent) {
+        var dropdownItems by remember {
+            mutableStateOf(
+                programStages.map { DropdownItem(it.displayName().orEmpty()) },
+            )
+        }
         InputDropDown(
             title = stringResource(id = R.string.program_stage),
             state = InputShellState.UNFOCUSED,
-            dropdownItems = programStages.map { DropdownItem(it.displayName().orEmpty()) },
+            fetchItem = { index -> dropdownItems[index] },
+            itemCount = dropdownItems.size,
+            onSearchOption = { query ->
+                dropdownItems = if (query.isNotEmpty()) {
+                    dropdownItems.filter { it.label.contains(query) }
+                } else {
+                    programStages.map { DropdownItem(it.displayName().orEmpty()) }
+                }
+            },
+            useDropDown = dropdownItems.size < 15,
+            loadOptions = {
+                /*no-op*/
+            },
             selectedItem = DropdownItem(selectedProgramStage?.displayName().orEmpty()),
             onResetButtonClicked = {},
-            onItemSelected = { item ->
-                programStages.find { it.displayName() == item.label }
-                    ?.let { viewModel.updateStage(it) }
+            onItemSelected = { index, _ ->
+                programStages[index].let { viewModel.updateStage(it) }
             },
         )
     }
diff --git a/app/src/main/java/org/dhis2/utils/optionset/OptionSetPresenter.kt b/app/src/main/java/org/dhis2/utils/optionset/OptionSetPresenter.kt
index 3bcaf6253c..64f166260b 100644
--- a/app/src/main/java/org/dhis2/utils/optionset/OptionSetPresenter.kt
+++ b/app/src/main/java/org/dhis2/utils/optionset/OptionSetPresenter.kt
@@ -16,28 +16,16 @@ class OptionSetPresenter(
     val d2: D2,
     val schedulerProvider: SchedulerProvider,
 ) {
-
-    private lateinit var optionSetOptionHandler: OptionSetOptionsHandler
     var disposable: CompositeDisposable = CompositeDisposable()
     private var optionSetUid: String? = null
 
     fun init(optionSet: FieldUiModel) {
         this.optionSetUid = optionSet.optionSet
-        this.optionSetOptionHandler = OptionSetOptionsHandler(
-            optionSet.optionSetConfiguration?.optionsToHide,
-            optionSet.optionSetConfiguration?.optionsToShow,
-            null,
-        )
         getOptions()
     }
 
     fun init(optionSetTable: TableSpinnerViewModel) {
         this.optionSetUid = optionSetTable.optionSet()
-        this.optionSetOptionHandler = OptionSetOptionsHandler(
-            optionSetTable.optionsToHide,
-            null,
-            optionSetTable.optionGroupsToHide,
-        )
         getOptions()
     }
 
@@ -50,17 +38,6 @@ class OptionSetPresenter(
                     var optionRepository = d2.optionModule().options()
                         .byOptionSetUid().eq(optionSetUid)
 
-                    val(finalOptionsToHide, finalOptionsToShow) =
-                        optionSetOptionHandler.handleOptions()
-
-                    if (finalOptionsToShow.isNotEmpty()) {
-                        optionRepository = optionRepository.byUid().`in`(finalOptionsToShow)
-                    }
-
-                    if (finalOptionsToHide.isNotEmpty()) {
-                        optionRepository = optionRepository.byUid().notIn(finalOptionsToHide)
-                    }
-
                     if (textToSearch.isNotEmpty()) {
                         optionRepository = optionRepository.byDisplayName().like("%$textToSearch%")
                     }
diff --git a/form/src/main/java/org/dhis2/form/data/DataEntryBaseRepository.kt b/form/src/main/java/org/dhis2/form/data/DataEntryBaseRepository.kt
index 83295fea20..1e9d3d3a74 100644
--- a/form/src/main/java/org/dhis2/form/data/DataEntryBaseRepository.kt
+++ b/form/src/main/java/org/dhis2/form/data/DataEntryBaseRepository.kt
@@ -1,18 +1,35 @@
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package org.dhis2.form.data
 
+import androidx.compose.ui.graphics.Color
+import androidx.paging.PagingData
+import androidx.paging.map
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import org.dhis2.commons.resources.MetadataIconProvider
 import org.dhis2.form.data.metadata.FormBaseConfiguration
 import org.dhis2.form.model.FieldUiModel
+import org.dhis2.form.model.OptionSetConfiguration
 import org.dhis2.form.model.SectionUiModelImpl
 import org.dhis2.form.ui.FieldViewModelFactory
 import org.hisp.dhis.android.core.imports.TrackerImportConflict
 import org.hisp.dhis.android.core.program.SectionRenderingType
+import timber.log.Timber
 
 abstract class DataEntryBaseRepository(
     private val conf: FormBaseConfiguration,
     private val fieldFactory: FieldViewModelFactory,
+    private val metadataIconProvider: MetadataIconProvider,
 ) : DataEntryRepository {
 
     abstract val programUid: String?
+    abstract val defaultStyleColor: Color
     override fun firstSectionToOpen(): String? {
         return sectionUids().blockingFirst().firstOrNull()
     }
@@ -41,26 +58,7 @@ abstract class DataEntryBaseRepository(
         optionGroupsToHide: List<String>,
         optionGroupsToShow: List<String>,
     ): FieldUiModel {
-        val optionsInGroupsToHide = optionsFromGroups(optionGroupsToHide)
-        val optionsInGroupsToShow = optionsFromGroups(optionGroupsToShow)
-
-        val item = when {
-            fieldUiModel.optionSet != null -> {
-                fieldUiModel.apply {
-                    this.optionSetConfiguration =
-                        optionSetConfiguration?.updateOptionsToHideAndShow(
-                            optionsToHide = listOf(optionsToHide, optionsInGroupsToHide).flatten(),
-                            optionsToShow = optionsInGroupsToShow,
-                        )
-                }
-            }
-
-            else -> {
-                fieldUiModel
-            }
-        }
-
-        return warningMessage?.let { item.setError(it) } ?: item
+        return warningMessage?.let { fieldUiModel.setError(it) } ?: fieldUiModel
     }
 
     private fun optionsFromGroups(optionGroupUids: List<String>): List<String> {
@@ -77,6 +75,38 @@ abstract class DataEntryBaseRepository(
         return optionsFromGroups
     }
 
+    override fun options(
+        optionSetUid: String,
+        optionsToHide: List<String>,
+        optionGroupsToHide: List<String>,
+        optionGroupsToShow: List<String>,
+    ): Pair<MutableStateFlow<String>, Flow<PagingData<OptionSetConfiguration.OptionData>>> {
+        val searchFlow = MutableStateFlow("")
+        return Pair(
+            searchFlow,
+            searchFlow.debounce(300)
+                .flatMapLatest { query ->
+                    conf.options(
+                        optionSetUid,
+                        query,
+                        optionsToHide,
+                        optionGroupsToHide,
+                        optionGroupsToShow,
+                    ).map { pagingData ->
+                        pagingData.map { option ->
+                            OptionSetConfiguration.OptionData(
+                                option,
+                                metadataIconProvider(option.style(), defaultStyleColor),
+                            )
+                        }
+                    }
+                }
+                .catch {
+                    Timber.e(it)
+                },
+        )
+    }
+
     override fun dateFormatConfiguration(): String? {
         return conf.dateFormatConfiguration()
     }
diff --git a/form/src/main/java/org/dhis2/form/data/DataEntryRepository.kt b/form/src/main/java/org/dhis2/form/data/DataEntryRepository.kt
index 4d54b1e408..5572dfe725 100644
--- a/form/src/main/java/org/dhis2/form/data/DataEntryRepository.kt
+++ b/form/src/main/java/org/dhis2/form/data/DataEntryRepository.kt
@@ -1,8 +1,12 @@
 package org.dhis2.form.data
 
+import androidx.paging.PagingData
 import io.reactivex.Flowable
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import org.dhis2.form.model.EventMode
 import org.dhis2.form.model.FieldUiModel
+import org.dhis2.form.model.OptionSetConfiguration
 import org.hisp.dhis.android.core.common.ValidationStrategy
 
 interface DataEntryRepository {
@@ -37,4 +41,11 @@ interface DataEntryRepository {
     fun disableCollapsableSections(): Boolean?
 
     fun getSpecificDataEntryItems(uid: String): List<FieldUiModel>
+
+    fun options(
+        optionSetUid: String,
+        optionsToHide: List<String>,
+        optionGroupsToHide: List<String>,
+        optionGroupsToShow: List<String>,
+    ): Pair<MutableStateFlow<String>, Flow<PagingData<OptionSetConfiguration.OptionData>>>
 }
diff --git a/form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt b/form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt
index 8e37fe7ed1..1cad5159f9 100644
--- a/form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt
+++ b/form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt
@@ -4,6 +4,7 @@ import io.reactivex.Flowable
 import io.reactivex.Single
 import org.dhis2.commons.date.DateUtils
 import org.dhis2.commons.orgunitselector.OrgUnitSelectorScope
+import org.dhis2.commons.resources.MetadataIconProvider
 import org.dhis2.form.data.metadata.EnrollmentConfiguration
 import org.dhis2.form.model.EnrollmentMode
 import org.dhis2.form.model.EventMode
@@ -14,6 +15,7 @@ import org.dhis2.form.ui.FieldViewModelFactory
 import org.dhis2.form.ui.provider.EnrollmentFormLabelsProvider
 import org.dhis2.form.ui.provider.inputfield.DEFAULT_MAX_DATE
 import org.dhis2.form.ui.provider.inputfield.DEFAULT_MIN_DATE
+import org.dhis2.ui.toColor
 import org.hisp.dhis.android.core.arch.helpers.UidsHelper.getUidsList
 import org.hisp.dhis.android.core.common.FeatureType
 import org.hisp.dhis.android.core.common.ObjectStyle
@@ -25,6 +27,7 @@ import org.hisp.dhis.android.core.program.ProgramTrackedEntityAttribute
 import org.hisp.dhis.android.core.program.SectionRenderingType
 import org.hisp.dhis.android.core.trackedentity.TrackedEntityAttribute
 import org.hisp.dhis.mobile.ui.designsystem.component.SelectableDates
+import org.hisp.dhis.mobile.ui.designsystem.theme.SurfaceColor
 import timber.log.Timber
 import java.util.Date
 
@@ -33,7 +36,8 @@ class EnrollmentRepository(
     private val conf: EnrollmentConfiguration,
     private val enrollmentMode: EnrollmentMode,
     private val enrollmentFormLabelsProvider: EnrollmentFormLabelsProvider,
-) : DataEntryBaseRepository(conf, fieldFactory) {
+    metadataIconProvider: MetadataIconProvider,
+) : DataEntryBaseRepository(conf, fieldFactory, metadataIconProvider) {
 
     override val programUid by lazy {
         conf.program()?.uid()
@@ -43,17 +47,23 @@ class EnrollmentRepository(
         conf.sections()
     }
 
+    override val defaultStyleColor by lazy {
+        conf.program()?.style()?.color()?.toColor() ?: SurfaceColor.Primary
+    }
+
     private fun canBeEdited(): Boolean {
         val selectedProgram = conf.program()
         val programAccess = selectedProgram?.access()?.data()?.write() == true
         val teTypeAccess = conf.trackedEntityType()?.access()?.data()?.write() == true
         return programAccess && teTypeAccess
     }
+
     override fun getSpecificDataEntryItems(uid: String): List<FieldUiModel> {
         return when (uid) {
             ORG_UNIT_UID -> {
                 getEnrollmentData()
             }
+
             else -> {
                 emptyList()
             }
@@ -166,7 +176,17 @@ class EnrollmentRepository(
 
         var optionSetConfig: OptionSetConfiguration? = null
         if (!optionSet.isNullOrEmpty()) {
-            optionSetConfig = conf.optionSetConfig(optionSet)
+            val (searchEmitter, optionFlow) = options(
+                optionSetUid = optionSet,
+                optionsToHide = emptyList(),
+                optionGroupsToHide = emptyList(),
+                optionGroupsToShow = emptyList(),
+            )
+            optionSetConfig = OptionSetConfiguration(
+                searchEmitter = searchEmitter,
+                optionFlow = optionFlow,
+                onSearch = { searchEmitter.value = it },
+            )
         }
 
         var (error, warning) = getConflictErrorsAndWarnings(attribute.uid(), dataValue)
@@ -288,7 +308,12 @@ class EnrollmentRepository(
 
     private fun getEnrollmentData(): MutableList<FieldUiModel> {
         val enrollmentDataList = ArrayList<FieldUiModel>()
-        enrollmentDataList.add(getEnrollmentDataSection(conf.program()?.displayName(), conf.program()?.description()))
+        enrollmentDataList.add(
+            getEnrollmentDataSection(
+                conf.program()?.displayName(),
+                conf.program()?.description(),
+            ),
+        )
 
         enrollmentDataList.add(
             getEnrollmentDateField(
@@ -342,12 +367,14 @@ class EnrollmentRepository(
             (selectedOrgUnit?.closedDate() == null && java.lang.Boolean.FALSE == selectedProgram?.selectEnrollmentDatesInFuture()) -> {
                 maxDate = Date(System.currentTimeMillis())
             }
+
             (selectedOrgUnit?.closedDate() != null && java.lang.Boolean.FALSE == selectedProgram?.selectEnrollmentDatesInFuture()) -> {
-                maxDate = if (selectedOrgUnit.closedDate()!!.before(Date(System.currentTimeMillis()))) {
-                    selectedOrgUnit.closedDate()
-                } else {
-                    Date(System.currentTimeMillis())
-                }
+                maxDate =
+                    if (selectedOrgUnit.closedDate()!!.before(Date(System.currentTimeMillis()))) {
+                        selectedOrgUnit.closedDate()
+                    } else {
+                        Date(System.currentTimeMillis())
+                    }
             }
 
             (selectedOrgUnit?.closedDate() != null && java.lang.Boolean.TRUE == selectedProgram?.selectEnrollmentDatesInFuture()) -> {
diff --git a/form/src/main/java/org/dhis2/form/data/EventRepository.kt b/form/src/main/java/org/dhis2/form/data/EventRepository.kt
index 05bee4fb1a..c684137015 100644
--- a/form/src/main/java/org/dhis2/form/data/EventRepository.kt
+++ b/form/src/main/java/org/dhis2/form/data/EventRepository.kt
@@ -49,12 +49,12 @@ class EventRepository(
     private val fieldFactory: FieldViewModelFactory,
     private val eventUid: String,
     private val d2: D2,
-    private val metadataIconProvider: MetadataIconProvider,
+    metadataIconProvider: MetadataIconProvider,
     private val resources: ResourceManager,
     private val eventResourcesProvider: EventResourcesProvider,
     private val dateUtils: DateUtils,
     private val eventMode: EventMode,
-) : DataEntryBaseRepository(FormBaseConfiguration(d2), fieldFactory) {
+) : DataEntryBaseRepository(FormBaseConfiguration(d2), fieldFactory, metadataIconProvider) {
 
     private var event = d2.eventModule().events().uid(eventUid).blockingGet()
 
@@ -65,7 +65,7 @@ class EventRepository(
             .blockingGet()
     }
 
-    private val defaultStyleColor by lazy {
+    override val defaultStyleColor by lazy {
         programStage?.program()?.uid()?.let {
             d2.program(it)?.style()?.color()?.toColor()
         } ?: SurfaceColor.Primary
@@ -189,7 +189,8 @@ class EventRepository(
     }
 
     override fun validationStrategy(): ValidationStrategy? {
-        return d2.programModule().programStages().uid(programStage?.uid()).blockingGet()?.validationStrategy()
+        return d2.programModule().programStages().uid(programStage?.uid()).blockingGet()
+            ?.validationStrategy()
     }
 
     private fun getEventDetails(): MutableList<FieldUiModel> {
@@ -440,7 +441,8 @@ class EventRepository(
             .withTrackedEntityType()
             .byUid().eq(programUid)
             .one().blockingGet()?.let { program ->
-                val firstAvailablePeriodDate = getFirstAvailablePeriod(event?.enrollment(), programStage)
+                val firstAvailablePeriodDate =
+                    getFirstAvailablePeriod(event?.enrollment(), programStage)
                 var minDate = dateUtils.expDate(
                     firstAvailablePeriodDate,
                     program.expiryDays() ?: 0,
@@ -477,7 +479,11 @@ class EventRepository(
         }
         val calendar = DateUtils.getInstance().getCalendarByDate(minEventDate)
 
-        return dateUtils.getNextPeriod(programStage?.periodType(), calendar.time ?: event?.eventDate(), if (stageLastDate == null) 0 else 1)
+        return dateUtils.getNextPeriod(
+            programStage?.periodType(),
+            calendar.time ?: event?.eventDate(),
+            if (stageLastDate == null) 0 else 1,
+        )
     }
 
     private fun getStageLastDate(): Date? {
@@ -488,12 +494,14 @@ class EventRepository(
                 .eq(enrollmentUid).byProgramStageUid()
                 .eq(programStageUid)
                 .byDeleted().isFalse
-                .orderByEventDate(RepositoryScope.OrderByDirection.DESC).blockingGet().filter { it.uid() != eventUid }
+                .orderByEventDate(RepositoryScope.OrderByDirection.DESC).blockingGet()
+                .filter { it.uid() != eventUid }
         val scheduleEvents =
             d2.eventModule().events().byEnrollmentUid().eq(enrollmentUid).byProgramStageUid()
                 .eq(programStageUid)
                 .byDeleted().isFalse
-                .orderByDueDate(RepositoryScope.OrderByDirection.DESC).blockingGet().filter { it.uid() != eventUid }
+                .orderByDueDate(RepositoryScope.OrderByDirection.DESC).blockingGet()
+                .filter { it.uid() != eventUid }
 
         var activeDate: Date? = null
         var scheduleDate: Date? = null
@@ -632,21 +640,17 @@ class EventRepository(
                         .byCode()
                         .eq(dataValue).one().blockingGet()?.displayName()
             }
-            val optionCount =
-                d2.optionModule().options().byOptionSetUid().eq(optionSet)
-                    .blockingCount()
-            optionSetConfig = OptionSetConfiguration.config(optionCount) {
-                val options = d2.optionModule().options().byOptionSetUid().eq(optionSet)
-                    .orderBySortOrder(RepositoryScope.OrderByDirection.ASC).blockingGet()
-
-                val metadataIconMap =
-                    options.associate { it.uid() to metadataIconProvider(it.style(), defaultStyleColor) }
-
-                OptionSetConfiguration.OptionConfigData(
-                    options = options,
-                    metadataIconMap = metadataIconMap,
-                )
-            }
+            val (searchEmitter, optionFlow) = options(
+                optionSetUid = optionSet!!,
+                optionsToHide = emptyList(),
+                optionGroupsToHide = emptyList(),
+                optionGroupsToShow = emptyList(),
+            )
+            optionSetConfig = OptionSetConfiguration(
+                searchEmitter = searchEmitter,
+                optionFlow = optionFlow,
+                onSearch = { searchEmitter.value = it },
+            )
         }
         val fieldRendering = getValueTypeDeviceRendering(programStageDataElement)
         val objectStyle = getObjectStyle(de)
diff --git a/form/src/main/java/org/dhis2/form/data/FormRepository.kt b/form/src/main/java/org/dhis2/form/data/FormRepository.kt
index 37203fc693..d3bef68f3d 100644
--- a/form/src/main/java/org/dhis2/form/data/FormRepository.kt
+++ b/form/src/main/java/org/dhis2/form/data/FormRepository.kt
@@ -32,4 +32,5 @@ interface FormRepository {
     fun getListFromPreferences(uid: String): MutableList<String>
     fun saveListToPreferences(uid: String, list: List<String>)
     fun activateEvent()
+    fun fetchOptions(id: String, optionSetUid: String)
 }
diff --git a/form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt b/form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt
index 12bc763b88..ac791f613d 100644
--- a/form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt
+++ b/form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt
@@ -7,6 +7,7 @@ import org.dhis2.commons.prefs.PreferenceProvider
 import org.dhis2.form.data.EnrollmentRepository.Companion.ENROLLMENT_DATE_UID
 import org.dhis2.form.model.ActionType
 import org.dhis2.form.model.FieldUiModel
+import org.dhis2.form.model.OptionSetConfiguration
 import org.dhis2.form.model.RowAction
 import org.dhis2.form.model.SectionUiModelImpl
 import org.dhis2.form.model.StoreResult
@@ -234,48 +235,87 @@ class FormRepositoryImpl(
             (itemsWithErrors.isEmpty() && itemsWithWarning.isEmpty() && mandatoryItemsWithoutValue.isEmpty()) -> {
                 getSuccessfulResult(eventStatus)
             }
+
             (itemsWithErrors.isNotEmpty()) -> {
-                getFieldWithErrorResult(eventStatus, itemsWithErrors, itemsWithWarning, validationStrategy, backPressed)
+                getFieldWithErrorResult(
+                    eventStatus,
+                    itemsWithErrors,
+                    itemsWithWarning,
+                    validationStrategy,
+                    backPressed,
+                )
             }
+
             (mandatoryItemsWithoutValue.isNotEmpty()) -> {
-                getMissingMandatoryResult(eventStatus, itemsWithErrors, itemsWithWarning, validationStrategy, backPressed)
+                getMissingMandatoryResult(
+                    eventStatus,
+                    itemsWithErrors,
+                    itemsWithWarning,
+                    validationStrategy,
+                    backPressed,
+                )
             }
+
             else -> {
                 getFieldWithWarningResult(eventStatus, itemsWithWarning, validationStrategy)
             }
         }
     }
 
-    private fun getFieldWithWarningResult(eventStatus: EventStatus?, itemsWithWarning: List<FieldWithIssue>, validationStrategy: ValidationStrategy?): FieldsWithWarningResult {
+    private fun getFieldWithWarningResult(
+        eventStatus: EventStatus?,
+        itemsWithWarning: List<FieldWithIssue>,
+        validationStrategy: ValidationStrategy?,
+    ): FieldsWithWarningResult {
         return when (eventStatus) {
             EventStatus.ACTIVE -> {
                 FieldsWithWarningResult(
                     fieldUidWarningList = itemsWithWarning,
                     canComplete = ruleEffectsResult?.canComplete ?: true,
                     onCompleteMessage = ruleEffectsResult?.messageOnComplete,
-                    eventResultDetails = EventResultDetails(formValueStore.eventState(), dataEntryRepository.eventMode(), validationStrategy),
+                    eventResultDetails = EventResultDetails(
+                        formValueStore.eventState(),
+                        dataEntryRepository.eventMode(),
+                        validationStrategy,
+                    ),
                 )
             }
+
             EventStatus.COMPLETED -> {
                 FieldsWithWarningResult(
                     fieldUidWarningList = itemsWithWarning,
                     canComplete = false,
                     onCompleteMessage = ruleEffectsResult?.messageOnComplete,
-                    eventResultDetails = EventResultDetails(formValueStore.eventState(), dataEntryRepository.eventMode(), null),
+                    eventResultDetails = EventResultDetails(
+                        formValueStore.eventState(),
+                        dataEntryRepository.eventMode(),
+                        null,
+                    ),
                 )
             }
+
             else -> {
                 FieldsWithWarningResult(
                     fieldUidWarningList = itemsWithWarning,
                     canComplete = ruleEffectsResult?.canComplete ?: false,
                     onCompleteMessage = ruleEffectsResult?.messageOnComplete,
-                    eventResultDetails = EventResultDetails(formValueStore.eventState(), dataEntryRepository.eventMode(), validationStrategy),
+                    eventResultDetails = EventResultDetails(
+                        formValueStore.eventState(),
+                        dataEntryRepository.eventMode(),
+                        validationStrategy,
+                    ),
                 )
             }
         }
     }
 
-    private fun getMissingMandatoryResult(eventStatus: EventStatus?, itemsWithErrors: List<FieldWithIssue>, itemsWithWarning: List<FieldWithIssue>, validationStrategy: ValidationStrategy?, backPressed: Boolean): DataIntegrityCheckResult {
+    private fun getMissingMandatoryResult(
+        eventStatus: EventStatus?,
+        itemsWithErrors: List<FieldWithIssue>,
+        itemsWithWarning: List<FieldWithIssue>,
+        validationStrategy: ValidationStrategy?,
+        backPressed: Boolean,
+    ): DataIntegrityCheckResult {
         return when (eventStatus) {
             EventStatus.ACTIVE -> {
                 MissingMandatoryResult(
@@ -285,10 +325,15 @@ class FormRepositoryImpl(
                     canComplete = ruleEffectsResult?.canComplete ?: true,
                     onCompleteMessage = ruleEffectsResult?.messageOnComplete,
                     allowDiscard = backPressed,
-                    eventResultDetails = EventResultDetails(formValueStore.eventState(), dataEntryRepository.eventMode(), validationStrategy),
+                    eventResultDetails = EventResultDetails(
+                        formValueStore.eventState(),
+                        dataEntryRepository.eventMode(),
+                        validationStrategy,
+                    ),
 
                 )
             }
+
             EventStatus.COMPLETED -> {
                 MissingMandatoryResult(
                     mandatoryFields = mandatoryItemsWithoutValue,
@@ -297,9 +342,14 @@ class FormRepositoryImpl(
                     canComplete = false,
                     onCompleteMessage = ruleEffectsResult?.messageOnComplete,
                     allowDiscard = backPressed,
-                    eventResultDetails = EventResultDetails(formValueStore.eventState(), dataEntryRepository.eventMode(), null),
+                    eventResultDetails = EventResultDetails(
+                        formValueStore.eventState(),
+                        dataEntryRepository.eventMode(),
+                        null,
+                    ),
                 )
             }
+
             else -> {
                 MissingMandatoryResult(
                     mandatoryFields = mandatoryItemsWithoutValue,
@@ -308,7 +358,11 @@ class FormRepositoryImpl(
                     canComplete = ruleEffectsResult?.canComplete ?: false,
                     onCompleteMessage = ruleEffectsResult?.messageOnComplete,
                     allowDiscard = backPressed,
-                    eventResultDetails = EventResultDetails(formValueStore.eventState(), dataEntryRepository.eventMode(), validationStrategy),
+                    eventResultDetails = EventResultDetails(
+                        formValueStore.eventState(),
+                        dataEntryRepository.eventMode(),
+                        validationStrategy,
+                    ),
                 )
             }
         }
@@ -330,9 +384,14 @@ class FormRepositoryImpl(
                     canComplete = ruleEffectsResult?.canComplete ?: true,
                     onCompleteMessage = ruleEffectsResult?.messageOnComplete,
                     allowDiscard = backPressed,
-                    eventResultDetails = EventResultDetails(formValueStore.eventState(), dataEntryRepository.eventMode(), validationStrategy),
+                    eventResultDetails = EventResultDetails(
+                        formValueStore.eventState(),
+                        dataEntryRepository.eventMode(),
+                        validationStrategy,
+                    ),
                 )
             }
+
             EventStatus.COMPLETED -> {
                 FieldsWithErrorResult(
                     mandatoryFields = mandatoryItemsWithoutValue,
@@ -341,9 +400,14 @@ class FormRepositoryImpl(
                     canComplete = false,
                     onCompleteMessage = ruleEffectsResult?.messageOnComplete,
                     allowDiscard = backPressed,
-                    eventResultDetails = EventResultDetails(formValueStore.eventState(), dataEntryRepository.eventMode(), dataEntryRepository.validationStrategy()),
+                    eventResultDetails = EventResultDetails(
+                        formValueStore.eventState(),
+                        dataEntryRepository.eventMode(),
+                        dataEntryRepository.validationStrategy(),
+                    ),
                 )
             }
+
             else -> {
                 FieldsWithErrorResult(
                     mandatoryFields = mandatoryItemsWithoutValue,
@@ -352,7 +416,11 @@ class FormRepositoryImpl(
                     canComplete = ruleEffectsResult?.canComplete ?: false,
                     onCompleteMessage = ruleEffectsResult?.messageOnComplete,
                     allowDiscard = backPressed,
-                    eventResultDetails = EventResultDetails(formValueStore.eventState(), dataEntryRepository.eventMode(), validationStrategy),
+                    eventResultDetails = EventResultDetails(
+                        formValueStore.eventState(),
+                        dataEntryRepository.eventMode(),
+                        validationStrategy,
+                    ),
                 )
             }
         }
@@ -364,25 +432,40 @@ class FormRepositoryImpl(
                 SuccessfulResult(
                     canComplete = ruleEffectsResult?.canComplete ?: true,
                     onCompleteMessage = ruleEffectsResult?.messageOnComplete,
-                    eventResultDetails = EventResultDetails(formValueStore.eventState(), dataEntryRepository.eventMode(), dataEntryRepository.validationStrategy()),
+                    eventResultDetails = EventResultDetails(
+                        formValueStore.eventState(),
+                        dataEntryRepository.eventMode(),
+                        dataEntryRepository.validationStrategy(),
+                    ),
                 )
             }
+
             EventStatus.COMPLETED -> {
                 SuccessfulResult(
                     canComplete = false,
                     onCompleteMessage = ruleEffectsResult?.messageOnComplete,
-                    eventResultDetails = EventResultDetails(formValueStore.eventState(), dataEntryRepository.eventMode(), dataEntryRepository.validationStrategy()),
+                    eventResultDetails = EventResultDetails(
+                        formValueStore.eventState(),
+                        dataEntryRepository.eventMode(),
+                        dataEntryRepository.validationStrategy(),
+                    ),
                 )
             }
+
             else -> {
                 SuccessfulResult(
                     canComplete = ruleEffectsResult?.canComplete ?: false,
                     onCompleteMessage = ruleEffectsResult?.messageOnComplete,
-                    eventResultDetails = EventResultDetails(formValueStore.eventState(), dataEntryRepository.eventMode(), validationStrategy = dataEntryRepository.validationStrategy()),
+                    eventResultDetails = EventResultDetails(
+                        formValueStore.eventState(),
+                        dataEntryRepository.eventMode(),
+                        validationStrategy = dataEntryRepository.validationStrategy(),
+                    ),
                 )
             }
         }
     }
+
     override fun completedFieldsPercentage(value: List<FieldUiModel>): Float {
         return completionPercentage
     }
@@ -438,6 +521,13 @@ class FormRepositoryImpl(
                     updateValueOnList(field.uid, fieldWithNewValue.newValue, field.valueType)
                 }
             }
+
+        ruleEffectsResult?.fieldsWithOptionEffects()?.forEach { fieldWithOptionEffect ->
+            itemList.find { it.uid == fieldWithOptionEffect }?.let {
+                it.optionSet?.let { optionSetUid -> fetchOptions(it.uid, optionSetUid) }
+            }
+        }
+
         return if (ruleEffectsResult?.fieldsToUpdate?.isNotEmpty() == true &&
             calculationLoop < loopThreshold
         ) {
@@ -710,6 +800,35 @@ class FormRepositoryImpl(
         }
     }
 
+    override fun fetchOptions(uid: String, optionSetUid: String) {
+        val (searchEmitter, flow) = dataEntryRepository.options(
+            optionSetUid = optionSetUid,
+            optionsToHide = ruleEffectsResult?.optionsToHide(uid) ?: emptyList(),
+            optionGroupsToHide = ruleEffectsResult?.optionGroupsToHide(uid) ?: emptyList(),
+            optionGroupsToShow = ruleEffectsResult?.optionGroupsToShow(uid) ?: emptyList(),
+        )
+
+        val newConf = OptionSetConfiguration(
+            searchEmitter = searchEmitter,
+            onSearch = { query ->
+                searchEmitter.value = query
+            },
+            optionFlow = flow,
+        )
+
+        itemList.let { list ->
+            list.find { item ->
+                item.uid == uid
+            }?.let { item ->
+                item.optionSetConfiguration = newConf
+                itemList = list.updated(
+                    list.indexOf(item),
+                    item,
+                )
+            }
+        }
+    }
+
     override fun clearFocusItem() {
         focusedItemId = null
     }
diff --git a/form/src/main/java/org/dhis2/form/data/GeometryController.kt b/form/src/main/java/org/dhis2/form/data/GeometryController.kt
index 584abe82c3..66c87da794 100644
--- a/form/src/main/java/org/dhis2/form/data/GeometryController.kt
+++ b/form/src/main/java/org/dhis2/form/data/GeometryController.kt
@@ -50,7 +50,6 @@ class GeometryController(private val geometryParser: GeometryParser) {
 
             override fun recyclerViewUiEvents(uiEvent: RecyclerViewUiEvents) {
                 when (uiEvent) {
-                    is RecyclerViewUiEvents.RequestCurrentLocation -> currentLocation(uiEvent.uid)
                     is RecyclerViewUiEvents.RequestLocationByMap -> mapRequest(
                         uiEvent.uid,
                         uiEvent.featureType.name,
diff --git a/form/src/main/java/org/dhis2/form/data/RuleUtilsProviderResult.kt b/form/src/main/java/org/dhis2/form/data/RuleUtilsProviderResult.kt
index 584c2e1498..d000d055e7 100644
--- a/form/src/main/java/org/dhis2/form/data/RuleUtilsProviderResult.kt
+++ b/form/src/main/java/org/dhis2/form/data/RuleUtilsProviderResult.kt
@@ -49,6 +49,9 @@ data class RuleUtilsProviderResult(
     fun optionGroupsToShow(fieldUid: String): List<String> {
         return optionGroupsToShow[fieldUid] ?: mutableListOf()
     }
+
+    fun fieldsWithOptionEffects(): List<String> =
+        (optionsToHide.keys + optionGroupsToHide.keys + optionGroupsToShow.keys).toList()
 }
 
 data class FieldWithNewValue(val fieldUid: String, val newValue: String?)
diff --git a/form/src/main/java/org/dhis2/form/data/metadata/EnrollmentConfiguration.kt b/form/src/main/java/org/dhis2/form/data/metadata/EnrollmentConfiguration.kt
index fded07c53b..cf0afbd5c2 100644
--- a/form/src/main/java/org/dhis2/form/data/metadata/EnrollmentConfiguration.kt
+++ b/form/src/main/java/org/dhis2/form/data/metadata/EnrollmentConfiguration.kt
@@ -7,19 +7,14 @@ import org.dhis2.commons.bindings.program
 import org.dhis2.commons.bindings.tei
 import org.dhis2.commons.bindings.teiAttribute
 import org.dhis2.commons.bindings.trackedEntityType
-import org.dhis2.commons.resources.MetadataIconProvider
-import org.dhis2.form.model.OptionSetConfiguration
-import org.dhis2.ui.toColor
 import org.hisp.dhis.android.core.D2
 import org.hisp.dhis.android.core.arch.repositories.scope.RepositoryScope
 import org.hisp.dhis.android.core.enrollment.Enrollment
 import org.hisp.dhis.android.core.organisationunit.OrganisationUnit
-import org.hisp.dhis.mobile.ui.designsystem.theme.SurfaceColor
 
 class EnrollmentConfiguration(
     private val d2: D2,
     private val enrollmentUid: String,
-    private val metadataIconProvider: MetadataIconProvider,
 ) :
     FormBaseConfiguration(d2) {
     private val _enrollment: Enrollment? by lazy {
@@ -32,10 +27,6 @@ class EnrollmentConfiguration(
         d2.program(it)
     }
 
-    private val defaultStyleColor by lazy {
-        program()?.style()?.color()?.toColor() ?: SurfaceColor.Primary
-    }
-
     fun tei() = enrollment()?.trackedEntityInstance()?.let { d2.tei(it) }
     fun trackedEntityType() = d2.trackedEntityType(program()?.trackedEntityType()?.uid()!!)
     fun sections() = d2.programModule().programSections()
@@ -43,7 +34,8 @@ class EnrollmentConfiguration(
         .byProgramUid().eq(enrollment()?.program())
         .blockingGet()
 
-    fun orgUnit(orgUnitUid: String) = d2.organisationUnitModule().organisationUnits().uid(orgUnitUid).blockingGet()
+    fun orgUnit(orgUnitUid: String) =
+        d2.organisationUnitModule().organisationUnits().uid(orgUnitUid).blockingGet()
 
     fun programAttributes() =
         d2.programModule().programTrackedEntityAttributes()
@@ -122,22 +114,4 @@ class EnrollmentConfiguration(
     fun getValue(attributeUid: String) = d2.trackedEntityModule().trackedEntityAttributeValues()
         .value(attributeUid, tei()?.uid()!!)
         .blockingGet()
-
-    fun optionSetConfig(optionSetUid: String) =
-        d2.optionModule().options().byOptionSetUid().eq(optionSetUid).blockingCount()
-            .let { optionCount ->
-                OptionSetConfiguration.config(optionCount) {
-                    val options = d2.optionModule().options()
-                        .byOptionSetUid().eq(optionSetUid)
-                        .orderBySortOrder(RepositoryScope.OrderByDirection.ASC)
-                        .blockingGet()
-
-                    val metadataIconMap = options.associate { it.uid() to metadataIconProvider(it.style(), defaultStyleColor) }
-
-                    OptionSetConfiguration.OptionConfigData(
-                        options = options,
-                        metadataIconMap = metadataIconMap,
-                    )
-                }
-            }
 }
diff --git a/form/src/main/java/org/dhis2/form/data/metadata/FormBaseConfiguration.kt b/form/src/main/java/org/dhis2/form/data/metadata/FormBaseConfiguration.kt
index 9800966246..511e379d38 100644
--- a/form/src/main/java/org/dhis2/form/data/metadata/FormBaseConfiguration.kt
+++ b/form/src/main/java/org/dhis2/form/data/metadata/FormBaseConfiguration.kt
@@ -1,7 +1,12 @@
 package org.dhis2.form.data.metadata
 
+import androidx.paging.PagingData
+import androidx.paging.filter
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
 import org.dhis2.commons.bindings.disableCollapsableSectionsInProgram
 import org.hisp.dhis.android.core.D2
+import org.hisp.dhis.android.core.option.Option
 
 open class FormBaseConfiguration(private val d2: D2) {
     fun optionGroups(optionGroupUids: List<String>) = d2.optionModule().optionGroups()
@@ -14,4 +19,35 @@ open class FormBaseConfiguration(private val d2: D2) {
 
     fun dateFormatConfiguration() =
         d2.systemInfoModule().systemInfo().blockingGet()?.dateFormat()
+
+    fun options(
+        optionSetUid: String,
+        query: String,
+        optionsToHide: List<String>,
+        optionGroupsToHide: List<String>,
+        optionGroupsToShow: List<String>,
+    ): Flow<PagingData<Option>> {
+        return when {
+            query.isEmpty() -> d2.optionModule()
+                .options()
+                .byOptionSetUid().eq(optionSetUid)
+                .getPagingData(10)
+
+            else ->
+                d2.optionModule()
+                    .options()
+                    .byOptionSetUid().eq(optionSetUid)
+                    .byDisplayName().like("%$query%")
+                    .getPagingData(10)
+        }.map { pagingData ->
+            pagingData.filter { option ->
+                !optionsToHide.contains(option.uid()) &&
+                    !optionGroupsToHide.contains(option.uid()) &&
+                    (
+                        optionGroupsToShow.isEmpty() ||
+                            optionGroupsToShow.contains(option.uid())
+                        )
+            }
+        }
+    }
 }
diff --git a/form/src/main/java/org/dhis2/form/di/Injector.kt b/form/src/main/java/org/dhis2/form/di/Injector.kt
index 2278271089..5055ab1035 100644
--- a/form/src/main/java/org/dhis2/form/di/Injector.kt
+++ b/form/src/main/java/org/dhis2/form/di/Injector.kt
@@ -141,10 +141,10 @@ object Injector {
             conf = EnrollmentConfiguration(
                 provideD2(),
                 enrollmentRecords.enrollmentUid,
-                metadataIconProvider,
             ),
             enrollmentMode = enrollmentRecords.enrollmentMode,
             enrollmentFormLabelsProvider = provideEnrollmentFormLabelsProvider(context),
+            metadataIconProvider = metadataIconProvider,
         )
     }
 
diff --git a/form/src/main/java/org/dhis2/form/model/ActionType.kt b/form/src/main/java/org/dhis2/form/model/ActionType.kt
index 36d127010e..72529d5cb3 100644
--- a/form/src/main/java/org/dhis2/form/model/ActionType.kt
+++ b/form/src/main/java/org/dhis2/form/model/ActionType.kt
@@ -11,4 +11,5 @@ enum class ActionType {
     ON_CANCEL_REQUEST_COORDINATES,
     ON_STORE_FILE,
     ON_ADD_IMAGE_FINISHED,
+    ON_FETCH_OPTIONS,
 }
diff --git a/form/src/main/java/org/dhis2/form/model/FieldUiModelImpl.kt b/form/src/main/java/org/dhis2/form/model/FieldUiModelImpl.kt
index 46fa01a3e0..be2a049e7f 100644
--- a/form/src/main/java/org/dhis2/form/model/FieldUiModelImpl.kt
+++ b/form/src/main/java/org/dhis2/form/model/FieldUiModelImpl.kt
@@ -61,13 +61,7 @@ data class FieldUiModelImpl(
 
     override fun invokeUiEvent(uiEventType: UiEventType) {
         callback?.intent(FormIntent.OnRequestCoordinates(uid))
-        if (uiEventType != UiEventType.QR_CODE &&
-            uiEventType != UiEventType.EMAIL &&
-            uiEventType != UiEventType.PHONE_NUMBER &&
-            !focused
-        ) {
-            onItemClick()
-        }
+
         uiEventFactory?.generateEvent(value, uiEventType, renderingType, this)?.let {
             callback?.recyclerViewUiEvents(it)
         }
diff --git a/form/src/main/java/org/dhis2/form/model/OptionSetConfiguration.kt b/form/src/main/java/org/dhis2/form/model/OptionSetConfiguration.kt
index 8b632b290a..54d3776403 100644
--- a/form/src/main/java/org/dhis2/form/model/OptionSetConfiguration.kt
+++ b/form/src/main/java/org/dhis2/form/model/OptionSetConfiguration.kt
@@ -1,93 +1,35 @@
 package org.dhis2.form.model
 
+import androidx.paging.PagingData
+import androidx.paging.map
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
 import org.dhis2.ui.MetadataIconData
 import org.hisp.dhis.android.core.option.Option
 
-sealed class OptionSetConfiguration(
-    open val options: List<Option> = emptyList(),
-    open val optionsToHide: List<String>,
-    open val optionsToShow: List<String>,
-    open val optionMetadataIcon: Map<String, MetadataIconData>,
+data class OptionSetConfiguration(
+    val searchEmitter: StateFlow<String>? = null,
+    val onSearch: (String) -> Unit,
+    val optionFlow: Flow<PagingData<OptionData>>,
 ) {
-    data class DefaultOptionSet(
-        override val options: List<Option>,
-        override val optionsToHide: List<String> = emptyList(),
-        override val optionsToShow: List<String> = emptyList(),
-        override val optionMetadataIcon: Map<String, MetadataIconData>,
-    ) : OptionSetConfiguration(
-        options = options,
-        optionsToHide = optionsToHide,
-        optionsToShow = optionsToShow,
-        optionMetadataIcon = optionMetadataIcon,
-    )
-
-    data class BigOptionSet(
-        override val options: List<Option>,
-        override val optionsToHide: List<String> = emptyList(),
-        override val optionsToShow: List<String> = emptyList(),
-        override val optionMetadataIcon: Map<String, MetadataIconData>,
-    ) : OptionSetConfiguration(
-        options = options,
-        optionsToHide = optionsToHide,
-        optionsToShow = optionsToShow,
-        optionMetadataIcon = optionMetadataIcon,
-    )
-
-    fun optionsToDisplay() = options.filter { option ->
-        when {
-            optionsToShow.isNotEmpty() ->
-                optionsToShow.contains(option.uid())
-
-            else ->
-                !optionsToHide.contains(option.uid())
-        }
-    }.sortedBy { it.sortOrder() }
-
     companion object {
-        fun config(
-            optionCount: Int,
-            optionRequestCallback: () -> OptionConfigData,
-        ): OptionSetConfiguration {
-            return when {
-                optionCount > 15 -> with(optionRequestCallback()) {
-                    BigOptionSet(
-                        options = options,
-                        optionMetadataIcon = metadataIconMap,
-                    )
-                }
-                else -> with(optionRequestCallback()) {
-                    DefaultOptionSet(
-                        options = options,
-                        optionMetadataIcon = metadataIconMap,
+        fun optionDataFlow(
+            flow: Flow<PagingData<Option>>,
+            fetchMetadataIconData: (option: Option) -> MetadataIconData,
+        ) =
+            flow.map { pagingData ->
+                pagingData.map { option ->
+                    OptionData(
+                        option = option,
+                        metadataIconData = fetchMetadataIconData(option),
                     )
                 }
             }
-        }
     }
 
-    data class OptionConfigData(
-        val options: List<Option>,
-        val metadataIconMap: Map<String, MetadataIconData>,
+    data class OptionData(
+        val option: Option,
+        val metadataIconData: MetadataIconData,
     )
-
-    fun updateOptionsToHideAndShow(
-        optionsToHide: List<String>,
-        optionsToShow: List<String>,
-    ): OptionSetConfiguration {
-        return setOptionsToShow(optionsToShow).setOptionsToHide(optionsToHide)
-    }
-
-    private fun setOptionsToHide(optionsToHide: List<String>): OptionSetConfiguration {
-        return when (this) {
-            is BigOptionSet -> copy(optionsToHide = optionsToHide)
-            is DefaultOptionSet -> copy(optionsToHide = optionsToHide)
-        }
-    }
-
-    private fun setOptionsToShow(optionsToShow: List<String>): OptionSetConfiguration {
-        return when (this) {
-            is BigOptionSet -> copy(optionsToShow = optionsToShow)
-            is DefaultOptionSet -> copy(optionsToShow = optionsToShow)
-        }
-    }
 }
diff --git a/form/src/main/java/org/dhis2/form/model/OptionSetDialogViewModel.kt b/form/src/main/java/org/dhis2/form/model/OptionSetDialogViewModel.kt
index 6b884b7130..0fca9cac59 100644
--- a/form/src/main/java/org/dhis2/form/model/OptionSetDialogViewModel.kt
+++ b/form/src/main/java/org/dhis2/form/model/OptionSetDialogViewModel.kt
@@ -37,8 +37,8 @@ class OptionSetDialogViewModel(
             searchOptionSetOption(
                 field.optionSet,
                 textToSearch,
-                field.optionSetConfiguration?.optionsToShow ?: emptyList(),
-                field.optionSetConfiguration?.optionsToHide ?: emptyList(),
+                emptyList(),
+                emptyList(),
             )
         }
     }
diff --git a/form/src/main/java/org/dhis2/form/model/UiEventType.kt b/form/src/main/java/org/dhis2/form/model/UiEventType.kt
index 5e7a85142e..81eb03f23b 100644
--- a/form/src/main/java/org/dhis2/form/model/UiEventType.kt
+++ b/form/src/main/java/org/dhis2/form/model/UiEventType.kt
@@ -1,21 +1,9 @@
 package org.dhis2.form.model
 
 enum class UiEventType {
-    DATE_TIME,
-    AGE_CALENDAR,
-    ORG_UNIT,
-    REQUEST_CURRENT_LOCATION,
     REQUEST_LOCATION_BY_MAP,
     ADD_PICTURE,
-    SHOW_PICTURE,
-    SHOW_DESCRIPTION,
-    COPY_TO_CLIPBOARD,
-    QR_CODE,
-    OPTION_SET,
-    ADD_SIGNATURE,
     ADD_FILE,
     OPEN_FILE,
-    EMAIL,
-    PHONE_NUMBER,
     SHARE_IMAGE,
 }
diff --git a/form/src/main/java/org/dhis2/form/ui/FormView.kt b/form/src/main/java/org/dhis2/form/ui/FormView.kt
index bb1d1e70f1..83388f2477 100644
--- a/form/src/main/java/org/dhis2/form/ui/FormView.kt
+++ b/form/src/main/java/org/dhis2/form/ui/FormView.kt
@@ -3,22 +3,16 @@ package org.dhis2.form.ui
 import android.Manifest
 import android.app.Activity.RESULT_OK
 import android.content.ActivityNotFoundException
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.Context
 import android.content.DialogInterface
 import android.content.Intent
 import android.content.pm.PackageManager
-import android.graphics.Bitmap
 import android.net.Uri
 import android.os.Build
 import android.os.Bundle
-import android.text.format.DateFormat
 import android.view.ContextThemeWrapper
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import android.widget.DatePicker
 import android.widget.Toast
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.annotation.RequiresApi
@@ -32,9 +26,6 @@ import androidx.fragment.app.Fragment
 import androidx.fragment.app.FragmentManager
 import androidx.fragment.app.viewModels
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.timepicker.MaterialTimePicker
-import com.google.android.material.timepicker.TimeFormat.CLOCK_12H
-import com.google.android.material.timepicker.TimeFormat.CLOCK_24H
 import com.journeyapps.barcodescanner.ScanOptions
 import org.dhis2.commons.ActivityResultObservable
 import org.dhis2.commons.ActivityResultObserver
@@ -48,14 +39,9 @@ import org.dhis2.commons.date.DateUtils
 import org.dhis2.commons.dialogs.AlertBottomDialog
 import org.dhis2.commons.dialogs.CustomDialog
 import org.dhis2.commons.dialogs.PeriodDialog
-import org.dhis2.commons.dialogs.calendarpicker.CalendarPicker
-import org.dhis2.commons.dialogs.calendarpicker.OnDatePickerListener
-import org.dhis2.commons.dialogs.imagedetail.ImageDetailActivity
 import org.dhis2.commons.extensions.closeKeyboard
 import org.dhis2.commons.extensions.serializable
-import org.dhis2.commons.extensions.truncate
 import org.dhis2.commons.locationprovider.LocationProvider
-import org.dhis2.commons.locationprovider.LocationSettingLauncher
 import org.dhis2.commons.orgunitselector.OUTreeFragment
 import org.dhis2.commons.orgunitselector.OrgUnitSelectorScope
 import org.dhis2.form.R
@@ -75,9 +61,7 @@ import org.dhis2.form.model.InfoUiModel
 import org.dhis2.form.model.RowAction
 import org.dhis2.form.model.UiRenderType
 import org.dhis2.form.model.exception.RepositoryRecordsException
-import org.dhis2.form.ui.dialog.OptionSetDialog
 import org.dhis2.form.ui.dialog.QRDetailBottomDialog
-import org.dhis2.form.ui.event.DialogDelegate
 import org.dhis2.form.ui.event.RecyclerViewUiEvents
 import org.dhis2.form.ui.idling.FormCountingIdlingResource
 import org.dhis2.form.ui.intent.FormIntent
@@ -92,16 +76,12 @@ import org.dhis2.ui.dialogs.bottomsheet.BottomSheetDialog
 import org.dhis2.ui.dialogs.bottomsheet.BottomSheetDialogUiModel
 import org.dhis2.ui.dialogs.bottomsheet.FieldWithIssue
 import org.dhis2.ui.dialogs.bottomsheet.IssueType
-import org.dhis2.ui.dialogs.signature.SignatureDialog
 import org.hisp.dhis.android.core.arch.helpers.FileResourceDirectoryHelper
-import org.hisp.dhis.android.core.arch.helpers.GeometryHelper
-import org.hisp.dhis.android.core.common.FeatureType
 import org.hisp.dhis.android.core.common.ValueType
 import org.hisp.dhis.android.core.common.ValueTypeRenderingType
 import org.hisp.dhis.android.core.event.EventStatus
 import timber.log.Timber
 import java.io.File
-import java.util.Calendar
 import java.util.Date
 
 class FormView : Fragment() {
@@ -236,50 +216,6 @@ class FormView : Fragment() {
             }
         }
 
-    private val requestLocationPermissions =
-        registerForActivityResult(
-            ActivityResultContracts.RequestMultiplePermissions(),
-        ) { result ->
-            if (result.values.all { isGranted -> isGranted }) {
-                viewModel.getFocusedItemUid()?.let {
-                    requestCurrentLocation(RecyclerViewUiEvents.RequestCurrentLocation(it))
-                }
-            } else {
-                displayCoordinatesPermissionDeclined()
-            }
-        }
-
-    private val permissionSettings =
-        registerForActivityResult(
-            ActivityResultContracts.StartActivityForResult(),
-        ) {
-            val result = requireActivity().checkCallingOrSelfPermission(
-                Manifest.permission.ACCESS_FINE_LOCATION,
-            )
-            if (result == PackageManager.PERMISSION_GRANTED) {
-                viewModel.getFocusedItemUid()?.let {
-                    requestCurrentLocation(RecyclerViewUiEvents.RequestCurrentLocation(it))
-                }
-            } else {
-                viewModel.getFocusedItemUid()?.let {
-                    viewModel.submitIntent(FormIntent.OnCancelRequestCoordinates(it))
-                }
-            }
-        }
-
-    private val locationDisabledSettings =
-        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
-            if (locationProvider?.hasLocationEnabled() == true) {
-                viewModel.getFocusedItemUid()?.let {
-                    requestCurrentLocation(RecyclerViewUiEvents.RequestCurrentLocation(it))
-                }
-            } else {
-                viewModel.getFocusedItemUid()?.let {
-                    viewModel.submitIntent(FormIntent.OnCancelRequestCoordinates(it))
-                }
-            }
-        }
-
     private val requestStoragePermissions =
         registerForActivityResult(
             ActivityResultContracts.RequestPermission(),
@@ -306,7 +242,6 @@ class FormView : Fragment() {
         )
     }
 
-    private lateinit var dialogDelegate: DialogDelegate
     private lateinit var formSectionMapper: FormSectionMapper
     var scrollCallback: ((Boolean) -> Unit)? = null
     private var displayConfErrors = true
@@ -334,7 +269,6 @@ class FormView : Fragment() {
         savedInstanceState: Bundle?,
     ): View {
         val contextWrapper = ContextThemeWrapper(context, R.style.searchFormInputText)
-        dialogDelegate = DialogDelegate()
         formSectionMapper = FormSectionMapper()
 
         FormFileProvider.init(contextWrapper.applicationContext)
@@ -462,7 +396,10 @@ class FormView : Fragment() {
     }
 
     @Composable
-    private fun DialogContent(fieldsWithIssues: List<FieldWithIssue>, bottomSheetDialog: BottomSheetDialog): Unit? {
+    private fun DialogContent(
+        fieldsWithIssues: List<FieldWithIssue>,
+        bottomSheetDialog: BottomSheetDialog,
+    ): Unit? {
         return if (fieldsWithIssues.isEmpty()) {
             null
         } else {
@@ -509,12 +446,14 @@ class FormView : Fragment() {
                 } else {
                     showBottomSheetDialog()
                 }
+
                 EventStatus.SKIPPED -> {
                     if (fieldsWithIssues.isEmpty()) {
                         viewModel.activateEvent()
                     }
                     showBottomSheetDialog()
                 }
+
                 else -> onFinishDataEntry?.invoke()
             }
             if (result.eventResultDetails.eventStatus == null && result is NotSavedResult) {
@@ -528,7 +467,8 @@ class FormView : Fragment() {
         isEventCompleted: Boolean,
         bottomSheetDialog: BottomSheetDialog,
     ) {
-        val errorsInField = fieldsWithIssues.isNotEmpty() || fieldsWithIssues.any { it.issueType == IssueType.ERROR }
+        val errorsInField =
+            fieldsWithIssues.isNotEmpty() || fieldsWithIssues.any { it.issueType == IssueType.ERROR }
         if (errorsInField) {
             bottomSheetDialog.dismiss()
         } else if (isEventCompleted) {
@@ -563,6 +503,7 @@ class FormView : Fragment() {
                     result = result,
                 )
             }
+
             is FieldsWithWarningResult -> formResultDialogUiProvider?.invoke(
                 canComplete = result.canComplete,
                 onCompleteMessage = result.onCompleteMessage,
@@ -609,42 +550,13 @@ class FormView : Fragment() {
             .show()
     }
 
-    private fun displayCoordinatesPermissionDeclined() {
-        MaterialAlertDialogBuilder(requireContext(), R.style.DhisMaterialDialog)
-            .setTitle(getString(R.string.info))
-            .setMessage(getString(R.string.location_permission_denied))
-            .setPositiveButton(R.string.action_accept) { _, _ ->
-                val intent = Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
-                intent.data = Uri.fromParts("package", requireActivity().packageName, null)
-                permissionSettings.launch(intent)
-            }
-            .setNegativeButton(R.string.action_close) { _, _ ->
-                viewModel.getFocusedItemUid()?.let {
-                    viewModel.submitIntent(FormIntent.OnCancelRequestCoordinates(it))
-                }
-            }
-            .setCancelable(false)
-            .show()
-    }
-
     private fun uiEventHandler(uiEvent: RecyclerViewUiEvents) {
         when (uiEvent) {
-            is RecyclerViewUiEvents.OpenCustomCalendar -> showCustomCalendar(uiEvent)
-            is RecyclerViewUiEvents.OpenTimePicker -> showTimePicker(uiEvent)
-            is RecyclerViewUiEvents.ShowDescriptionLabelDialog -> showDescriptionLabelDialog(
-                uiEvent,
-            )
-
-            is RecyclerViewUiEvents.RequestCurrentLocation -> requestCurrentLocation(uiEvent)
             is RecyclerViewUiEvents.RequestLocationByMap -> requestLocationByMap(uiEvent)
             is RecyclerViewUiEvents.DisplayQRCode -> displayQRImage(uiEvent)
             is RecyclerViewUiEvents.ScanQRCode -> requestQRScan(uiEvent)
             is RecyclerViewUiEvents.OpenOrgUnitDialog -> showOrgUnitDialog(uiEvent)
             is RecyclerViewUiEvents.AddImage -> requestAddImage(uiEvent)
-            is RecyclerViewUiEvents.ShowImage -> showFullPicture(uiEvent)
-            is RecyclerViewUiEvents.CopyToClipboard -> copyToClipboard(uiEvent.value)
-            is RecyclerViewUiEvents.OpenOptionSetDialog -> showOptionSetDialog(uiEvent)
-            is RecyclerViewUiEvents.AddSignature -> showSignatureDialog(uiEvent)
             is RecyclerViewUiEvents.OpenFile -> openFile(uiEvent)
             is RecyclerViewUiEvents.OpenFileSelector -> openFileSelector(uiEvent)
             is RecyclerViewUiEvents.OpenChooserIntent -> openChooserIntent(uiEvent)
@@ -732,22 +644,6 @@ class FormView : Fragment() {
         }
     }
 
-    private fun copyToClipboard(value: String?) {
-        val clipboard =
-            requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
-        value?.let {
-            if (it.isNotEmpty()) {
-                val clip = ClipData.newPlainText("copy", it)
-                clipboard.setPrimaryClip(clip)
-                Toast.makeText(
-                    context,
-                    requireContext().getString(R.string.copied_text),
-                    Toast.LENGTH_SHORT,
-                ).show()
-            }
-        }
-    }
-
     private fun render(items: List<FieldUiModel>) {
         viewModel.calculateCompletedFields()
         viewModel.updateConfigurationErrors()
@@ -762,119 +658,6 @@ class FormView : Fragment() {
         viewModel.submitIntent(intent)
     }
 
-    private fun showCustomCalendar(intent: RecyclerViewUiEvents.OpenCustomCalendar) {
-        val dialog = CalendarPicker(requireContext()).apply {
-            setTitle(intent.label)
-            setInitialDate(intent.date)
-            isFutureDatesAllowed(intent.allowFutureDates)
-            setListener(object : OnDatePickerListener {
-                override fun onNegativeClick() {
-                    intentHandler(FormIntent.ClearValue(intent.uid))
-                }
-
-                override fun onPositiveClick(datePicker: DatePicker) {
-                    when (intent.isDateTime) {
-                        true -> uiEventHandler(
-                            dialogDelegate.handleDateTimeInput(
-                                intent.uid,
-                                intent.label,
-                                intent.date,
-                                datePicker.year,
-                                datePicker.month,
-                                datePicker.dayOfMonth,
-                            ),
-                        )
-
-                        else -> intentHandler(
-                            dialogDelegate.handleDateInput(
-                                intent.uid,
-                                datePicker.year,
-                                datePicker.month,
-                                datePicker.dayOfMonth,
-                            ),
-                        )
-                    }
-                }
-            })
-        }
-        dialog.show()
-    }
-
-    private fun showTimePicker(intent: RecyclerViewUiEvents.OpenTimePicker) {
-        val calendar = Calendar.getInstance()
-        intent.date?.let { calendar.time = it }
-        val is24HourFormat = DateFormat.is24HourFormat(requireContext())
-        MaterialTimePicker.Builder()
-            .setTheme(R.style.TimePicker)
-            .setTimeFormat(CLOCK_24H.takeIf { is24HourFormat } ?: CLOCK_12H)
-            .setHour(calendar[Calendar.HOUR_OF_DAY])
-            .setMinute(calendar[Calendar.MINUTE])
-            .setTitleText(intent.label)
-            .build().apply {
-                addOnPositiveButtonClickListener {
-                    intentHandler(
-                        dialogDelegate.handleTimeInput(
-                            intent.uid,
-                            if (intent.isDateTime == true) intent.date else null,
-                            hour,
-                            minute,
-                        ),
-                    )
-                }
-            }
-            .show(childFragmentManager, "timePicker")
-    }
-
-    private fun showDescriptionLabelDialog(
-        intent: RecyclerViewUiEvents.ShowDescriptionLabelDialog,
-    ) {
-        CustomDialog(
-            requireContext(),
-            intent.title,
-            intent.message ?: requireContext().getString(R.string.empty_description),
-            requireContext().getString(R.string.action_close),
-            null,
-            Constants.DESCRIPTION_DIALOG,
-            null,
-        ).show()
-    }
-
-    private fun requestCurrentLocation(event: RecyclerViewUiEvents.RequestCurrentLocation) {
-        locationProvider?.getLastKnownLocation(
-            { location ->
-                val geometry = GeometryHelper.createPointGeometry(
-                    location.longitude.truncate(),
-                    location.latitude.truncate(),
-                )
-                val intent = FormIntent.SelectLocationFromCoordinates(
-                    event.uid,
-                    geometry.coordinates(),
-                    FeatureType.POINT.name,
-                )
-
-                intentHandler(intent)
-            },
-            {
-                requestLocationPermissions.launch(
-                    arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
-                )
-            },
-            {
-                LocationSettingLauncher.requestEnableLocationSetting(
-                    requireContext(),
-                    {
-                        locationDisabledSettings.launch(
-                            LocationSettingLauncher.locationSourceSettingIntent(),
-                        )
-                    },
-                    {
-                        viewModel.submitIntent(FormIntent.OnCancelRequestCoordinates(event.uid))
-                    },
-                )
-            },
-        )
-    }
-
     private fun requestLocationByMap(event: RecyclerViewUiEvents.RequestLocationByMap) {
         onActivityForResult?.invoke()
         mapContent.launch(
@@ -972,16 +755,6 @@ class FormView : Fragment() {
             .show()
     }
 
-    private fun showFullPicture(event: RecyclerViewUiEvents.ShowImage) {
-        val intent = ImageDetailActivity.intent(
-            title = event.label,
-            imagePath = event.value,
-            context = requireActivity(),
-        )
-
-        startActivity(intent)
-    }
-
     private fun openFileSelector(event: RecyclerViewUiEvents.OpenFileSelector) {
         onSavePicture = { file ->
             intentHandler(
@@ -1077,43 +850,6 @@ class FormView : Fragment() {
         }
     }
 
-    private fun showOptionSetDialog(uiEvent: RecyclerViewUiEvents.OpenOptionSetDialog) {
-        OptionSetDialog(
-            field = uiEvent.field,
-            onClearValue = {
-                intentHandler(FormIntent.ClearValue(uiEvent.field.uid))
-            },
-        ) { code ->
-            intentHandler(
-                FormIntent.OnSave(
-                    uiEvent.field.uid,
-                    code,
-                    uiEvent.field.valueType,
-                ),
-            )
-        }.show(this@FormView.childFragmentManager)
-    }
-
-    private fun showSignatureDialog(uiEvent: RecyclerViewUiEvents.AddSignature) {
-        SignatureDialog(uiEvent.label) {
-            val file = File(
-                FileResourceDirectoryHelper.getFileResourceDirectory(requireContext()),
-                TEMP_FILE,
-            )
-            file.outputStream().use { out ->
-                it.compress(Bitmap.CompressFormat.PNG, 85, out)
-                out.flush()
-            }
-            intentHandler(
-                FormIntent.OnStoreFile(
-                    uiEvent.uid,
-                    file.path,
-                    ValueType.IMAGE,
-                ),
-            )
-        }.show(this@FormView.childFragmentManager)
-    }
-
     fun onBackPressed() {
         viewModel.runDataIntegrityCheck(backButtonPressed = true)
     }
diff --git a/form/src/main/java/org/dhis2/form/ui/FormViewModel.kt b/form/src/main/java/org/dhis2/form/ui/FormViewModel.kt
index dfa03988b6..50b9aab0b2 100644
--- a/form/src/main/java/org/dhis2/form/ui/FormViewModel.kt
+++ b/form/src/main/java/org/dhis2/form/ui/FormViewModel.kt
@@ -216,9 +216,18 @@ class FormViewModel(
 
             ActionType.ON_ADD_IMAGE_FINISHED -> handleOnAddImageFinishedAction(action)
             ActionType.ON_STORE_FILE -> handleOnStoreFileAction(action)
+            ActionType.ON_FETCH_OPTIONS -> handleFetchOptionsAction(action)
         }
     }
 
+    private fun handleFetchOptionsAction(action: RowAction): StoreResult {
+        repository.fetchOptions(action.id, action.extraData!!)
+        return StoreResult(
+            action.id,
+            ValueStoreResult.VALUE_CHANGED,
+        )
+    }
+
     private fun handleOnSaveAction(action: RowAction): StoreResult {
         if (action.valueType == ValueType.COORDINATE) {
             repository.setFieldRequestingCoordinates(action.id, false)
@@ -545,6 +554,14 @@ class FormViewModel(
                     valueType = intent.valueType,
                 )
             }
+
+            is FormIntent.FetchOptions ->
+                createRowAction(
+                    uid = intent.uid,
+                    value = intent.value,
+                    extraData = intent.optionSetUid,
+                    actionType = ActionType.ON_FETCH_OPTIONS,
+                )
         }
     }
 
diff --git a/form/src/main/java/org/dhis2/form/ui/event/DialogDelegate.kt b/form/src/main/java/org/dhis2/form/ui/event/DialogDelegate.kt
deleted file mode 100644
index ff848a54e0..0000000000
--- a/form/src/main/java/org/dhis2/form/ui/event/DialogDelegate.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-package org.dhis2.form.ui.event
-
-import org.dhis2.commons.date.DateUtils
-import org.dhis2.form.ui.intent.FormIntent
-import org.hisp.dhis.android.core.common.ValueType
-import java.util.Calendar
-import java.util.Date
-
-class DialogDelegate {
-
-    fun handleDateInput(uid: String, year: Int, month: Int, day: Int): FormIntent {
-        val currentCalendar = Calendar.getInstance()
-        val ageDate = with(currentCalendar) {
-            set(Calendar.YEAR, year)
-            set(Calendar.MONTH, month)
-            set(Calendar.DAY_OF_MONTH, day)
-            set(Calendar.HOUR_OF_DAY, 0)
-            set(Calendar.MINUTE, 0)
-            set(Calendar.SECOND, 0)
-            set(Calendar.MILLISECOND, 0)
-            return@with time
-        }
-        val date = DateUtils.oldUiDateFormat().format(ageDate)
-
-        return FormIntent.OnSave(
-            uid = uid,
-            value = date,
-            valueType = ValueType.DATE,
-        )
-    }
-
-    fun handleYearMonthDayInput(uid: String, year: Int, month: Int, day: Int): FormIntent {
-        val currentCalendar = Calendar.getInstance()
-        val ageDate = with(currentCalendar) {
-            add(Calendar.YEAR, year)
-            add(Calendar.MONTH, month)
-            add(Calendar.DAY_OF_MONTH, day)
-            set(Calendar.HOUR_OF_DAY, 0)
-            set(Calendar.MINUTE, 0)
-            set(Calendar.SECOND, 0)
-            set(Calendar.MILLISECOND, 0)
-            return@with time
-        }
-        val date = DateUtils.oldUiDateFormat().format(ageDate)
-
-        return FormIntent.OnSave(
-            uid = uid,
-            value = date,
-            valueType = ValueType.DATE,
-        )
-    }
-
-    fun handleTimeInput(uid: String, date: Date?, hourOfDay: Int, minutes: Int): FormIntent {
-        val currentCalendar = Calendar.getInstance()
-        val dateTime = with(currentCalendar) {
-            date?.let { time = it }
-            set(Calendar.HOUR_OF_DAY, hourOfDay)
-            set(Calendar.MINUTE, minutes)
-            set(Calendar.SECOND, 0)
-            set(Calendar.MILLISECOND, 0)
-            return@with time
-        }
-        val dateValue = when (date) {
-            null -> DateUtils.timeFormat().format(dateTime)
-            else -> DateUtils.databaseDateFormatNoSeconds().format(dateTime)
-        }
-        return FormIntent.OnSave(
-            uid = uid,
-            value = dateValue,
-            valueType = date?.let { ValueType.DATETIME } ?: ValueType.TIME,
-        )
-    }
-
-    fun handleDateTimeInput(
-        uid: String,
-        label: String,
-        date: Date?,
-        year: Int,
-        month: Int,
-        day: Int,
-    ): RecyclerViewUiEvents {
-        val currentCalendar = Calendar.getInstance()
-        val dateTime = with(currentCalendar) {
-            date?.let { time = it }
-            set(Calendar.YEAR, year)
-            set(Calendar.MONTH, month)
-            set(Calendar.DAY_OF_MONTH, day)
-            return@with time
-        }
-        return RecyclerViewUiEvents.OpenTimePicker(uid, label, dateTime, isDateTime = true)
-    }
-}
diff --git a/form/src/main/java/org/dhis2/form/ui/event/RecyclerViewUiEvents.kt b/form/src/main/java/org/dhis2/form/ui/event/RecyclerViewUiEvents.kt
index 017b751638..f28f0d830e 100644
--- a/form/src/main/java/org/dhis2/form/ui/event/RecyclerViewUiEvents.kt
+++ b/form/src/main/java/org/dhis2/form/ui/event/RecyclerViewUiEvents.kt
@@ -9,30 +9,6 @@ import java.util.Date
 
 sealed class RecyclerViewUiEvents {
 
-    data class OpenCustomCalendar(
-        val uid: String,
-        val label: String,
-        val date: Date?,
-        val allowFutureDates: Boolean,
-        val isDateTime: Boolean? = false,
-    ) : RecyclerViewUiEvents()
-
-    data class OpenTimePicker(
-        val uid: String,
-        val label: String,
-        val date: Date?,
-        val isDateTime: Boolean? = false,
-    ) : RecyclerViewUiEvents()
-
-    data class ShowDescriptionLabelDialog(
-        val title: String,
-        val message: String?,
-    ) : RecyclerViewUiEvents()
-
-    data class RequestCurrentLocation(
-        val uid: String,
-    ) : RecyclerViewUiEvents()
-
     data class RequestLocationByMap(
         val uid: String,
         val featureType: FeatureType,
@@ -65,24 +41,6 @@ sealed class RecyclerViewUiEvents {
         val uid: String,
     ) : RecyclerViewUiEvents()
 
-    data class AddSignature(
-        val uid: String,
-        val label: String,
-    ) : RecyclerViewUiEvents()
-
-    data class ShowImage(
-        val label: String,
-        val value: String,
-    ) : RecyclerViewUiEvents()
-
-    data class CopyToClipboard(
-        val value: String?,
-    ) : RecyclerViewUiEvents()
-
-    data class OpenOptionSetDialog(
-        val field: FieldUiModel,
-    ) : RecyclerViewUiEvents()
-
     data class OpenFileSelector(
         val field: FieldUiModel,
     ) : RecyclerViewUiEvents()
diff --git a/form/src/main/java/org/dhis2/form/ui/event/UiEventFactoryImpl.kt b/form/src/main/java/org/dhis2/form/ui/event/UiEventFactoryImpl.kt
index ee52d3439f..f05f27c5aa 100644
--- a/form/src/main/java/org/dhis2/form/ui/event/UiEventFactoryImpl.kt
+++ b/form/src/main/java/org/dhis2/form/ui/event/UiEventFactoryImpl.kt
@@ -1,27 +1,13 @@
 package org.dhis2.form.ui.event
 
 import android.content.Intent
-import org.dhis2.commons.date.DateUtils
-import org.dhis2.commons.extensions.toDate
 import org.dhis2.form.model.FieldUiModel
 import org.dhis2.form.model.UiEventType
 import org.dhis2.form.model.UiEventType.ADD_FILE
 import org.dhis2.form.model.UiEventType.ADD_PICTURE
-import org.dhis2.form.model.UiEventType.ADD_SIGNATURE
-import org.dhis2.form.model.UiEventType.AGE_CALENDAR
-import org.dhis2.form.model.UiEventType.COPY_TO_CLIPBOARD
-import org.dhis2.form.model.UiEventType.DATE_TIME
-import org.dhis2.form.model.UiEventType.EMAIL
 import org.dhis2.form.model.UiEventType.OPEN_FILE
-import org.dhis2.form.model.UiEventType.OPTION_SET
-import org.dhis2.form.model.UiEventType.ORG_UNIT
-import org.dhis2.form.model.UiEventType.PHONE_NUMBER
-import org.dhis2.form.model.UiEventType.QR_CODE
-import org.dhis2.form.model.UiEventType.REQUEST_CURRENT_LOCATION
 import org.dhis2.form.model.UiEventType.REQUEST_LOCATION_BY_MAP
 import org.dhis2.form.model.UiEventType.SHARE_IMAGE
-import org.dhis2.form.model.UiEventType.SHOW_DESCRIPTION
-import org.dhis2.form.model.UiEventType.SHOW_PICTURE
 import org.dhis2.form.model.UiRenderType
 import org.hisp.dhis.android.core.common.FeatureType
 import org.hisp.dhis.android.core.common.ValueType
@@ -44,104 +30,15 @@ class UiEventFactoryImpl(
         var uiEvent: RecyclerViewUiEvents? = null
         try {
             uiEvent = when (uiEventType) {
-                DATE_TIME -> {
-                    when (valueType) {
-                        ValueType.DATE -> RecyclerViewUiEvents.OpenCustomCalendar(
-                            uid,
-                            label,
-                            value?.let { DateUtils.oldUiDateFormat().parse(it) },
-                            allowFutureDates ?: true,
-                        )
-
-                        ValueType.DATETIME -> RecyclerViewUiEvents.OpenCustomCalendar(
-                            uid,
-                            label,
-                            value?.let { DateUtils.databaseDateFormatNoSeconds().parse(it) },
-                            allowFutureDates ?: true,
-                            isDateTime = true,
-                        )
-
-                        ValueType.TIME -> RecyclerViewUiEvents.OpenTimePicker(
-                            uid,
-                            label,
-                            value?.let { DateUtils.timeFormat().parse(it) },
-                        )
-
-                        else -> null
-                    }
-                }
-
-                AGE_CALENDAR -> RecyclerViewUiEvents.OpenCustomCalendar(
-                    uid = uid,
-                    label = label,
-                    date = value?.toDate(),
-                    allowFutureDates = allowFutureDates ?: false,
-                )
-
-                ORG_UNIT -> RecyclerViewUiEvents.OpenOrgUnitDialog(
-                    uid,
-                    label,
-                    value,
-                    fieldUiModel.orgUnitSelectorScope,
-                )
-
-                REQUEST_CURRENT_LOCATION -> RecyclerViewUiEvents.RequestCurrentLocation(
-                    uid = uid,
-                )
-
                 REQUEST_LOCATION_BY_MAP -> RecyclerViewUiEvents.RequestLocationByMap(
                     uid = uid,
                     featureType = getFeatureType(renderingType),
                     value = value,
                 )
 
-                SHOW_DESCRIPTION -> RecyclerViewUiEvents.ShowDescriptionLabelDialog(
-                    title = label,
-                    message = description,
-                )
-
                 ADD_PICTURE -> RecyclerViewUiEvents.AddImage(uid)
-                SHOW_PICTURE -> RecyclerViewUiEvents.ShowImage(
-                    label,
-                    value ?: "",
-                )
-
-                COPY_TO_CLIPBOARD -> RecyclerViewUiEvents.CopyToClipboard(
-                    value = value,
-                )
-
-                QR_CODE -> {
-                    if (value.isNullOrEmpty() && fieldUiModel.editable) {
-                        RecyclerViewUiEvents.ScanQRCode(
-                            uid = uid,
-                            optionSet = optionSet,
-                            renderingType = renderingType,
-                        )
-                    } else if (value != null) {
-                        RecyclerViewUiEvents.DisplayQRCode(
-                            uid = uid,
-                            optionSet = optionSet,
-                            value = value,
-                            renderingType = renderingType,
-                            editable = fieldUiModel.editable,
-                            label = label,
-                        )
-                    } else {
-                        null
-                    }
-                }
-
-                OPTION_SET -> RecyclerViewUiEvents.OpenOptionSetDialog(fieldUiModel)
-                ADD_SIGNATURE -> RecyclerViewUiEvents.AddSignature(uid, label)
                 ADD_FILE -> RecyclerViewUiEvents.OpenFileSelector(fieldUiModel)
                 OPEN_FILE -> RecyclerViewUiEvents.OpenFile(fieldUiModel)
-                EMAIL -> RecyclerViewUiEvents.OpenChooserIntent(Intent.ACTION_SENDTO, value, uid)
-                PHONE_NUMBER -> RecyclerViewUiEvents.OpenChooserIntent(
-                    Intent.ACTION_DIAL,
-                    value,
-                    uid,
-                )
-
                 SHARE_IMAGE -> RecyclerViewUiEvents.OpenChooserIntent(
                     Intent.ACTION_SEND,
                     fieldUiModel.displayName,
diff --git a/form/src/main/java/org/dhis2/form/ui/intent/FormIntent.kt b/form/src/main/java/org/dhis2/form/ui/intent/FormIntent.kt
index 1f37d3b459..98d2c34343 100644
--- a/form/src/main/java/org/dhis2/form/ui/intent/FormIntent.kt
+++ b/form/src/main/java/org/dhis2/form/ui/intent/FormIntent.kt
@@ -89,4 +89,10 @@ sealed class FormIntent {
         val valueType: ValueType?,
         val allowFutureDates: Boolean = true,
     ) : FormIntent()
+
+    data class FetchOptions(
+        val uid: String,
+        val optionSetUid: String,
+        val value: String?,
+    ) : FormIntent()
 }
diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/CategorySelectorProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/CategorySelectorProvider.kt
index 65203b9123..221c87acf3 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/CategorySelectorProvider.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/CategorySelectorProvider.kt
@@ -81,7 +81,11 @@ private fun ProvideCategorySelector(
     }
 
     if (category.options.isNotEmpty()) {
-        val dropdownItems = category.options.map { DropdownItem(it.name) }
+        var dropdownItems by remember {
+            mutableStateOf(
+                category.options.map { DropdownItem(it.name) },
+            )
+        }
 
         InputDropDown(
             modifier = modifier,
@@ -92,16 +96,26 @@ private fun ProvideCategorySelector(
             onResetButtonClicked = {
                 onCategoryOptionSelected(null)
             },
-            onItemSelected = { newSelectedItem ->
+            onItemSelected = { _, newSelectedItem ->
                 onCategoryOptionSelected(
                     category.options.firstOrNull {
                         it.name == newSelectedItem.label
                     },
                 )
             },
-            dropdownItems = dropdownItems,
+            fetchItem = { index -> dropdownItems[index] },
+            itemCount = dropdownItems.size,
+            onSearchOption = { query ->
+                dropdownItems = if (query.isNotEmpty()) {
+                    dropdownItems.filter { it.label.contains(query) }
+                } else {
+                    category.options.map { DropdownItem(it.name) }
+                }
+            },
             isRequiredField = fieldUiModel.mandatory,
             legendData = fieldUiModel.legend(),
+            loadOptions = {
+            },
         )
     } else {
         ProvideEmptyCategorySelector(
@@ -122,6 +136,8 @@ fun ProvideEmptyCategorySelector(
         mutableStateOf("")
     }
 
+    val emptyItems = listOf(DropdownItem(stringResource(id = R.string.no_options)))
+
     InputDropDown(
         modifier = modifier,
         title = name,
@@ -131,11 +147,17 @@ fun ProvideEmptyCategorySelector(
         onResetButtonClicked = {
             selectedItem = ""
         },
-        onItemSelected = { newSelectedDropdownItem ->
+        onItemSelected = { _, newSelectedDropdownItem ->
             selectedItem = newSelectedDropdownItem.label
         },
-        dropdownItems = listOf(DropdownItem(stringResource(id = R.string.no_options))),
+        fetchItem = { index ->
+            emptyItems[index]
+        },
+        itemCount = 1,
+        onSearchOption = { /*no-op*/ },
         isRequiredField = false,
+        loadOptions = {
+        },
     )
 }
 
diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/CheckBoxProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/CheckBoxProvider.kt
index cb82e88131..39a5888107 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/CheckBoxProvider.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/CheckBoxProvider.kt
@@ -2,6 +2,7 @@ package org.dhis2.form.ui.provider.inputfield
 
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import androidx.paging.compose.collectAsLazyPagingItems
 import org.dhis2.commons.resources.ResourceManager
 import org.dhis2.form.R
 import org.dhis2.form.extensions.inputState
@@ -22,14 +23,24 @@ internal fun ProvideCheckBoxInput(
     fieldUiModel: FieldUiModel,
     intentHandler: (FormIntent) -> Unit,
 ) {
-    val data = fieldUiModel.optionSetConfiguration?.optionsToDisplay()?.map { option ->
-        CheckBoxData(
-            uid = option.uid(),
-            checked = fieldUiModel.displayName == option.displayName(),
-            enabled = true,
-            textInput = option.displayName() ?: "",
-        )
-    } ?: emptyList()
+    val dataMap = buildMap {
+        fieldUiModel.optionSetConfiguration?.optionFlow?.collectAsLazyPagingItems()?.let { paging ->
+            repeat(paging.itemCount) { index ->
+                val optionData = paging[index]
+                put(
+                    optionData?.option?.code() ?: "",
+                    CheckBoxData(
+                        uid = optionData?.option?.uid() ?: "",
+                        checked = fieldUiModel.displayName == optionData?.option?.displayName(),
+                        enabled = true,
+                        textInput = optionData?.option?.displayName() ?: "",
+                    ),
+                )
+            }
+        }
+    }
+
+    val (codeList, data) = dataMap.toList().unzip()
 
     InputCheckBox(
         modifier = modifier,
@@ -42,11 +53,11 @@ internal fun ProvideCheckBoxInput(
         legendData = fieldUiModel.legend(),
         isRequired = fieldUiModel.mandatory,
         onItemChange = { item ->
+            val selectedIndex = data.indexOf(item)
             intentHandler(
                 FormIntent.OnSave(
                     fieldUiModel.uid,
-                    fieldUiModel.optionSetConfiguration?.optionsToDisplay()
-                        ?.find { it.uid() == item.uid }?.code(),
+                    codeList[selectedIndex],
                     fieldUiModel.valueType,
                 ),
             )
diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/DropdownProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/DropdownProvider.kt
index 00ce8262e9..98a50668b3 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/DropdownProvider.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/DropdownProvider.kt
@@ -1,11 +1,13 @@
 package org.dhis2.form.ui.provider.inputfield
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.paging.compose.collectAsLazyPagingItems
 import org.dhis2.form.extensions.inputState
 import org.dhis2.form.extensions.legend
 import org.dhis2.form.extensions.supportingText
@@ -19,14 +21,27 @@ fun ProvideDropdownInput(
     modifier: Modifier,
     inputStyle: InputStyle,
     fieldUiModel: FieldUiModel,
+    fetchOptions: () -> Unit,
 ) {
     var selectedItem by remember(fieldUiModel) {
         mutableStateOf(DropdownItem(fieldUiModel.displayName ?: ""))
     }
 
-    val selectableOptions = fieldUiModel.optionSetConfiguration?.optionsToDisplay()
+    val optionSetConfiguration by remember(fieldUiModel) {
+        mutableStateOf(fieldUiModel.optionSetConfiguration)
+    }
+
+    val optionsData = optionSetConfiguration?.optionFlow?.collectAsLazyPagingItems()
+
+    val useDropdown by remember {
+        derivedStateOf {
+            optionSetConfiguration?.searchEmitter?.value?.isEmpty() == true && (
+                optionsData?.itemCount
+                    ?: 0
+                ) < 15
+        }
+    }
 
-    val dropdownItems = selectableOptions?.map { DropdownItem(it.displayName() ?: it.code() ?: "") }
     InputDropDown(
         modifier = modifier,
         inputStyle = inputStyle,
@@ -37,14 +52,23 @@ fun ProvideDropdownInput(
         legendData = fieldUiModel.legend(),
         isRequiredField = fieldUiModel.mandatory,
         onResetButtonClicked = { fieldUiModel.onClear() },
-        dropdownItems = dropdownItems ?: emptyList(),
-        onItemSelected = { newSelectedItem ->
+        fetchItem = { index ->
+            DropdownItem(optionsData?.get(index)?.option?.displayName() ?: "")
+        },
+        onSearchOption = { query ->
+            fieldUiModel.optionSetConfiguration?.onSearch?.invoke(query)
+        },
+        itemCount = optionsData?.itemCount ?: 0,
+        useDropDown = useDropdown,
+        onItemSelected = { index, newSelectedItem ->
             selectedItem = newSelectedItem
             fieldUiModel.onSave(
-                selectableOptions?.firstOrNull {
-                    it.displayName() == newSelectedItem.label
-                }?.code(),
+                optionsData?.get(index)?.option?.code(),
             )
         },
+        loadOptions = fetchOptions,
+        onDismiss = {
+            fieldUiModel.optionSetConfiguration?.onSearch?.invoke("")
+        },
     )
 }
diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt
index 6599f68d7d..7b415964ef 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt
@@ -113,6 +113,15 @@ fun FieldProvider(
             fieldUiModel = fieldUiModel,
             intentHandler = intentHandler,
             context = context,
+            fetchOptions = {
+                intentHandler(
+                    FormIntent.FetchOptions(
+                        fieldUiModel.uid,
+                        fieldUiModel.optionSet!!,
+                        value = fieldUiModel.value,
+                    ),
+                )
+            },
         )
 
         fieldUiModel.eventCategories != null -> ProvideCategorySelectorInput(
@@ -476,6 +485,7 @@ fun ProvideByOptionSet(
     fieldUiModel: FieldUiModel,
     intentHandler: (FormIntent) -> Unit,
     context: Context,
+    fetchOptions: () -> Unit,
 ) {
     when (fieldUiModel.renderingType) {
         UiRenderType.HORIZONTAL_RADIOBUTTONS,
@@ -527,6 +537,7 @@ fun ProvideByOptionSet(
                 modifier = modifier,
                 inputStyle = inputStyle,
                 fieldUiModel = fieldUiModel,
+                fetchOptions = fetchOptions,
             )
         }
     }
diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MatrixInputProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MatrixInputProvider.kt
index e98cf5d6ca..b926d87596 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MatrixInputProvider.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MatrixInputProvider.kt
@@ -6,6 +6,7 @@ import androidx.compose.runtime.getValue
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.paging.compose.collectAsLazyPagingItems
 import org.dhis2.form.extensions.inputState
 import org.dhis2.form.extensions.legend
 import org.dhis2.form.extensions.supportingText
@@ -23,10 +24,21 @@ internal fun ProvideMatrixInput(
     context: Context,
     intentHandler: (FormIntent) -> Unit,
 ) {
-    val inputCardDataList = rememberInputCardList(
-        options = fieldUiModel.optionSetConfiguration?.optionsToDisplay(),
-        optionMetadataIconMap = fieldUiModel.optionSetConfiguration?.optionMetadataIcon,
-    )
+    val inputCardDataList: MutableList<ImageCardData> = mutableListOf()
+
+    fieldUiModel.optionSetConfiguration?.optionFlow?.collectAsLazyPagingItems()?.let { paging ->
+        repeat(paging.itemCount) { index ->
+            val optionData = paging[index]
+            inputCardDataList.add(
+                imageCardDataWithUidAndLabel(
+                    optionData!!.metadataIconData.imageCardData,
+                    optionData.option.code() ?: "",
+                    optionData.option.displayName() ?: "",
+                ),
+            )
+        }
+    }
+
     var matrixSelectedItem by rememberSelectedOption(
         fieldUiModel = fieldUiModel,
         inputCardDataList = inputCardDataList,
diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MatrixSequentialUtilites.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MatrixSequentialUtilites.kt
index 8eaf068c1a..6fbe9b727d 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MatrixSequentialUtilites.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MatrixSequentialUtilites.kt
@@ -29,7 +29,7 @@ fun rememberInputCardList(
     } ?: emptyList()
 }
 
-private fun imageCardDataWithUidAndLabel(
+fun imageCardDataWithUidAndLabel(
     imageCardData: ImageCardData,
     optionCode: String,
     label: String,
diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MultiSelectionInputProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MultiSelectionInputProvider.kt
index 18ea54e79b..976fda619d 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MultiSelectionInputProvider.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MultiSelectionInputProvider.kt
@@ -2,6 +2,7 @@ package org.dhis2.form.ui.provider.inputfield
 
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import androidx.paging.compose.collectAsLazyPagingItems
 import org.dhis2.form.extensions.inputState
 import org.dhis2.form.extensions.legend
 import org.dhis2.form.extensions.supportingText
@@ -16,16 +17,25 @@ internal fun ProvideMultiSelectionInput(
     fieldUiModel: FieldUiModel,
     intentHandler: (FormIntent) -> Unit,
 ) {
-    val optionsToDisplay = fieldUiModel.optionSetConfiguration?.optionsToDisplay() ?: emptyList()
-    val data = optionsToDisplay.map { option ->
-        CheckBoxData(
-            uid = option.uid(),
-            checked = option.code()?.let { fieldUiModel.value?.split(",")?.contains(it) } ?: false,
-            enabled = true,
-            textInput = option.displayName() ?: "",
-        )
+    val dataMap = buildMap {
+        fieldUiModel.optionSetConfiguration?.optionFlow?.collectAsLazyPagingItems()?.let { paging ->
+            repeat(paging.itemCount) { index ->
+                val optionData = paging[index]
+                put(
+                    optionData?.option?.code() ?: "",
+                    CheckBoxData(
+                        uid = optionData?.option?.uid() ?: "",
+                        checked = optionData?.option?.code()?.let { fieldUiModel.value?.split(",")?.contains(it) } ?: false,
+                        enabled = true,
+                        textInput = optionData?.option?.displayName() ?: "",
+                    ),
+                )
+            }
+        }
     }
 
+    val (codeList, data) = dataMap.toList().unzip()
+
     InputMultiSelection(
         modifier = modifier,
         title = fieldUiModel.label,
@@ -35,8 +45,9 @@ internal fun ProvideMultiSelectionInput(
         legendData = fieldUiModel.legend(),
         isRequired = fieldUiModel.mandatory,
         onItemsSelected = {
-            val checkedValues = it.filter { item -> item.checked }.mapNotNull {
-                optionsToDisplay.find { option -> option.uid() == it.uid }?.code()
+            val checkedValues = it.filter { item -> item.checked }.map { checkBoxData ->
+                val selectedIndex = data.indexOf(checkBoxData)
+                codeList[selectedIndex]
             }
 
             intentHandler(
diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/PeriodSelectorProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/PeriodSelectorProvider.kt
index 860ae6fbd7..18857aa51c 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/PeriodSelectorProvider.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/PeriodSelectorProvider.kt
@@ -85,6 +85,8 @@ fun ProvideEmptyPeriodSelector(
         mutableStateOf("")
     }
 
+    val options = listOf(DropdownItem(stringResource(id = R.string.no_periods)))
+
     InputDropDown(
         modifier = modifier,
         title = name,
@@ -94,10 +96,19 @@ fun ProvideEmptyPeriodSelector(
         onResetButtonClicked = {
             selectedItem = ""
         },
-        onItemSelected = { newSelectedDropdownItem ->
+        onItemSelected = { _, newSelectedDropdownItem ->
             selectedItem = newSelectedDropdownItem.label
         },
-        dropdownItems = listOf(DropdownItem(stringResource(id = R.string.no_periods))),
+        itemCount = 1,
+        fetchItem = {
+            options[it]
+        },
+        loadOptions = {
+            /*no-op*/
+        },
+        onSearchOption = {
+            /*no-op*/
+        },
         isRequiredField = false,
     )
 }
diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/RadioButtonProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/RadioButtonProvider.kt
index 5ce3379166..19a6181636 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/RadioButtonProvider.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/RadioButtonProvider.kt
@@ -2,6 +2,7 @@ package org.dhis2.form.ui.provider.inputfield
 
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import androidx.paging.compose.collectAsLazyPagingItems
 import org.dhis2.commons.resources.ResourceManager
 import org.dhis2.form.R
 import org.dhis2.form.extensions.inputState
@@ -21,14 +22,24 @@ internal fun ProvideRadioButtonInput(
     fieldUiModel: FieldUiModel,
     intentHandler: (FormIntent) -> Unit,
 ) {
-    val data = fieldUiModel.optionSetConfiguration?.optionsToDisplay()?.map { option ->
-        RadioButtonData(
-            uid = option.uid(),
-            selected = fieldUiModel.displayName == option.displayName(),
-            enabled = true,
-            textInput = option.displayName() ?: "",
-        )
-    } ?: emptyList()
+    val dataMap = buildMap {
+        fieldUiModel.optionSetConfiguration?.optionFlow?.collectAsLazyPagingItems()?.let { paging ->
+            repeat(paging.itemCount) { index ->
+                val optionData = paging[index]
+                put(
+                    optionData?.option?.code() ?: "",
+                    RadioButtonData(
+                        uid = optionData?.option?.uid() ?: "",
+                        selected = fieldUiModel.displayName == optionData?.option?.displayName(),
+                        enabled = true,
+                        textInput = optionData?.option?.displayName() ?: "",
+                    ),
+                )
+            }
+        }
+    }
+
+    val (codeList, data) = dataMap.toList().unzip()
 
     InputRadioButton(
         modifier = modifier,
@@ -42,11 +53,11 @@ internal fun ProvideRadioButtonInput(
         isRequired = fieldUiModel.mandatory,
         itemSelected = data.find { it.selected },
         onItemChange = { item ->
+            val selectedIndex = data.indexOf(item)
             intentHandler(
                 FormIntent.OnSave(
                     fieldUiModel.uid,
-                    fieldUiModel.optionSetConfiguration?.optionsToDisplay()
-                        ?.find { it.uid() == item?.uid }?.code(),
+                    codeList[selectedIndex],
                     fieldUiModel.valueType,
                 ),
             )
diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/SequentialInputProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/SequentialInputProvider.kt
index d021b6d141..acdd7e235a 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/SequentialInputProvider.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/SequentialInputProvider.kt
@@ -6,6 +6,7 @@ import androidx.compose.runtime.getValue
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.paging.compose.collectAsLazyPagingItems
 import org.dhis2.form.extensions.inputState
 import org.dhis2.form.extensions.legend
 import org.dhis2.form.extensions.supportingText
@@ -23,10 +24,21 @@ internal fun ProvideSequentialInput(
     context: Context,
     intentHandler: (FormIntent) -> Unit,
 ) {
-    val inputCardDataList = rememberInputCardList(
-        options = fieldUiModel.optionSetConfiguration?.optionsToDisplay(),
-        optionMetadataIconMap = fieldUiModel.optionSetConfiguration?.optionMetadataIcon,
-    )
+    val inputCardDataList: MutableList<ImageCardData> = mutableListOf()
+
+    fieldUiModel.optionSetConfiguration?.optionFlow?.collectAsLazyPagingItems()?.let { paging ->
+        repeat(paging.itemCount) { index ->
+            val optionData = paging[index]
+            inputCardDataList.add(
+                imageCardDataWithUidAndLabel(
+                    optionData!!.metadataIconData.imageCardData,
+                    optionData.option.code() ?: "",
+                    optionData.option.displayName() ?: "",
+                ),
+            )
+        }
+    }
+
     var matrixSelectedItem by rememberSelectedOption(
         fieldUiModel = fieldUiModel,
         inputCardDataList = inputCardDataList,
diff --git a/form/src/test/java/org/dhis2/form/data/EnrollmentRepositoryTest.kt b/form/src/test/java/org/dhis2/form/data/EnrollmentRepositoryTest.kt
index 16a79d78d9..5ca3bb5c20 100644
--- a/form/src/test/java/org/dhis2/form/data/EnrollmentRepositoryTest.kt
+++ b/form/src/test/java/org/dhis2/form/data/EnrollmentRepositoryTest.kt
@@ -1,6 +1,7 @@
 package org.dhis2.form.data
 
 import junit.framework.TestCase.assertTrue
+import org.dhis2.commons.resources.MetadataIconProvider
 import org.dhis2.form.data.EnrollmentRepository.Companion.ORG_UNIT_UID
 import org.dhis2.form.data.metadata.EnrollmentConfiguration
 import org.dhis2.form.model.EnrollmentMode
@@ -20,6 +21,7 @@ class EnrollmentRepositoryTest {
     private val conf: EnrollmentConfiguration = mock()
     private val enrollmentMode: EnrollmentMode = mock()
     private val enrolmentFormLabelsProvider: EnrollmentFormLabelsProvider = mock()
+    private val metadataIconProvider: MetadataIconProvider = mock()
     lateinit var repository: DataEntryRepository
     val programSection: ProgramSection = mock()
 
@@ -56,6 +58,7 @@ class EnrollmentRepositoryTest {
             conf,
             enrollmentMode,
             enrolmentFormLabelsProvider,
+            metadataIconProvider,
         )
     }
 
diff --git a/form/src/test/java/org/dhis2/form/data/FieldUiModelTest.kt b/form/src/test/java/org/dhis2/form/data/FieldUiModelTest.kt
deleted file mode 100644
index 1cb9a1c178..0000000000
--- a/form/src/test/java/org/dhis2/form/data/FieldUiModelTest.kt
+++ /dev/null
@@ -1,92 +0,0 @@
-package org.dhis2.form.data
-
-import org.dhis2.form.model.FieldUiModelImpl
-import org.dhis2.form.model.OptionSetConfiguration
-import org.hisp.dhis.android.core.common.ValueType
-import org.hisp.dhis.android.core.option.Option
-import org.junit.Test
-
-class FieldUiModelTest {
-
-    @Test
-    fun `should set optionsToDisplay when there is optionsToHide`() {
-        val optionsToHide = listOf("1", "2")
-        val matrixOptionSetModel = fieldUiModel().also {
-            val conf = it.optionSetConfiguration
-            it.optionSetConfiguration = when (conf) {
-                is OptionSetConfiguration.BigOptionSet ->
-                    conf.copy(optionsToHide = optionsToHide)
-                is OptionSetConfiguration.DefaultOptionSet ->
-                    conf.copy(optionsToHide = optionsToHide)
-                null -> conf
-            }
-        }
-        assert(
-            matrixOptionSetModel.optionSetConfiguration?.optionsToDisplay()
-                ?.map { it.uid() } == listOf("3", "4", "5"),
-        )
-    }
-
-    @Test
-    fun `should set optionsToDisplay when there is optionsToShow`() {
-        val optionsInGroupToShow = listOf("1", "2")
-        val matrixOptionSetModel = fieldUiModel().also {
-            val conf = it.optionSetConfiguration
-            it.optionSetConfiguration = when (conf) {
-                is OptionSetConfiguration.BigOptionSet ->
-                    conf.copy(optionsToShow = optionsInGroupToShow)
-                is OptionSetConfiguration.DefaultOptionSet ->
-                    conf.copy(optionsToShow = optionsInGroupToShow)
-                null -> conf
-            }
-        }
-        assert(
-            matrixOptionSetModel.optionSetConfiguration?.optionsToDisplay()
-                ?.map { it.uid() } == listOf("1", "2"),
-        )
-    }
-
-    @Test
-    fun `should set optionsToDisplay when there are optionsToShow and optionsToHide`() {
-        val optionsToHide = listOf("1")
-        val optionsInGroupToShow = listOf("3", "5")
-
-        val matrixOptionSetModel = fieldUiModel().also {
-            val conf = it.optionSetConfiguration
-            it.optionSetConfiguration = when (conf) {
-                is OptionSetConfiguration.BigOptionSet -> conf.copy(
-                    optionsToHide = optionsToHide,
-                    optionsToShow = optionsInGroupToShow,
-                )
-                is OptionSetConfiguration.DefaultOptionSet -> conf.copy(
-                    optionsToHide = optionsToHide,
-                    optionsToShow = optionsInGroupToShow,
-                )
-                null -> conf
-            }
-        }
-        assert(
-            matrixOptionSetModel.optionSetConfiguration?.optionsToDisplay()
-                ?.map { it.uid() } == listOf("3", "5"),
-        )
-    }
-
-    private fun fieldUiModel() = FieldUiModelImpl(
-        "uid",
-        label = "label",
-        valueType = ValueType.TEXT,
-        optionSetConfiguration = OptionSetConfiguration.config(5) {
-            OptionSetConfiguration.OptionConfigData(
-                listOf(
-                    Option.builder().uid("1").build(),
-                    Option.builder().uid("2").build(),
-                    Option.builder().uid("3").build(),
-                    Option.builder().uid("4").build(),
-                    Option.builder().uid("5").build(),
-                ),
-                emptyMap(),
-            )
-        },
-        autocompleteList = null,
-    )
-}
diff --git a/form/src/test/java/org/dhis2/form/data/FormRepositoryIntegrationTest.kt b/form/src/test/java/org/dhis2/form/data/FormRepositoryIntegrationTest.kt
index fb2d09ab5c..dd5d9b4a7a 100644
--- a/form/src/test/java/org/dhis2/form/data/FormRepositoryIntegrationTest.kt
+++ b/form/src/test/java/org/dhis2/form/data/FormRepositoryIntegrationTest.kt
@@ -1,6 +1,7 @@
 package org.dhis2.form.data
 
 import org.dhis2.commons.prefs.PreferenceProvider
+import org.dhis2.commons.resources.MetadataIconProvider
 import org.dhis2.form.data.metadata.EnrollmentConfiguration
 import org.dhis2.form.model.EnrollmentMode
 import org.dhis2.form.model.SectionUiModelImpl
@@ -43,6 +44,7 @@ class FormRepositoryIntegrationTest {
         on { provideEnrollmentOrgUnitLabel() } doReturn "OrgUnit label"
         on { provideEnrollmentDataSectionLabel(any()) } doReturn "Enrollment data"
     }
+    private val metadataIconProvider: MetadataIconProvider = mock()
 
     private val program: Program = mock {
         on { uid() } doReturn "programUid"
@@ -173,6 +175,7 @@ class FormRepositoryIntegrationTest {
             conf,
             enrollmentMode,
             enrollmentFormLabelsProvider,
+            metadataIconProvider,
         )
 
         return FormRepositoryImpl(
diff --git a/form/src/test/java/org/dhis2/form/data/GeometryControllerTest.kt b/form/src/test/java/org/dhis2/form/data/GeometryControllerTest.kt
index 8f326c3425..294f376828 100644
--- a/form/src/test/java/org/dhis2/form/data/GeometryControllerTest.kt
+++ b/form/src/test/java/org/dhis2/form/data/GeometryControllerTest.kt
@@ -1,7 +1,5 @@
 package org.dhis2.form.data
 
-import org.dhis2.form.ui.event.RecyclerViewUiEvents
-import org.dhis2.form.ui.intent.FormIntent
 import org.hisp.dhis.android.core.common.FeatureType
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -45,37 +43,4 @@ class GeometryControllerTest {
                 result.coordinates()?.isNotEmpty() == true,
         )
     }
-
-    @Test
-    fun `Should return coordinates callback`() {
-        var currentCallback: Int = -1
-        val coordinateCallback = controller.getCoordinatesCallback(
-            {
-                currentCallback = 0
-            },
-            { currentCallback = 1 },
-            { _, _, _ -> currentCallback = 2 },
-        )
-
-        coordinateCallback.intent(
-            FormIntent.SaveCurrentLocation(
-                uid = "fieldUid",
-                value = null,
-                featureType = "none",
-            ),
-        )
-        assertTrue(currentCallback == 0)
-        coordinateCallback.recyclerViewUiEvents(
-            RecyclerViewUiEvents.RequestCurrentLocation("fieldUid"),
-        )
-        assertTrue(currentCallback == 1)
-        coordinateCallback.recyclerViewUiEvents(
-            RecyclerViewUiEvents.RequestLocationByMap(
-                "fieldUid",
-                FeatureType.POINT,
-                null,
-            ),
-        )
-        assertTrue(currentCallback == 2)
-    }
 }
diff --git a/form/src/test/java/org/dhis2/form/integration/ProgramRulesTest.kt b/form/src/test/java/org/dhis2/form/integration/ProgramRulesTest.kt
index 1c5d88d8d1..610a6809fa 100644
--- a/form/src/test/java/org/dhis2/form/integration/ProgramRulesTest.kt
+++ b/form/src/test/java/org/dhis2/form/integration/ProgramRulesTest.kt
@@ -6,6 +6,8 @@ import io.reactivex.Flowable
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.resetMain
@@ -31,9 +33,7 @@ import org.dhis2.form.ui.FormViewModel
 import org.dhis2.form.ui.intent.FormIntent
 import org.dhis2.mobileProgramRules.RuleEngineHelper
 import org.hisp.dhis.android.core.D2
-import org.hisp.dhis.android.core.common.ObjectWithUid
 import org.hisp.dhis.android.core.common.ValueType
-import org.hisp.dhis.android.core.option.Option
 import org.hisp.dhis.android.core.program.ProgramRuleActionType
 import org.hisp.dhis.rules.models.RuleAction
 import org.hisp.dhis.rules.models.RuleEffect
@@ -48,6 +48,7 @@ import org.mockito.kotlin.any
 import org.mockito.kotlin.anyOrNull
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
 class ProgramRulesTest {
@@ -106,7 +107,10 @@ class ProgramRulesTest {
             invocationOnMock.getArgument(0) as FieldUiModel
         }
 
-        whenever(formValueStore.save(any(), anyOrNull(), anyOrNull())) doReturn StoreResult("", ValueStoreResult.VALUE_CHANGED)
+        whenever(formValueStore.save(any(), anyOrNull(), anyOrNull())) doReturn StoreResult(
+            "",
+            ValueStoreResult.VALUE_CHANGED,
+        )
 
         repository = FormRepositoryImpl(
             formValueStore = formValueStore,
@@ -356,6 +360,15 @@ class ProgramRulesTest {
             ),
         )
 
+        whenever(
+            dataEntryRepository.options(
+                any(),
+                any(),
+                any(),
+                any(),
+            ),
+        ) doReturn Pair(MutableStateFlow(""), emptyFlow())
+
         val intent = FormIntent.OnSave(
             uid = "uid004",
             value = "value04",
@@ -370,15 +383,12 @@ class ProgramRulesTest {
         formViewModel.submitIntent(intent)
         advanceUntilIdle()
 
-        val items = formViewModel.items.value ?: emptyList()
-
-        val optionsToDisplay: List<Option> =
-            items.find { it.uid == "uid006" }!!.optionSetConfiguration!!.optionsToDisplay()
-
-        assertTrue(optionsToDisplay.size == 3)
-        assertTrue(optionsToDisplay[0].uid() == "Option2")
-        assertTrue(optionsToDisplay[1].uid() == "Option3")
-        assertTrue(optionsToDisplay[2].uid() == "Option4")
+        verify(dataEntryRepository).options(
+            optionSetUid = "optionSetUid",
+            optionsToHide = emptyList(),
+            optionGroupsToHide = emptyList(),
+            optionGroupsToShow = listOf("optionGroupId"),
+        )
     }
 
     @OptIn(ExperimentalCoroutinesApi::class)
@@ -400,6 +410,15 @@ class ProgramRulesTest {
             ),
         )
 
+        whenever(
+            dataEntryRepository.options(
+                any(),
+                any(),
+                any(),
+                any(),
+            ),
+        ) doReturn Pair(MutableStateFlow(""), emptyFlow())
+
         val intent = FormIntent.OnSave(
             uid = "uid004",
             value = "value04",
@@ -408,21 +427,18 @@ class ProgramRulesTest {
 
         whenever(formValueStore.deleteOptionValueIfSelected(any(), any())) doReturn StoreResult(
             "uid007",
-            ValueStoreResult.VALUE_CHANGED,
+            ValueStoreResult.VALUE_HAS_NOT_CHANGED,
         )
 
         formViewModel.submitIntent(intent)
         advanceUntilIdle()
 
-        val items = formViewModel.items.value ?: emptyList()
-
-        val optionsToDisplay: List<Option> =
-            items.last().optionSetConfiguration!!.optionsToDisplay()
-
-        optionsToDisplay.forEach {
-            assert(it.uid() != "Option2")
-        }
-        assert(optionsToDisplay.size == 4)
+        verify(dataEntryRepository).options(
+            optionSetUid = "optionSetUid",
+            optionsToHide = listOf("Option2"),
+            optionGroupsToHide = emptyList(),
+            optionGroupsToShow = emptyList(),
+        )
     }
 
     private fun provideItemList() = listOf(
@@ -486,11 +502,10 @@ class ProgramRulesTest {
             value = "value06",
             label = "field6",
             valueType = ValueType.MULTI_TEXT,
-            optionSetConfiguration = OptionSetConfiguration.DefaultOptionSet(
-                options = "optionSetUid".listOfOptions(),
-                optionsToHide = listOf(),
-                optionsToShow = listOf("Option2", "Option3", "Option4"),
-                optionMetadataIcon = mapOf(),
+            optionSetConfiguration = OptionSetConfiguration(
+                MutableStateFlow(""),
+                {},
+                emptyFlow(),
             ),
             autocompleteList = null,
             programStageSection = "section2",
@@ -501,31 +516,14 @@ class ProgramRulesTest {
             value = "value07",
             label = "field7",
             valueType = ValueType.MULTI_TEXT,
-            optionSetConfiguration = OptionSetConfiguration.DefaultOptionSet(
-                options = "optionSetUid".listOfOptions(),
-                optionsToHide = listOf("Option2"),
-                optionsToShow = listOf(),
-                optionMetadataIcon = mapOf(),
+            optionSetConfiguration = OptionSetConfiguration(
+                MutableStateFlow(""),
+                {},
+                emptyFlow(),
             ),
             autocompleteList = null,
             programStageSection = "section2",
             optionSet = "optionSetUid",
         ),
     )
-
-    private fun String.listOfOptions(): List<Option> {
-        val optionSetUid = ObjectWithUid.create(this)
-        val options: MutableList<Option> = mutableListOf()
-        repeat(5) { index ->
-            options.add(
-                Option.builder()
-                    .uid("Option$index")
-                    .displayName("name$index")
-                    .code("code$index")
-                    .optionSet(optionSetUid)
-                    .build(),
-            )
-        }
-        return options.toList()
-    }
 }
diff --git a/form/src/test/java/org/dhis2/form/model/OptionSetDialogViewModelTest.kt b/form/src/test/java/org/dhis2/form/model/OptionSetDialogViewModelTest.kt
index 3331da2c34..b414d5713e 100644
--- a/form/src/test/java/org/dhis2/form/model/OptionSetDialogViewModelTest.kt
+++ b/form/src/test/java/org/dhis2/form/model/OptionSetDialogViewModelTest.kt
@@ -91,67 +91,6 @@ class OptionSetDialogViewModelTest {
         )
     }
 
-    @Test
-    fun `Should search and filter options to hide`() {
-        val optionsToHide = listOf("Option1")
-        whenever(field.optionSetConfiguration) doReturn OptionSetConfiguration.DefaultOptionSet(
-            options = emptyList(),
-            optionsToHide = optionsToHide,
-            optionsToShow = emptyList(),
-            emptyMap(),
-        )
-        viewModel.onSearchingOption("test")
-        testingDispatcher.scheduler.advanceUntilIdle()
-        assertTrue(viewModel.searchValue.value == "test")
-        verify(searchOptionSetOption, times(1))(
-            optionSetUid,
-            "test",
-            emptyList(),
-            optionsToHide,
-        )
-    }
-
-    @Test
-    fun `Should search and filter options to show`() {
-        val optionsToShow = listOf("Option1")
-        whenever(field.optionSetConfiguration) doReturn OptionSetConfiguration.DefaultOptionSet(
-            options = emptyList(),
-            optionsToHide = emptyList(),
-            optionsToShow = optionsToShow,
-            emptyMap(),
-        )
-        viewModel.onSearchingOption("test")
-        assertTrue(viewModel.searchValue.value == "test")
-        testingDispatcher.scheduler.advanceUntilIdle()
-        verify(searchOptionSetOption, times(1))(
-            optionSetUid,
-            "test",
-            optionsToShow,
-            emptyList(),
-        )
-    }
-
-    @Test
-    fun `Should search and filter options to show and hide`() {
-        val optionsToShow = listOf("Option1")
-        val optionsToHide = listOf("Option1")
-        whenever(field.optionSetConfiguration) doReturn OptionSetConfiguration.DefaultOptionSet(
-            options = emptyList(),
-            optionsToHide = optionsToHide,
-            optionsToShow = optionsToShow,
-            emptyMap(),
-        )
-        viewModel.onSearchingOption("test")
-        testingDispatcher.scheduler.advanceUntilIdle()
-        assertTrue(viewModel.searchValue.value == "test")
-        verify(searchOptionSetOption, times(1))(
-            optionSetUid,
-            "test",
-            optionsToShow,
-            optionsToHide,
-        )
-    }
-
     private val mockedOptions = mutableListOf<Option>().apply {
         repeat(times = 5) { index ->
             add(
diff --git a/form/src/test/java/org/dhis2/form/ui/DataEntryIntegrationTest.kt b/form/src/test/java/org/dhis2/form/ui/DataEntryIntegrationTest.kt
index 56496f09d2..5e263f6da7 100644
--- a/form/src/test/java/org/dhis2/form/ui/DataEntryIntegrationTest.kt
+++ b/form/src/test/java/org/dhis2/form/ui/DataEntryIntegrationTest.kt
@@ -3,9 +3,13 @@ package org.dhis2.form.ui
 import androidx.arch.core.executor.testing.InstantTaskExecutorRule
 import androidx.databinding.ObservableField
 import androidx.lifecycle.Observer
+import androidx.paging.PagingData
 import io.reactivex.Flowable
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.test.setMain
@@ -30,6 +34,7 @@ import org.dhis2.form.ui.provider.DisplayNameProvider
 import org.dhis2.form.ui.provider.LegendValueProvider
 import org.dhis2.form.ui.validation.FieldErrorMessageProvider
 import org.dhis2.mobileProgramRules.RuleEngineHelper
+import org.dhis2.ui.MetadataIconData
 import org.hisp.dhis.android.core.common.ValueType
 import org.hisp.dhis.android.core.option.Option
 import org.junit.Before
@@ -253,7 +258,8 @@ class DataEntryIntegrationTest {
             observedItems.last().find { it.uid == "EVENT_ORG_UNIT_UID" }?.value == "g8upMTyEZGZ",
         )
         assert(
-            observedItems.last().find { it.uid == "INPUT_NUMBER_WITH_LEGEND_UID" }?.legend == legendValueItem,
+            observedItems.last()
+                .find { it.uid == "INPUT_NUMBER_WITH_LEGEND_UID" }?.legend == legendValueItem,
         )
         assert(
             observedItems.last().find { it.uid == "qrur9Dvnyt5" }?.value == "20",
@@ -267,6 +273,7 @@ class DataEntryIntegrationTest {
     }
 
     private fun provideMalariaCaseRegistrationEventItems(): List<FieldUiModel> {
+        val optionSearchFlow = MutableStateFlow("")
         return listOf(
             SectionUiModelImpl(
                 uid = "EVENT_DETAILS_SECTION_UID",
@@ -334,24 +341,37 @@ class DataEntryIntegrationTest {
                 label = "Gender",
                 programStageSection = "EVENT_DATA_SECTION_UID",
                 autocompleteList = emptyList(),
-                optionSetConfiguration = OptionSetConfiguration.DefaultOptionSet(
-                    options = listOf(
-                        Option.builder()
-                            .uid("rBvjJYbMCVx")
-                            .code("Male")
-                            .displayName("Male")
-                            .name("Male")
-                            .sortOrder(1)
-                            .build(),
-                        Option.builder()
-                            .uid("Mnp3oXrpAbK")
-                            .code("Female")
-                            .displayName("Female")
-                            .name("Female")
-                            .sortOrder(2)
-                            .build(),
-                    ),
-                    optionMetadataIcon = emptyMap(),
+                optionSetConfiguration = OptionSetConfiguration(
+                    optionSearchFlow,
+                    { optionSearchFlow.value = it },
+                    optionSearchFlow.flatMapLatest {
+                        flow {
+                            PagingData.from(
+                                listOf(
+                                    OptionSetConfiguration.OptionData(
+                                        Option.builder()
+                                            .uid("rBvjJYbMCVx")
+                                            .code("Male")
+                                            .displayName("Male")
+                                            .name("Male")
+                                            .sortOrder(1)
+                                            .build(),
+                                        MetadataIconData.defaultIcon(),
+                                    ),
+                                    OptionSetConfiguration.OptionData(
+                                        Option.builder()
+                                            .uid("Mnp3oXrpAbK")
+                                            .code("Female")
+                                            .displayName("Female")
+                                            .name("Female")
+                                            .sortOrder(2)
+                                            .build(),
+                                        MetadataIconData.defaultIcon(),
+                                    ),
+                                ),
+                            )
+                        }
+                    },
                 ),
                 valueType = ValueType.MULTI_TEXT,
                 mandatory = true,
diff --git a/form/src/test/java/org/dhis2/form/ui/event/UiEventFactoryImplTest.kt b/form/src/test/java/org/dhis2/form/ui/event/UiEventFactoryImplTest.kt
deleted file mode 100644
index c30f4db510..0000000000
--- a/form/src/test/java/org/dhis2/form/ui/event/UiEventFactoryImplTest.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-package org.dhis2.form.ui.event
-
-import org.dhis2.form.model.FieldUiModelImpl
-import org.dhis2.form.model.UiEventType
-import org.hamcrest.MatcherAssert.assertThat
-import org.hisp.dhis.android.core.common.ValueType
-import org.junit.Test
-
-class UiEventFactoryImplTest {
-
-    private var uiEventFactory: UiEventFactory? = null
-
-    @Test
-    fun `Should return UiEvent OpenCustomCalendar for ValueType DATE`() {
-        uiEventFactory = provideEventForType(ValueType.DATE)
-
-        val event = uiEventFactory?.generateEvent(
-            value = "2021-09-27",
-            uiEventType = UiEventType.DATE_TIME,
-            fieldUiModel = provideFieldUiModel(),
-        )
-        assertThat(
-            "Event is OpenCustomCalendar",
-            event is RecyclerViewUiEvents.OpenCustomCalendar,
-        )
-    }
-
-    @Test
-    fun `Should return UiEvent OpenCustomCalendar for ValueType DATETIME`() {
-        uiEventFactory = provideEventForType(ValueType.DATETIME)
-
-        val event = uiEventFactory?.generateEvent(
-            value = "2021-09-27T10:20",
-            uiEventType = UiEventType.DATE_TIME,
-            fieldUiModel = provideFieldUiModel(),
-        )
-        assertThat(
-            "Event is OpenCustomCalendar",
-            event is RecyclerViewUiEvents.OpenCustomCalendar,
-        )
-    }
-
-    @Test
-    fun `Should return UiEvent OpenCustomCalendar for ValueType TIME`() {
-        uiEventFactory = provideEventForType(ValueType.TIME)
-
-        val event = uiEventFactory?.generateEvent(
-            value = "10:20",
-            uiEventType = UiEventType.DATE_TIME,
-            fieldUiModel = provideFieldUiModel(),
-        )
-        assertThat(
-            "Event is OpenTimePicker",
-            event is RecyclerViewUiEvents.OpenTimePicker,
-        )
-    }
-
-    private fun provideEventForType(valueType: ValueType) = UiEventFactoryImpl(
-        uid = "uid",
-        label = "label",
-        description = "description",
-        valueType = valueType,
-        true,
-        null,
-    )
-
-    private fun provideFieldUiModel() = FieldUiModelImpl(
-        uid = "uid",
-        label = "label",
-        valueType = ValueType.TEXT,
-        optionSetConfiguration = null,
-        autocompleteList = null,
-    )
-}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index fe0f21ba6b..bbe4029d56 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -7,7 +7,7 @@ gradle = "8.6.1"
 kotlin = '2.0.20'
 hilt = '2.47'
 jacoco = '0.8.10'
-designSystem = "0.4.0"
+designSystem = "0.4.0.1-SNAPSHOT"
 dhis2sdk = "1.11.0.1-SNAPSHOT"
 ruleEngine = "3.0.0"
 expressionParser = "1.1.0"

From f0fddc88bf6dfea2bb3b0f724073134d22945879 Mon Sep 17 00:00:00 2001
From: Xavier Molloy <xavi@dhis2.org>
Date: Tue, 3 Dec 2024 12:08:36 +0100
Subject: [PATCH 06/11] build: update sdk and mobile ui to release artifacts

---
 gradle/libs.versions.toml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index bbe4029d56..187b14725f 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -7,8 +7,8 @@ gradle = "8.6.1"
 kotlin = '2.0.20'
 hilt = '2.47'
 jacoco = '0.8.10'
-designSystem = "0.4.0.1-SNAPSHOT"
-dhis2sdk = "1.11.0.1-SNAPSHOT"
+designSystem = "0.4.0.1"
+dhis2sdk = "1.11.0.1"
 ruleEngine = "3.0.0"
 expressionParser = "1.1.0"
 appcompat = "1.6.1"

From ffd2b1baa4eed8db78e97817315ad382ab5b7130 Mon Sep 17 00:00:00 2001
From: Xavier Molloy <xavi@dhis2.org>
Date: Tue, 3 Dec 2024 12:12:05 +0100
Subject: [PATCH 07/11] build: update version code

---
 gradle/libs.versions.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 187b14725f..9d65f0672a 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,7 +1,7 @@
 [versions]
 sdk = "34"
 minSdk = "21"
-vCode = "137"
+vCode = "138"
 vName = "3.1.0.1"
 gradle = "8.6.1"
 kotlin = '2.0.20'

From c6c0d837c658165c282d34e3d21eab89f19801de Mon Sep 17 00:00:00 2001
From: Xavier Molloy <xavi@dhis2.org>
Date: Tue, 3 Dec 2024 17:38:27 +0100
Subject: [PATCH 08/11] build: 3.1.0.1 release

---
 RELEASE.md              | 59 ++++-------------------------------------
 whatsnew/whatsnew-en-US | 14 +++++-----
 2 files changed, 11 insertions(+), 62 deletions(-)

diff --git a/RELEASE.md b/RELEASE.md
index a42609a8c0..39a76ba947 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -1,58 +1,9 @@
+# Release notes - Android App for DHIS2 - 3.1.0.1
 
-## NEW FUNCTIONALITY AND WEB PARITY
+### Bug
 
-**New Capture Coordinates process:** The 3.1 version introduces a list of new features designed to enhance the capture coordinates process. These improvements aim to provide greater accuracy, flexibility, and control over location data capture.
-- **Accuracy:** The capture coordinates process now includes a feature that displays the precision of the captured location. This allows users to see how accurate their location data is in real-time. This parameter can also be restricted using the Android Settings WebApp.
-- **Search Functionality:** A new search functionality has been added, allowing users to look up specific locations by name or address. Users are also able to navigate through the map and perform area searches to discover other locations within a specified region.
-- **Block Manual Capture:** Using the Android Settings Web App, administrators now have the option to block manual location capture. When this setting is enabled, users can only capture the current location and cannot manually select or search a different one. This ensures that location data remains consistent and accurate.
+[ANDROAPP-6653](https://dhis2.atlassian.net/browse/ANDROAPP-6653) Large option sets freeze the app
 
-[Jira](https://dhis2.atlassian.net/browse/ANDROAPP-6330) | [Card1](https://s3.eu-west-1.amazonaws.com/content.dhis2.org/dhis2-android/release+notes+3.1/release+cards/Android-3-1-disabled-manual-capture.png) | [Card2](https://s3.eu-west-1.amazonaws.com/content.dhis2.org/dhis2-android/release+notes+3.1/release+cards/Android-3-1-map-accuracy.png) | [Card3](https://s3.eu-west-1.amazonaws.com/content.dhis2.org/dhis2-android/release+notes+3.1/release+cards/Android-3-1-map-search.png) | [Documentation](https://docs.dhis2.org/en/use/android-app/program-features.html#capture_app_programs_common_features_map_accuracy)
+[ANDROAPP-6665](https://dhis2.atlassian.net/browse/ANDROAPP-6665) Filters persists when exiting the program or data set
 
-**Improve transfers flow:** Significant enhancements to the transfer flow, aimed at making the process more user-friendly and transparent. The transfer button has been moved to a more accessible location within the three dot menu in the TEI Dashboard, ensuring that users can easily find and initiate transfers without unnecessary navigation. It also has introduced new dialogs throughout the transfer process. These dialogs provide clear, step-by-step guidance, ensuring that users understand each part of the process.
-
-[Jira](https://dhis2.atlassian.net/browse/ANDROAPP-6228) | [Documentation](https://docs.dhis2.org/en/use/android-app/program-features.html#capture_app_programs_transfers)
-
-**New relationship section:** Major updates have been made in the relationship tabs,  enhancing both functionality and user experience. Relationship cards have been updated with the new design to offer a more intuitive and visually appealing experience. The new design emphasizes clarity and usability, making it easier to view and manage relationships at a glance.
-
-To prevent accidental deletions and enhance user control, a new confirmation dialog also has been added when deleting a relationship. This dialog will prompt users to confirm their action, ensuring that relationships are only deleted intentionally.
-
-[Jira](https://dhis2.atlassian.net/browse/ANDROAPP-6362) | [Card1](https://s3.eu-west-1.amazonaws.com/content.dhis2.org/dhis2-android/release+notes+3.1/release+cards/Android-3-1-relationship-sections.png) | [Card2](https://s3.eu-west-1.amazonaws.com/content.dhis2.org/dhis2-android/release+notes+3.1/release+cards/Android-3-1-new-relationship-cards.png) | [Card3](https://s3.eu-west-1.amazonaws.com/content.dhis2.org/dhis2-android/release+notes+3.1/release+cards/Android-3-1-relationship-deletion.png) | [Documentation](https://docs.dhis2.org/en/use/android-app/program-features.html#capture_app_programs_common_features_relationships)
-
-**Sort of unique attributes in the search screen:** Aimed at aligning it with the web instance for a more consistent user experience, this version of the Android app, by default, sorts the unique attributes (QR, barcode) at the top of the list of searchable attributes. Users can quickly and easily find the attributes for a more exact search.
-
-[Jira](https://dhis2.atlassian.net/browse/ANDROAPP-6039) | [Documentation](https://docs.dhis2.org/en/use/android-app/program-features.html#capture_app_programs_unique_qrBar_search)
-
-**Support of biometric dialog:** An enhancement to the biometric authentication feature has been made in 3.1.0. When there is only one account configured, the user can configure biometric authentication (fingerprint or face ID).
-
-[Jira](https://dhis2.atlassian.net/browse/ANDROAPP-4676) | [Documentation](https://docs.dhis2.org/en/use/android-app/android-specific-features.html#capture_app_generic_biometrics_login)
-
-**Line Listing improvements:** This version of the Android App introduces support for the Category Option Dimension in line listings. This enhancement enables users to apply category options directly within line listings to filter data according to precise criteria, improving data exploration and decision-making processes. This feature greatly enhances the versatility and utility of line listings, empowering users to perform more sophisticated reporting.
-
-Additionally, it has been improved the text alignment within the Line Listing tables to support left alignment. This enhancement ensures better readability and a cleaner presentation of data, making it easier for users to review and analyze their information quickly.
-
-[Jira1](https://dhis2.atlassian.net/browse/ANDROAPP-6353) | [Jira2](https://dhis2.atlassian.net/browse/ANDROAPP-6121) | [Documentation](https://docs.dhis2.org/en/use/android-app/visual-configurations.html#capture_app_visual_event_visualizations)
-
-## USER EXPERIENCE
-
-**Responsive Home Screen:** In this Android App version a new dynamic home screen that adapts to the number of programs available has been implemented. This update replaces the old static list that didn’t adjust to the screen, providing a more responsive and user-friendly interface.The responsive design makes better use of screen real estate, providing a more engaging and functional home screen layout.
-
-[Jira](https://dhis2.atlassian.net/browse/ANDROAPP-5394) | [Card](https://s3.eu-west-1.amazonaws.com/content.dhis2.org/dhis2-android/release+notes+3.1/release+cards/Android-3-1-responsive-home-screen.png) | [Documentation](https://docs.dhis2.org/en/use/android-app/android-specific-features.html#capture_app_home)
-
-**Scheduled events dialog:** As a continuation of the new schedule dialog introduced in the version 3.0, a new  intuitive and user-friendly schedule dialog has been implemented to enhance the overall user experience, making it easier to book, reschedule, or cancel events.
-
-[Jira](https://dhis2.atlassian.net/browse/ANDROAPP-6229) | [Card1](https://s3.eu-west-1.amazonaws.com/content.dhis2.org/dhis2-android/release+notes+3.1/release+cards/Android-3-1-schedule-new.png) | [Card2](https://s3.eu-west-1.amazonaws.com/content.dhis2.org/dhis2-android/release+notes+3.1/release+cards/Android-3-1-enter-cancel-reschedule.png) | [Documentation](https://docs.dhis2.org/en/use/android-app/program-features.html#capture_app_programs_scheduling)
-
-**Improve menus and navigation bar:** A revamped of the menus and navigation bar has been made to be more user-friendly and accessible. It includes a cleaner, more modern look that improves readability and usability. These updates are designed to provide a more efficient and enjoyable user experience.
-
-[Jira1](https://dhis2.atlassian.net/browse/ANDROAPP-6036) | [Jira2](https://dhis2.atlassian.net/browse/ANDROAPP-6113) | [Card1](https://s3.eu-west-1.amazonaws.com/content.dhis2.org/dhis2-android/release+notes+3.1/release+cards/Android-3-1-menu.png) | [Card2](https://s3.eu-west-1.amazonaws.com/content.dhis2.org/dhis2-android/release+notes+3.1/release+cards/Android-3-1-navigation-bar.png) | [Documentation
-](https://docs.dhis2.org/en/use/android-app/visual-configurations.html#capture_app_visual_menu_bars_update)
-## CROSS PRODUCT
-
-**Support for customized Tracker terminology:** Some DHIS2 terminology is not familiar for the end users. For this reason, we are gradually enabling the possibility to customize it to each particular use case. In this version, the term "event" (program label context) is customizable. The admin user will be able to configure it for each program using the Maintenance App, and the Android Capture App will display the customized term instead of the generic one.
-
-[Jira](https://dhis2.atlassian.net/browse/ANDROAPP-5947) | [Documentation](https://docs.dhis2.org/en/use/android-app/program-features.html#capture_app_programs_common_features_customized_terminology)
-
----
-
-##### **DETAILS**
-You can find the list of all new features and all bugs fixed in 3.1.0 [here.](https://dhis2.atlassian.net/projects/ANDROAPP/versions/10851/tab/release-report-all-issues)
+[ANDROAPP-6691](https://dhis2.atlassian.net/browse/ANDROAPP-6691) NullPointerException: Dataset table
\ No newline at end of file
diff --git a/whatsnew/whatsnew-en-US b/whatsnew/whatsnew-en-US
index 28fa4b8f3d..d8699e3b3a 100644
--- a/whatsnew/whatsnew-en-US
+++ b/whatsnew/whatsnew-en-US
@@ -1,8 +1,6 @@
-Capture Coordinates: Improved accuracy, search, and manual capture.
-Transfer Flows: Easier access and step-by-step guidance.
-Relationship Section: Redesigned module.
-Unique Attribute Sorting: QR/barcodes sorted for quick searches.
-Biometric Login: For single accounts.
-Line Listings: Category Option filters, text improvements.
-UX Updates: Dynamic home screen, revamped menus, and scheduling dialog.
-Custom Tracker Terminology: Tailor terms like “event” per program.
\ No newline at end of file
+This is a patch version that fixes:
+- ANDROAPP-6653 Large option sets freeze the app
+- ANDROAPP-6665 Filters persists when exiting the program or data set
+- ANDROAPP-6691 NullPointerException: Dataset table
+
+You can find all the details on Jira and Github.
\ No newline at end of file

From 2ee4dbf3b849984b1a9317deac190a70b26df0bf Mon Sep 17 00:00:00 2001
From: Pablo Pajuelo Cabezas <pablo@dhis2.org>
Date: Wed, 4 Dec 2024 11:28:43 +0100
Subject: [PATCH 09/11] fix: options not hiding/showing with program rules

---
 .../org/dhis2/form/data/FormRepositoryImpl.kt | 18 ++++++++++--
 .../data/metadata/FormBaseConfiguration.kt    | 28 +++++++++++++++----
 2 files changed, 38 insertions(+), 8 deletions(-)

diff --git a/form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt b/form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt
index ac791f613d..0895fd5083 100644
--- a/form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt
+++ b/form/src/main/java/org/dhis2/form/data/FormRepositoryImpl.kt
@@ -48,6 +48,7 @@ class FormRepositoryImpl(
     private var runDataIntegrity: Boolean = false
     private var calculationLoop: Int = 0
     private var backupList: List<FieldUiModel> = emptyList()
+    private val fieldsWithOptionEffects = mutableListOf<FieldUiModel>()
 
     private val disableCollapsableSections: Boolean? =
         dataEntryRepository.disableCollapsableSections()
@@ -522,9 +523,22 @@ class FormRepositoryImpl(
                 }
             }
 
+        fieldsWithOptionEffects.forEach { field ->
+            field.optionSet?.let { optionSetUid ->
+                fetchOptions(field.uid, optionSetUid)
+            }
+        }
+
+        fieldsWithOptionEffects.clear()
+
         ruleEffectsResult?.fieldsWithOptionEffects()?.forEach { fieldWithOptionEffect ->
-            itemList.find { it.uid == fieldWithOptionEffect }?.let {
-                it.optionSet?.let { optionSetUid -> fetchOptions(it.uid, optionSetUid) }
+            val item = itemList.find { it.uid == fieldWithOptionEffect }
+
+            item?.let { field ->
+                field.optionSet?.let { optionSetUid ->
+                    fetchOptions(field.uid, optionSetUid)
+                }
+                fieldsWithOptionEffects.add(field)
             }
         }
 
diff --git a/form/src/main/java/org/dhis2/form/data/metadata/FormBaseConfiguration.kt b/form/src/main/java/org/dhis2/form/data/metadata/FormBaseConfiguration.kt
index 511e379d38..cacc2cc5ea 100644
--- a/form/src/main/java/org/dhis2/form/data/metadata/FormBaseConfiguration.kt
+++ b/form/src/main/java/org/dhis2/form/data/metadata/FormBaseConfiguration.kt
@@ -41,12 +41,28 @@ open class FormBaseConfiguration(private val d2: D2) {
                     .getPagingData(10)
         }.map { pagingData ->
             pagingData.filter { option ->
-                !optionsToHide.contains(option.uid()) &&
-                    !optionGroupsToHide.contains(option.uid()) &&
-                    (
-                        optionGroupsToShow.isEmpty() ||
-                            optionGroupsToShow.contains(option.uid())
-                        )
+
+                val optionInGroupToHide = d2.optionModule().optionGroups()
+                    .withOptions()
+                    .byUid().`in`(optionGroupsToHide)
+                    .blockingGet().find { optionGroup ->
+                        optionGroup.options()?.map { it.uid() }?.contains(option.uid()) == true
+                    } != null
+
+                val optionInGroupToShow = d2.optionModule().optionGroups()
+                    .withOptions()
+                    .byUid().`in`(optionGroupsToShow)
+                    .blockingGet().find { optionGroup ->
+                        optionGroup.options()?.map { it.uid() }?.contains(option.uid()) == true
+                    } != null
+
+                val hideOption = if (optionGroupsToShow.isEmpty()) {
+                    optionsToHide.contains(option.uid()) || optionInGroupToHide
+                } else {
+                    !optionInGroupToShow
+                }
+
+                !hideOption
             }
         }
     }

From ba3b8cdfe90b5b868560025cf24f0c710f4fdf4a Mon Sep 17 00:00:00 2001
From: Pablo Pajuelo Cabezas <pablo@dhis2.org>
Date: Wed, 4 Dec 2024 12:42:17 +0100
Subject: [PATCH 10/11] fix: remove code smells

---
 .../data/metadata/FormBaseConfiguration.kt    |  8 ++--
 .../dhis2/form/data/FormRepositoryImplTest.kt | 40 ++++++++++++++++++-
 2 files changed, 42 insertions(+), 6 deletions(-)

diff --git a/form/src/main/java/org/dhis2/form/data/metadata/FormBaseConfiguration.kt b/form/src/main/java/org/dhis2/form/data/metadata/FormBaseConfiguration.kt
index cacc2cc5ea..6155035ccc 100644
--- a/form/src/main/java/org/dhis2/form/data/metadata/FormBaseConfiguration.kt
+++ b/form/src/main/java/org/dhis2/form/data/metadata/FormBaseConfiguration.kt
@@ -45,16 +45,16 @@ open class FormBaseConfiguration(private val d2: D2) {
                 val optionInGroupToHide = d2.optionModule().optionGroups()
                     .withOptions()
                     .byUid().`in`(optionGroupsToHide)
-                    .blockingGet().find { optionGroup ->
+                    .blockingGet().any { optionGroup ->
                         optionGroup.options()?.map { it.uid() }?.contains(option.uid()) == true
-                    } != null
+                    }
 
                 val optionInGroupToShow = d2.optionModule().optionGroups()
                     .withOptions()
                     .byUid().`in`(optionGroupsToShow)
-                    .blockingGet().find { optionGroup ->
+                    .blockingGet().any { optionGroup ->
                         optionGroup.options()?.map { it.uid() }?.contains(option.uid()) == true
-                    } != null
+                    }
 
                 val hideOption = if (optionGroupsToShow.isEmpty()) {
                     optionsToHide.contains(option.uid()) || optionInGroupToHide
diff --git a/form/src/test/java/org/dhis2/form/data/FormRepositoryImplTest.kt b/form/src/test/java/org/dhis2/form/data/FormRepositoryImplTest.kt
index abc6debd58..09fec26410 100644
--- a/form/src/test/java/org/dhis2/form/data/FormRepositoryImplTest.kt
+++ b/form/src/test/java/org/dhis2/form/data/FormRepositoryImplTest.kt
@@ -2,11 +2,14 @@ package org.dhis2.form.data
 
 import androidx.databinding.ObservableField
 import io.reactivex.Flowable
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.emptyFlow
 import org.dhis2.commons.prefs.PreferenceProvider
 import org.dhis2.form.model.ActionType
 import org.dhis2.form.model.EventCategory
 import org.dhis2.form.model.FieldUiModel
 import org.dhis2.form.model.FieldUiModelImpl
+import org.dhis2.form.model.OptionSetConfiguration
 import org.dhis2.form.model.RowAction
 import org.dhis2.form.model.SectionUiModelImpl
 import org.dhis2.form.model.StoreResult
@@ -30,6 +33,7 @@ import org.junit.Before
 import org.junit.Test
 import org.mockito.kotlin.any
 import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.atLeast
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.doReturnConsecutively
 import org.mockito.kotlin.mock
@@ -200,6 +204,14 @@ class FormRepositoryImplTest {
                     mutableMapOf(Pair("field", "uid001")),
                 ),
             ),
+            RuleEffect(
+                "rule2",
+                RuleAction(
+                    "option1",
+                    ProgramRuleActionType.HIDEOPTION.name,
+                    mutableMapOf(Pair("field", "uid004")),
+                ),
+            ),
         )
 
         whenever(dataEntryRepository.isEvent()) doReturn true
@@ -215,12 +227,21 @@ class FormRepositoryImplTest {
             fieldsToUpdate = listOf(FieldWithNewValue("uid001", "newValue")),
             configurationErrors = emptyList(),
             stagesToHide = emptyList(),
-            optionsToHide = emptyMap(),
+            optionsToHide = mapOf(
+                "uid004" to listOf("option1"),
+            ),
             optionGroupsToHide = emptyMap(),
             optionGroupsToShow = emptyMap(),
         )
 
-        verify(rulesUtilsProvider, times(1)).applyRuleEffects(
+        whenever(dataEntryRepository.options(any(), any(), any(), any()))doReturn Pair(
+            MutableStateFlow(""),
+            emptyFlow(),
+        )
+
+        repository.composeList()
+
+        verify(rulesUtilsProvider, atLeast(1)).applyRuleEffects(
             any(),
             any(),
             any(),
@@ -441,6 +462,21 @@ class FormRepositoryImplTest {
             optionSetConfiguration = null,
             autocompleteList = null,
         ),
+        FieldUiModelImpl(
+            uid = "uid004",
+            value = null,
+            label = "field4",
+            valueType = ValueType.TEXT,
+            programStageSection = "section1",
+            uiEventFactory = null,
+            optionSet = "optionSetUid",
+            optionSetConfiguration = OptionSetConfiguration(
+                MutableStateFlow(""),
+                {},
+                emptyFlow(),
+            ),
+            autocompleteList = null,
+        ),
     )
 
     private fun section1() = SectionUiModelImpl(

From b72122ff8fa8a47b90353aa47602d5562165299b Mon Sep 17 00:00:00 2001
From: Xavier Molloy <xavi@dhis2.org>
Date: Thu, 5 Dec 2024 08:41:14 +0100
Subject: [PATCH 11/11] fix: [ANDROAPP-6703] refresh selected item when
 inputCardData list is modified

---
 .../ui/provider/inputfield/FieldProvider.kt   |  7 ------
 .../inputfield/MatrixInputProvider.kt         |  2 --
 .../inputfield/MatrixSequentialUtilites.kt    | 25 +------------------
 .../inputfield/SequentialInputProvider.kt     |  3 ---
 4 files changed, 1 insertion(+), 36 deletions(-)

diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt
index 7b415964ef..2515ddf353 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/FieldProvider.kt
@@ -1,6 +1,5 @@
 package org.dhis2.form.ui.provider.inputfield
 
-import android.content.Context
 import android.content.Intent
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.focusable
@@ -23,7 +22,6 @@ import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.TextFieldValue
@@ -68,7 +66,6 @@ fun FieldProvider(
     focusManager: FocusManager,
     onNextClicked: () -> Unit,
 ) {
-    val context = LocalContext.current
     val bringIntoViewRequester = remember { BringIntoViewRequester() }
     val focusRequester = remember { FocusRequester() }
     var visibleArea by remember { mutableStateOf(Rect.Zero) }
@@ -112,7 +109,6 @@ fun FieldProvider(
             inputStyle = inputStyle,
             fieldUiModel = fieldUiModel,
             intentHandler = intentHandler,
-            context = context,
             fetchOptions = {
                 intentHandler(
                     FormIntent.FetchOptions(
@@ -484,7 +480,6 @@ fun ProvideByOptionSet(
     inputStyle: InputStyle,
     fieldUiModel: FieldUiModel,
     intentHandler: (FormIntent) -> Unit,
-    context: Context,
     fetchOptions: () -> Unit,
 ) {
     when (fieldUiModel.renderingType) {
@@ -516,7 +511,6 @@ fun ProvideByOptionSet(
                 inputStyle = inputStyle,
                 fieldUiModel = fieldUiModel,
                 intentHandler = intentHandler,
-                context = context,
             )
         }
 
@@ -526,7 +520,6 @@ fun ProvideByOptionSet(
                 inputStyle = inputStyle,
                 fieldUiModel = fieldUiModel,
                 intentHandler = intentHandler,
-                context = context,
             )
         }
 
diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MatrixInputProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MatrixInputProvider.kt
index b926d87596..ed0adf2a36 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MatrixInputProvider.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MatrixInputProvider.kt
@@ -1,6 +1,5 @@
 package org.dhis2.form.ui.provider.inputfield
 
-import android.content.Context
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.setValue
@@ -21,7 +20,6 @@ internal fun ProvideMatrixInput(
     modifier: Modifier,
     inputStyle: InputStyle,
     fieldUiModel: FieldUiModel,
-    context: Context,
     intentHandler: (FormIntent) -> Unit,
 ) {
     val inputCardDataList: MutableList<ImageCardData> = mutableListOf()
diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MatrixSequentialUtilites.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MatrixSequentialUtilites.kt
index 6fbe9b727d..a55f3ebcad 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MatrixSequentialUtilites.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/MatrixSequentialUtilites.kt
@@ -4,31 +4,8 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import org.dhis2.form.model.FieldUiModel
-import org.dhis2.ui.MetadataIconData
-import org.hisp.dhis.android.core.option.Option
 import org.hisp.dhis.mobile.ui.designsystem.component.ImageCardData
 
-@Composable
-fun rememberInputCardList(
-    options: List<Option>?,
-    optionMetadataIconMap: Map<String, MetadataIconData>?,
-) = remember(options) {
-    options?.map { option ->
-        val metadataIconData =
-            optionMetadataIconMap?.get(option.uid()) ?: throw IllegalArgumentException()
-
-        var icon = option.style().icon() ?: "dhis2_dhis2_logo_positive"
-        if (!icon.startsWith("dhis2_")) {
-            icon = "dhis2_$icon"
-        }
-        imageCardDataWithUidAndLabel(
-            metadataIconData.imageCardData,
-            option.code() ?: "",
-            option.displayName() ?: "",
-        )
-    } ?: emptyList()
-}
-
 fun imageCardDataWithUidAndLabel(
     imageCardData: ImageCardData,
     optionCode: String,
@@ -42,7 +19,7 @@ fun imageCardDataWithUidAndLabel(
 
 @Composable
 fun rememberSelectedOption(fieldUiModel: FieldUiModel, inputCardDataList: List<ImageCardData>) =
-    remember(fieldUiModel.displayName) {
+    remember(inputCardDataList, fieldUiModel.displayName) {
         mutableStateOf(
             inputCardDataList.find { it.uid == fieldUiModel.displayName || it.label == fieldUiModel.displayName },
         )
diff --git a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/SequentialInputProvider.kt b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/SequentialInputProvider.kt
index acdd7e235a..a1036ceef8 100644
--- a/form/src/main/java/org/dhis2/form/ui/provider/inputfield/SequentialInputProvider.kt
+++ b/form/src/main/java/org/dhis2/form/ui/provider/inputfield/SequentialInputProvider.kt
@@ -1,6 +1,5 @@
 package org.dhis2.form.ui.provider.inputfield
 
-import android.content.Context
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.setValue
@@ -21,7 +20,6 @@ internal fun ProvideSequentialInput(
     modifier: Modifier,
     inputStyle: InputStyle,
     fieldUiModel: FieldUiModel,
-    context: Context,
     intentHandler: (FormIntent) -> Unit,
 ) {
     val inputCardDataList: MutableList<ImageCardData> = mutableListOf()
@@ -38,7 +36,6 @@ internal fun ProvideSequentialInput(
             )
         }
     }
-
     var matrixSelectedItem by rememberSelectedOption(
         fieldUiModel = fieldUiModel,
         inputCardDataList = inputCardDataList,