diff --git a/packages/shared_preferences/shared_preferences/CHANGELOG.md b/packages/shared_preferences/shared_preferences/CHANGELOG.md index 3f4edca10dc2..a890441c0452 100644 --- a/packages/shared_preferences/shared_preferences/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.11 + +* Adds support for Windows. + ## 0.5.10 * Update package:e2e -> package:integration_test diff --git a/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e_test.dart b/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e_test.dart index 7a2c21338786..97c34f5452f5 100644 --- a/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e_test.dart +++ b/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e_test.dart @@ -1,4 +1,4 @@ -// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. diff --git a/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart b/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart index b8d3452a0a0e..034a647e8deb 100644 --- a/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart +++ b/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart @@ -7,10 +7,12 @@ import 'dart:io' show Platform; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:meta/meta.dart'; +import 'package:platform/platform.dart'; import 'package:shared_preferences_linux/shared_preferences_linux.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; import 'package:shared_preferences_platform_interface/method_channel_shared_preferences.dart'; +import 'package:shared_preferences_windows/shared_preferences_windows.dart'; /// Wraps NSUserDefaults (on iOS) and SharedPreferences (on Android), providing /// a persistent store for simple data. @@ -24,17 +26,20 @@ class SharedPreferences { static bool _manualDartRegistrationNeeded = true; static SharedPreferencesStorePlatform get _store { - // This is to manually endorse the Linux implementation until automatic - // registration of dart plugins is implemented. For details see + // This is to manually endorse the Linux and Windows implementations until + // automatic registration of dart plugins is implemented. For details see // https://github.com/flutter/flutter/issues/52267. if (_manualDartRegistrationNeeded) { // Only do the initial registration if it hasn't already been overridden // with a non-default instance. if (!kIsWeb && - Platform.isLinux && SharedPreferencesStorePlatform.instance is MethodChannelSharedPreferencesStore) { - SharedPreferencesStorePlatform.instance = SharedPreferencesLinux(); + if (Platform.isLinux) { + SharedPreferencesStorePlatform.instance = SharedPreferencesLinux(); + } else if (Platform.isWindows) { + SharedPreferencesStorePlatform.instance = SharedPreferencesWindows(); + } } _manualDartRegistrationNeeded = false; } diff --git a/packages/shared_preferences/shared_preferences/pubspec.yaml b/packages/shared_preferences/shared_preferences/pubspec.yaml index 04b7813a2a99..03c99ff2f107 100644 --- a/packages/shared_preferences/shared_preferences/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/shared_prefere # 0.5.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.5.10 +version: 0.5.11 flutter: plugin: @@ -26,6 +26,7 @@ dependencies: meta: ^1.0.4 flutter: sdk: flutter + platform: 2.2.1 shared_preferences_platform_interface: ^1.0.0 # The design on https://flutter.dev/go/federated-plugins was to leave # this constraint as "any". We cannot do it right now as it fails pub publish @@ -35,6 +36,8 @@ dependencies: shared_preferences_linux: ^0.0.2 shared_preferences_macos: ^0.0.1 shared_preferences_web: ^0.1.2 + shared_preferences_windows: + path: ../shared_preferences_windows dev_dependencies: flutter_test: diff --git a/packages/shared_preferences/shared_preferences_macos/example/test_driver/shared_preferences_e2e_test.dart b/packages/shared_preferences/shared_preferences_macos/example/test_driver/shared_preferences_e2e_test.dart index 7a2c21338786..97c34f5452f5 100644 --- a/packages/shared_preferences/shared_preferences_macos/example/test_driver/shared_preferences_e2e_test.dart +++ b/packages/shared_preferences/shared_preferences_macos/example/test_driver/shared_preferences_e2e_test.dart @@ -1,4 +1,4 @@ -// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. diff --git a/packages/shared_preferences/shared_preferences_web/LICENSE b/packages/shared_preferences/shared_preferences_web/LICENSE index 0c382ce171cc..c89293372cf3 100644 --- a/packages/shared_preferences/shared_preferences_web/LICENSE +++ b/packages/shared_preferences/shared_preferences_web/LICENSE @@ -1,4 +1,4 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. +// Copyright 2017 The Chromium Authors. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are diff --git a/packages/shared_preferences/shared_preferences_windows/.metadata b/packages/shared_preferences/shared_preferences_windows/.metadata new file mode 100644 index 000000000000..55d1df5ced9a --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: df90bb5fd64e2066594151b9e311d45cd687a80c + channel: master + +project_type: plugin diff --git a/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md new file mode 100644 index 000000000000..b3a547cdac34 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* Initial release to support shared_preferences on Windows. diff --git a/packages/shared_preferences/shared_preferences_windows/LICENSE b/packages/shared_preferences/shared_preferences_windows/LICENSE new file mode 100644 index 000000000000..c89293372cf3 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/shared_preferences/shared_preferences_windows/README.md b/packages/shared_preferences/shared_preferences_windows/README.md new file mode 100644 index 000000000000..dd710f4c7336 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/README.md @@ -0,0 +1,23 @@ +# shared_preferences_windows + +The Windows implementation of [`shared_preferences`][1]. + +## Usage + +### Import the package + +This package has been endorsed, meaning that you only need to add `shared_preferences` +as a dependency in your `pubspec.yaml`. It will be automatically included in your app +when you depend on `package:shared_preferences`. + +This is what the above means to your `pubspec.yaml`: + +```yaml +... +dependencies: + ... + shared_preferences: ^0.5.7 + ... +``` + +[1]: ../ diff --git a/packages/shared_preferences/shared_preferences_windows/example/.gitignore b/packages/shared_preferences/shared_preferences_windows/example/.gitignore new file mode 100644 index 000000000000..1ba9c339effb --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/example/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/packages/shared_preferences/shared_preferences_windows/example/.metadata b/packages/shared_preferences/shared_preferences_windows/example/.metadata new file mode 100644 index 000000000000..d39696748cee --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: df90bb5fd64e2066594151b9e311d45cd687a80c + channel: master + +project_type: app diff --git a/packages/shared_preferences/shared_preferences_windows/example/CHANGELOG.md b/packages/shared_preferences/shared_preferences_windows/example/CHANGELOG.md new file mode 100644 index 000000000000..41cc7d8192ec --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/example/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/packages/shared_preferences/shared_preferences_windows/example/LICENSE b/packages/shared_preferences/shared_preferences_windows/example/LICENSE new file mode 100644 index 000000000000..c89293372cf3 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/example/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/shared_preferences/shared_preferences_windows/example/README.md b/packages/shared_preferences/shared_preferences_windows/example/README.md new file mode 100644 index 000000000000..d85bb4107622 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/example/README.md @@ -0,0 +1,16 @@ +# shared_preferences_windows_example + +Demonstrates how to use the shared_preferences_windows plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/packages/shared_preferences/shared_preferences_windows/example/lib/main.dart b/packages/shared_preferences/shared_preferences_windows/example/lib/main.dart new file mode 100644 index 000000000000..46daeff6706f --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/example/lib/main.dart @@ -0,0 +1,87 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'SharedPreferences Demo', + home: SharedPreferencesDemo(), + ); + } +} + +class SharedPreferencesDemo extends StatefulWidget { + SharedPreferencesDemo({Key key}) : super(key: key); + + @override + SharedPreferencesDemoState createState() => SharedPreferencesDemoState(); +} + +class SharedPreferencesDemoState extends State { + Future _prefs = SharedPreferences.getInstance(); + Future _counter; + + Future _incrementCounter() async { + final SharedPreferences prefs = await _prefs; + final int counter = (prefs.getInt('counter') ?? 0) + 1; + + setState(() { + _counter = prefs.setInt("counter", counter).then((bool success) { + return counter; + }); + }); + } + + @override + void initState() { + super.initState(); + _counter = _prefs.then((SharedPreferences prefs) { + return (prefs.getInt('counter') ?? 0); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("SharedPreferences Demo"), + ), + body: Center( + child: FutureBuilder( + future: _counter, + builder: (BuildContext context, AsyncSnapshot snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.waiting: + return const CircularProgressIndicator(); + default: + if (snapshot.hasError) { + return Text('Error: ${snapshot.error}'); + } else { + return Text( + 'Button tapped ${snapshot.data} time${snapshot.data == 1 ? '' : 's'}.\n\n' + 'This should persist across restarts.', + ); + } + } + })), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml new file mode 100644 index 000000000000..53c78da33f22 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/example/pubspec.yaml @@ -0,0 +1,24 @@ +name: shared_preferences_windows_example +description: Demonstrates how to use the shared_preferences_windows plugin. + +environment: + sdk: ">=2.7.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + + shared_preferences: + path: ../../shared_preferences + shared_preferences_windows: + path: ../ + +dev_dependencies: + flutter_driver: + sdk: flutter + test: any + e2e: ^0.2.0 + pedantic: ^1.8.0 + +flutter: + uses-material-design: true diff --git a/packages/shared_preferences/shared_preferences_windows/example/test_driver/shared_preferences_e2e.dart b/packages/shared_preferences/shared_preferences_windows/example/test_driver/shared_preferences_e2e.dart new file mode 100644 index 000000000000..a4c5f5f9f3f1 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/example/test_driver/shared_preferences_e2e.dart @@ -0,0 +1,93 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. 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 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:e2e/e2e.dart'; + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + + group('$SharedPreferences', () { + const Map kTestValues = { + 'flutter.String': 'hello world', + 'flutter.bool': true, + 'flutter.int': 42, + 'flutter.double': 3.14159, + 'flutter.List': ['foo', 'bar'], + }; + + const Map kTestValues2 = { + 'flutter.String': 'goodbye world', + 'flutter.bool': false, + 'flutter.int': 1337, + 'flutter.double': 2.71828, + 'flutter.List': ['baz', 'quox'], + }; + + SharedPreferences preferences; + + setUp(() async { + preferences = await SharedPreferences.getInstance(); + }); + + tearDown(() { + preferences.clear(); + }); + + test('reading', () 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); + }); + + test('writing', () async { + await Future.wait(>[ + preferences.setString('String', kTestValues2['flutter.String']), + preferences.setBool('bool', kTestValues2['flutter.bool']), + preferences.setInt('int', kTestValues2['flutter.int']), + preferences.setDouble('double', kTestValues2['flutter.double']), + preferences.setStringList('List', kTestValues2['flutter.List']) + ]); + expect(preferences.getString('String'), kTestValues2['flutter.String']); + expect(preferences.getBool('bool'), kTestValues2['flutter.bool']); + expect(preferences.getInt('int'), kTestValues2['flutter.int']); + expect(preferences.getDouble('double'), kTestValues2['flutter.double']); + expect(preferences.getStringList('List'), kTestValues2['flutter.List']); + }); + + test('removing', () async { + const String key = 'testKey'; + await preferences.setString(key, kTestValues['flutter.String']); + await preferences.setBool(key, kTestValues['flutter.bool']); + await preferences.setInt(key, kTestValues['flutter.int']); + await preferences.setDouble(key, kTestValues['flutter.double']); + await preferences.setStringList(key, kTestValues['flutter.List']); + await preferences.remove(key); + expect(preferences.get('testKey'), isNull); + }); + + test('clearing', () async { + await preferences.setString('String', kTestValues['flutter.String']); + await preferences.setBool('bool', kTestValues['flutter.bool']); + await preferences.setInt('int', kTestValues['flutter.int']); + await preferences.setDouble('double', kTestValues['flutter.double']); + await preferences.setStringList('List', kTestValues['flutter.List']); + 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); + }); + }); +} diff --git a/packages/shared_preferences/shared_preferences_windows/example/test_driver/shared_preferences_e2e_test.dart b/packages/shared_preferences/shared_preferences_windows/example/test_driver/shared_preferences_e2e_test.dart new file mode 100644 index 000000000000..102dfdfef708 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/example/test_driver/shared_preferences_e2e_test.dart @@ -0,0 +1,15 @@ +// Copyright 2017, the Chromium project authors. Please see the AUTHORS file +// for details. 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:io'; +import 'package:flutter_driver/flutter_driver.dart'; + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 1)); + await driver.close(); + exit(result == 'pass' ? 0 : 1); +} diff --git a/packages/shared_preferences/shared_preferences_windows/lib/shared_preferences_windows.dart b/packages/shared_preferences/shared_preferences_windows/lib/shared_preferences_windows.dart new file mode 100644 index 000000000000..57d42553ec68 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/lib/shared_preferences_windows.dart @@ -0,0 +1,117 @@ +// Copyright 2020 The Chromium 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:convert' show json; +import 'package:file/file.dart'; +import 'package:file/local.dart'; +import 'package:meta/meta.dart'; +import 'package:path/path.dart' as path; +import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; + +import 'src/win32.dart' as win32; + +/// Wrapper to the win32 ffi functions for testing. +class Win32Wrapper { + /// Calls the win32 function for [getLocalDataPath]. + String getLocalDataPath() { + return win32.getLocalDataPath(); + } + + /// Calls the win32 function for [getModuleFileName]. + String getModuleFileName() { + return win32.getModuleFileName(); + } +} + +/// The Windows implementation of [SharedPreferencesStorePlatform]. +/// +/// This class implements the `package:shared_preferences` functionality for Windows. +class SharedPreferencesWindows extends SharedPreferencesStorePlatform { + /// The name of the parent directory for [fileName]. + final String fileDirectory = 'Flutter'; + + /// File system used to store to disk. Exposed for testing only. + @visibleForTesting + FileSystem fs = LocalFileSystem(); + + /// Wrapper for win32 ffi calls. + @visibleForTesting + Win32Wrapper win32Wrapper = Win32Wrapper(); + + String _localDataFilePath; + + /// The path to the file in disk were the preferences are stored. + @visibleForTesting + String get getLocalDataFilePath { + if (_localDataFilePath != null) { + return _localDataFilePath; + } + String appPath = win32Wrapper.getModuleFileName(); + String appName = path.basenameWithoutExtension(appPath); + String localDataPath = win32Wrapper.getLocalDataPath(); + // Append app-specific identifiers. + _localDataFilePath = + path.join(localDataPath, fileDirectory, '$appName.json'); + return _localDataFilePath; + } + + Map _cachedPreferences; + + /// The in-memory representation of the map saved to a file in disk; + @visibleForTesting + Map get getCachedPreferences { + if (_cachedPreferences == null) { + _cachedPreferences = {}; + File localDataFile = fs.file(getLocalDataFilePath); + if (localDataFile.existsSync()) { + String stringMap = localDataFile.readAsStringSync(); + if (stringMap.isNotEmpty) { + _cachedPreferences = json.decode(stringMap) as Map; + } + } + } + return _cachedPreferences; + } + + /// Writes the cached preferences to disk. Returns [true] if the operation + /// succeeded. + bool _writePreferencesToFile() { + try { + File localDataFile = fs.file(getLocalDataFilePath); + if (!localDataFile.existsSync()) { + localDataFile.createSync(recursive: true); + } + String stringMap = json.encode(getCachedPreferences); + localDataFile.writeAsStringSync(stringMap); + } catch (e) { + print("Error saving preferences to disk: $e"); + return false; + } + return true; + } + + @override + Future clear() async { + getCachedPreferences.clear(); + return _writePreferencesToFile(); + } + + @override + Future> getAll() async { + return getCachedPreferences; + } + + @override + Future remove(String key) async { + getCachedPreferences.remove(key); + return _writePreferencesToFile(); + } + + @override + Future setValue(String valueType, String key, Object value) async { + getCachedPreferences[key] = value; + return _writePreferencesToFile(); + } +} diff --git a/packages/shared_preferences/shared_preferences_windows/lib/src/win32.dart b/packages/shared_preferences/shared_preferences_windows/lib/src/win32.dart new file mode 100644 index 000000000000..d4d0d65dc3ae --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/lib/src/win32.dart @@ -0,0 +1,91 @@ +// Copyright 2020 The Chromium 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:ffi'; +import 'dart:typed_data'; +import 'package:ffi/ffi.dart'; + +// Constants: + +/// Typedef for kNull values. +const kNull = 0; + +/// Typedef for MAX_VALUE. +const kMaxPath = 260; + +// Function definitions: + +// Definition for SHGetFolderPathW. Retrieves a folder path given a CSIDL. +// +// SHFOLDERAPI SHGetFolderPathW( +// HWND hwnd, +// int csidl, +// HANDLE hToken, +// DWORD dwFlags, +// LPWSTR pszPath +// ); +// +// This is a deprecated API. It is being used in place of the recommended +// API SHGetKnownFolderPath, since it has been challenging to retrieve the REFKNOWNFOLDERID +// parameter with ffi calls. It is safe to use this API since (per Microsoft's documentation) +// as of Windows Vista, this function is merely a wrapper for SHGetKnownFolderPath. +typedef shGetFolderPathNative = Int32 Function(Int64 hwnd, Int32 csidl, + Int64 hToken, Int32 dwFlags, Pointer pszPath); +typedef shGetFolderPathDart = int Function( + int hwnd, int csidl, int hToken, int dwFlags, Pointer pszPath); + +// Definition for GetModuleFileNameW. Retrieves the path to the current process. +// +// DWORD GetModuleFileNameW( +// HMODULE hModule, +// LPWSTR lpFilename, +// DWORD nSize +// ); +typedef GetModuleFileNameC = Int32 Function( + Int32 hModule, + Pointer fileName, + Int16 nSize, +); +typedef GetModuleFileNameDart = int Function( + int hModule, + Pointer fileName, + int nSize, +); + +/// Reference to the Shell32 dynamic library. +final shell32 = DynamicLibrary.open('shell32.dll'); + +/// Reference to the Kernel32 dynamic library. +final kernel32 = DynamicLibrary.open('kernel32.dll'); + +/// Dart invocation of the win32 SHGetFolderPathW function. +final shGetFolderPath = + shell32.lookupFunction( + 'SHGetFolderPathW'); + +/// Dart invocation of the win32 GetModuleFileNameW function. +final getModuleFileNameW = + kernel32.lookupFunction( + 'GetModuleFileNameW'); + +/// Convenience method to get the AppData Local folder path. +String getLocalDataPath() { + final CSIDL_LOCAL_APPDATA = 0x001c; + final CSIDL_FLAG_CREATE = 0x8000; + Pointer path = allocate(count: kMaxPath); + shGetFolderPath( + kNull, CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE, kNull, 0, path); + + Uint16List pathData = path.asTypedList(kMaxPath); + return String.fromCharCodes( + path.asTypedList(kMaxPath), 0, pathData.indexOf(0)); +} + +/// Convenience method to retrieve the path of the current process. +String getModuleFileName() { + Pointer rs = allocate(count: kMaxPath); + getModuleFileNameW(kNull, rs, kMaxPath); + Uint16List pathData = rs.asTypedList(kMaxPath); + return String.fromCharCodes(rs.asTypedList(kMaxPath), 0, pathData.indexOf(0)); +} diff --git a/packages/shared_preferences/shared_preferences_windows/pubspec.yaml b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml new file mode 100644 index 000000000000..cc0e94de1bf4 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/pubspec.yaml @@ -0,0 +1,29 @@ +name: shared_preferences_windows +description: Windows implementation of shared_preferences +homepage: https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences_windows +version: 0.0.1 + +flutter: + plugin: + platforms: + windows: + dartPluginClass: SharedPreferencesWindows + pluginClass: none + +environment: + sdk: ">=2.1.0 <3.0.0" + flutter: ">=1.12.8 <2.0.0" + +dependencies: + shared_preferences_platform_interface: ^1.0.0 + flutter: + sdk: flutter + meta: ^1.1.7 + path: ^1.6.4 + ffi: ^0.1.3 + file: ^5.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + pedantic: ^1.8.0 diff --git a/packages/shared_preferences/shared_preferences_windows/test/shared_preferences_windows_test.dart b/packages/shared_preferences/shared_preferences_windows/test/shared_preferences_windows_test.dart new file mode 100644 index 000000000000..b09bac55de71 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_windows/test/shared_preferences_windows_test.dart @@ -0,0 +1,90 @@ +// Copyright 2017 The Chromium 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:flutter_test/flutter_test.dart'; +import 'package:file/memory.dart'; +import 'package:shared_preferences_windows/shared_preferences_windows.dart'; + +MemoryFileSystem fs = MemoryFileSystem.test(); + +void main() { + const String kTestKey = 'testKey'; + const String kTestValue = 'testValue'; + + SharedPreferencesWindows sharedPreferences; + + setUp(() { + sharedPreferences = SharedPreferencesWindows(); + sharedPreferences.win32Wrapper = MockWin32Wrapper(); + sharedPreferences.fs = fs; + }); + + tearDown(() { + fs.file(sharedPreferences.getLocalDataFilePath) + ..deleteSync(recursive: true); + }); + + /// Writes the test file to disk and loads the contents to the + /// sharedPreferences cache. + void _writeTestFile() { + fs.file(sharedPreferences.getLocalDataFilePath) + ..createSync(recursive: true) + ..writeAsStringSync(''' + { + "$kTestKey": "$kTestValue" + } + '''); + // Loads the file contents into the shared preferences store's cache. + sharedPreferences.getCachedPreferences; + } + + String _readTestFile() { + return fs.file(sharedPreferences.getLocalDataFilePath).readAsStringSync(); + } + + group('shared preferences', () { + test('getAll', () async { + _writeTestFile(); + + final Map allData = await sharedPreferences.getAll(); + expect(allData, hasLength(1)); + expect(allData[kTestKey], kTestValue); + }); + + test('remove', () async { + _writeTestFile(); + expect(sharedPreferences.getCachedPreferences[kTestKey], isNotNull); + expect(await sharedPreferences.remove(kTestKey), isTrue); + expect(sharedPreferences.getCachedPreferences.containsKey(kTestKey), + isFalse); + expect(_readTestFile(), '{}'); + }); + + test('setValue', () async { + _writeTestFile(); + const String kNewKey = 'NewKey'; + const String kNewValue = 'NewValue'; + expect(sharedPreferences.getCachedPreferences[kNewKey], isNull); + expect(await sharedPreferences.setValue('String', kNewKey, kNewValue), + isTrue); + expect(sharedPreferences.getCachedPreferences[kNewKey], isNotNull); + expect(_readTestFile(), + '{"$kTestKey":"$kTestValue","$kNewKey":"$kNewValue"}'); + }); + + test('clear', () async { + _writeTestFile(); + expect(await sharedPreferences.clear(), isTrue); + expect(sharedPreferences.getCachedPreferences.isEmpty, isTrue); + expect(_readTestFile(), '{}'); + }); + }); +} + +class MockWin32Wrapper extends Win32Wrapper { + @override + String getLocalDataPath() { + return fs.directory('\\test').path; + } +}