Skip to content

Migrate ML Downloader to DataStore#8418

Open
emilypgoogle wants to merge 1 commit into
mainfrom
ep/datastore-ml-downloader
Open

Migrate ML Downloader to DataStore#8418
emilypgoogle wants to merge 1 commit into
mainfrom
ep/datastore-ml-downloader

Conversation

@emilypgoogle

Copy link
Copy Markdown
Contributor

Following previous PRs, updates ML Model Downloader's Shared Preference utility class to use DataStore instead. This includes removing synchronized on a lot of methods since this is handled by our DataStore wrapper innately. Additionally, added helper methods for several keys with duplicate code.

@gemini-code-assist

Copy link
Copy Markdown
Contributor
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

@github-actions

Copy link
Copy Markdown
Contributor

📝 PRs merging into main branch

Our main branch should always be in a releasable state. If you are working on a larger change, or if you don't want this change to see the light of the day just yet, consider using a feature branch first, and only merge into the main branch when the code complete and ready to be released.

@milaGGL

milaGGL commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request migrates SharedPreferencesUtil from Android's SharedPreferences to Jetpack DataStore-backed JavaDataStorage. The review feedback highlights several performance and consistency issues arising from the migration. Specifically, the reviewer recommends retrieving all preferences in a single snapshot using dataStore.getAllSync() to avoid multiple sequential, non-atomic blocking reads in getCustomModelDetails and getDownloadingCustomModelDetails. Additionally, they suggest compiling regex patterns outside of loops and optimizing getCustomModelStatsCollectionFlag to avoid redundant sequential blocking calls.

