-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[shared_preferences] Tool for migrating from legacy shared_preference…
…s to shared_preferences_async (#8229) fixes flutter/flutter#150732 fixes flutter/flutter#123153
- Loading branch information
1 parent
8024c08
commit 3d28a90
Showing
8 changed files
with
334 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
214 changes: 214 additions & 0 deletions
214
...s/shared_preferences/example/integration_test/shared_preferences_migration_util_test.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
import 'dart:io'; | ||
|
||
import 'package:flutter_test/flutter_test.dart'; | ||
import 'package:integration_test/integration_test.dart'; | ||
import 'package:shared_preferences/shared_preferences.dart'; | ||
import 'package:shared_preferences/util/legacy_to_async_migration_util.dart'; | ||
import 'package:shared_preferences_android/shared_preferences_android.dart'; | ||
import 'package:shared_preferences_foundation/shared_preferences_foundation.dart'; | ||
import 'package:shared_preferences_linux/shared_preferences_linux.dart'; | ||
import 'package:shared_preferences_platform_interface/types.dart'; | ||
import 'package:shared_preferences_windows/shared_preferences_windows.dart'; | ||
|
||
const String stringKey = 'testString'; | ||
const String boolKey = 'testBool'; | ||
const String intKey = 'testInt'; | ||
const String doubleKey = 'testDouble'; | ||
const String listKey = 'testList'; | ||
|
||
const String testString = 'hello world'; | ||
const bool testBool = true; | ||
const int testInt = 42; | ||
const double testDouble = 3.14159; | ||
const List<String> testList = <String>['foo', 'bar']; | ||
|
||
const String migrationCompletedKey = 'migrationCompleted'; | ||
|
||
void main() { | ||
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); | ||
|
||
group('SharedPreferences without setting prefix', () { | ||
runAllGroups(() {}); | ||
}); | ||
|
||
group('SharedPreferences with setPrefix', () { | ||
runAllGroups(() { | ||
SharedPreferences.setPrefix('prefix.'); | ||
}); | ||
}); | ||
|
||
group('SharedPreferences with setPrefix and allowList', () { | ||
runAllGroups( | ||
() { | ||
final Set<String> allowList = <String>{ | ||
'prefix.$boolKey', | ||
'prefix.$intKey', | ||
'prefix.$doubleKey', | ||
'prefix.$listKey' | ||
}; | ||
SharedPreferences.setPrefix('prefix.', allowList: allowList); | ||
}, | ||
stringValue: null, | ||
); | ||
}); | ||
|
||
group('SharedPreferences with prefix set to empty string', () { | ||
runAllGroups( | ||
() { | ||
SharedPreferences.setPrefix(''); | ||
}, | ||
keysCollide: true, | ||
); | ||
}); | ||
} | ||
|
||
void runAllGroups(void Function() legacySharedPrefsConfig, | ||
{String? stringValue = testString, bool keysCollide = false}) { | ||
group('default sharedPreferencesAsyncOptions', () { | ||
const SharedPreferencesOptions sharedPreferencesAsyncOptions = | ||
SharedPreferencesOptions(); | ||
|
||
runTests( | ||
sharedPreferencesAsyncOptions, | ||
legacySharedPrefsConfig, | ||
stringValue: stringValue, | ||
keysAndNamesCollide: keysCollide, | ||
); | ||
}); | ||
|
||
group('file name (or equivalent) sharedPreferencesAsyncOptions', () { | ||
final SharedPreferencesOptions sharedPreferencesAsyncOptions; | ||
if (Platform.isAndroid) { | ||
sharedPreferencesAsyncOptions = | ||
const SharedPreferencesAsyncAndroidOptions( | ||
backend: SharedPreferencesAndroidBackendLibrary.SharedPreferences, | ||
originalSharedPreferencesOptions: AndroidSharedPreferencesStoreOptions( | ||
fileName: 'fileName', | ||
), | ||
); | ||
} else if (Platform.isIOS || Platform.isMacOS) { | ||
sharedPreferencesAsyncOptions = | ||
SharedPreferencesAsyncFoundationOptions(suiteName: 'group.fileName'); | ||
} else if (Platform.isLinux) { | ||
sharedPreferencesAsyncOptions = const SharedPreferencesLinuxOptions( | ||
fileName: 'fileName', | ||
); | ||
} else if (Platform.isWindows) { | ||
sharedPreferencesAsyncOptions = | ||
const SharedPreferencesWindowsOptions(fileName: 'fileName'); | ||
} else { | ||
sharedPreferencesAsyncOptions = const SharedPreferencesOptions(); | ||
} | ||
|
||
runTests( | ||
sharedPreferencesAsyncOptions, | ||
legacySharedPrefsConfig, | ||
stringValue: stringValue, | ||
); | ||
}); | ||
|
||
if (Platform.isAndroid) { | ||
group('Android default sharedPreferences', () { | ||
const SharedPreferencesOptions sharedPreferencesAsyncOptions = | ||
SharedPreferencesAsyncAndroidOptions( | ||
backend: SharedPreferencesAndroidBackendLibrary.SharedPreferences, | ||
originalSharedPreferencesOptions: | ||
AndroidSharedPreferencesStoreOptions(), | ||
); | ||
|
||
runTests( | ||
sharedPreferencesAsyncOptions, | ||
legacySharedPrefsConfig, | ||
stringValue: stringValue, | ||
); | ||
}); | ||
} | ||
} | ||
|
||
void runTests(SharedPreferencesOptions sharedPreferencesAsyncOptions, | ||
void Function() legacySharedPrefsConfig, | ||
{String? stringValue = testString, bool keysAndNamesCollide = false}) { | ||
setUp(() async { | ||
// Configure and populate the source legacy shared preferences. | ||
SharedPreferences.resetStatic(); | ||
legacySharedPrefsConfig(); | ||
|
||
final SharedPreferences preferences = await SharedPreferences.getInstance(); | ||
await preferences.clear(); | ||
await preferences.setBool(boolKey, testBool); | ||
await preferences.setInt(intKey, testInt); | ||
await preferences.setDouble(doubleKey, testDouble); | ||
await preferences.setString(stringKey, testString); | ||
await preferences.setStringList(listKey, testList); | ||
}); | ||
|
||
tearDown(() async { | ||
await SharedPreferencesAsync(options: sharedPreferencesAsyncOptions) | ||
.clear(); | ||
}); | ||
|
||
testWidgets('data is successfully transferred to new system', (_) async { | ||
final SharedPreferences preferences = await SharedPreferences.getInstance(); | ||
await migrateLegacySharedPreferencesToSharedPreferencesAsyncIfNecessary( | ||
legacySharedPreferencesInstance: preferences, | ||
sharedPreferencesAsyncOptions: sharedPreferencesAsyncOptions, | ||
migrationCompletedKey: migrationCompletedKey, | ||
); | ||
|
||
final SharedPreferencesAsync asyncPreferences = | ||
SharedPreferencesAsync(options: sharedPreferencesAsyncOptions); | ||
|
||
expect(await asyncPreferences.getBool(boolKey), testBool); | ||
expect(await asyncPreferences.getInt(intKey), testInt); | ||
expect(await asyncPreferences.getDouble(doubleKey), testDouble); | ||
expect(await asyncPreferences.getString(stringKey), stringValue); | ||
expect(await asyncPreferences.getStringList(listKey), testList); | ||
}); | ||
|
||
testWidgets('migrationCompleted key is set', (_) async { | ||
final SharedPreferences preferences = await SharedPreferences.getInstance(); | ||
await migrateLegacySharedPreferencesToSharedPreferencesAsyncIfNecessary( | ||
legacySharedPreferencesInstance: preferences, | ||
sharedPreferencesAsyncOptions: sharedPreferencesAsyncOptions, | ||
migrationCompletedKey: migrationCompletedKey, | ||
); | ||
|
||
final SharedPreferencesAsync asyncPreferences = | ||
SharedPreferencesAsync(options: sharedPreferencesAsyncOptions); | ||
|
||
expect(await asyncPreferences.getBool(migrationCompletedKey), true); | ||
}); | ||
|
||
testWidgets( | ||
're-running migration tool does not overwrite data', | ||
(_) async { | ||
final SharedPreferences preferences = | ||
await SharedPreferences.getInstance(); | ||
await migrateLegacySharedPreferencesToSharedPreferencesAsyncIfNecessary( | ||
legacySharedPreferencesInstance: preferences, | ||
sharedPreferencesAsyncOptions: sharedPreferencesAsyncOptions, | ||
migrationCompletedKey: migrationCompletedKey, | ||
); | ||
|
||
final SharedPreferencesAsync asyncPreferences = | ||
SharedPreferencesAsync(options: sharedPreferencesAsyncOptions); | ||
await preferences.setInt(intKey, -0); | ||
await migrateLegacySharedPreferencesToSharedPreferencesAsyncIfNecessary( | ||
legacySharedPreferencesInstance: preferences, | ||
sharedPreferencesAsyncOptions: sharedPreferencesAsyncOptions, | ||
migrationCompletedKey: migrationCompletedKey, | ||
); | ||
expect(await asyncPreferences.getInt(intKey), testInt); | ||
}, | ||
// Skips platforms that would be adding the preferences to the same file. | ||
skip: keysAndNamesCollide && | ||
(Platform.isWindows || | ||
Platform.isLinux || | ||
Platform.isMacOS || | ||
Platform.isIOS), | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
packages/shared_preferences/shared_preferences/lib/util/legacy_to_async_migration_util.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
import 'package:shared_preferences_platform_interface/types.dart'; | ||
|
||
import '../shared_preferences.dart'; | ||
|
||
/// Migrates preferences from the legacy [SharedPreferences] system to | ||
/// [SharedPreferencesAsync]. | ||
/// | ||
/// This method can be run multiple times without worry of overwriting transferred data, | ||
/// as long as [migrationCompletedKey] is the same each time, and the value stored | ||
/// under [migrationCompletedKey] in the target preferences system is not modified. | ||
/// | ||
/// [legacySharedPreferencesInstance] should be an instance of [SharedPreferences] | ||
/// that has been instantiated the same way it has been used throughout your app. | ||
/// If you have called [SharedPreferences.setPrefix] that must be done before | ||
/// calling this method. | ||
/// | ||
/// [sharedPreferencesAsyncOptions] should be an instance of [SharedPreferencesOptions] | ||
/// that is set up the way you intend to use the new system going forward. | ||
/// This tool will allow for future use of [SharedPreferencesAsync] and [SharedPreferencesWithCache]. | ||
/// | ||
/// The [migrationCompletedKey] is a key that is stored in the target preferences | ||
/// which is used to check if the migration has run before, to avoid overwriting | ||
/// new data going forward. Make sure that there will not be any collisions with | ||
/// preferences you are or will be setting going forward, or there may be data loss. | ||
Future<void> migrateLegacySharedPreferencesToSharedPreferencesAsyncIfNecessary({ | ||
required SharedPreferences legacySharedPreferencesInstance, | ||
required SharedPreferencesOptions sharedPreferencesAsyncOptions, | ||
required String migrationCompletedKey, | ||
}) async { | ||
final SharedPreferencesAsync sharedPreferencesAsyncInstance = | ||
SharedPreferencesAsync(options: sharedPreferencesAsyncOptions); | ||
|
||
if (await sharedPreferencesAsyncInstance.containsKey(migrationCompletedKey)) { | ||
return; | ||
} | ||
|
||
await legacySharedPreferencesInstance.reload(); | ||
final Set<String> keys = legacySharedPreferencesInstance.getKeys(); | ||
|
||
for (final String key in keys) { | ||
final Object? value = legacySharedPreferencesInstance.get(key); | ||
switch (value.runtimeType) { | ||
case const (bool): | ||
await sharedPreferencesAsyncInstance.setBool(key, value! as bool); | ||
case const (int): | ||
await sharedPreferencesAsyncInstance.setInt(key, value! as int); | ||
case const (double): | ||
await sharedPreferencesAsyncInstance.setDouble(key, value! as double); | ||
case const (String): | ||
await sharedPreferencesAsyncInstance.setString(key, value! as String); | ||
case const (List<String>): | ||
case const (List<String?>): | ||
case const (List<Object?>): | ||
case const (List<dynamic>): | ||
try { | ||
await sharedPreferencesAsyncInstance.setStringList( | ||
key, (value! as List<Object?>).cast<String>()); | ||
} on TypeError catch (_) {} // Pass over Lists containing non-String values. | ||
} | ||
} | ||
|
||
await sharedPreferencesAsyncInstance.setBool(migrationCompletedKey, true); | ||
|
||
return; | ||
} |
Oops, something went wrong.