diff --git a/app/res/layout/entity_select_layout.xml b/app/res/layout/entity_select_layout.xml
index f59231a47f..307cb99d0f 100644
--- a/app/res/layout/entity_select_layout.xml
+++ b/app/res/layout/entity_select_layout.xml
@@ -53,6 +53,7 @@
android:visibility="gone"/>
@@ -63,8 +64,18 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"/>
+
+
+
+
notification-channel-push-notifications
Required CommCare App is not installed on device
Audio Recording Notification
+ Initializing list…
+ Processing %1s out of %2s
+ Finalizing list…
diff --git a/app/src/org/commcare/CommCareApplication.java b/app/src/org/commcare/CommCareApplication.java
index e421ca9fc9..7ef93bb27f 100644
--- a/app/src/org/commcare/CommCareApplication.java
+++ b/app/src/org/commcare/CommCareApplication.java
@@ -97,6 +97,8 @@
import org.commcare.tasks.DataPullTask;
import org.commcare.tasks.DeleteLogs;
import org.commcare.tasks.LogSubmissionTask;
+import org.commcare.tasks.PrimeEntityCache;
+import org.commcare.tasks.PrimeEntityCacheHelper;
import org.commcare.tasks.PurgeStaleArchivedFormsTask;
import org.commcare.tasks.templates.ManagedAsyncTask;
import org.commcare.update.UpdateHelper;
@@ -387,6 +389,7 @@ protected void cancelWorkManagerTasks() {
if (currentApp != null) {
WorkManager.getInstance(this).cancelUniqueWork(
FormSubmissionHelper.getFormSubmissionRequestName(currentApp.getUniqueId()));
+ PrimeEntityCacheHelper.cancelWork();
}
}
@@ -796,7 +799,7 @@ public void onServiceConnected(ComponentName className, IBinder service) {
purgeLogs();
cleanRawMedia();
-
+ PrimeEntityCacheHelper.schedulePrimeEntityCacheWorker();
}
TimedStatsTracker.registerStartSession();
diff --git a/app/src/org/commcare/activities/EntitySelectActivity.java b/app/src/org/commcare/activities/EntitySelectActivity.java
index 7c4fec9b0a..c1f51d0995 100755
--- a/app/src/org/commcare/activities/EntitySelectActivity.java
+++ b/app/src/org/commcare/activities/EntitySelectActivity.java
@@ -22,7 +22,6 @@
import android.widget.TextView;
import android.widget.Toast;
-import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;
import com.jakewharton.rxbinding2.widget.AdapterViewItemClickEvent;
@@ -58,6 +57,7 @@
import org.commcare.utils.EntityDetailUtils;
import org.commcare.utils.EntitySelectRefreshTimer;
import org.commcare.utils.SerializationUtil;
+import org.commcare.utils.StringUtils;
import org.commcare.views.EntityView;
import org.commcare.views.TabbedDetailView;
import org.commcare.views.UserfacingErrorHandling;
@@ -166,6 +166,8 @@ public class EntitySelectActivity extends SaveSessionCommCareActivity
// Handler for displaying alert dialog when no location providers are found
private final LocationNotificationHandler locationNotificationHandler =
new LocationNotificationHandler(this);
+ private AdapterView visibleView;
+ private TextView progressTv;
@Override
public void onCreateSessionSafe(Bundle savedInstanceState) {
@@ -254,7 +256,6 @@ private void setupUI(boolean isOrientationChange) {
setContentView(R.layout.entity_select_layout);
}
- AdapterView visibleView;
GridView gridView = this.findViewById(R.id.screen_entity_select_grid);
ListView listView = this.findViewById(R.id.screen_entity_select_list);
if (shortSelect.shouldBeLaidOutInGrid()) {
@@ -268,6 +269,7 @@ private void setupUI(boolean isOrientationChange) {
gridView.setVisibility(View.GONE);
EntitySelectViewSetup.setupDivider(this, listView, shortSelect.usesEntityTileView());
}
+ progressTv = findViewById(R.id.progress_text);
RxAdapterView.itemClickEvents(visibleView)
.subscribeOn(AndroidSchedulers.mainThread())
.throttleFirst(CLICK_DEBOUNCE_TIME, TimeUnit.MILLISECONDS)
@@ -356,7 +358,7 @@ private void setupUIFromAdapter(AdapterView view) {
if (view instanceof ListView) {
EntitySelectViewSetup.setupDivider(this, (ListView)view, shortSelect.usesEntityTileView());
}
- findViewById(R.id.entity_select_loading).setVisibility(View.GONE);
+ findViewById(R.id.progress_container).setVisibility(View.GONE);
entitySelectSearchUI.setSearchBannerState();
}
@@ -476,6 +478,7 @@ public boolean loadEntities() {
}
if (loader == null && !EntityLoaderTask.attachToActivity(this)) {
+ setProgressText(StringUtils.getStringRobust(this, R.string.entity_list_initializing));
EntityLoaderTask entityLoader = new EntityLoaderTask(shortSelect, evalContext());
entityLoader.attachListener(this);
entityLoader.executeParallel(selectDatum.getNodeset());
@@ -852,16 +855,7 @@ public void deliverLoadResult(List> entities,
List references,
NodeEntityFactory factory, int focusTargetIndex) {
loader = null;
-
- AdapterView visibleView;
- if (shortSelect.shouldBeLaidOutInGrid()) {
- visibleView = ((GridView)this.findViewById(R.id.screen_entity_select_grid));
- } else {
- ListView listView = this.findViewById(R.id.screen_entity_select_list);
- EntitySelectViewSetup.setupDivider(this, listView, shortSelect.usesEntityTileView());
- visibleView = listView;
- }
-
+ setProgressText(StringUtils.getStringRobust(this, R.string.entity_list_finalizing));
adapter = new EntityListAdapter(this, shortSelect, references, entities, factory,
hideActionsFromEntityList, shortSelect.getCustomActions(evalContext()), inAwesomeMode);
visibleView.setAdapter(adapter);
@@ -883,7 +877,7 @@ public void deliverLoadResult(List> entities,
}
}
- findViewById(R.id.entity_select_loading).setVisibility(View.GONE);
+ findViewById(R.id.progress_container).setVisibility(View.GONE);
if (adapter != null) {
// filter by additional session data (search string, callout result data)
@@ -907,6 +901,10 @@ public void deliverLoadResult(List> entities,
}
}
+ private void setProgressText(String message) {
+ progressTv.setText(message);
+ }
+
private void restoreAdapterStateFromSession() {
entitySelectSearchUI.restoreSearchString();
@@ -933,7 +931,7 @@ private void updateSelectedItem(TreeReference selected, boolean forceMove) {
@Override
public void attachLoader(EntityLoaderTask task) {
- findViewById(R.id.entity_select_loading).setVisibility(View.VISIBLE);
+ findViewById(R.id.progress_container).setVisibility(View.VISIBLE);
this.loader = task;
}
@@ -995,6 +993,15 @@ public void deliverLoadError(Exception e) {
displayCaseListLoadException(e);
}
+ @Override
+ public void deliverProgress(Integer[] values) {
+ // throttle to not update text too frequently
+ if (values[0] % 100 == 0) {
+ setProgressText(StringUtils.getStringRobust(this, R.string.entity_list_processing,
+ new String[]{String.valueOf(values[0]), String.valueOf(values[1])}));
+ }
+ }
+
@Override
protected boolean onForwardSwipe() {
// If user has picked an entity, move along to form entry
diff --git a/app/src/org/commcare/entity/AndroidAsyncNodeEntityFactory.kt b/app/src/org/commcare/entity/AndroidAsyncNodeEntityFactory.kt
new file mode 100644
index 0000000000..061baa6205
--- /dev/null
+++ b/app/src/org/commcare/entity/AndroidAsyncNodeEntityFactory.kt
@@ -0,0 +1,90 @@
+package org.commcare.entity
+
+import android.util.Pair
+import org.commcare.cases.entity.AsyncNodeEntityFactory
+import org.commcare.cases.entity.Entity
+import org.commcare.cases.entity.EntityStorageCache
+import org.commcare.suite.model.Detail
+import org.commcare.tasks.PrimeEntityCacheHelper
+import org.commcare.util.LogTypes
+import org.javarosa.core.model.condition.EvaluationContext
+import org.javarosa.core.model.instance.TreeReference
+import org.javarosa.core.services.Logger
+
+/**
+ * Android Specific Implementation of AsyncNodeEntityFactory
+ * Uses [PrimeEntityCacheHelper] to prime entity cache blocking the user when required
+ */
+class AndroidAsyncNodeEntityFactory(d: Detail, ec: EvaluationContext?, entityStorageCache: EntityStorageCache?) :
+ AsyncNodeEntityFactory(d, ec, entityStorageCache), PrimeEntityCacheListener {
+
+ companion object {
+ const val TWO_MINUTES = 2 * 60 * 1000
+ }
+
+ private var cachedEntities: List>? = null
+ private var completedCachePrime = false
+
+ override fun prepareEntitiesInternal(entities: MutableList>) {
+ if (detail.shouldCache()) {
+ // we only want to block if lazy load is not enabled
+ if (!detail.shouldLazyLoad()) {
+ val primeEntityCacheHelper = PrimeEntityCacheHelper.getInstance()
+ if (primeEntityCacheHelper.isInProgress()) {
+ // if we are priming something else at the moment, expedite the current detail
+ if (!primeEntityCacheHelper.isDetailInProgress(detail.id)) {
+ primeEntityCacheHelper.expediteDetailWithId(detail, entities)
+ } else {
+ // otherwise wait for existing prime process to complete
+ primeEntityCacheHelper.setListener(this)
+ waitForCachePrimeWork(entities, primeEntityCacheHelper)
+ if (cachedEntities != null) {
+ entities.clear()
+ entities.addAll(cachedEntities!!)
+ }
+ }
+ } else {
+ // we either have not started priming or already completed. In both cases
+ // we want to re-prime to make sure we calculate any uncalculated data first
+ primeEntityCacheHelper.primeEntityCacheForDetail(detail, entities)
+ }
+ }
+ } else {
+ super.prepareEntitiesInternal(entities)
+ }
+ }
+
+ private fun waitForCachePrimeWork(
+ entities: MutableList>,
+ primeEntityCacheHelper: PrimeEntityCacheHelper
+ ) {
+ val startTime = System.currentTimeMillis()
+ while (!completedCachePrime && (System.currentTimeMillis() - startTime) < TWO_MINUTES) {
+ // wait for it to be completed
+ try {
+ Thread.sleep(100)
+ } catch (_: InterruptedException) {
+ }
+ }
+ if (!completedCachePrime) {
+ Logger.log(LogTypes.TYPE_MAINTENANCE, "Still Waiting for cache priming work to complete")
+ // confirm if we are still priming in the worker. If yes, wait more
+ // otherwise recall prepareEntitiesInternal to re-evaluate the best thing to do
+ if (primeEntityCacheHelper.isInProgress() && primeEntityCacheHelper.isDetailInProgress(detail.id)) {
+ waitForCachePrimeWork(entities, primeEntityCacheHelper)
+ } else {
+ prepareEntitiesInternal(entities)
+ }
+ }
+ }
+
+ override fun onPrimeEntityCacheComplete(
+ currentDetailInProgress: String,
+ cachedEntitiesWithRefs: Pair>, List>
+ ) {
+ if (detail.id!!.contentEquals(currentDetailInProgress)) {
+ cachedEntities = cachedEntitiesWithRefs.first
+ completedCachePrime = true
+ }
+ }
+}
diff --git a/app/src/org/commcare/entity/PrimeEntityCacheListener.kt b/app/src/org/commcare/entity/PrimeEntityCacheListener.kt
new file mode 100644
index 0000000000..eaeb0e9d19
--- /dev/null
+++ b/app/src/org/commcare/entity/PrimeEntityCacheListener.kt
@@ -0,0 +1,13 @@
+package org.commcare.entity
+
+import android.util.Pair
+import org.commcare.cases.entity.Entity
+import org.javarosa.core.model.instance.TreeReference
+
+interface PrimeEntityCacheListener {
+
+ fun onPrimeEntityCacheComplete(
+ currentDetailInProgress: String,
+ cachedEntitiesWithRefs: Pair>, List>
+ )
+}
diff --git a/app/src/org/commcare/fragments/EntitySubnodeDetailFragment.java b/app/src/org/commcare/fragments/EntitySubnodeDetailFragment.java
index a66905956a..f8abe5244c 100755
--- a/app/src/org/commcare/fragments/EntitySubnodeDetailFragment.java
+++ b/app/src/org/commcare/fragments/EntitySubnodeDetailFragment.java
@@ -103,4 +103,9 @@ public void deliverLoadResult(List> entities,
public void deliverLoadError(Exception e) {
((CommCareActivity)getActivity()).displayCaseListLoadException(e);
}
+
+ @Override
+ public void deliverProgress(Integer[] values) {
+ // nothing to do
+ }
}
diff --git a/app/src/org/commcare/tasks/DataPullTask.java b/app/src/org/commcare/tasks/DataPullTask.java
index 7f5242b644..761ca366ec 100644
--- a/app/src/org/commcare/tasks/DataPullTask.java
+++ b/app/src/org/commcare/tasks/DataPullTask.java
@@ -437,6 +437,7 @@ private ResultAndError handleBadLocalState(AndroidTransactionPar
if (returnCode == PROGRESS_DONE) {
// Recovery was successful
onSuccessfulSync();
+ PrimeEntityCacheHelper.schedulePrimeEntityCacheWorker();
return new ResultAndError<>(PullTaskResult.DOWNLOAD_SUCCESS);
} else if (returnCode == PROGRESS_RECOVERY_FAIL_SAFE || returnCode == PROGRESS_RECOVERY_FAIL_BAD) {
wipeLoginIfItOccurred();
diff --git a/app/src/org/commcare/tasks/EntityLoaderHelper.kt b/app/src/org/commcare/tasks/EntityLoaderHelper.kt
index d4b40aeaf7..31fb99967f 100644
--- a/app/src/org/commcare/tasks/EntityLoaderHelper.kt
+++ b/app/src/org/commcare/tasks/EntityLoaderHelper.kt
@@ -7,6 +7,7 @@ import org.commcare.cases.entity.AsyncNodeEntityFactory
import org.commcare.cases.entity.Entity
import org.commcare.cases.entity.EntityStorageCache
import org.commcare.cases.entity.NodeEntityFactory
+import org.commcare.entity.AndroidAsyncNodeEntityFactory
import org.commcare.models.database.user.models.CommCareEntityStorageCache
import org.commcare.preferences.DeveloperPreferences
import org.commcare.suite.model.Detail
@@ -26,7 +27,7 @@ class EntityLoaderHelper(
evalCtx.addFunctionHandler(EntitySelectActivity.getHereFunctionHandler())
if (detail.useAsyncStrategy()) {
val entityStorageCache: EntityStorageCache = CommCareEntityStorageCache("case")
- factory = AsyncNodeEntityFactory(detail, evalCtx, entityStorageCache)
+ factory = AndroidAsyncNodeEntityFactory(detail, evalCtx, entityStorageCache)
} else {
factory = NodeEntityFactory(detail, evalCtx)
if (DeveloperPreferences.collectAndDisplayEntityTraces()) {
@@ -38,9 +39,12 @@ class EntityLoaderHelper(
/**
* Loads and prepares a list of entities derived from the given nodeset
*/
- fun loadEntities(nodeset: TreeReference): Pair>, List>? {
+ fun loadEntities(
+ nodeset: TreeReference,
+ progressListener: EntityLoadingProgressListener
+ ): Pair>, List>? {
val references = factory.expandReferenceList(nodeset)
- val entities = loadEntitiesWithReferences(references)
+ val entities = loadEntitiesWithReferences(references, progressListener)
entities?.let {
factory.prepareEntities(entities)
factory.printAndClearTraces("build")
@@ -49,15 +53,32 @@ class EntityLoaderHelper(
return null
}
+ /**
+ * Primes the entity cache
+ */
+ fun cacheEntities(nodeset: TreeReference): Pair>, List> {
+ val references = factory.expandReferenceList(nodeset)
+ val entities = loadEntitiesWithReferences(references, null)
+ cacheEntities(entities)
+ return Pair>, List>(entities, references)
+ }
+
+ fun cacheEntities(entities: MutableList>?) {
+ factory.cacheEntities(entities)
+ }
/**
* Loads a list of entities corresponding to the given references
*/
- private fun loadEntitiesWithReferences(references: List): MutableList>? {
+ private fun loadEntitiesWithReferences(
+ references: List,
+ progressListener: EntityLoadingProgressListener?
+ ): MutableList>? {
val entities: MutableList> = ArrayList()
focusTargetIndex = -1
var indexInFullList = 0
- for (ref in references) {
+ for ((index, ref) in references.withIndex()) {
+ progressListener?.publishEntityLoadingProgress(index, references.size)
if (stopLoading) {
return null
}
diff --git a/app/src/org/commcare/tasks/EntityLoaderListener.java b/app/src/org/commcare/tasks/EntityLoaderListener.java
index 6c9d47f2cd..4425957aae 100644
--- a/app/src/org/commcare/tasks/EntityLoaderListener.java
+++ b/app/src/org/commcare/tasks/EntityLoaderListener.java
@@ -13,4 +13,6 @@ void deliverLoadResult(List> entities, List
NodeEntityFactory factory, int focusTargetIndex);
void deliverLoadError(Exception e);
+
+ void deliverProgress(Integer... values);
}
diff --git a/app/src/org/commcare/tasks/EntityLoaderTask.java b/app/src/org/commcare/tasks/EntityLoaderTask.java
index 2f4b6ac58d..5b1fb4e97e 100644
--- a/app/src/org/commcare/tasks/EntityLoaderTask.java
+++ b/app/src/org/commcare/tasks/EntityLoaderTask.java
@@ -18,7 +18,8 @@
* @author ctsims
*/
public class EntityLoaderTask
- extends ManagedAsyncTask>, List>> {
+ extends ManagedAsyncTask>, List>> implements
+ EntityLoadingProgressListener {
private final static Object lock = new Object();
private static EntityLoaderTask pendingTask = null;
@@ -34,7 +35,7 @@ public EntityLoaderTask(Detail detail, EvaluationContext evalCtx) {
@Override
protected Pair>, List> doInBackground(TreeReference... nodeset) {
try {
- return entityLoaderHelper.loadEntities(nodeset[0]);
+ return entityLoaderHelper.loadEntities(nodeset[0], this);
} catch (XPathException xe) {
XPathErrorLogger.INSTANCE.logErrorToCurrentApp(xe);
Logger.exception("Error during EntityLoaderTask: " + ForceCloseLogger.getStackTrace(xe), xe);
@@ -112,4 +113,14 @@ protected void onCancelled() {
super.onCancelled();
entityLoaderHelper.cancel();
}
+
+ @Override
+ public void publishEntityLoadingProgress(int progress, int total) {
+ publishProgress(progress, total);
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... values) {
+ listener.deliverProgress(values);
+ }
}
diff --git a/app/src/org/commcare/tasks/EntityLoadingProgressListener.java b/app/src/org/commcare/tasks/EntityLoadingProgressListener.java
new file mode 100644
index 0000000000..f32c746aa0
--- /dev/null
+++ b/app/src/org/commcare/tasks/EntityLoadingProgressListener.java
@@ -0,0 +1,6 @@
+package org.commcare.tasks;
+
+public interface EntityLoadingProgressListener {
+
+ void publishEntityLoadingProgress(int progress, int total);
+}
diff --git a/app/src/org/commcare/tasks/PrimeEntityCache.kt b/app/src/org/commcare/tasks/PrimeEntityCache.kt
new file mode 100644
index 0000000000..f6a97aa1f9
--- /dev/null
+++ b/app/src/org/commcare/tasks/PrimeEntityCache.kt
@@ -0,0 +1,24 @@
+package org.commcare.tasks
+
+import android.content.Context
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import org.javarosa.core.services.Logger
+
+class PrimeEntityCache(appContext: Context, workerParams: WorkerParameters)
+ : Worker(appContext, workerParams) {
+
+ override fun doWork(): Result {
+ try {
+ PrimeEntityCacheHelper.getInstance().primeEntityCache()
+ return Result.success()
+ } catch (e: Exception) {
+ Logger.exception("Error while priming cache in worker", e)
+ }
+ return Result.failure()
+ }
+
+ override fun onStopped() {
+ PrimeEntityCacheHelper.getInstance().cancel()
+ }
+}
diff --git a/app/src/org/commcare/tasks/PrimeEntityCacheHelper.kt b/app/src/org/commcare/tasks/PrimeEntityCacheHelper.kt
new file mode 100644
index 0000000000..d2a9e572cf
--- /dev/null
+++ b/app/src/org/commcare/tasks/PrimeEntityCacheHelper.kt
@@ -0,0 +1,171 @@
+package org.commcare.tasks
+
+import android.util.Pair
+import androidx.work.ExistingWorkPolicy
+import androidx.work.OneTimeWorkRequest
+import androidx.work.WorkManager
+import io.reactivex.functions.Cancellable
+import org.commcare.CommCareApplication
+import org.commcare.cases.entity.Entity
+import org.commcare.entity.PrimeEntityCacheListener
+import org.commcare.suite.model.Detail
+import org.commcare.suite.model.EntityDatum
+import org.commcare.sync.FormSubmissionHelper
+import org.commcare.utils.AndroidCommCarePlatform
+import org.javarosa.core.model.condition.EvaluationContext
+import org.javarosa.core.model.instance.TreeReference
+import org.javarosa.core.services.Logger
+
+/**
+ * Helper to prime cache for all entity screens in the app
+ *
+ * Implemented as a singleton to restrict caller from starting another
+ * cache prime process if one is already in progress.
+ */
+class PrimeEntityCacheHelper private constructor() : Cancellable {
+
+ private var entityLoaderHelper: EntityLoaderHelper? = null
+ private var inProgress = false
+ private var currentDetailInProgress: String? = null
+ private var listener: PrimeEntityCacheListener? = null
+
+ companion object {
+ @Volatile
+ private var instance: PrimeEntityCacheHelper? = null
+
+ const val PRIME_ENTITY_CACHE_REQUEST = "prime-entity-cache-request"
+
+ @JvmStatic
+ fun getInstance() =
+ instance ?: synchronized(this) {
+ instance ?: PrimeEntityCacheHelper().also { instance = it }
+ }
+
+ /**
+ * Schedules a background worker request to prime cache for all
+ * cache backed entity list screens in the current seated app
+ */
+ @JvmStatic
+ fun schedulePrimeEntityCacheWorker() {
+ val primeEntityCacheRequest = OneTimeWorkRequest.Builder(PrimeEntityCache::class.java).build()
+ WorkManager.getInstance(CommCareApplication.instance())
+ .enqueueUniqueWork(
+ PRIME_ENTITY_CACHE_REQUEST,
+ ExistingWorkPolicy.KEEP,
+ primeEntityCacheRequest
+ )
+ }
+
+ @JvmStatic
+ fun cancelWork() {
+ WorkManager.getInstance(CommCareApplication.instance()).cancelUniqueWork(PRIME_ENTITY_CACHE_REQUEST)
+ }
+ }
+
+ /**
+ * Primes cache for all entity screens in the app
+ * @throws IllegalStateException if a cache prime is already in progress or user session is not active
+ */
+ fun primeEntityCache() {
+ checkPreConditions()
+ primeEntityCacheForApp(CommCareApplication.instance().commCarePlatform)
+ clearState()
+ }
+
+ /**
+ * Primes cache for given entities set against the [detail]
+ * @throws IllegalStateException if a cache prime is already in progress or user session is not active
+ */
+ fun primeEntityCacheForDetail(
+ detail: Detail,
+ entities: MutableList>
+ ) {
+ checkPreConditions()
+ primeCacheForDetail(detail, null, entities)
+ clearState()
+ }
+
+ /**
+ * Cancel any current cache prime process to expedite cache calculations for given [detail]
+ * Reschedules the work again in background afterwards
+ */
+ fun expediteDetailWithId(detail: Detail, entities: MutableList>) {
+ cancel()
+ primeEntityCacheForDetail(detail, entities)
+ schedulePrimeEntityCacheWorker()
+ }
+
+ fun isDetailInProgress(detailId: String): Boolean {
+ return currentDetailInProgress?.contentEquals(detailId) ?: false
+ }
+
+ private fun primeEntityCacheForApp(commCarePlatform: AndroidCommCarePlatform) {
+ inProgress = true
+ val commandMap = commCarePlatform.commandToEntryMap
+ for (command in commandMap.keys()) {
+ val entry = commandMap[command]!!
+ val sessionDatums = entry.sessionDataReqs
+ for (sessionDatum in sessionDatums) {
+ if (sessionDatum is EntityDatum) {
+ val shortDetailId = sessionDatum.shortDetail
+ if (shortDetailId != null) {
+ val detail = commCarePlatform.getDetail(shortDetailId)
+ primeCacheForDetail(detail, sessionDatum)
+ }
+ }
+ }
+ }
+ }
+
+ private fun primeCacheForDetail(detail: Detail, sessionDatum: EntityDatum? = null, entities: MutableList>? = null) {
+ if (!detail.shouldCache()) return
+ currentDetailInProgress = detail.id
+ entityLoaderHelper = EntityLoaderHelper(detail, evalCtx())
+
+ // Handle the cache operation based on the available input
+ val cachedEntitiesWithRefs = when {
+ sessionDatum != null -> entityLoaderHelper!!.cacheEntities(sessionDatum.nodeset)
+ entities != null -> {
+ entityLoaderHelper!!.cacheEntities(entities)
+ Pair(entities, null) as Pair>, List>
+ }
+ else -> return
+ }
+
+ // Call the listener with the appropriate result
+ listener?.onPrimeEntityCacheComplete(currentDetailInProgress!!,
+ cachedEntitiesWithRefs
+ )
+ currentDetailInProgress = null
+ }
+
+ private fun evalCtx(): EvaluationContext {
+ return CommCareApplication.instance().currentSessionWrapper.evaluationContext
+ }
+
+ private fun clearState() {
+ entityLoaderHelper = null
+ inProgress = false
+ listener = null
+ currentDetailInProgress = null
+ instance = null
+ }
+
+ private fun checkPreConditions() {
+ require(CommCareApplication.instance().session.isActive) { "User session must be active to prime entity cache" }
+ require(!inProgress) { "We are already priming the cache" }
+ }
+
+ override fun cancel() {
+ entityLoaderHelper?.cancel()
+ clearState()
+ }
+
+ fun isInProgress(): Boolean {
+ return inProgress
+ }
+
+ fun setListener(primeEntityCacheListener: PrimeEntityCacheListener) {
+ listener = primeEntityCacheListener
+ }
+}