Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
2518b23
Adds a worker task to prime entity cache
shubham1g5 Dec 9, 2024
878c09c
Schedule prime cache task after 412 and on initializing user session
shubham1g5 Dec 9, 2024
c1e6a35
Set up progress indicator on entity list screen
shubham1g5 Dec 11, 2024
9aa1220
Creates a Android specific async factory to bind to any existing prim…
shubham1g5 Dec 19, 2024
c4a68a6
Move methods to schedule/cancel cache work to cache helper
shubham1g5 Dec 19, 2024
d78960d
Re-schedule cancelled work when expediting a detail sepcific cache
shubham1g5 Dec 19, 2024
dd05545
Improve progress tracking by providing information on individual enti…
shubham1g5 Dec 23, 2024
acd3aa6
Handle for new cache config while initiating entity factory
shubham1g5 Dec 24, 2024
7122c92
Cancellation handling
shubham1g5 Dec 24, 2024
63743aa
Always clear state after prime cache worker run
shubham1g5 Dec 24, 2024
ede6caf
correct eval context by including command id
shubham1g5 Dec 24, 2024
f145dd6
Fixes progress tracking plus tracking the live worker in async task
shubham1g5 Dec 25, 2024
8b53cbf
Uses datum id to track progress instead of detail as a detail can pot…
shubham1g5 Dec 26, 2024
1e8dfd3
Thread safety
shubham1g5 Dec 26, 2024
32d554e
lint
shubham1g5 Dec 26, 2024
90b0c96
Make clear distinction in between old and new cache and index switch
shubham1g5 Jan 6, 2025
ab5d325
Priming cache for normal and sort values for the detail fields marked…
shubham1g5 Jan 9, 2025
6c9be03
Tweaks
shubham1g5 Jan 10, 2025
dbbcfd4
fixes test dependent on work manager init
shubham1g5 Jan 13, 2025
15cf20a
use a % indicator for progress
shubham1g5 Jan 23, 2025
7198161
Adds init validation for new android async factory
shubham1g5 Jan 23, 2025
aa65ba4
Bind PrimeEntityCacheHelper to CommCareApp lifecycle
shubham1g5 Jan 24, 2025
7860093
Only provide progress updates for new cache config
shubham1g5 Jan 29, 2025
55c8b2b
fixes cache priming bugs
shubham1g5 Feb 3, 2025
5726518
Use livedata/stateflow based observers for traching prime cache progr…
shubham1g5 Feb 3, 2025
d6dcfed
Lazy loading tweaks, introduces a flag to skip lazy loading in backgr…
shubham1g5 Feb 4, 2025
b24d580
Better status message
shubham1g5 Feb 4, 2025
6deec22
rename skipLazyLoad -> inBackground, better logic to calculate uncach…
shubham1g5 Feb 4, 2025
f1c9a26
optimize -> cache_enabled
shubham1g5 Feb 10, 2025
bab015c
Refactor inBackground as a constructor param to entity factory
shubham1g5 Feb 27, 2025
7530f94
reword progress message
shubham1g5 Feb 27, 2025
99329d3
uniformly use getCurrentAppId() when dealing with work manager
shubham1g5 Feb 27, 2025
89a054f
key prime cache request with app id
shubham1g5 Feb 27, 2025
0e7d208
Merge branch 'master' into primeCacheAheadOfTime
shubham1g5 Feb 27, 2025
b73d40e
Adds is_dirty and is_shallow columns to entity cache table and insert…
shubham1g5 Feb 11, 2025
bd45ea8
abstract to getAllOwners [no op refactor]
shubham1g5 Feb 12, 2025
bcf637c
Adds shallow records to cache and propogate them as dirty records in …
shubham1g5 Feb 14, 2025
630ec83
blocks cache priming to process shallow records as the first thing
shubham1g5 Feb 18, 2025
1a985d8
Test to verify cache invalidation
shubham1g5 Feb 18, 2025
1fd858f
rename method
shubham1g5 Feb 18, 2025
1f04887
schedule entity cache invalidation task after restore
shubham1g5 Feb 18, 2025
137251a
error handling
shubham1g5 Feb 18, 2025
6b57655
fix for workmanager initialization issues in test
shubham1g5 Feb 20, 2025
b6668b9
Skip cache invalidation for demo user to get past a weird robolectric…
shubham1g5 Feb 20, 2025
be42905
removed un-necessary demo user check and adds a more useful check on …
shubham1g5 Mar 2, 2025
867c254
schedule entity cache invalidation after form upload has finished, ch…
shubham1g5 Mar 2, 2025
78f2e5d
correct transaction calls
shubham1g5 Mar 2, 2025
4d1e04b
get db lock before processing shallow records
shubham1g5 Mar 3, 2025
fdcabfc
Avoid un-ncessary String conversion
shubham1g5 Mar 3, 2025
a3bd499
Correct method access, minor lint cleanup
shubham1g5 Mar 3, 2025
23836b6
use bulk query method
shubham1g5 Mar 3, 2025
190cf05
Better method names and docstring
shubham1g5 Mar 3, 2025
0c7268f
Merge pull request #2955 from dimagi/cacheExpirationChanges
shubham1g5 Mar 4, 2025
d1c9089
Only observe cache worker when caching is on
shubham1g5 Mar 4, 2025
e27a912
Correct bulk fetch of records ids
shubham1g5 Mar 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ dependencies {
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
testImplementation 'io.mockk:mockk:1.12.7'
testImplementation 'org.json:json:20231013'
testImplementation 'org.mockito:mockito-core:5.5.0'
testImplementation 'org.json:json:20140107'
testImplementation project(path: ':commcare-core', configuration: 'testsAsJar')

androidTestImplementation 'androidx.test:runner:1.4.0'
Expand Down
11 changes: 11 additions & 0 deletions app/res/layout/entity_select_layout.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
android:visibility="gone"/>

<RelativeLayout
android:id="@+id/progress_container"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_below="@id/entity_select_filter_dropdown">
Expand All @@ -63,8 +64,18 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"/>

<TextView
android:id="@+id/progress_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/entity_select_loading"
android:padding="8dp"
android:layout_centerInParent="true" />

</RelativeLayout>


<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
Expand Down
8 changes: 8 additions & 0 deletions app/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -465,5 +465,13 @@
<string name="app_with_id_not_found">Required CommCare App is not installed on device</string>
<string name="audio_recording_notification">Audio Recording Notification</string>

<!-- Entity List Progresss Indicators-->
<string name="entity_list_initializing" cc:translatable="true">Initializing list…</string>
<string name="entity_list_processing" cc:translatable="true">Processing cases: %s</string>
<string name="entity_list_loading_cache" cc:translatable="true">Loading Cache…</string>
<string name="entity_list_calculating" cc:translatable="true">Calculating cases %s</string>
<string name="entity_list_finishing" cc:translatable="true">Finishing case calculations…</string>


<string name="connect_db_corrupt" cc:translatable="true">A problem occurred with the database, please recover your account.</string>
</resources>
14 changes: 14 additions & 0 deletions app/src/org/commcare/CommCareApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.commcare.resources.model.ResourceTable;
import org.commcare.suite.model.Menu;
import org.commcare.suite.model.Suite;
import org.commcare.tasks.PrimeEntityCacheHelper;
import org.commcare.util.CommCarePlatform;
import org.commcare.util.LogTypes;
import org.commcare.utils.AndroidCommCarePlatform;
Expand All @@ -44,6 +45,7 @@
import org.javarosa.xpath.expr.FunctionUtils;
import org.javarosa.xpath.expr.XPathExpression;
import org.javarosa.xpath.parser.XPathSyntaxException;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.util.List;
Expand Down Expand Up @@ -76,6 +78,7 @@ public class CommCareApp implements AppFilePathBuilder {
private static Stylizer mStylizer;

private int resourceState;
private volatile PrimeEntityCacheHelper primeEntityCacheHelper;

public CommCareApp(ApplicationRecord record) {
this.record = record;
Expand Down Expand Up @@ -411,6 +414,17 @@ public SQLiteDatabase getHandle() {
};
}

public PrimeEntityCacheHelper getPrimeEntityCacheHelper() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the refactor, I think this coupling will make a lot of sense and keep things straight

if (primeEntityCacheHelper == null) {
synchronized (this) {
if (primeEntityCacheHelper == null) {
primeEntityCacheHelper = new PrimeEntityCacheHelper();
}
}
}
return primeEntityCacheHelper;
}

/**
* Initialize all of the properties that an app record should have and update it to the
* installed state.
Expand Down
31 changes: 25 additions & 6 deletions app/src/org/commcare/CommCareApplication.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.commcare;

import static org.commcare.AppUtils.getCurrentAppId;

import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.Application;
Expand Down Expand Up @@ -29,6 +31,7 @@
import androidx.work.ExistingWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.OutOfQuotaPolicy;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;

Expand Down Expand Up @@ -96,7 +99,9 @@
import org.commcare.tasks.AsyncRestoreHelper;
import org.commcare.tasks.DataPullTask;
import org.commcare.tasks.DeleteLogs;
import org.commcare.tasks.EntityCacheInvalidationWorker;
import org.commcare.tasks.LogSubmissionTask;
import org.commcare.tasks.PrimeEntityCacheHelper;
import org.commcare.tasks.PurgeStaleArchivedFormsTask;
import org.commcare.tasks.templates.ManagedAsyncTask;
import org.commcare.update.UpdateHelper;
Expand Down Expand Up @@ -163,6 +168,7 @@ public class CommCareApplication extends Application implements LifecycleEventOb
private static final long BACKOFF_DELAY_FOR_UPDATE_RETRY = 5 * 60 * 1000L; // 5 mins
private static final long BACKOFF_DELAY_FOR_FORM_SUBMISSION_RETRY = 5 * 60 * 1000L; // 5 mins
private static final long PERIODICITY_FOR_FORM_SUBMISSION_IN_HOURS = 1;
private static final String ENTITY_CACHE_INVALIDATION_REQUEST = "entity-cache-invalidation-request";
private static Markwon markwon;


Expand Down Expand Up @@ -388,10 +394,10 @@ public void closeUserSession() {
}

protected void cancelWorkManagerTasks() {
// Cancel form Submissions for this user
if (currentApp != null) {
WorkManager.getInstance(this).cancelUniqueWork(
FormSubmissionHelper.getFormSubmissionRequestName(currentApp.getUniqueId()));
currentApp.getPrimeEntityCacheHelper().cancelWork();
}
}

Expand Down Expand Up @@ -581,7 +587,7 @@ public boolean isSeated(ApplicationRecord record) {
public void unseat(ApplicationRecord record) {
// cancel all Workmanager tasks for the unseated record
WorkManager.getInstance(CommCareApplication.instance())
.cancelAllWorkByTag(record.getApplicationId());
.cancelAllWorkByTag(record.getUniqueId());
MissingMediaDownloadHelper.cancelAllDownloads();
if (isSeated(record)) {
this.currentApp.teardownSandbox();
Expand Down Expand Up @@ -801,7 +807,7 @@ public void onServiceConnected(ComponentName className, IBinder service) {

purgeLogs();
cleanRawMedia();

PrimeEntityCacheHelper.schedulePrimeEntityCacheWorker();
}

TimedStatsTracker.registerStartSession();
Expand Down Expand Up @@ -854,7 +860,7 @@ private void scheduleFormSubmissions() {

PeriodicWorkRequest formSubmissionRequest =
new PeriodicWorkRequest.Builder(FormSubmissionWorker.class, PERIODICITY_FOR_FORM_SUBMISSION_IN_HOURS, TimeUnit.HOURS)
.addTag(getCurrentApp().getAppRecord().getApplicationId())
.addTag(getCurrentAppId())
.setConstraints(constraints)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
Expand All @@ -863,7 +869,7 @@ private void scheduleFormSubmissions() {
.build();

WorkManager.getInstance(this).enqueueUniquePeriodicWork(
FormSubmissionHelper.getFormSubmissionRequestName(getCurrentApp().getUniqueId()),
FormSubmissionHelper.getFormSubmissionRequestName(getCurrentAppId()),
ExistingPeriodicWorkPolicy.KEEP,
formSubmissionRequest
);
Expand All @@ -880,7 +886,7 @@ private void scheduleAppUpdate() {

PeriodicWorkRequest updateRequest =
new PeriodicWorkRequest.Builder(UpdateWorker.class, UpdateHelper.getAutoUpdatePeriodicity(), TimeUnit.HOURS)
.addTag(getCurrentApp().getAppRecord().getApplicationId())
.addTag(getCurrentAppId())
.setConstraints(constraints)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
Expand Down Expand Up @@ -1250,4 +1256,17 @@ public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Ev
break;
}
}

public void scheduleEntityCacheInvalidation() {
CommCareEntityStorageCache entityStorageCache = new CommCareEntityStorageCache("case");
if (!entityStorageCache.isEmpty()) {
OneTimeWorkRequest entityCacheInvalidationRequest = new OneTimeWorkRequest.Builder(
EntityCacheInvalidationWorker.class)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build();
WorkManager wm = WorkManager.getInstance(CommCareApplication.instance());
wm.enqueueUniqueWork(ENTITY_CACHE_INVALIDATION_REQUEST, ExistingWorkPolicy.REPLACE,
entityCacheInvalidationRequest);
}
}
}
68 changes: 52 additions & 16 deletions app/src/org/commcare/activities/EntitySelectActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static org.commcare.activities.HomeScreenBaseActivity.RESULT_RESTART;

import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
Expand Down Expand Up @@ -35,6 +34,7 @@
import org.commcare.android.javarosa.IntentCallout;
import org.commcare.android.logging.ReportingUtils;
import org.commcare.cases.entity.Entity;
import org.commcare.cases.entity.EntityLoadingProgressListener;
import org.commcare.cases.entity.NodeEntityFactory;
import org.commcare.dalvik.R;
import org.commcare.fragments.ContainerFragment;
Expand All @@ -53,11 +53,13 @@
import org.commcare.suite.model.EntityDatum;
import org.commcare.tasks.EntityLoaderListener;
import org.commcare.tasks.EntityLoaderTask;
import org.commcare.tasks.PrimeEntityCacheHelper;
import org.commcare.utils.AndroidHereFunctionHandler;
import org.commcare.utils.AndroidInstanceInitializer;
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;
Expand Down Expand Up @@ -166,6 +168,9 @@ 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;
private int lastProgress = 0;

@Override
public void onCreateSessionSafe(Bundle savedInstanceState) {
Expand Down Expand Up @@ -254,7 +259,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()) {
Expand All @@ -268,6 +272,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)
Expand Down Expand Up @@ -356,7 +361,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();
}

Expand Down Expand Up @@ -476,14 +481,29 @@ public boolean loadEntities() {
}

if (loader == null && !EntityLoaderTask.attachToActivity(this)) {
EntityLoaderTask entityLoader = new EntityLoaderTask(shortSelect, evalContext());
setProgressText(StringUtils.getStringRobust(this, R.string.entity_list_initializing));
EntityLoaderTask entityLoader = new EntityLoaderTask(shortSelect, selectDatum, evalContext());
entityLoader.attachListener(this);
entityLoader.executeParallel(selectDatum.getNodeset());
observePrimeCacheWorker();
return true;
}
return false;
}

private void observePrimeCacheWorker() {
if (shortSelect.shouldOptimize()) {
PrimeEntityCacheHelper primeEntityCacheHelper =
CommCareApplication.instance().getCurrentApp().getPrimeEntityCacheHelper();
primeEntityCacheHelper.getProgressState().observe(this, triple -> {
if (triple.getFirst().contentEquals(selectDatum.getDataId()) &&
triple.getSecond().contentEquals(shortSelect.getId())) {
deliverProgress(triple.getThird());
}
});
}
}

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
Expand Down Expand Up @@ -852,16 +872,7 @@ public void deliverLoadResult(List<Entity<TreeReference>> entities,
List<TreeReference> 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_finishing));
adapter = new EntityListAdapter(this, shortSelect, references, entities, factory,
hideActionsFromEntityList, shortSelect.getCustomActions(evalContext()), inAwesomeMode);
visibleView.setAdapter(adapter);
Expand All @@ -883,7 +894,7 @@ public void deliverLoadResult(List<Entity<TreeReference>> 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)
Expand All @@ -907,6 +918,10 @@ public void deliverLoadResult(List<Entity<TreeReference>> entities,
}
}

private void setProgressText(String message) {
progressTv.setText(message);
}

private void restoreAdapterStateFromSession() {
entitySelectSearchUI.restoreSearchString();

Expand All @@ -933,7 +948,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;
}

Expand Down Expand Up @@ -994,6 +1009,27 @@ public void deliverLoadError(Exception e) {
displayCaseListLoadException(e);
}

@Override
public void deliverProgress(Integer[] values) {
EntityLoadingProgressListener.EntityLoadingProgressPhase phase =
EntityLoadingProgressListener.EntityLoadingProgressPhase.fromInt(values[0]);
int progress = values[1] * 100 / values[2];
if (progress != lastProgress) {
lastProgress = progress;
String progressDisplay = progress + "%";
switch (phase) {
case PHASE_PROCESSING -> setProgressText(
StringUtils.getStringRobust(this, R.string.entity_list_processing,
new String[]{progressDisplay}));
case PHASE_CACHING -> setProgressText(
StringUtils.getStringRobust(this, R.string.entity_list_loading_cache));
case PHASE_UNCACHED_CALCULATION -> setProgressText(
StringUtils.getStringRobust(this, R.string.entity_list_calculating,
new String[]{progressDisplay}));
}
}
}

@Override
protected boolean onForwardSwipe() {
// If user has picked an entity, move along to form entry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ public void handleFormUploadResult(FormUploadResult result, String formLabel, bo
updateUiForFormUploadResult(Localization.get(result.getLocaleKeyBase()), false);
break;
}
CommCareApplication.instance().scheduleEntityCacheInvalidation();
}

public void updateUiForFormUploadResult(String message, boolean success) {
Expand Down
Loading
Loading