Comment on lines +144 to +157
public CustomModel getCustomModelDetails(@NonNull String modelName) {
String modelHash = dataStore.getSync(localModelHashKey(modelName), null);

if (modelHash == null || modelHash.isEmpty()) {
// no model downloaded - check if model is being downloaded.
return getDownloadingCustomModelDetails(modelName);
}

String filePath =
getSharedPreferences()
.getString(String.format(LOCAL_MODEL_FILE_PATH_PATTERN, persistenceKey, modelName), "");
String filePath = dataStore.getSync(localModelFilePathKey(modelName), "");

long fileSize =
getSharedPreferences()
.getLong(String.format(LOCAL_MODEL_FILE_SIZE_PATTERN, persistenceKey, modelName), 0);
long fileSize = dataStore.getSync(localModelFileSizeKey(modelName), 0L);

// if no-zero - local model is present and new model being downloaded
long id =
getSharedPreferences()
.getLong(String.format(DOWNLOADING_MODEL_ID_PATTERN, persistenceKey, modelName), 0);
long id = dataStore.getSync(downloadingModelIdKey(modelName), 0L);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Calling dataStore.getSync(...) multiple times sequentially performs multiple blocking flow collections, which is inefficient. Furthermore, because these reads are not atomic, a concurrent write could modify the preferences between reads, leading to an inconsistent CustomModel state.

We can retrieve all preferences in a single consistent snapshot using dataStore.getAllSync() and extract the values from the map.

Suggested change
public CustomModel getCustomModelDetails(@NonNull String modelName) {
String modelHash = dataStore.getSync(localModelHashKey(modelName), null);
if (modelHash == null || modelHash.isEmpty()) {
// no model downloaded - check if model is being downloaded.
return getDownloadingCustomModelDetails(modelName);
}
String filePath =
getSharedPreferences()
.getString(String.format(LOCAL_MODEL_FILE_PATH_PATTERN, persistenceKey, modelName), "");
String filePath = dataStore.getSync(localModelFilePathKey(modelName), "");
long fileSize =
getSharedPreferences()
.getLong(String.format(LOCAL_MODEL_FILE_SIZE_PATTERN, persistenceKey, modelName), 0);
long fileSize = dataStore.getSync(localModelFileSizeKey(modelName), 0L);
// if no-zero - local model is present and new model being downloaded
long id =
getSharedPreferences()
.getLong(String.format(DOWNLOADING_MODEL_ID_PATTERN, persistenceKey, modelName), 0);
long id = dataStore.getSync(downloadingModelIdKey(modelName), 0L);
public CustomModel getCustomModelDetails(@NonNull String modelName) {
Map<Preferences.Key<?>, Object> allPrefs = dataStore.getAllSync();
String modelHash = (String) allPrefs.get(localModelHashKey(modelName));
if (modelHash == null || modelHash.isEmpty()) {
// no model downloaded - check if model is being downloaded.
return getDownloadingCustomModelDetails(modelName);
}
String filePath = (String) allPrefs.getOrDefault(localModelFilePathKey(modelName), "");
long fileSize = (Long) allPrefs.getOrDefault(localModelFileSizeKey(modelName), 0L);
// if no-zero - local model is present and new model being downloaded
long id = (Long) allPrefs.getOrDefault(downloadingModelIdKey(modelName), 0L);

Comment on lines +171 to +181
public CustomModel getDownloadingCustomModelDetails(@NonNull String modelName) {
String modelHash = dataStore.getSync(downloadingModelHashKey(modelName), null);

if (modelHash == null || modelHash.isEmpty()) {
// no model hash means no download in progress
return null;
}

long fileSize =
getSharedPreferences()
.getLong(String.format(DOWNLOADING_MODEL_SIZE_PATTERN, persistenceKey, modelName), 0);
long fileSize = dataStore.getSync(downloadingModelSizeKey(modelName), 0L);

long id =
getSharedPreferences()
.getLong(String.format(DOWNLOADING_MODEL_ID_PATTERN, persistenceKey, modelName), 0);
long id = dataStore.getSync(downloadingModelIdKey(modelName), 0L);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Calling dataStore.getSync(...) multiple times sequentially performs multiple blocking flow collections, which is inefficient. Furthermore, because these reads are not atomic, a concurrent write could modify the preferences between reads, leading to an inconsistent CustomModel state.

We can retrieve all preferences in a single consistent snapshot using dataStore.getAllSync() and extract the values from the map.

Suggested change
public CustomModel getDownloadingCustomModelDetails(@NonNull String modelName) {
String modelHash = dataStore.getSync(downloadingModelHashKey(modelName), null);
if (modelHash == null || modelHash.isEmpty()) {
// no model hash means no download in progress
return null;
}
long fileSize =
getSharedPreferences()
.getLong(String.format(DOWNLOADING_MODEL_SIZE_PATTERN, persistenceKey, modelName), 0);
long fileSize = dataStore.getSync(downloadingModelSizeKey(modelName), 0L);
long id =
getSharedPreferences()
.getLong(String.format(DOWNLOADING_MODEL_ID_PATTERN, persistenceKey, modelName), 0);
long id = dataStore.getSync(downloadingModelIdKey(modelName), 0L);
public CustomModel getDownloadingCustomModelDetails(@NonNull String modelName) {
Map<Preferences.Key<?>, Object> allPrefs = dataStore.getAllSync();
String modelHash = (String) allPrefs.get(downloadingModelHashKey(modelName));
if (modelHash == null || modelHash.isEmpty()) {
// no model hash means no download in progress
return null;
}
long fileSize = (Long) allPrefs.getOrDefault(downloadingModelSizeKey(modelName), 0L);
long id = (Long) allPrefs.getOrDefault(downloadingModelIdKey(modelName), 0L);

Comment on lines +293 to +300
public Set<CustomModel> listDownloadedModels() {
Set<CustomModel> customModels = new HashSet<>();
Set<String> keySet = getSharedPreferences().getAll().keySet();
Set<Preferences.Key<?>> keySet = dataStore.getAllSync().keySet();

for (String key : keySet) {
for (Preferences.Key<?> key : keySet) {
String keyName = key.getName();
// if a local file path is present - get model details.
Matcher matcher = Pattern.compile(LOCAL_MODEL_FILE_PATH_MATCHER).matcher(key);
Matcher matcher = Pattern.compile(LOCAL_MODEL_FILE_PATH_MATCHER).matcher(keyName);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Compiling the Pattern inside the loop for every key is inefficient. Additionally, calling getCustomModelDetails(modelName) inside the loop performs multiple sequential blocking reads (dataStore.getSync(...)) for each model. For $N$ models, this results in $4N$ blocking flow collections, which can significantly degrade performance.

We can optimize this by compiling the pattern once outside the loop, and retrieving all preferences in a single consistent snapshot using dataStore.getAllSync().

  public Set<CustomModel> listDownloadedModels() {
    Set<CustomModel> customModels = new HashSet<>();
    Map<Preferences.Key<?>, Object> allPrefs = dataStore.getAllSync();
    Pattern pathPattern = Pattern.compile(LOCAL_MODEL_FILE_PATH_MATCHER);

    for (Preferences.Key<?> key : allPrefs.keySet()) {
      String keyName = key.getName();
      // if a local file path is present - get model details.
      Matcher matcher = pathPattern.matcher(keyName);

Comment on lines +318 to 321
public boolean getCustomModelStatsCollectionFlag() {
if (dataStore.contains(eventLoggingEnabledKey())) {
return dataStore.getSync(eventLoggingEnabledKey(), true);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Calling dataStore.contains(...) followed by dataStore.getSync(...) results in two sequential blocking reads. We can optimize this to a single read by calling getSync with a default value of null and checking if the result is non-null.

Suggested change
public boolean getCustomModelStatsCollectionFlag() {
if (dataStore.contains(eventLoggingEnabledKey())) {
return dataStore.getSync(eventLoggingEnabledKey(), true);
}
public boolean getCustomModelStatsCollectionFlag() {
Boolean enabled = dataStore.getSync(eventLoggingEnabledKey(), null);
if (enabled != null) {
return enabled;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants