mirror of
https://github.com/flutter/packages.git
synced 2025-07-01 23:51:55 +08:00
[shared_preferences] Fix initialization race (#4159)
During the NNBD transition, the structure of the completer logic in the initialization flow was incorrectly changed to set the field after an `await` instead of immediately. Also updates the error handling to handle `Error` the same way it currently handles `Exception`, which this change surfaced. Fixes https://github.com/flutter/flutter/issues/42407
This commit is contained in:
@ -1,5 +1,7 @@
|
||||
## NEXT
|
||||
## 2.1.2
|
||||
|
||||
* Fixes singleton initialization race condition introduced during NNBD
|
||||
transition.
|
||||
* Updates minimum supported macOS version to 10.14.
|
||||
* Updates minimum supported SDK version to Flutter 3.3/Dart 2.18.
|
||||
|
||||
|
@ -62,11 +62,12 @@ class SharedPreferences {
|
||||
if (_completer == null) {
|
||||
final Completer<SharedPreferences> completer =
|
||||
Completer<SharedPreferences>();
|
||||
_completer = completer;
|
||||
try {
|
||||
final Map<String, Object> preferencesMap =
|
||||
await _getSharedPreferencesMap();
|
||||
completer.complete(SharedPreferences._(preferencesMap));
|
||||
} on Exception catch (e) {
|
||||
} catch (e) {
|
||||
// If there's an error, explicitly return the future with an error.
|
||||
// then set the completer to null so we can retry.
|
||||
completer.completeError(e);
|
||||
@ -74,7 +75,6 @@ class SharedPreferences {
|
||||
_completer = null;
|
||||
return sharedPrefsFuture;
|
||||
}
|
||||
_completer = completer;
|
||||
}
|
||||
return _completer!.future;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ description: Flutter plugin for reading and writing simple key-value pairs.
|
||||
Wraps NSUserDefaults on iOS and SharedPreferences on Android.
|
||||
repository: https://github.com/flutter/packages/tree/main/packages/shared_preferences/shared_preferences
|
||||
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+shared_preferences%22
|
||||
version: 2.1.1
|
||||
version: 2.1.2
|
||||
|
||||
environment:
|
||||
sdk: ">=2.18.0 <4.0.0"
|
||||
|
@ -10,7 +10,6 @@ import 'package:shared_preferences_platform_interface/shared_preferences_platfor
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('SharedPreferences', () {
|
||||
const String testString = 'hello world';
|
||||
const bool testBool = true;
|
||||
const int testInt = 42;
|
||||
@ -147,8 +146,7 @@ void main() {
|
||||
await preferences.setString('String', testString);
|
||||
expect(preferences.getString('String'), testString);
|
||||
|
||||
SharedPreferences.setMockInitialValues(
|
||||
testValues2.cast<String, Object>());
|
||||
SharedPreferences.setMockInitialValues(testValues2.cast<String, Object>());
|
||||
expect(preferences.getString('String'), testString);
|
||||
|
||||
await preferences.reload();
|
||||
@ -203,7 +201,6 @@ void main() {
|
||||
|
||||
expect(preferences.getStringList('myList'), <String>[]);
|
||||
});
|
||||
});
|
||||
|
||||
test('calling mock initial values with non-prefixed keys succeeds', () async {
|
||||
SharedPreferences.setMockInitialValues(<String, Object>{
|
||||
@ -214,6 +211,16 @@ void main() {
|
||||
expect(value, 'foo');
|
||||
});
|
||||
|
||||
test('getInstance always returns the same instance', () async {
|
||||
SharedPreferencesStorePlatform.instance = SlowInitSharedPreferencesStore();
|
||||
|
||||
final Future<SharedPreferences> firstFuture =
|
||||
SharedPreferences.getInstance();
|
||||
final Future<SharedPreferences> secondFuture =
|
||||
SharedPreferences.getInstance();
|
||||
expect(identical(await firstFuture, await secondFuture), true);
|
||||
});
|
||||
|
||||
test('calling setPrefix after getInstance throws', () async {
|
||||
const String newPrefix = 'newPrefix';
|
||||
|
||||
@ -367,6 +374,15 @@ class UnimplementedSharedPreferencesStore
|
||||
}
|
||||
}
|
||||
|
||||
class SlowInitSharedPreferencesStore
|
||||
extends UnimplementedSharedPreferencesStore {
|
||||
@override
|
||||
Future<Map<String, Object>> getAll() async {
|
||||
await Future<void>.delayed(const Duration(seconds: 1));
|
||||
return <String, Object>{};
|
||||
}
|
||||
}
|
||||
|
||||
class ThrowingSharedPreferencesStore extends SharedPreferencesStorePlatform {
|
||||
@override
|
||||
Future<bool> clear() {
|
||||
|
Reference in New Issue
Block a user