diff --git a/app/src/org/commcare/activities/EntitySelectActivity.java b/app/src/org/commcare/activities/EntitySelectActivity.java index a01309c4d..3b396ea02 100755 --- a/app/src/org/commcare/activities/EntitySelectActivity.java +++ b/app/src/org/commcare/activities/EntitySelectActivity.java @@ -480,7 +480,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(), this); + EntityLoaderTask entityLoader = new EntityLoaderTask(shortSelect, evalContext()); entityLoader.attachListener(this); entityLoader.executeParallel(selectDatum.getNodeset()); return true; diff --git a/app/src/org/commcare/entity/AndroidAsyncNodeEntityFactory.kt b/app/src/org/commcare/entity/AndroidAsyncNodeEntityFactory.kt index 6a00dfb00..6a4e96ef1 100644 --- a/app/src/org/commcare/entity/AndroidAsyncNodeEntityFactory.kt +++ b/app/src/org/commcare/entity/AndroidAsyncNodeEntityFactory.kt @@ -1,6 +1,8 @@ package org.commcare.entity -import androidx.lifecycle.LifecycleOwner +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout import org.commcare.CommCareApplication import org.commcare.cases.entity.AsyncNodeEntityFactory import org.commcare.cases.entity.Entity @@ -19,17 +21,13 @@ import org.javarosa.core.services.Logger class AndroidAsyncNodeEntityFactory( d: Detail, ec: EvaluationContext?, - entityStorageCache: EntityStorageCache?, - private val lifecycleOwner: LifecycleOwner? = null -) : - AsyncNodeEntityFactory(d, ec, entityStorageCache) { + entityStorageCache: EntityStorageCache? +) : AsyncNodeEntityFactory(d, ec, entityStorageCache) { companion object { - const val TWO_MINUTES = 2 * 60 * 1000 + const val TEN_MINUTES = 10 * 60 * 1000L } - private var completedCachePrime = false - override fun prepareEntitiesInternal( entities: MutableList> ) { @@ -40,24 +38,25 @@ class AndroidAsyncNodeEntityFactory( if (primeEntityCacheHelper.isInProgress()) { // if we are priming something else at the moment, expedite the current detail if (!primeEntityCacheHelper.isDetailInProgress(detail.id)) { - primeEntityCacheHelper.expediteDetailWithId(getCurrentCommandId(), detail, entities, progressListener) + primeEntityCacheHelper.expediteDetailWithId( + getCurrentCommandId(), + detail, + entities, + progressListener + ) } else { primeEntityCacheHelper.setListener(progressListener) - primeEntityCacheHelper.cachedEntitiesLiveData.observe(lifecycleOwner!!) { cachedEntities -> - if (cachedEntities != null) { - entities.clear() - entities.addAll(cachedEntities) - primeEntityCacheHelper.cachedEntitiesLiveData.removeObservers(lifecycleOwner) - } - } - - // otherwise wait for existing prime process to complete - waitForCachePrimeWork(entities, primeEntityCacheHelper) + observePrimeCacheWork(primeEntityCacheHelper, entities) } } 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(getCurrentCommandId(), detail, entities, progressListener) + primeEntityCacheHelper.primeEntityCacheForDetail( + getCurrentCommandId(), + detail, + entities, + progressListener + ) } } } else { @@ -65,31 +64,39 @@ class AndroidAsyncNodeEntityFactory( } } - private fun getCurrentCommandId(): String { - return CommCareApplication.instance().currentSession.command - } - - private fun waitForCachePrimeWork( - entities: MutableList>, + private fun observePrimeCacheWork( primeEntityCacheHelper: PrimeEntityCacheHelper, + entities: MutableList> ) { - val startTime = System.currentTimeMillis() - while (!completedCachePrime && (System.currentTimeMillis() - startTime) < TWO_MINUTES) { - // wait for it to be completed + runBlocking { 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) + withTimeout(TEN_MINUTES) { + primeEntityCacheHelper.cachedEntitiesState.collect { cachedEntities -> + if (cachedEntities != null) { + entities.clear() + entities.addAll(cachedEntities) + return@collect + } + } + } + } catch (e: TimeoutCancellationException) { + Logger.log( + LogTypes.TYPE_MAINTENANCE, + "Timeout while waiting for the prime cache worker to finish" + ) + if (primeEntityCacheHelper.isInProgress() && + primeEntityCacheHelper.isDetailInProgress(detail.id) + ) { + // keep observing + observePrimeCacheWork(primeEntityCacheHelper, entities) + } else { + prepareEntitiesInternal(entities) + } } } } + + private fun getCurrentCommandId(): String { + return CommCareApplication.instance().currentSession.command + } } diff --git a/app/src/org/commcare/fragments/EntitySubnodeDetailFragment.java b/app/src/org/commcare/fragments/EntitySubnodeDetailFragment.java index 8e607a80b..f8abe5244 100755 --- a/app/src/org/commcare/fragments/EntitySubnodeDetailFragment.java +++ b/app/src/org/commcare/fragments/EntitySubnodeDetailFragment.java @@ -53,7 +53,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa if (this.adapter == null && this.loader == null && !EntityLoaderTask.attachToActivity(this)) { // Set up task to fetch entity data EntityLoaderTask theLoader = - new EntityLoaderTask(detailToDisplay, getFactoryContextForRef(referenceToDisplay), getActivity()); + new EntityLoaderTask(detailToDisplay, getFactoryContextForRef(referenceToDisplay)); theLoader.attachListener(this); theLoader.executeParallel(detailToDisplay.getNodeset().contextualize(referenceToDisplay)); diff --git a/app/src/org/commcare/tasks/EntityLoaderHelper.kt b/app/src/org/commcare/tasks/EntityLoaderHelper.kt index 2c786e2e6..089df279e 100644 --- a/app/src/org/commcare/tasks/EntityLoaderHelper.kt +++ b/app/src/org/commcare/tasks/EntityLoaderHelper.kt @@ -1,11 +1,8 @@ package org.commcare.tasks -import android.content.Context import android.util.Pair -import androidx.lifecycle.LifecycleOwner import io.reactivex.functions.Cancellable import org.commcare.activities.EntitySelectActivity -import org.commcare.cases.entity.AsyncNodeEntityFactory import org.commcare.cases.entity.Entity import org.commcare.cases.entity.EntityLoadingProgressListener import org.commcare.cases.entity.EntityStorageCache @@ -20,7 +17,6 @@ import org.javarosa.core.model.instance.TreeReference class EntityLoaderHelper( detail: Detail, evalCtx: EvaluationContext, - lifecycleOwner: LifecycleOwner? = null, ) : Cancellable { var focusTargetIndex: Int = -1 @@ -31,7 +27,7 @@ class EntityLoaderHelper( evalCtx.addFunctionHandler(EntitySelectActivity.getHereFunctionHandler()) if (detail.useAsyncStrategy() || detail.shouldCache()) { val entityStorageCache: EntityStorageCache = CommCareEntityStorageCache("case") - factory = AndroidAsyncNodeEntityFactory(detail, evalCtx, entityStorageCache, lifecycleOwner) + factory = AndroidAsyncNodeEntityFactory(detail, evalCtx, entityStorageCache) } else { factory = NodeEntityFactory(detail, evalCtx) if (DeveloperPreferences.collectAndDisplayEntityTraces()) { @@ -105,6 +101,6 @@ class EntityLoaderHelper( override fun cancel() { stopLoading = true - factory.cancel() + factory.cancelLoading() } } diff --git a/app/src/org/commcare/tasks/EntityLoaderTask.java b/app/src/org/commcare/tasks/EntityLoaderTask.java index d9192fc71..3dd206a69 100644 --- a/app/src/org/commcare/tasks/EntityLoaderTask.java +++ b/app/src/org/commcare/tasks/EntityLoaderTask.java @@ -2,8 +2,6 @@ import android.util.Pair; -import androidx.lifecycle.LifecycleOwner; - import org.commcare.android.logging.ForceCloseLogger; import org.commcare.cases.entity.Entity; import org.commcare.cases.entity.EntityLoadingProgressListener; @@ -31,8 +29,8 @@ public class EntityLoaderTask private final EntityLoaderHelper entityLoaderHelper; private Exception mException = null; - public EntityLoaderTask(Detail detail, EvaluationContext evalCtx, LifecycleOwner lifecycleOwner) { - entityLoaderHelper = new EntityLoaderHelper(detail, evalCtx, lifecycleOwner); + public EntityLoaderTask(Detail detail, EvaluationContext evalCtx) { + entityLoaderHelper = new EntityLoaderHelper(detail, evalCtx); } @Override diff --git a/app/src/org/commcare/tasks/PrimeEntityCacheHelper.kt b/app/src/org/commcare/tasks/PrimeEntityCacheHelper.kt index 543850143..74680770f 100644 --- a/app/src/org/commcare/tasks/PrimeEntityCacheHelper.kt +++ b/app/src/org/commcare/tasks/PrimeEntityCacheHelper.kt @@ -1,11 +1,11 @@ package org.commcare.tasks -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import io.reactivex.functions.Cancellable +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import org.commcare.CommCareApplication import org.commcare.cases.entity.Entity import org.commcare.cases.entity.EntityLoadingProgressListener @@ -31,8 +31,8 @@ class PrimeEntityCacheHelper private constructor() : Cancellable { private var currentDetailInProgress: String? = null private var listener: EntityLoadingProgressListener? = null - private val _cachedEntitiesLiveData = MutableLiveData>>() - val cachedEntitiesLiveData: LiveData>> get() = _cachedEntitiesLiveData + private val _cachedEntitiesState = MutableStateFlow>?>(null) + val cachedEntitiesState: StateFlow>?> get() = _cachedEntitiesState companion object { @@ -161,7 +161,7 @@ class PrimeEntityCacheHelper private constructor() : Cancellable { } else -> return } - _cachedEntitiesLiveData.postValue(cachedEntities) + _cachedEntitiesState.value = cachedEntities currentDetailInProgress = null }