Skip to content
This repository was 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
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions packages/shared_preferences/shared_preferences/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.6.0

* Possibility to specify the file under which the preferences are stored on Android.

## 0.5.4+8

* Switch `package:shared_preferences` to `package:shared_preferences_platform_interface`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,40 +29,62 @@
@SuppressWarnings("unchecked")
class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {

private static final String SHARED_PREFERENCES_NAME = "FlutterSharedPreferences";
private static final String SHARED_PREFERENCES_DEFAULT_NAME = "FlutterSharedPreferences";
private static final String CHANNEL_NAME = "plugins.flutter.io/shared_preferences";
private static final String PREFIX = "flutter.";

// Fun fact: The following is a base64 encoding of the string "This is the prefix for a list."
// Fun fact: The following is a base64 encoding of the string "This is the
// prefix for a list."
private static final String LIST_IDENTIFIER = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu";
private static final String BIG_INTEGER_PREFIX = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBCaWdJbnRlZ2Vy";
private static final String DOUBLE_PREFIX = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBEb3VibGUu";

private final android.content.SharedPreferences preferences;
private final Context context;
private final HashMap<String, SharedPreferences> instances;

/**
* Constructs a {@link MethodCallHandlerImpl} instance. Creates a {@link
* android.content.SharedPreferences} based on the {@code context}.
* Constructs a {@link MethodCallHandlerImpl} instance, and sets the {@link Context}. This should
* be used as a singleton. Use {@link #getPreferences} to get an instance of {@link
* SharedPreferences} associated to a specific file.
*/
MethodCallHandlerImpl(Context context) {
preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
this.context = context;
this.instances = new HashMap<>();
}

/**
* @param filename The file to store the preferences.
* @return An instance of {@link SharedPreferences}.
*/
private SharedPreferences getPreferences(String filename) {
if (filename == null) filename = SHARED_PREFERENCES_DEFAULT_NAME;
SharedPreferences instance = instances.get(filename);
if (instance == null) {
instance = context.getSharedPreferences(filename, Context.MODE_PRIVATE);
instances.put(filename, instance);
}
return instance;
}

@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
String key = call.argument("key");
final String key = call.argument("key");
final String filename = call.argument("filename");
final SharedPreferences preferences = getPreferences(filename);
try {
switch (call.method) {
case "setBool":
commitAsync(preferences.edit().putBoolean(key, (boolean) call.argument("value")), result);
break;
case "setDouble":
double doubleValue = ((Number) call.argument("value")).doubleValue();
String doubleValueStr = Double.toString(doubleValue);
final double doubleValue = ((Number) call.argument("value")).doubleValue();
final String doubleValueStr = Double.toString(doubleValue);
commitAsync(preferences.edit().putString(key, DOUBLE_PREFIX + doubleValueStr), result);
break;
case "setInt":
Number number = call.argument("value");
final Number number = call.argument("value");
if (number instanceof BigInteger) {
BigInteger integerValue = (BigInteger) number;
final BigInteger integerValue = (BigInteger) number;
commitAsync(
preferences
.edit()
Expand All @@ -74,7 +96,7 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
}
break;
case "setString":
String value = (String) call.argument("value");
final String value = (String) call.argument("value");
if (value.startsWith(LIST_IDENTIFIER) || value.startsWith(BIG_INTEGER_PREFIX)) {
result.error(
"StorageError",
Expand All @@ -85,7 +107,7 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
commitAsync(preferences.edit().putString(key, value), result);
break;
case "setStringList":
List<String> list = call.argument("value");
final List<String> list = call.argument("value");
commitAsync(
preferences.edit().putString(key, LIST_IDENTIFIER + encodeList(list)), result);
break;
Expand All @@ -94,14 +116,14 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
result.success(true);
break;
case "getAll":
result.success(getAllPrefs());
result.success(getAllPrefs(filename));
return;
case "remove":
commitAsync(preferences.edit().remove(key), result);
break;
case "clear":
Set<String> keySet = getAllPrefs().keySet();
SharedPreferences.Editor clearEditor = preferences.edit();
final Set<String> keySet = getAllPrefs(filename).keySet();
final SharedPreferences.Editor clearEditor = preferences.edit();
for (String keyToDelete : keySet) {
clearEditor.remove(keyToDelete);
}
Expand Down Expand Up @@ -161,36 +183,39 @@ private String encodeList(List<String> list) throws IOException {
}

// Filter preferences to only those set by the flutter app.
private Map<String, Object> getAllPrefs() throws IOException {
Map<String, ?> allPrefs = preferences.getAll();
Map<String, Object> filteredPrefs = new HashMap<>();
private Map<String, Object> getAllPrefs(String filename) throws IOException {
final SharedPreferences preferences = getPreferences(filename);
final Map<String, ?> allPrefs = preferences.getAll();
final Map<String, Object> filteredPrefs = new HashMap<>();
for (String key : allPrefs.keySet()) {
if (key.startsWith("flutter.")) {
if (key.startsWith(PREFIX)) {
Object value = allPrefs.get(key);
if (value instanceof String) {
String stringValue = (String) value;
final String stringValue = (String) value;
if (stringValue.startsWith(LIST_IDENTIFIER)) {
value = decodeList(stringValue.substring(LIST_IDENTIFIER.length()));
} else if (stringValue.startsWith(BIG_INTEGER_PREFIX)) {
String encoded = stringValue.substring(BIG_INTEGER_PREFIX.length());
final String encoded = stringValue.substring(BIG_INTEGER_PREFIX.length());
value = new BigInteger(encoded, Character.MAX_RADIX);
} else if (stringValue.startsWith(DOUBLE_PREFIX)) {
String doubleStr = stringValue.substring(DOUBLE_PREFIX.length());
final String doubleStr = stringValue.substring(DOUBLE_PREFIX.length());
value = Double.valueOf(doubleStr);
}
} else if (value instanceof Set) {
// This only happens for previous usage of setStringSet. The app expects a list.
List<String> listValue = new ArrayList<>((Set) value);
final List<String> listValue = new ArrayList<>((Set) value);
// Let's migrate the value too while we are at it.
boolean success =
final boolean success =
preferences
.edit()
.remove(key)
.putString(key, LIST_IDENTIFIER + encodeList(listValue))
.commit();
if (!success) {
// If we are unable to migrate the existing preferences, it means we potentially lost them.
// In this case, an error from getAllPrefs() is appropriate since it will alert the app during plugin initialization.
// If we are unable to migrate the existing preferences, it means we potentially
// lost them.
// In this case, an error from getAllPrefs() is appropriate since it will alert
// the app during plugin initialization.
throw new IOException("Could not migrate set to list");
}
value = listValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,46 +23,120 @@ void main() {
'flutter.List': <String>['baz', 'quox'],
};

SharedPreferences preferences;
const String filename1 = "SharedPreferencesTests1";
const String filename2 = "SharedPreferencesTests2";

SharedPreferences preferences1;
SharedPreferences preferences2;

setUp(() async {
preferences = await SharedPreferences.getInstance();
preferences1 = await SharedPreferences.getInstanceForFile(filename: filename1);
preferences2 = await SharedPreferences.getInstanceForFile(filename: filename2);
});

tearDown(() {
preferences.clear();
preferences1.clear();
});

test('reading', () async {
expect(preferences.get('String'), isNull);
expect(preferences.get('bool'), isNull);
expect(preferences.get('int'), isNull);
expect(preferences.get('double'), isNull);
expect(preferences.get('List'), isNull);
expect(preferences.getString('String'), isNull);
expect(preferences.getBool('bool'), isNull);
expect(preferences.getInt('int'), isNull);
expect(preferences.getDouble('double'), isNull);
expect(preferences.getStringList('List'), isNull);
expect(preferences1.get('String'), isNull);
expect(preferences1.get('bool'), isNull);
expect(preferences1.get('int'), isNull);
expect(preferences1.get('double'), isNull);
expect(preferences1.get('List'), isNull);
expect(preferences1.getString('String'), isNull);
expect(preferences1.getBool('bool'), isNull);
expect(preferences1.getInt('int'), isNull);
expect(preferences1.getDouble('double'), isNull);
expect(preferences1.getStringList('List'), isNull);

expect(preferences2.get('String'), isNull);
expect(preferences2.get('bool'), isNull);
expect(preferences2.get('int'), isNull);
expect(preferences2.get('double'), isNull);
expect(preferences2.get('List'), isNull);
expect(preferences2.getString('String'), isNull);
expect(preferences2.getBool('bool'), isNull);
expect(preferences2.getInt('int'), isNull);
expect(preferences2.getDouble('double'), isNull);
expect(preferences2.getStringList('List'), isNull);
});

test('writing', () async {
await Future.wait(<Future<bool>>[
preferences.setString('String', kTestValues2['flutter.String']),
preferences.setBool('bool', kTestValues2['flutter.bool']),
preferences.setInt('int', kTestValues2['flutter.int']),
preferences.setDouble('double', kTestValues2['flutter.double']),
preferences.setStringList('List', kTestValues2['flutter.List'])
preferences1.setString('String', kTestValues2['flutter.String']),
preferences1.setBool('bool', kTestValues2['flutter.bool']),
preferences1.setInt('int', kTestValues2['flutter.int']),
preferences1.setDouble('double', kTestValues2['flutter.double']),
preferences1.setStringList('List', kTestValues2['flutter.List']),
preferences2.setString('String', kTestValues2['flutter.String']),
preferences2.setBool('bool', kTestValues2['flutter.bool']),
preferences2.setInt('int', kTestValues2['flutter.int']),
preferences2.setDouble('double', kTestValues2['flutter.double']),
preferences2.setStringList('List', kTestValues2['flutter.List'])
]);
expect(preferences.getString('String'), kTestValues2['flutter.String']);
expect(preferences.getBool('bool'), kTestValues2['flutter.bool']);
expect(preferences.getInt('int'), kTestValues2['flutter.int']);
expect(preferences.getDouble('double'), kTestValues2['flutter.double']);
expect(preferences.getStringList('List'), kTestValues2['flutter.List']);
expect(preferences1.getString('String'), kTestValues2['flutter.String']);
expect(preferences1.getBool('bool'), kTestValues2['flutter.bool']);
expect(preferences1.getInt('int'), kTestValues2['flutter.int']);
expect(preferences1.getDouble('double'), kTestValues2['flutter.double']);
expect(preferences1.getStringList('List'), kTestValues2['flutter.List']);

expect(preferences2.getString('String'), kTestValues2['flutter.String']);
expect(preferences2.getBool('bool'), kTestValues2['flutter.bool']);
expect(preferences2.getInt('int'), kTestValues2['flutter.int']);
expect(preferences2.getDouble('double'), kTestValues2['flutter.double']);
expect(preferences2.getStringList('List'), kTestValues2['flutter.List']);
});

test('removing', () async {
const String key = 'testKey';
<<<<<<< HEAD
preferences1
..setString(key, kTestValues['flutter.String'])
..setBool(key, kTestValues['flutter.bool'])
..setInt(key, kTestValues['flutter.int'])
..setDouble(key, kTestValues['flutter.double'])
..setStringList(key, kTestValues['flutter.List']);
await preferences1.remove(key);
expect(preferences1.get('testKey'), isNull);

preferences2
..setString(key, kTestValues['flutter.String'])
..setBool(key, kTestValues['flutter.bool'])
..setInt(key, kTestValues['flutter.int'])
..setDouble(key, kTestValues['flutter.double'])
..setStringList(key, kTestValues['flutter.List']);
await preferences2.remove(key);
expect(preferences2.get('testKey'), isNull);
});

test('clearing', () async {
preferences1
..setString('String', kTestValues['flutter.String'])
..setBool('bool', kTestValues['flutter.bool'])
..setInt('int', kTestValues['flutter.int'])
..setDouble('double', kTestValues['flutter.double'])
..setStringList('List', kTestValues['flutter.List']);
await preferences1.clear();
expect(preferences1.getString('String'), null);
expect(preferences1.getBool('bool'), null);
expect(preferences1.getInt('int'), null);
expect(preferences1.getDouble('double'), null);
expect(preferences1.getStringList('List'), null);

preferences2
..setString('String', kTestValues['flutter.String'])
..setBool('bool', kTestValues['flutter.bool'])
..setInt('int', kTestValues['flutter.int'])
..setDouble('double', kTestValues['flutter.double'])
..setStringList('List', kTestValues['flutter.List']);
await preferences2.clear();
expect(preferences2.getString('String'), null);
expect(preferences2.getBool('bool'), null);
expect(preferences2.getInt('int'), null);
expect(preferences2.getDouble('double'), null);
expect(preferences2.getStringList('List'), null);
=======
await preferences.setString(key, kTestValues['flutter.String']);
await preferences.setBool(key, kTestValues['flutter.bool']);
await preferences.setInt(key, kTestValues['flutter.int']);
Expand All @@ -84,6 +158,7 @@ void main() {
expect(preferences.getInt('int'), null);
expect(preferences.getDouble('double'), null);
expect(preferences.getStringList('List'), null);
>>>>>>> upstream/master
});
});
}
Loading