diff --git a/packages/shared_preferences/CHANGELOG.md b/packages/shared_preferences/CHANGELOG.md index 37246bba1..8f9e27ace 100644 --- a/packages/shared_preferences/CHANGELOG.md +++ b/packages/shared_preferences/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2.3.0 + +* Update shared_preferences to 2.3.2. +* Update shared_preferences_interface to 2.4.0. +* Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Supports `SharedPreferencesAsync` and `SharedPreferencesWithCache` APIs. + ## 2.2.1 * Update minimum Flutter and Dart version to 3.13 and 3.1. diff --git a/packages/shared_preferences/README.md b/packages/shared_preferences/README.md index 5ee913a18..b2aa75de3 100644 --- a/packages/shared_preferences/README.md +++ b/packages/shared_preferences/README.md @@ -10,8 +10,8 @@ This package is not an _endorsed_ implementation of `shared_preferences`. Theref ```yaml dependencies: - shared_preferences: ^2.2.0 - shared_preferences_tizen: ^2.2.1 + shared_preferences: ^2.3.2 + shared_preferences_tizen: ^2.3.0 ``` Then you can import `shared_preferences` in your Dart code: diff --git a/packages/shared_preferences/example/integration_test/shared_preferences_test.dart b/packages/shared_preferences/example/integration_test/shared_preferences_test.dart index 443ac6fc4..504ad899e 100644 --- a/packages/shared_preferences/example/integration_test/shared_preferences_test.dart +++ b/packages/shared_preferences/example/integration_test/shared_preferences_test.dart @@ -19,147 +19,661 @@ void main() { const bool testBool2 = false; const int testInt2 = 1337; const double testDouble2 = 2.71828; - const List testList2 = ['baz', 'quox']; - - late SharedPreferences preferences; - - void runAllTests() { - testWidgets('reading', (WidgetTester _) async { - expect(preferences.get('String'), isNull); - expect(preferences.get('bool'), isNull); - expect(preferences.get('int'), isNull); - expect(preferences.get('double'), isNull); - expect(preferences.get('List'), isNull); - expect(preferences.getString('String'), isNull); - expect(preferences.getBool('bool'), isNull); - expect(preferences.getInt('int'), isNull); - expect(preferences.getDouble('double'), isNull); - expect(preferences.getStringList('List'), isNull); - }); + const List testList2 = ['baz', 'qux']; - testWidgets('writing', (WidgetTester _) async { - await Future.wait(>[ - preferences.setString('String', testString2), - preferences.setBool('bool', testBool2), - preferences.setInt('int', testInt2), - preferences.setDouble('double', testDouble2), - preferences.setStringList('List', testList2) - ]); - expect(preferences.getString('String'), testString2); - expect(preferences.getBool('bool'), testBool2); - expect(preferences.getInt('int'), testInt2); - expect(preferences.getDouble('double'), testDouble2); - expect(preferences.getStringList('List'), testList2); - }); + group('shared_preferences', () { + late SharedPreferences preferences; - testWidgets('removing', (WidgetTester _) async { - const String key = 'testKey'; - await preferences.setString(key, testString); - await preferences.setBool(key, testBool); - await preferences.setInt(key, testInt); - await preferences.setDouble(key, testDouble); - await preferences.setStringList(key, testList); - await preferences.remove(key); - expect(preferences.get('testKey'), isNull); - }); + void runAllTests() { + testWidgets('set and get String', (WidgetTester _) async { + expect(preferences.get('String'), isNull); + await preferences.setString('String', testString2); + expect(preferences.getString('String'), testString2); + }); - testWidgets('clearing', (WidgetTester _) async { - await preferences.setString('String', testString); - await preferences.setBool('bool', testBool); - await preferences.setInt('int', testInt); - await preferences.setDouble('double', testDouble); - await preferences.setStringList('List', testList); - await preferences.clear(); - expect(preferences.getString('String'), null); - expect(preferences.getBool('bool'), null); - expect(preferences.getInt('int'), null); - expect(preferences.getDouble('double'), null); - expect(preferences.getStringList('List'), null); - }); + testWidgets('set and get Bool', (WidgetTester _) async { + expect(preferences.get('Bool'), isNull); + await preferences.setBool('Bool', testBool2); + expect(preferences.getBool('Bool'), testBool2); + }); - testWidgets('simultaneous writes', (WidgetTester _) async { - final List> writes = >[]; - const int writeCount = 100; - for (int i = 1; i <= writeCount; i++) { - writes.add(preferences.setInt('int', i)); - } - final List result = await Future.wait(writes, eagerError: true); - // All writes should succeed. - expect(result.where((bool element) => !element), isEmpty); - // The last write should win. - expect(preferences.getInt('int'), writeCount); - }); - } + testWidgets('set and get Int', (WidgetTester _) async { + expect(preferences.get('Int'), isNull); + await preferences.setInt('Int', testInt2); + expect(preferences.getInt('Int'), testInt2); + }); + + testWidgets('set and get Double', (WidgetTester _) async { + expect(preferences.get('Double'), isNull); + await preferences.setDouble('Double', testDouble2); + expect(preferences.getDouble('Double'), testDouble2); + }); - group('SharedPreferences', () { - setUp(() async { - preferences = await SharedPreferences.getInstance(); + testWidgets('set and get StringList', (WidgetTester _) async { + expect(preferences.get('StringList'), isNull); + await preferences.setStringList('StringList', testList2); + expect(preferences.getStringList('StringList'), testList2); + }); + + testWidgets('removing', (WidgetTester _) async { + const String key = 'testKey'; + await preferences.setString(key, testString); + await preferences.remove(key); + expect(preferences.get('testKey'), isNull); + }); + + testWidgets('clearing', (WidgetTester _) async { + await preferences.setString('String', testString); + await preferences.setBool('bool', testBool); + await preferences.setInt('int', testInt); + await preferences.setDouble('double', testDouble); + await preferences.setStringList('List', testList); + await preferences.clear(); + expect(preferences.getString('String'), null); + expect(preferences.getBool('bool'), null); + expect(preferences.getInt('int'), null); + expect(preferences.getDouble('double'), null); + expect(preferences.getStringList('List'), null); + }); + + testWidgets('simultaneous writes', (WidgetTester _) async { + final List> writes = >[]; + const int writeCount = 100; + for (int i = 1; i <= writeCount; i++) { + writes.add(preferences.setInt('int', i)); + } + final List result = await Future.wait(writes, eagerError: true); + // All writes should succeed. + expect(result.where((bool element) => !element), isEmpty); + // The last write should win. + expect(preferences.getInt('int'), writeCount); + }); + } + + group('SharedPreferences', () { + setUp(() async { + preferences = await SharedPreferences.getInstance(); + }); + + tearDown(() async { + await preferences.clear(); + SharedPreferences.resetStatic(); + }); + + runAllTests(); }); - tearDown(() async { - await preferences.clear(); - SharedPreferences.resetStatic(); + group('setPrefix', () { + setUp(() async { + SharedPreferences.resetStatic(); + SharedPreferences.setPrefix('prefix.'); + preferences = await SharedPreferences.getInstance(); + }); + + tearDown(() async { + await preferences.clear(); + SharedPreferences.resetStatic(); + }); + + runAllTests(); }); - runAllTests(); - }); + group('setNoPrefix', () { + setUp(() async { + SharedPreferences.resetStatic(); + SharedPreferences.setPrefix(''); + preferences = await SharedPreferences.getInstance(); + }); - group('setPrefix', () { - setUp(() async { - SharedPreferences.resetStatic(); - SharedPreferences.setPrefix('prefix.'); - preferences = await SharedPreferences.getInstance(); + tearDown(() async { + await preferences.clear(); + SharedPreferences.resetStatic(); + }); + + runAllTests(); }); - tearDown(() async { - await preferences.clear(); + testWidgets('allowList only gets allowed items', (WidgetTester _) async { + const String allowedString = 'stringKey'; + const String allowedBool = 'boolKey'; + const String notAllowedDouble = 'doubleKey'; + const String resultString = 'resultString'; + + const Set allowList = {allowedString, allowedBool}; + SharedPreferences.resetStatic(); - }); + SharedPreferences.setPrefix('', allowList: allowList); + + final SharedPreferences prefs = await SharedPreferences.getInstance(); + + await prefs.setString(allowedString, resultString); + await prefs.setBool(allowedBool, true); + await prefs.setDouble(notAllowedDouble, 3.14); + + await prefs.reload(); - runAllTests(); + final String? testString = prefs.getString(allowedString); + expect(testString, resultString); + + final bool? testBool = prefs.getBool(allowedBool); + expect(testBool, true); + + final double? testDouble = prefs.getDouble(notAllowedDouble); + expect(testDouble, null); + }); }); - group('setNoPrefix', () { - setUp(() async { - SharedPreferences.resetStatic(); - SharedPreferences.setPrefix(''); - preferences = await SharedPreferences.getInstance(); + group('shared_preferences_async', () { + 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 testList = ['foo', 'bar']; + + group('Async', () { + Future getPreferences() async { + final SharedPreferencesAsync preferences = SharedPreferencesAsync(); + await preferences.clear(); + return preferences; + } + + testWidgets('set and get String', (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + + await preferences.setString(stringKey, testString); + expect(await preferences.getString(stringKey), testString); + }); + + testWidgets('set and get bool', (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + + await preferences.setBool(boolKey, testBool); + expect(await preferences.getBool(boolKey), testBool); + }); + + testWidgets('set and get int', (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + + await preferences.setInt(intKey, testInt); + expect(await preferences.getInt(intKey), testInt); + }); + + testWidgets('set and get double', (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + + await preferences.setDouble(doubleKey, testDouble); + expect(await preferences.getDouble(doubleKey), testDouble); + }); + + testWidgets('set and get StringList', (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + + await preferences.setStringList(listKey, testList); + expect(await preferences.getStringList(listKey), testList); + }); + + testWidgets('getStringList returns mutable list', (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + + await preferences.setStringList(listKey, testList); + final List? list = await preferences.getStringList(listKey); + list?.add('value'); + expect(list?.length, testList.length + 1); + }); + + testWidgets('getAll', (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString), + preferences.setBool(boolKey, testBool), + preferences.setInt(intKey, testInt), + preferences.setDouble(doubleKey, testDouble), + preferences.setStringList(listKey, testList) + ]); + + final Map gotAll = await preferences.getAll(); + + expect(gotAll.length, 5); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + expect(gotAll[intKey], testInt); + expect(gotAll[doubleKey], testDouble); + expect(gotAll[listKey], testList); + }); + + testWidgets('getAll with filter', (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString), + preferences.setBool(boolKey, testBool), + preferences.setInt(intKey, testInt), + preferences.setDouble(doubleKey, testDouble), + preferences.setStringList(listKey, testList) + ]); + + final Map gotAll = + await preferences.getAll(allowList: {stringKey, boolKey}); + + expect(gotAll.length, 2); + expect(gotAll[stringKey], testString); + expect(gotAll[boolKey], testBool); + }); + + testWidgets('getKeys', (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString), + preferences.setBool(boolKey, testBool), + preferences.setInt(intKey, testInt), + preferences.setDouble(doubleKey, testDouble), + preferences.setStringList(listKey, testList) + ]); + + final Set keys = await preferences.getKeys(); + + expect(keys.length, 5); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + expect(keys, contains(intKey)); + expect(keys, contains(doubleKey)); + expect(keys, contains(listKey)); + }); + + testWidgets('getKeys with filter', (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString), + preferences.setBool(boolKey, testBool), + preferences.setInt(intKey, testInt), + preferences.setDouble(doubleKey, testDouble), + preferences.setStringList(listKey, testList) + ]); + + final Set keys = + await preferences.getKeys(allowList: {stringKey, boolKey}); + + expect(keys.length, 2); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + }); + + testWidgets('containsKey', (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + const String key = 'testKey'; + + expect(false, await preferences.containsKey(key)); + + await preferences.setString(key, 'test'); + expect(true, await preferences.containsKey(key)); + }); + + testWidgets('clear', (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString), + preferences.setBool(boolKey, testBool), + preferences.setInt(intKey, testInt), + preferences.setDouble(doubleKey, testDouble), + preferences.setStringList(listKey, testList) + ]); + await preferences.clear(); + expect(await preferences.getString(stringKey), null); + expect(await preferences.getBool(boolKey), null); + expect(await preferences.getInt(intKey), null); + expect(await preferences.getDouble(doubleKey), null); + expect(await preferences.getStringList(listKey), null); + }); + + testWidgets('clear with filter', (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString), + preferences.setBool(boolKey, testBool), + preferences.setInt(intKey, testInt), + preferences.setDouble(doubleKey, testDouble), + preferences.setStringList(listKey, testList) + ]); + await preferences.clear(allowList: {stringKey, boolKey}); + expect(await preferences.getString(stringKey), null); + expect(await preferences.getBool(boolKey), null); + expect(await preferences.getInt(intKey), testInt); + expect(await preferences.getDouble(doubleKey), testDouble); + expect(await preferences.getStringList(listKey), testList); + }); + + testWidgets('throws TypeError when returned getBool type is incorrect', + (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + await preferences.setString(stringKey, testString); + + expect(() async { + await preferences.getBool(stringKey); + }, throwsA(isA())); + }); + + testWidgets('throws TypeError when returned getString type is incorrect', + (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + await preferences.setInt(stringKey, testInt); + + expect(() async { + await preferences.getString(stringKey); + }, throwsA(isA())); + }); + + testWidgets('throws TypeError when returned getInt type is incorrect', + (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + await preferences.setString(stringKey, testString); + + expect(() async { + await preferences.getInt(stringKey); + }, throwsA(isA())); + }); + + testWidgets('throws TypeError when returned getDouble type is incorrect', + (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + await preferences.setString(stringKey, testString); + + expect(() async { + await preferences.getDouble(stringKey); + }, throwsA(isA())); + }); + + testWidgets( + 'throws TypeError when returned getStringList type is incorrect', + (WidgetTester _) async { + final SharedPreferencesAsync preferences = await getPreferences(); + await preferences.setString(stringKey, testString); + + expect(() async { + await preferences.getStringList(stringKey); + }, throwsA(isA())); + }); }); - tearDown(() async { - await preferences.clear(); - SharedPreferences.resetStatic(); + group('withCache', () { + Future< + ( + SharedPreferencesWithCache, + Map, + )> getPreferences() async { + final Map cache = {}; + final SharedPreferencesWithCache preferences = + await SharedPreferencesWithCache.create( + cache: cache, + cacheOptions: const SharedPreferencesWithCacheOptions(), + ); + await preferences.clear(); + return (preferences, cache); + } + + testWidgets('set and get String', (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); + + await preferences.setString(stringKey, testString); + expect(preferences.getString(stringKey), testString); + }); + + testWidgets('set and get bool', (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); + + await preferences.setBool(boolKey, testBool); + expect(preferences.getBool(boolKey), testBool); + }); + + testWidgets('set and get int', (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); + + await preferences.setInt(intKey, testInt); + expect(preferences.getInt(intKey), testInt); + }); + + testWidgets('set and get double', (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); + + await preferences.setDouble(doubleKey, testDouble); + expect(preferences.getDouble(doubleKey), testDouble); + }); + + testWidgets('set and get StringList', (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); + + await preferences.setStringList(listKey, testList); + expect(preferences.getStringList(listKey), testList); + }); + + testWidgets('reloading', (WidgetTester _) async { + final ( + SharedPreferencesWithCache preferences, + Map cache + ) = await getPreferences(); + await preferences.clear(); + await preferences.setString(stringKey, testString); + expect(preferences.getString(stringKey), testString); + + cache.clear(); + expect(preferences.getString(stringKey), null); + + await preferences.reloadCache(); + expect(preferences.getString(stringKey), testString); + }); + + testWidgets('containsKey', (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); + const String key = 'testKey'; + + expect(false, preferences.containsKey(key)); + + await preferences.setString(key, 'test'); + expect(true, preferences.containsKey(key)); + }); + + testWidgets('getKeys', (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString), + preferences.setBool(boolKey, testBool), + preferences.setInt(intKey, testInt), + preferences.setDouble(doubleKey, testDouble), + preferences.setStringList(listKey, testList) + ]); + + final Set keys = preferences.keys; + + expect(keys.length, 5); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + expect(keys, contains(intKey)); + expect(keys, contains(doubleKey)); + expect(keys, contains(listKey)); + }); + + testWidgets('clear', (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString), + preferences.setBool(boolKey, testBool), + preferences.setInt(intKey, testInt), + preferences.setDouble(doubleKey, testDouble), + preferences.setStringList(listKey, testList) + ]); + await preferences.clear(); + expect(preferences.getString(stringKey), null); + expect(preferences.getBool(boolKey), null); + expect(preferences.getInt(intKey), null); + expect(preferences.getDouble(doubleKey), null); + expect(preferences.getStringList(listKey), null); + }); }); - runAllTests(); - }); + group('withCache with filter', () { + Future< + ( + SharedPreferencesWithCache, + Map, + )> getPreferences() async { + final Map cache = {}; + final SharedPreferencesWithCache preferences = + await SharedPreferencesWithCache.create( + cache: cache, + cacheOptions: const SharedPreferencesWithCacheOptions( + allowList: { + stringKey, + boolKey, + intKey, + doubleKey, + listKey, + }, + ), + ); + await preferences.clear(); + return (preferences, cache); + } + + testWidgets('throws ArgumentError if key is not included in filter', + (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); + const String key = 'testKey'; + + expect(() async => preferences.setString(key, 'test'), + throwsArgumentError); + }); + + testWidgets('set and get String', (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); - testWidgets('allowList only gets allowed items', (WidgetTester _) async { - const String allowedString = 'stringKey'; - const String allowedBool = 'boolKey'; - const String notAllowedDouble = 'doubleKey'; - const String resultString = 'resultString'; + await preferences.setString(stringKey, testString); + expect(preferences.getString(stringKey), testString); + }); - const Set allowList = {allowedString, allowedBool}; + testWidgets('set and get bool', (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); - SharedPreferences.resetStatic(); - SharedPreferences.setPrefix('', allowList: allowList); + await preferences.setBool(boolKey, testBool); + expect(preferences.getBool(boolKey), testBool); + }); - final SharedPreferences prefs = await SharedPreferences.getInstance(); + testWidgets('set and get int', (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); - await prefs.setString(allowedString, resultString); - await prefs.setBool(allowedBool, true); - await prefs.setDouble(notAllowedDouble, 3.14); + await preferences.setInt(intKey, testInt); + expect(preferences.getInt(intKey), testInt); + }); - await prefs.reload(); + testWidgets('set and get double', (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); - final String? testString = prefs.getString(allowedString); - expect(testString, resultString); + await preferences.setDouble(doubleKey, testDouble); + expect(preferences.getDouble(doubleKey), testDouble); + }); - final bool? testBool = prefs.getBool(allowedBool); - expect(testBool, true); + testWidgets('set and get StringList', (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); - final double? testDouble = prefs.getDouble(notAllowedDouble); - expect(testDouble, null); + await preferences.setStringList(listKey, testList); + expect(preferences.getStringList(listKey), testList); + }); + + testWidgets('get StringList handles List', + (WidgetTester _) async { + final ( + SharedPreferencesWithCache preferences, + Map cache + ) = await getPreferences(); + final List listObject = ['one', 'two']; + cache[listKey] = listObject; + expect(preferences.getStringList(listKey), listObject); + }); + + testWidgets('reloading', (WidgetTester _) async { + final ( + SharedPreferencesWithCache preferences, + Map cache + ) = await getPreferences(); + await preferences.clear(); + await preferences.setString(stringKey, testString); + expect(preferences.getString(stringKey), testString); + + cache.clear(); + expect(preferences.getString(stringKey), null); + + await preferences.reloadCache(); + expect(preferences.getString(stringKey), testString); + }); + + testWidgets('containsKey', (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); + + expect(false, preferences.containsKey(stringKey)); + + await preferences.setString(stringKey, 'test'); + expect(true, preferences.containsKey(stringKey)); + }); + + testWidgets('getKeys', (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString), + preferences.setBool(boolKey, testBool), + preferences.setInt(intKey, testInt), + preferences.setDouble(doubleKey, testDouble), + preferences.setStringList(listKey, testList) + ]); + + final Set keys = preferences.keys; + + expect(keys.length, 5); + expect(keys, contains(stringKey)); + expect(keys, contains(boolKey)); + expect(keys, contains(intKey)); + expect(keys, contains(doubleKey)); + expect(keys, contains(listKey)); + }); + + testWidgets('clear', (WidgetTester _) async { + final (SharedPreferencesWithCache preferences, _) = + await getPreferences(); + await Future.wait(>[ + preferences.setString(stringKey, testString), + preferences.setBool(boolKey, testBool), + preferences.setInt(intKey, testInt), + preferences.setDouble(doubleKey, testDouble), + preferences.setStringList(listKey, testList) + ]); + await preferences.clear(); + + expect(preferences.getString(stringKey), null); + expect(preferences.getBool(boolKey), null); + // The data for the next few tests is still stored on the platform, but not in the cache. + // This will cause the results to be null. + expect(preferences.getInt(intKey), null); + expect(preferences.getDouble(doubleKey), null); + expect(preferences.getStringList(listKey), null); + }); + }); }); } diff --git a/packages/shared_preferences/example/lib/main.dart b/packages/shared_preferences/example/lib/main.dart index 8f9dcd6d1..003d8d911 100644 --- a/packages/shared_preferences/example/lib/main.dart +++ b/packages/shared_preferences/example/lib/main.dart @@ -19,7 +19,7 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return const MaterialApp( - title: 'SharedPreferences Demo', + title: 'SharedPreferencesWithCache Demo', home: SharedPreferencesDemo(), ); } @@ -33,33 +33,49 @@ class SharedPreferencesDemo extends StatefulWidget { } class SharedPreferencesDemoState extends State { - final Future _prefs = SharedPreferences.getInstance(); + final Future _prefs = + SharedPreferencesWithCache.create( + cacheOptions: const SharedPreferencesWithCacheOptions( + // This cache will only accept the key 'counter'. + allowList: {'counter'})); late Future _counter; + int _externalCounter = 0; Future _incrementCounter() async { - final SharedPreferences prefs = await _prefs; + final SharedPreferencesWithCache prefs = await _prefs; final int counter = (prefs.getInt('counter') ?? 0) + 1; setState(() { - _counter = prefs.setInt('counter', counter).then((bool success) { + _counter = prefs.setInt('counter', counter).then((_) { return counter; }); }); } + /// Gets external button presses that could occur in another instance, thread, + /// or via some native system. + Future _getExternalCounter() async { + final SharedPreferencesAsync prefs = SharedPreferencesAsync(); + setState(() async { + _externalCounter = (await prefs.getInt('externalCounter')) ?? 0; + }); + } + @override void initState() { super.initState(); - _counter = _prefs.then((SharedPreferences prefs) { + _counter = _prefs.then((SharedPreferencesWithCache prefs) { return prefs.getInt('counter') ?? 0; }); + + _getExternalCounter(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('SharedPreferences Demo'), + title: const Text('SharedPreferencesWithCache Demo'), ), body: Center( child: FutureBuilder( @@ -75,7 +91,7 @@ class SharedPreferencesDemoState extends State { return Text('Error: ${snapshot.error}'); } else { return Text( - 'Button tapped ${snapshot.data} time${snapshot.data == 1 ? '' : 's'}.\n\n' + 'Button tapped ${snapshot.data ?? 0 + _externalCounter} time${(snapshot.data ?? 0 + _externalCounter) == 1 ? '' : 's'}.\n\n' 'This should persist across restarts.', ); } diff --git a/packages/shared_preferences/example/pubspec.yaml b/packages/shared_preferences/example/pubspec.yaml index 370134e0d..f9cc2d4f4 100644 --- a/packages/shared_preferences/example/pubspec.yaml +++ b/packages/shared_preferences/example/pubspec.yaml @@ -3,13 +3,13 @@ description: Demonstrates how to use the shared_preferences_tizen plugin. publish_to: "none" environment: - sdk: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" dependencies: flutter: sdk: flutter - shared_preferences: ^2.2.0 + shared_preferences: ^2.3.2 shared_preferences_tizen: path: ../ diff --git a/packages/shared_preferences/lib/shared_preferences_tizen.dart b/packages/shared_preferences/lib/shared_preferences_tizen.dart index beab137f1..dca9aacdb 100644 --- a/packages/shared_preferences/lib/shared_preferences_tizen.dart +++ b/packages/shared_preferences/lib/shared_preferences_tizen.dart @@ -1,172 +1,6 @@ -// Copyright 2020 Samsung Electronics Co., Ltd. All rights reserved. +// Copyright 2024 Samsung Electronics Co., Ltd. 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:async'; -import 'dart:ffi'; - -import 'package:ffi/ffi.dart'; -import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; -import 'package:shared_preferences_platform_interface/types.dart'; -import 'package:tizen_interop/6.0/tizen.dart'; - -/// The Tizen implementation of [SharedPreferencesStorePlatform]. -class SharedPreferencesPlugin extends SharedPreferencesStorePlatform { - /// Registers this class as the default instance of [SharedPreferencesStorePlatform]. - static void register() { - SharedPreferencesStorePlatform.instance = SharedPreferencesPlugin(); - } - - static const String _defaultPrefix = 'flutter.'; - - static Map? _cachedPreferences; - static const String _separator = '␞'; - - static bool _preferenceItemCallback(Pointer pKey, Pointer data) { - final String key = pKey.toDartString(); - - using((Arena arena) { - final Pointer pBool = arena(); - if (tizen.preference_get_boolean(pKey, pBool) == 0) { - _cachedPreferences![key] = pBool.value; - return; - } - - final Pointer pDouble = arena(); - if (tizen.preference_get_double(pKey, pDouble) == 0) { - _cachedPreferences![key] = pDouble.value; - return; - } - - final Pointer pInt = arena(); - if (tizen.preference_get_int(pKey, pInt) == 0) { - _cachedPreferences![key] = pInt.value; - return; - } - - final Pointer> ppString = arena(); - if (tizen.preference_get_string(pKey, ppString) == 0) { - final Pointer pString = ppString.value; - final String stringValue = pString.toDartString(); - if (stringValue == _separator) { - _cachedPreferences![key] = []; - } else if (stringValue.contains(_separator)) { - final List list = stringValue.split(_separator); - _cachedPreferences![key] = list.getRange(1, list.length - 1).toList(); - } else { - _cachedPreferences![key] = stringValue; - } - arena.using(pString, calloc.free); - return; - } - }); - - return true; - } - - Map get _preferences { - if (_cachedPreferences != null) { - return _cachedPreferences!; - } - _cachedPreferences = {}; - - final int ret = tizen.preference_foreach_item( - Pointer.fromFunction(_preferenceItemCallback, false), nullptr); - if (ret == 0) { - return _cachedPreferences!; - } - return {}; - } - - @override - Future clear() async { - return clearWithParameters(ClearParameters( - filter: PreferencesFilter(prefix: _defaultPrefix), - )); - } - - @override - Future clearWithParameters(ClearParameters parameters) async { - final PreferencesFilter filter = parameters.filter; - final List keys = List.of(_preferences.keys); - - for (final String key in keys) { - if (key.startsWith(filter.prefix) && - (filter.allowList == null || filter.allowList!.contains(key))) { - if (!(await remove(key))) { - return false; - } - } - } - return true; - } - - @override - Future> getAll() async { - return getAllWithParameters( - GetAllParameters(filter: PreferencesFilter(prefix: _defaultPrefix)), - ); - } - - @override - Future> getAllWithParameters( - GetAllParameters parameters) async { - final PreferencesFilter filter = parameters.filter; - final Map withPrefix = - Map.from(_preferences); - withPrefix.removeWhere((String key, _) => !(key.startsWith(filter.prefix) && - (filter.allowList?.contains(key) ?? true))); - return withPrefix; - } - - @override - Future remove(String key) async { - return using((Arena arena) { - final bool ret = - tizen.preference_remove(key.toNativeChar(allocator: arena)) == 0; - if (ret) { - _preferences.remove(key); - } - return ret; - }); - } - - @override - Future setValue(String valueType, String key, Object value) async { - final int ret = using((Arena arena) { - final Pointer pKey = key.toNativeChar(allocator: arena); - switch (valueType) { - case 'Bool': - return tizen.preference_set_boolean(pKey, value as bool); - case 'Double': - return tizen.preference_set_double(pKey, value as double); - case 'Int': - return tizen.preference_set_int(pKey, value as int); - case 'String': - return tizen.preference_set_string( - pKey, - (value as String).toNativeChar(allocator: arena), - ); - case 'StringList': - return tizen.preference_set_string( - pKey, - _joinStringList(value as List) - .toNativeChar(allocator: arena), - ); - default: - throw UnimplementedError('Not supported type: $valueType'); - } - }); - if (ret == 0) { - _preferences[key] = value; - return true; - } - return false; - } - - String _joinStringList(List list) { - return list.isEmpty - ? _separator - : _separator + list.join(_separator) + _separator; - } -} +export 'src/shared_preferences_async_tizen.dart'; +export 'src/shared_preferences_tizen.dart'; diff --git a/packages/shared_preferences/lib/src/shared_preferences_async_tizen.dart b/packages/shared_preferences/lib/src/shared_preferences_async_tizen.dart new file mode 100644 index 000000000..b19925838 --- /dev/null +++ b/packages/shared_preferences/lib/src/shared_preferences_async_tizen.dart @@ -0,0 +1,329 @@ +// Copyright 2024 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:async'; +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; +import 'package:shared_preferences_platform_interface/types.dart'; +import 'package:tizen_interop/6.0/tizen.dart'; + +/// The Tizen implementation of [SharedPreferencesAsyncPlatform]. +/// +/// This class implements the `package:shared_preferences` functionality for Tizen. +base class SharedPreferencesAsyncTizen extends SharedPreferencesAsyncPlatform { + /// Registers this class as the default instance of [SharedPreferencesAsyncPlatform]. + static void register() { + SharedPreferencesAsyncPlatform.instance = SharedPreferencesAsyncTizen(); + } + + static const String _separator = '␞'; + + @override + Future setString( + String key, + String value, + SharedPreferencesOptions options, + ) async { + using((Arena arena) { + final Pointer pKey = key.toNativeChar(allocator: arena); + _checkResult( + tizen.preference_set_string( + pKey, + value.toNativeChar(allocator: arena), + ), + ); + }); + } + + @override + Future setInt( + String key, + int value, + SharedPreferencesOptions options, + ) async { + using((Arena arena) { + final Pointer pKey = key.toNativeChar(allocator: arena); + _checkResult(tizen.preference_set_int(pKey, value)); + }); + } + + @override + Future setStringList( + String key, + List value, + SharedPreferencesOptions options, + ) async { + using((Arena arena) { + final Pointer pKey = key.toNativeChar(allocator: arena); + _checkResult(tizen.preference_set_string( + pKey, + _joinStringList(value).toNativeChar(allocator: arena), + )); + }); + } + + String _joinStringList(List list) { + return list.isEmpty + ? _separator + : _separator + list.join(_separator) + _separator; + } + + @override + Future setBool( + String key, + bool value, + SharedPreferencesOptions options, + ) async { + using((Arena arena) { + final Pointer pKey = key.toNativeChar(allocator: arena); + _checkResult(tizen.preference_set_boolean(pKey, value)); + }); + } + + @override + Future setDouble( + String key, + double value, + SharedPreferencesOptions options, + ) async { + using((Arena arena) { + final Pointer pKey = key.toNativeChar(allocator: arena); + _checkResult(tizen.preference_set_double(pKey, value)); + }); + } + + @override + Future getString( + String key, + SharedPreferencesOptions options, + ) async { + String? value; + using((Arena arena) { + final Pointer pKey = key.toNativeChar(allocator: arena); + final Pointer> ppString = arena(); + if (_checkResult(tizen.preference_get_string(pKey, ppString))) { + final Pointer pString = ppString.value; + final String stringValue = pString.toDartString(); + arena.using(pString, calloc.free); + value = stringValue; + } + }); + return value; + } + + @override + Future getBool( + String key, + SharedPreferencesOptions options, + ) async { + bool? value; + using((Arena arena) { + final Pointer pKey = key.toNativeChar(allocator: arena); + final Pointer pBool = arena(); + if (_checkResult(tizen.preference_get_boolean(pKey, pBool))) { + value = pBool.value; + } + }); + return value; + } + + @override + Future getDouble( + String key, + SharedPreferencesOptions options, + ) async { + double? value; + using((Arena arena) { + final Pointer pKey = key.toNativeChar(allocator: arena); + final Pointer pDouble = arena(); + if (_checkResult(tizen.preference_get_double(pKey, pDouble))) { + value = pDouble.value; + } + }); + return value; + } + + @override + Future getInt( + String key, + SharedPreferencesOptions options, + ) async { + int? value; + using((Arena arena) { + final Pointer pKey = key.toNativeChar(allocator: arena); + final Pointer pInt = arena(); + if (_checkResult(tizen.preference_get_int(pKey, pInt))) { + value = pInt.value; + } + }); + return value; + } + + @override + Future?> getStringList( + String key, + SharedPreferencesOptions options, + ) async { + List? value; + using((Arena arena) { + final Pointer pKey = key.toNativeChar(allocator: arena); + final Pointer> ppString = arena(); + if (_checkResult(tizen.preference_get_string(pKey, ppString))) { + final Pointer pString = ppString.value; + final String stringValue = pString.toDartString(); + arena.using(pString, calloc.free); + if (stringValue == _separator) { + value = []; + } else if (stringValue.contains(_separator)) { + final List list = stringValue.split(_separator); + value = list.getRange(1, list.length - 1).toList(); + } else { + throw TypeError(); + } + } + }); + return value; + } + + @override + Future clear( + ClearPreferencesParameters parameters, + SharedPreferencesOptions options, + ) async { + final PreferencesFilters filter = parameters.filter; + + List keyList = []; + await getKeys(GetPreferencesParameters(filter: parameters.filter), options) + .then((Set keys) { + keyList = keys.toList(); + }); + + for (final String key in keyList) { + if (filter.allowList == null || filter.allowList!.contains(key)) { + if (!(await _remove(key))) { + return; + } + } + } + } + + Future _remove(String key) async { + return using((Arena arena) { + final bool ret = + tizen.preference_remove(key.toNativeChar(allocator: arena)) == 0; + return ret; + }); + } + + static late PreferencesFilters _keysFilters; + static late Set? _keys; + + static bool _getKeysCallback(Pointer pKey, Pointer data) { + final String key = pKey.toDartString(); + if (_keysFilters.allowList == null || + _keysFilters.allowList!.contains(key)) { + _keys!.add(key); + } + return true; + } + + @override + Future> getKeys( + GetPreferencesParameters parameters, + SharedPreferencesOptions options, + ) async { + _keysFilters = parameters.filter; + _keys = {}; + + final int ret = tizen.preference_foreach_item( + Pointer.fromFunction( + _getKeysCallback, + false, + ), + nullptr); + if (ret != 0) { + return {}; + } + return _keys!; + } + + static late PreferencesFilters _preferencesFilters; + static late Map? _preferences; + + static bool _getPreferenceCallback(Pointer pKey, Pointer data) { + final String key = pKey.toDartString(); + if (_preferencesFilters.allowList == null || + _preferencesFilters.allowList!.contains(key)) { + using((Arena arena) { + final Pointer pBool = arena(); + if (tizen.preference_get_boolean(pKey, pBool) == 0) { + _preferences![key] = pBool.value; + return; + } + + final Pointer pDouble = arena(); + if (tizen.preference_get_double(pKey, pDouble) == 0) { + _preferences![key] = pDouble.value; + return; + } + + final Pointer pInt = arena(); + if (tizen.preference_get_int(pKey, pInt) == 0) { + _preferences![key] = pInt.value; + return; + } + + final Pointer> ppString = arena(); + if (tizen.preference_get_string(pKey, ppString) == 0) { + final Pointer pString = ppString.value; + final String stringValue = pString.toDartString(); + if (stringValue == _separator) { + _preferences![key] = []; + } else if (stringValue.contains(_separator)) { + final List list = stringValue.split(_separator); + _preferences![key] = list.getRange(1, list.length - 1).toList(); + } else { + _preferences![key] = stringValue; + } + arena.using(pString, calloc.free); + return; + } + }); + } + return true; + } + + @override + Future> getPreferences( + GetPreferencesParameters parameters, + SharedPreferencesOptions options, + ) async { + _preferencesFilters = parameters.filter; + _preferences = {}; + + final int ret = tizen.preference_foreach_item( + Pointer.fromFunction( + _getPreferenceCallback, + false, + ), + nullptr); + if (ret != 0) { + return {}; + } + return _preferences!; + } + + bool _checkResult(int error) { + if (error == preference_error_e.PREFERENCE_ERROR_NONE) { + return true; + } else if (error == preference_error_e.PREFERENCE_ERROR_INVALID_PARAMETER) { + throw TypeError(); + } else if (error == preference_error_e.PREFERENCE_ERROR_OUT_OF_MEMORY) { + throw const OutOfMemoryError(); + } + return false; + } +} diff --git a/packages/shared_preferences/lib/src/shared_preferences_tizen.dart b/packages/shared_preferences/lib/src/shared_preferences_tizen.dart new file mode 100644 index 000000000..523176cce --- /dev/null +++ b/packages/shared_preferences/lib/src/shared_preferences_tizen.dart @@ -0,0 +1,198 @@ +// Copyright 2024 Samsung Electronics Co., Ltd. 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:async'; +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; +import 'package:shared_preferences_platform_interface/types.dart'; +import 'package:tizen_interop/6.0/tizen.dart'; +import 'shared_preferences_async_tizen.dart'; + +/// The Tizen implementation of [SharedPreferencesStorePlatform]. +class SharedPreferencesPlugin extends SharedPreferencesStorePlatform { + /// Registers this class as the default instance of [SharedPreferencesStorePlatform]. + static void register() { + SharedPreferencesStorePlatform.instance = SharedPreferencesPlugin(); + + // A temporary work-around for having two plugins contained in a single package. + SharedPreferencesAsyncTizen.register(); + } + + static const String _defaultPrefix = 'flutter.'; + + static Map? _cachedPreferences; + static const String _separator = '␞'; + + static bool _preferenceItemCallback(Pointer pKey, Pointer data) { + if (pKey == nullptr) { + return false; + } + final String key = pKey.toDartString(); + + using((Arena arena) { + final Pointer pBool = arena(); + if (tizen.preference_get_boolean(pKey, pBool) == 0) { + _cachedPreferences![key] = pBool.value; + return; + } + + final Pointer pDouble = arena(); + if (tizen.preference_get_double(pKey, pDouble) == 0) { + _cachedPreferences![key] = pDouble.value; + return; + } + + final Pointer pInt = arena(); + if (tizen.preference_get_int(pKey, pInt) == 0) { + _cachedPreferences![key] = pInt.value; + return; + } + + final Pointer> ppString = arena(); + if (tizen.preference_get_string(pKey, ppString) == 0) { + final Pointer pString = ppString.value; + final String stringValue = pString.toDartString(); + if (stringValue == _separator) { + _cachedPreferences![key] = []; + } else if (stringValue.contains(_separator)) { + final List list = stringValue.split(_separator); + _cachedPreferences![key] = list.getRange(1, list.length - 1).toList(); + } else { + _cachedPreferences![key] = stringValue; + } + arena.using(pString, calloc.free); + return; + } + }); + + return true; + } + + Map get _preferences { + if (_cachedPreferences != null) { + return _cachedPreferences!; + } + _cachedPreferences = {}; + + final int ret = tizen.preference_foreach_item( + Pointer.fromFunction(_preferenceItemCallback, false), nullptr); + if (ret == 0) { + return _cachedPreferences!; + } + return {}; + } + + @override + Future clear() async { + return clearWithParameters( + ClearParameters( + filter: PreferencesFilter(prefix: _defaultPrefix), + ), + ); + } + + @override + Future clearWithPrefix(String prefix) async { + return clearWithParameters( + ClearParameters( + filter: PreferencesFilter(prefix: prefix), + ), + ); + } + + @override + Future clearWithParameters(ClearParameters parameters) async { + final PreferencesFilter filter = parameters.filter; + final List keys = List.of(_preferences.keys); + + for (final String key in keys) { + if (key.startsWith(filter.prefix) && + (filter.allowList == null || filter.allowList!.contains(key))) { + if (!(await remove(key))) { + return false; + } + } + } + return true; + } + + @override + Future> getAll() async { + return getAllWithParameters( + GetAllParameters( + filter: PreferencesFilter(prefix: _defaultPrefix), + ), + ); + } + + @override + Future> getAllWithPrefix(String prefix) async { + return getAllWithParameters( + GetAllParameters(filter: PreferencesFilter(prefix: prefix))); + } + + @override + Future> getAllWithParameters( + GetAllParameters parameters) async { + final PreferencesFilter filter = parameters.filter; + final Map withPrefix = + Map.from(_preferences); + withPrefix.removeWhere((String key, _) => !(key.startsWith(filter.prefix) && + (filter.allowList?.contains(key) ?? true))); + return withPrefix; + } + + @override + Future remove(String key) async { + return using((Arena arena) { + final bool ret = + tizen.preference_remove(key.toNativeChar(allocator: arena)) == 0; + if (ret) { + _preferences.remove(key); + } + return ret; + }); + } + + @override + Future setValue(String valueType, String key, Object value) async { + final int ret = using((Arena arena) { + final Pointer pKey = key.toNativeChar(allocator: arena); + switch (valueType) { + case 'Bool': + return tizen.preference_set_boolean(pKey, value as bool); + case 'Double': + return tizen.preference_set_double(pKey, value as double); + case 'Int': + return tizen.preference_set_int(pKey, value as int); + case 'String': + return tizen.preference_set_string( + pKey, + (value as String).toNativeChar(allocator: arena), + ); + case 'StringList': + return tizen.preference_set_string( + pKey, + _joinStringList(value as List) + .toNativeChar(allocator: arena), + ); + default: + throw UnimplementedError('Not supported type: $valueType'); + } + }); + if (ret == 0) { + _preferences[key] = value; + return true; + } + return false; + } + + String _joinStringList(List list) { + return list.isEmpty + ? _separator + : _separator + list.join(_separator) + _separator; + } +} diff --git a/packages/shared_preferences/pubspec.yaml b/packages/shared_preferences/pubspec.yaml index 12a1eed4c..a79d6f9d2 100644 --- a/packages/shared_preferences/pubspec.yaml +++ b/packages/shared_preferences/pubspec.yaml @@ -2,11 +2,11 @@ name: shared_preferences_tizen description: Tizen implementation of the shared_preferences plugin. homepage: https://github.com/flutter-tizen/plugins repository: https://github.com/flutter-tizen/plugins/tree/master/packages/shared_preferences -version: 2.2.1 +version: 2.3.0 environment: - sdk: ">=3.1.0 <4.0.0" - flutter: ">=3.13.0" + sdk: ^3.4.0 + flutter: ">=3.22.0" flutter: plugin: @@ -18,5 +18,5 @@ dependencies: ffi: ^2.0.1 flutter: sdk: flutter - shared_preferences_platform_interface: ^2.3.0 + shared_preferences_platform_interface: ^2.4.0 tizen_interop: ^0.3.0