-
Notifications
You must be signed in to change notification settings - Fork 3.6k
[shared_preferences] Tool for migrating from legacy shared_preferences to shared_preferences_async #8229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
[shared_preferences] Tool for migrating from legacy shared_preferences to shared_preferences_async #8229
Changes from 5 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
7407d46
adds migration tool and tests
tarrinneal dd92917
Merge branch 'main' of github.com:flutter/packages into migration
tarrinneal cd2130e
Merge branch 'main' of github.com:flutter/packages into migration
tarrinneal 229944d
comments and test structure
tarrinneal 9b3e6a5
readme
tarrinneal 91e3b8f
clarifying shampoo
tarrinneal f90c2d9
reorder changelog
tarrinneal 7407576
snippet
tarrinneal File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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_tool_test.dart
This file contains hidden or 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( | ||
| preferences, | ||
| sharedPreferencesAsyncOptions, | ||
| 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( | ||
| preferences, | ||
| sharedPreferencesAsyncOptions, | ||
| 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( | ||
| preferences, | ||
| sharedPreferencesAsyncOptions, | ||
| migrationCompletedKey, | ||
| ); | ||
|
|
||
| final SharedPreferencesAsync asyncPreferences = | ||
| SharedPreferencesAsync(options: sharedPreferencesAsyncOptions); | ||
| await preferences.setInt(intKey, -0); | ||
| await migrateLegacySharedPreferencesToSharedPreferencesAsyncIfNecessary( | ||
| preferences, | ||
| sharedPreferencesAsyncOptions, | ||
| 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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. | ||
tarrinneal marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /// | ||
| /// [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( | ||
| SharedPreferences legacySharedPreferencesInstance, | ||
| SharedPreferencesOptions sharedPreferencesAsyncOptions, | ||
| 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; | ||
| } | ||
This file contains hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.