Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[shared_preferences] Handling multiple files on Android #2040

Closed
wants to merge 7 commits into from

Conversation

axel-op
Copy link
Contributor

@axel-op axel-op commented Sep 4, 2019

Description

This PR gives the possibility to use multiple files to save the SharedPreferences.
This for now only works on Android. On iOs, the previous behaviour is preserved.
See flutter/flutter#14337 (comment).

Related Issues

Fix flutter/flutter#14337

Checklist

Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes ([x]). This will ensure a smooth and quick review process.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • My PR includes unit or integration tests for all changed/updated/fixed behaviors (See Contributor Guide).
  • All existing and new tests are passing.
    -> one test seems to be unfinished or/and broken.
  • I updated/added relevant documentation (doc comments with ///).
  • I read and followed the Flutter Style Guide.
  • The analyzer (flutter analyze) does not report any problems on my PR.
  • The title of the PR starts with the name of the plugin surrounded by square brackets, e.g. [shared_preferences]
  • I updated pubspec.yaml with an appropriate new version according to the pub versioning philosophy.
  • I updated CHANGELOG.md to add a description of the change.
  • I signed the CLA.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Does your PR require plugin users to manually update their apps to accommodate your change?

  • Yes, this is a breaking change (please indicate a breaking change in CHANGELOG.md and increment major revision).
  • No, this is not a breaking change.

@collinjackson
Copy link
Contributor

@mehmetf do you have any opinions on this? It relates to your comment on another shared_preferences PR.

@mehmetf
Copy link
Contributor

mehmetf commented Oct 17, 2019

This PR is fine.

I believe SharedPreferences instances are cached by name: https://developer.android.com/reference/android/content/Context#getSharedPreferences(java.lang.String,%20int) . So this should not be a noticeable change. It would be good to validate that there's no performance regression by calling putString() N times before and after.

static void setMockInitialValues(Map<String, dynamic> values) {
_kChannel.setMockMethodCallHandler((MethodCall methodCall) async {
void setMockInitialValues(Map<String, dynamic> values) {
throw UnimplementedError("Mocking function seems unfinished");
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure why you commented this out. Would it make sense to bring back? It is useful for unit testing apps that rely on the SharedPreferences plugin (although now that we have e2e testing that might be a reasonable alternative).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@collinjackson I don't understand the purpose of this function. As you can see, its implementation doesn't seem complete. What do you want me to do with this exactly?

Copy link
Contributor

Choose a reason for hiding this comment

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

This method is used by apps that want to set mock SharedPreferences values for testing. It shouldn't be commented out.

@axel-op
Copy link
Contributor Author

axel-op commented Oct 19, 2019

This PR is fine.
I believe SharedPreferences instances are cached by name: https://developer.android.com/reference/android/content/Context#getSharedPreferences(java.lang.String,%20int) . So this should not be a noticeable change. It would be good to validate that there's no performance regression by calling putString() N times before and after.

@mehmetf Do you want me to do this myself?

@mehmetf
Copy link
Contributor

mehmetf commented Oct 19, 2019

@axel-op Yes. We would not want to rollback the change because it introduced a regression.

@collinjackson
Copy link
Contributor

@axel-op are you still working on this? I'm happy to help land this one @mehmetf's feedback is addressed.

@axel-op
Copy link
Contributor Author

axel-op commented Nov 27, 2019

I believe SharedPreferences instances are cached by name: https://developer.android.com/reference/android/content/Context#getSharedPreferences(java.lang.String,%20int) . So this should not be a noticeable change. It would be good to validate that there's no performance regression by calling putString() N times before and after.

@mehmetf Here are my results with N = 10^5 (time is in microseconds):

. Previous impl / write Previous impl / read New impl / write New impl / read
min 28 0 36 0
max 149136 347 849831 452
mean 115.6 0.1 152.5 0.1
std dev 1098.9 1.6 4431.5 1.9

Is this what you wanted?

@axel-op
Copy link
Contributor Author

axel-op commented Nov 27, 2019

After creating some sort of cache on the Java side with a HashMap, here's what I get:

. New impl with caching / write
min 35
max 124522
mean 84.27
std dev 782.10

However I'm not sure these differences are significant

@axel-op
Copy link
Contributor Author

axel-op commented Nov 27, 2019

Furthermore, I'm struggling with the setMockInitialValues method with this new implementation. Could you help me with this one?

Copy link
Contributor

@mehmetf mehmetf left a comment

Choose a reason for hiding this comment

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

Thanks for doing the experiment. The numbers look find with caching.

///
/// For Android, see https://developer.android.com/training/data-storage/shared-preferences.html for more details on the platform implementation.
static Future<SharedPreferences> getInstance(
{String filename = "FlutterSharedPreferences"}) async {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would leave this API alone and define a new one for Android specifically:

getAndroidPreferencesForFile(String preferencesFile)

Otherwise, it is misleading. Your application code will potentially contain multiple shared preferences instances and on iOS, these will clobber each other.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I created the method getInstanceForFile, which will throw an AssertionError if the current platform isn't Android. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks Axel.

The more I think about it, the more I am worried about this API. What is your primary motivation?

  • Do you intend to create multiple files backing different shared preferences?
  • or are you in a migration scenario where you have an existing app that is using a different file name and you want to hook shared preferences plugin to it? (and you have no intention to use multiple files?)

The main problem with #1 is that multiple files is not supported on iOS so your app needs to be structured and behave differently on iOS when it comes to preferences. That's a tall order because this is not just a simple switch. For instance, on iOS, if you wanted to separate your preferences, you would need to namespace them. Let's say you were separating preferences for each user. You would do this on iOS:

user_id1:preference1
user_id1:preference2
user_id2:preference1
user_id2:preference2

but then you would write to separate files on Android? Why? You could do the same on Android too and you would not have to worry about differing behavior on different platforms.

Copy link
Contributor

Choose a reason for hiding this comment

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

I apologize for not spotting this earlier. This PR has been open for a while and we did not give proper attention to it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My intention was to use multiple files in Flutter to separate the data. It seemed to me that it could be easier to manage because then you can use functions like clear and getAll on an entire file without the need to filter the preferences.
The reason why I didn't code the iOs-native part is because I don't have the knowledge to do so, but with your comment I now understand that it cannot be done at all. So it's probably better to not change the API indeed.

@mehmetf
Copy link
Contributor

mehmetf commented Jan 9, 2020

I am going to close this PR. Please see my update above date Nov 28 about my concerns with the API. If you want to do a migration from a non-conforming file, you can do so entirely in native code and then use this plugin.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[shared_preferences] Allow specifying the preferences store name on Android
4 participants