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

Commit 19c8734

Browse files
committed
[shared_preferences] Handling multiple files on Android
1 parent bb604ef commit 19c8734

File tree

6 files changed

+256
-162
lines changed

6 files changed

+256
-162
lines changed

packages/shared_preferences/shared_preferences/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.6.0
2+
3+
* Possibility to specify the file under which the preferences are stored on Android.
4+
15
## 0.5.4+7
26

37
* Restructure the project for Web support.

packages/shared_preferences/shared_preferences/android/src/main/java/io/flutter/plugins/sharedpreferences/MethodCallHandlerImpl.java

Lines changed: 95 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -20,104 +20,116 @@
2020
import java.util.HashMap;
2121
import java.util.List;
2222
import java.util.Map;
23+
import java.util.Optional;
2324
import java.util.Set;
2425

2526
/**
26-
* Implementation of the {@link MethodChannel.MethodCallHandler} for the plugin. It is also
27-
* responsible of managing the {@link android.content.SharedPreferences}.
27+
* Implementation of the {@link MethodChannel.MethodCallHandler} for the plugin.
28+
* It is also responsible of managing the
29+
* {@link android.content.SharedPreferences}.
2830
*/
2931
@SuppressWarnings("unchecked")
3032
class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler {
3133

32-
private static final String SHARED_PREFERENCES_NAME = "FlutterSharedPreferences";
34+
private static final String SHARED_PREFERENCES_DEFAULT_NAME = "FlutterSharedPreferences";
35+
private static final String CHANNEL_NAME = "plugins.flutter.io/shared_preferences";
36+
private static final String PREFIX = "flutter.";
3337

34-
// Fun fact: The following is a base64 encoding of the string "This is the prefix for a list."
38+
// Fun fact: The following is a base64 encoding of the string "This is the
39+
// prefix for a list."
3540
private static final String LIST_IDENTIFIER = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu";
3641
private static final String BIG_INTEGER_PREFIX = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBCaWdJbnRlZ2Vy";
3742
private static final String DOUBLE_PREFIX = "VGhpcyBpcyB0aGUgcHJlZml4IGZvciBEb3VibGUu";
3843

39-
private final android.content.SharedPreferences preferences;
44+
private final Context context;
4045

4146
/**
42-
* Constructs a {@link MethodCallHandlerImpl} instance. Creates a {@link
43-
* android.content.SharedPreferences} based on the {@code context}.
47+
* Constructs a {@link MethodCallHandlerImpl} instance, and sets the
48+
* {@link Context}. This should be used as a singleton. Use
49+
* {@link #getPreferences} to get an instance of {@link SharedPreferences}
50+
* associated to a specific file.
4451
*/
4552
MethodCallHandlerImpl(Context context) {
46-
preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
53+
this.context = context;
54+
}
55+
56+
/**
57+
*
58+
* @param filename The file to store the preferences.
59+
* @return An instance of {@link SharedPreferences}.
60+
*/
61+
private SharedPreferences getPreferences(String filename) {
62+
return context.getSharedPreferences(Optional.ofNullable(filename).orElse(SHARED_PREFERENCES_DEFAULT_NAME),
63+
Context.MODE_PRIVATE);
4764
}
4865

4966
@Override
5067
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
51-
String key = call.argument("key");
68+
final String key = call.argument("key");
69+
final String filename = call.argument("filename");
70+
final SharedPreferences preferences = getPreferences(filename);
5271
try {
5372
switch (call.method) {
54-
case "setBool":
55-
commitAsync(preferences.edit().putBoolean(key, (boolean) call.argument("value")), result);
56-
break;
57-
case "setDouble":
58-
double doubleValue = ((Number) call.argument("value")).doubleValue();
59-
String doubleValueStr = Double.toString(doubleValue);
60-
commitAsync(preferences.edit().putString(key, DOUBLE_PREFIX + doubleValueStr), result);
61-
break;
62-
case "setInt":
63-
Number number = call.argument("value");
64-
if (number instanceof BigInteger) {
65-
BigInteger integerValue = (BigInteger) number;
66-
commitAsync(
67-
preferences
68-
.edit()
69-
.putString(
70-
key, BIG_INTEGER_PREFIX + integerValue.toString(Character.MAX_RADIX)),
71-
result);
72-
} else {
73-
commitAsync(preferences.edit().putLong(key, number.longValue()), result);
74-
}
75-
break;
76-
case "setString":
77-
String value = (String) call.argument("value");
78-
if (value.startsWith(LIST_IDENTIFIER) || value.startsWith(BIG_INTEGER_PREFIX)) {
79-
result.error(
80-
"StorageError",
81-
"This string cannot be stored as it clashes with special identifier prefixes.",
82-
null);
83-
return;
84-
}
85-
commitAsync(preferences.edit().putString(key, value), result);
86-
break;
87-
case "setStringList":
88-
List<String> list = call.argument("value");
73+
case "setBool":
74+
commitAsync(preferences.edit().putBoolean(key, (boolean) call.argument("value")), result);
75+
break;
76+
case "setDouble":
77+
final double doubleValue = ((Number) call.argument("value")).doubleValue();
78+
final String doubleValueStr = Double.toString(doubleValue);
79+
commitAsync(preferences.edit().putString(key, DOUBLE_PREFIX + doubleValueStr), result);
80+
break;
81+
case "setInt":
82+
final Number number = call.argument("value");
83+
if (number instanceof BigInteger) {
84+
final BigInteger integerValue = (BigInteger) number;
8985
commitAsync(
90-
preferences.edit().putString(key, LIST_IDENTIFIER + encodeList(list)), result);
91-
break;
92-
case "commit":
93-
// We've been committing the whole time.
94-
result.success(true);
95-
break;
96-
case "getAll":
97-
result.success(getAllPrefs());
86+
preferences.edit().putString(key, BIG_INTEGER_PREFIX + integerValue.toString(Character.MAX_RADIX)),
87+
result);
88+
} else {
89+
commitAsync(preferences.edit().putLong(key, number.longValue()), result);
90+
}
91+
break;
92+
case "setString":
93+
final String value = (String) call.argument("value");
94+
if (value.startsWith(LIST_IDENTIFIER) || value.startsWith(BIG_INTEGER_PREFIX)) {
95+
result.error("StorageError", "This string cannot be stored as it clashes with special identifier prefixes.",
96+
null);
9897
return;
99-
case "remove":
100-
commitAsync(preferences.edit().remove(key), result);
101-
break;
102-
case "clear":
103-
Set<String> keySet = getAllPrefs().keySet();
104-
SharedPreferences.Editor clearEditor = preferences.edit();
105-
for (String keyToDelete : keySet) {
106-
clearEditor.remove(keyToDelete);
107-
}
108-
commitAsync(clearEditor, result);
109-
break;
110-
default:
111-
result.notImplemented();
112-
break;
98+
}
99+
commitAsync(preferences.edit().putString(key, value), result);
100+
break;
101+
case "setStringList":
102+
final List<String> list = call.argument("value");
103+
commitAsync(preferences.edit().putString(key, LIST_IDENTIFIER + encodeList(list)), result);
104+
break;
105+
case "commit":
106+
// We've been committing the whole time.
107+
result.success(true);
108+
break;
109+
case "getAll":
110+
result.success(getAllPrefs(filename));
111+
return;
112+
case "remove":
113+
commitAsync(preferences.edit().remove(key), result);
114+
break;
115+
case "clear":
116+
final Set<String> keySet = getAllPrefs(filename).keySet();
117+
final SharedPreferences.Editor clearEditor = preferences.edit();
118+
for (String keyToDelete : keySet) {
119+
clearEditor.remove(keyToDelete);
120+
}
121+
commitAsync(clearEditor, result);
122+
break;
123+
default:
124+
result.notImplemented();
125+
break;
113126
}
114127
} catch (IOException e) {
115128
result.error("IOException encountered", call.method, e);
116129
}
117130
}
118131

