Skip to content

Commit 03c73a6

Browse files
authored
[shared_preferences] Adds allowList to setPrefix method. (#3794)
Creates optional allow list for preference keys when setting custom prefix. Also makes integration tests more homogenous across platforms. Fixes flutter/flutter#128948
1 parent 8522793 commit 03c73a6

File tree

6 files changed

+110
-20
lines changed

6 files changed

+110
-20
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+
## 2.2.0
2+
3+
* Adds `allowList` option to setPrefix.
4+
15
## 2.1.2
26

37
* Fixes singleton initialization race condition introduced during NNBD

packages/shared_preferences/shared_preferences/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ by any non-flutter versions of the app (for migrating from a native app to flutt
100100
If the prefix is set to a value such as `''` that causes it to read values that were
101101
not originally stored by the `SharedPreferences`, initializing `SharedPreferences`
102102
may fail if any of the values are of types that are not supported by `SharedPreferences`.
103+
In this case, you can set an `allowList` that contains only preferences of supported types.
103104

104105
If you decide to remove the prefix entirely, you can still access previously created
105106
preferences by manually adding the previous prefix `flutter.` to the beginning of

packages/shared_preferences/shared_preferences/example/integration_test/shared_preferences_test.dart

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ void main() {
9696
preferences = await SharedPreferences.getInstance();
9797
});
9898

99-
tearDown(() {
100-
preferences.clear();
99+
tearDown(() async {
100+
await preferences.clear();
101101
SharedPreferences.resetStatic();
102102
});
103103

@@ -111,8 +111,8 @@ void main() {
111111
preferences = await SharedPreferences.getInstance();
112112
});
113113

114-
tearDown(() {
115-
preferences.clear();
114+
tearDown(() async {
115+
await preferences.clear();
116116
SharedPreferences.resetStatic();
117117
});
118118

@@ -126,11 +126,40 @@ void main() {
126126
preferences = await SharedPreferences.getInstance();
127127
});
128128

129-
tearDown(() {
130-
preferences.clear();
129+
tearDown(() async {
130+
await preferences.clear();
131131
SharedPreferences.resetStatic();
132132
});
133133

134134
runAllTests();
135135
});
136+
137+
testWidgets('allowList only gets allowed items', (WidgetTester _) async {
138+
const String allowedString = 'stringKey';
139+
const String allowedBool = 'boolKey';
140+
const String notAllowedDouble = 'doubleKey';
141+
const String resultString = 'resultString';
142+
143+
const Set<String> allowList = <String>{allowedString, allowedBool};
144+
145+
SharedPreferences.resetStatic();
146+
SharedPreferences.setPrefix('', allowList: allowList);
147+
148+
final SharedPreferences prefs = await SharedPreferences.getInstance();
149+
150+
await prefs.setString(allowedString, resultString);
151+
await prefs.setBool(allowedBool, true);
152+
await prefs.setDouble(notAllowedDouble, 3.14);
153+
154+
await prefs.reload();
155+
156+
final String? testString = prefs.getString(allowedString);
157+
expect(testString, resultString);
158+
159+
final bool? testBool = prefs.getBool(allowedBool);
160+
expect(testBool, true);
161+
162+
final double? testDouble = prefs.getDouble(notAllowedDouble);
163+
expect(testDouble, null);
164+
});
136165
}

packages/shared_preferences/shared_preferences/lib/shared_preferences.dart

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:async';
66

77
import 'package:flutter/foundation.dart' show visibleForTesting;
88
import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart';
9+
import 'package:shared_preferences_platform_interface/types.dart';
910

1011
/// Wraps NSUserDefaults (on iOS) and SharedPreferences (on Android), providing
1112
/// a persistent store for simple data.
@@ -18,6 +19,8 @@ class SharedPreferences {
1819

1920
static bool _prefixHasBeenChanged = false;
2021

22+
static Set<String>? _allowList;
23+
2124
static Completer<SharedPreferences>? _completer;
2225

2326
static SharedPreferencesStorePlatform get _store =>
@@ -33,17 +36,23 @@ class SharedPreferences {
3336
/// previous behavior of this plugin. To use preferences with no prefix,
3437
/// set [prefix] to ''.
3538
///
39+
/// If [prefix] is set to '', you may encounter preferences that are
40+
/// incompatible with shared_preferences. The optional parameter
41+
/// [allowList] will cause the plugin to only return preferences that
42+
/// are both contained in the list AND match the provided prefix.
43+
///
3644
/// No migration of existing preferences is performed by this method.
3745
/// If you set a different prefix, and have previously stored preferences,
3846
/// you will need to handle any migration yourself.
3947
///
4048
/// This cannot be called after `getInstance`.
41-
static void setPrefix(String prefix) {
49+
static void setPrefix(String prefix, {Set<String>? allowList}) {
4250
if (_completer != null) {
4351
throw StateError('setPrefix cannot be called after getInstance');
4452
}
4553
_prefix = prefix;
4654
_prefixHasBeenChanged = true;
55+
_allowList = allowList;
4756
}
4857

4958
/// Resets class's static values to allow for testing of setPrefix flow.
@@ -52,6 +61,7 @@ class SharedPreferences {
5261
_completer = null;
5362
_prefix = 'flutter.';
5463
_prefixHasBeenChanged = false;
64+
_allowList = null;
5565
}
5666

5767
/// Loads and parses the [SharedPreferences] for this app from disk.
@@ -182,8 +192,14 @@ class SharedPreferences {
182192
_preferenceCache.clear();
183193
if (_prefixHasBeenChanged) {
184194
try {
185-
// ignore: deprecated_member_use
186-
return _store.clearWithPrefix(_prefix);
195+
return _store.clearWithParameters(
196+
ClearParameters(
197+
filter: PreferencesFilter(
198+
prefix: _prefix,
199+
allowList: _allowList,
200+
),
201+
),
202+
);
187203
} catch (e) {
188204
// Catching and clarifying UnimplementedError to provide a more robust message.
189205
if (e is UnimplementedError) {
@@ -214,8 +230,16 @@ Either update the implementation to support setPrefix, or do not call setPrefix.
214230
final Map<String, Object> fromSystem = <String, Object>{};
215231
if (_prefixHasBeenChanged) {
216232
try {
217-
// ignore: deprecated_member_use
218-
fromSystem.addAll(await _store.getAllWithPrefix(_prefix));
233+
fromSystem.addAll(
234+
await _store.getAllWithParameters(
235+
GetAllParameters(
236+
filter: PreferencesFilter(
237+
prefix: _prefix,
238+
allowList: _allowList,
239+
),
240+
),
241+
),
242+
);
219243
} catch (e) {
220244
// Catching and clarifying UnimplementedError to provide a more robust message.
221245
if (e is UnimplementedError) {

packages/shared_preferences/shared_preferences/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ description: Flutter plugin for reading and writing simple key-value pairs.
33
Wraps NSUserDefaults on iOS and SharedPreferences on Android.
44
repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences
55
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22
6-
version: 2.1.2
6+
version: 2.2.0
77

88
environment:
99
sdk: ">=2.18.0 <4.0.0"
@@ -31,7 +31,7 @@ dependencies:
3131
shared_preferences_android: ^2.1.0
3232
shared_preferences_foundation: ^2.2.0
3333
shared_preferences_linux: ^2.2.0
34-
shared_preferences_platform_interface: ^2.2.0
34+
shared_preferences_platform_interface: ^2.3.0
3535
shared_preferences_web: ^2.1.0
3636
shared_preferences_windows: ^2.2.0
3737

packages/shared_preferences/shared_preferences/test/shared_preferences_test.dart

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:flutter/services.dart';
66
import 'package:flutter_test/flutter_test.dart';
77
import 'package:shared_preferences/shared_preferences.dart';
88
import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart';
9+
import 'package:shared_preferences_platform_interface/types.dart';
910

1011
void main() {
1112
TestWidgetsFlutterBinding.ensureInitialized();
@@ -256,6 +257,30 @@ void main() {
256257
expect(testDouble, 3.14);
257258
});
258259

260+
test('allowList only gets allowed items', () async {
261+
const Set<String> allowList = <String>{'stringKey', 'boolKey'};
262+
263+
SharedPreferences.resetStatic();
264+
SharedPreferences.setPrefix('', allowList: allowList);
265+
266+
final SharedPreferences prefs = await SharedPreferences.getInstance();
267+
268+
await prefs.setString('stringKey', 'test');
269+
await prefs.setBool('boolKey', true);
270+
await prefs.setDouble('doubleKey', 3.14);
271+
272+
await prefs.reload();
273+
274+
final String? testString = prefs.getString('stringKey');
275+
expect(testString, 'test');
276+
277+
final bool? testBool = prefs.getBool('boolKey');
278+
expect(testBool, true);
279+
280+
final double? testDouble = prefs.getDouble('doubleKey');
281+
expect(testDouble, null);
282+
});
283+
259284
test('using reload after setPrefix properly reloads the cache', () async {
260285
const String newPrefix = 'newPrefix';
261286

@@ -274,7 +299,7 @@ void main() {
274299
expect(testStrings, 'test');
275300
});
276301

277-
test('unimplemented errors in withPrefix methods are updated', () async {
302+
test('unimplemented errors in withParameters methods are updated', () async {
278303
final UnimplementedSharedPreferencesStore localStore =
279304
UnimplementedSharedPreferencesStore();
280305
SharedPreferencesStorePlatform.instance = localStore;
@@ -294,7 +319,7 @@ void main() {
294319
"Shared Preferences doesn't yet support the setPrefix method"));
295320
});
296321

297-
test('non-Unimplemented errors pass through withPrefix methods correctly',
322+
test('non-Unimplemented errors pass through withParameters methods correctly',
298323
() async {
299324
final ThrowingSharedPreferencesStore localStore =
300325
ThrowingSharedPreferencesStore();
@@ -326,17 +351,23 @@ class FakeSharedPreferencesStore extends SharedPreferencesStorePlatform {
326351
return backend.clear();
327352
}
328353

354+
@override
355+
Future<bool> clearWithParameters(ClearParameters parameters) {
356+
log.add(const MethodCall('clearWithParameters'));
357+
return backend.clearWithParameters(parameters);
358+
}
359+
329360
@override
330361
Future<Map<String, Object>> getAll() {
331362
log.add(const MethodCall('getAll'));
332363
return backend.getAll();
333364
}
334365

335366
@override
336-
Future<Map<String, Object>> getAllWithPrefix(String prefix) {
337-
log.add(const MethodCall('getAllWithPrefix'));
338-
// ignore: deprecated_member_use
339-
return backend.getAllWithPrefix(prefix);
367+
Future<Map<String, Object>> getAllWithParameters(
368+
GetAllParameters parameters) {
369+
log.add(const MethodCall('getAllWithParameters'));
370+
return backend.getAllWithParameters(parameters);
340371
}
341372

342373
@override
@@ -406,7 +437,8 @@ class ThrowingSharedPreferencesStore extends SharedPreferencesStorePlatform {
406437
}
407438

408439
@override
409-
Future<Map<String, Object>> getAllWithPrefix(String prefix) {
440+
Future<Map<String, Object>> getAllWithParameters(
441+
GetAllParameters parameters) {
410442
throw StateError('State Error');
411443
}
412444
}

0 commit comments

Comments
 (0)