119-
private void commitAsync(
120-
final SharedPreferences.Editor editor, final MethodChannel.Result result) {
132+
private void commitAsync(final SharedPreferences.Editor editor, final MethodChannel.Result result) {
121133
new AsyncTask<Void, Void, Boolean>() {
122134
@Override
123135
protected Boolean doInBackground(Void... voids) {
@@ -161,36 +173,35 @@ private String encodeList(List<String> list) throws IOException {
161173
}
162174

163175
// Filter preferences to only those set by the flutter app.
164-
private Map<String, Object> getAllPrefs() throws IOException {
165-
Map<String, ?> allPrefs = preferences.getAll();
166-
Map<String, Object> filteredPrefs = new HashMap<>();
176+
private Map<String, Object> getAllPrefs(String filename) throws IOException {
177+
final SharedPreferences preferences = getPreferences(filename);
178+
final Map<String, ?> allPrefs = preferences.getAll();
179+
final Map<String, Object> filteredPrefs = new HashMap<>();
167180
for (String key : allPrefs.keySet()) {
168-
if (key.startsWith("flutter.")) {
181+
if (key.startsWith(PREFIX)) {
169182
Object value = allPrefs.get(key);
170183
if (value instanceof String) {
171-
String stringValue = (String) value;
184+
final String stringValue = (String) value;
172185
if (stringValue.startsWith(LIST_IDENTIFIER)) {
173186
value = decodeList(stringValue.substring(LIST_IDENTIFIER.length()));
174187
} else if (stringValue.startsWith(BIG_INTEGER_PREFIX)) {
175-
String encoded = stringValue.substring(BIG_INTEGER_PREFIX.length());
188+
final String encoded = stringValue.substring(BIG_INTEGER_PREFIX.length());
176189
value = new BigInteger(encoded, Character.MAX_RADIX);
177190
} else if (stringValue.startsWith(DOUBLE_PREFIX)) {
178-
String doubleStr = stringValue.substring(DOUBLE_PREFIX.length());
191+
final String doubleStr = stringValue.substring(DOUBLE_PREFIX.length());
179192
value = Double.valueOf(doubleStr);
180193
}
181194
} else if (value instanceof Set) {
182195
// This only happens for previous usage of setStringSet. The app expects a list.
183-
List<String> listValue = new ArrayList<>((Set) value);
196+
final List<String> listValue = new ArrayList<>((Set) value);
184197
// Let's migrate the value too while we are at it.
185-
boolean success =
186-
preferences
187-
.edit()
188-
.remove(key)
189-
.putString(key, LIST_IDENTIFIER + encodeList(listValue))
190-
.commit();
198+
final boolean success = preferences.edit().remove(key).putString(key, LIST_IDENTIFIER + encodeList(listValue))
199+
.commit();
191200
if (!success) {
192-
// If we are unable to migrate the existing preferences, it means we potentially lost them.
193-
// In this case, an error from getAllPrefs() is appropriate since it will alert the app during plugin initialization.
201+
// If we are unable to migrate the existing preferences, it means we potentially
202+
// lost them.
203+
// In this case, an error from getAllPrefs() is appropriate since it will alert
204+
// the app during plugin initialization.
194205
throw new IOException("Could not migrate set to list");
195206
}
196207
value = listValue;

packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e.dart

Lines changed: 83 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -23,69 +23,119 @@ void main() {
2323
'flutter.List': <String>['baz', 'quox'],
2424
};
2525

26-
SharedPreferences preferences;
26+
const String filename1 = "SharedPreferencesTests1";
27+
const String filename2 = "SharedPreferencesTests2";
28+
29+
SharedPreferences preferences1;
30+
SharedPreferences preferences2;
2731

2832
setUp(() async {
29-
preferences = await SharedPreferences.getInstance();
33+
preferences1 = await SharedPreferences.getInstance(filename: filename1);
34+
preferences2 = await SharedPreferences.getInstance(filename: filename2);
3035
});
3136

3237
tearDown(() {
33-
preferences.clear();
38+
preferences1.clear();
3439
});
3540

3641
test('reading', () async {
37-
expect(preferences.get('String'), isNull);
38-
expect(preferences.get('bool'), isNull);
39-
expect(preferences.get('int'), isNull);
40-
expect(preferences.get('double'), isNull);
41-
expect(preferences.get('List'), isNull);
42-
expect(preferences.getString('String'), isNull);
43-
expect(preferences.getBool('bool'), isNull);
44-
expect(preferences.getInt('int'), isNull);
45-
expect(preferences.getDouble('double'), isNull);
46-
expect(preferences.getStringList('List'), isNull);
42+
expect(preferences1.get('String'), isNull);
43+
expect(preferences1.get('bool'), isNull);
44+
expect(preferences1.get('int'), isNull);
45+
expect(preferences1.get('double'), isNull);
46+
expect(preferences1.get('List'), isNull);
47+
expect(preferences1.getString('String'), isNull);
48+
expect(preferences1.getBool('bool'), isNull);
49+
expect(preferences1.getInt('int'), isNull);
50+
expect(preferences1.getDouble('double'), isNull);
51+
expect(preferences1.getStringList('List'), isNull);
52+
53+
expect(preferences2.get('String'), isNull);
54+
expect(preferences2.get('bool'), isNull);
55+
expect(preferences2.get('int'), isNull);
56+
expect(preferences2.get('double'), isNull);
57+
expect(preferences2.get('List'), isNull);
58+
expect(preferences2.getString('String'), isNull);
59+
expect(preferences2.getBool('bool'), isNull);
60+
expect(preferences2.getInt('int'), isNull);
61+
expect(preferences2.getDouble('double'), isNull);
62+
expect(preferences2.getStringList('List'), isNull);
4763
});
4864

4965
test('writing', () async {
5066
await Future.wait(<Future<bool>>[
51-
preferences.setString('String', kTestValues2['flutter.String']),
52-
preferences.setBool('bool', kTestValues2['flutter.bool']),
53-
preferences.setInt('int', kTestValues2['flutter.int']),
54-
preferences.setDouble('double', kTestValues2['flutter.double']),
55-
preferences.setStringList('List', kTestValues2['flutter.List'])
67+
preferences1.setString('String', kTestValues2['flutter.String']),
68+
preferences1.setBool('bool', kTestValues2['flutter.bool']),
69+
preferences1.setInt('int', kTestValues2['flutter.int']),
70+
preferences1.setDouble('double', kTestValues2['flutter.double']),
71+
preferences1.setStringList('List', kTestValues2['flutter.List']),
72+
73+
preferences2.setString('String', kTestValues2['flutter.String']),
74+
preferences2.setBool('bool', kTestValues2['flutter.bool']),
75+
preferences2.setInt('int', kTestValues2['flutter.int']),
76+
preferences2.setDouble('double', kTestValues2['flutter.double']),
77+
preferences2.setStringList('List', kTestValues2['flutter.List'])
5678
]);
57-
expect(preferences.getString('String'), kTestValues2['flutter.String']);
58-
expect(preferences.getBool('bool'), kTestValues2['flutter.bool']);
59-
expect(preferences.getInt('int'), kTestValues2['flutter.int']);
60-
expect(preferences.getDouble('double'), kTestValues2['flutter.double']);
61-
expect(preferences.getStringList('List'), kTestValues2['flutter.List']);
79+
expect(preferences1.getString('String'), kTestValues2['flutter.String']);
80+
expect(preferences1.getBool('bool'), kTestValues2['flutter.bool']);
81+
expect(preferences1.getInt('int'), kTestValues2['flutter.int']);
82+
expect(preferences1.getDouble('double'), kTestValues2['flutter.double']);
83+
expect(preferences1.getStringList('List'), kTestValues2['flutter.List']);
84+
85+
expect(preferences2.getString('String'), kTestValues2['flutter.String']);
86+
expect(preferences2.getBool('bool'), kTestValues2['flutter.bool']);
87+
expect(preferences2.getInt('int'), kTestValues2['flutter.int']);
88+
expect(preferences2.getDouble('double'), kTestValues2['flutter.double']);
89+
expect(preferences2.getStringList('List'), kTestValues2['flutter.List']);
6290
});
6391

6492
test('removing', () async {
6593
const String key = 'testKey';
66-
preferences
94+
preferences1
95+
..setString(key, kTestValues['flutter.String'])
96+
..setBool(key, kTestValues['flutter.bool'])
97+
..setInt(key, kTestValues['flutter.int'])
98+
..setDouble(key, kTestValues['flutter.double'])
99+
..setStringList(key, kTestValues['flutter.List']);
100+
await preferences1.remove(key);
101+
expect(preferences1.get('testKey'), isNull);
102+
103+
preferences2
67104
..setString(key, kTestValues['flutter.String'])
68105
..setBool(key, kTestValues['flutter.bool'])
69106
..setInt(key, kTestValues['flutter.int'])
70107
..setDouble(key, kTestValues['flutter.double'])
71108
..setStringList(key, kTestValues['flutter.List']);
72-
await preferences.remove(key);
73-
expect(preferences.get('testKey'), isNull);
109+
await preferences2.remove(key);
110+
expect(preferences2.get('testKey'), isNull);
74111
});
75112

76113
test('clearing', () async {
77-
preferences
114+
preferences1
115+
..setString('String', kTestValues['flutter.String'])
116+
..setBool('bool', kTestValues['flutter.bool'])
117+
..setInt('int', kTestValues['flutter.int'])
118+
..setDouble('double', kTestValues['flutter.double'])
119+
..setStringList('List', kTestValues['flutter.List']);
120+
await preferences1.clear();
121+
expect(preferences1.getString('String'), null);
122+
expect(preferences1.getBool('bool'), null);
123+
expect(preferences1.getInt('int'), null);
124+
expect(preferences1.getDouble('double'), null);
125+
expect(preferences1.getStringList('List'), null);
126+
127+
preferences2
78128
..setString('String', kTestValues['flutter.String'])
79129
..setBool('bool', kTestValues['flutter.bool'])
80130
..setInt('int', kTestValues['flutter.int'])
81131
..setDouble('double', kTestValues['flutter.double'])
82132
..setStringList('List', kTestValues['flutter.List']);
83-
await preferences.clear();
84-
expect(preferences.getString('String'), null);
85-
expect(preferences.getBool('bool'), null);
86-
expect(preferences.getInt('int'), null);
87-
expect(preferences.getDouble('double'), null);
88-
expect(preferences.getStringList('List'), null);
133+
await preferences2.clear();
134+
expect(preferences2.getString('String'), null);
135+
expect(preferences2.getBool('bool'), null);
136+
expect(preferences2.getInt('int'), null);
137+
expect(preferences2.getDouble('double'), null);
138+
expect(preferences2.getStringList('List'), null);
89139
});
90140
});
91141
}

0 commit comments

Comments
 (0)