From 2deb4629cce2747c8dd48dfa8d635afc2752d044 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 30 Jan 2024 16:11:18 +0100 Subject: [PATCH 01/11] refactor(neon_framework): introduce storage library to handle all the storage need for Neon Signed-off-by: Nikolas Rimikis --- packages/neon_framework/lib/neon.dart | 2 +- .../lib/src/models/app_implementation.dart | 2 +- .../lib/src/settings/models/option.dart | 1 + .../lib/src/settings/models/storage.dart | 116 ++---------------- .../lib/src/storage/app_storage.dart | 38 ++++++ .../lib/src/storage/single_value_store.dart | 56 +++++++++ .../lib/src/storage/storage_manager.dart | 46 +++++++ .../neon_framework/lib/src/testing/mocks.dart | 2 +- .../lib/src/utils/push_utils.dart | 1 + packages/neon_framework/lib/storage.dart | 8 ++ .../neon_framework/test/storage_test.dart | 1 + 11 files changed, 165 insertions(+), 108 deletions(-) create mode 100644 packages/neon_framework/lib/src/storage/app_storage.dart create mode 100644 packages/neon_framework/lib/src/storage/single_value_store.dart create mode 100644 packages/neon_framework/lib/src/storage/storage_manager.dart create mode 100644 packages/neon_framework/lib/storage.dart diff --git a/packages/neon_framework/lib/neon.dart b/packages/neon_framework/lib/neon.dart index 317a870db84..99986041161 100644 --- a/packages/neon_framework/lib/neon.dart +++ b/packages/neon_framework/lib/neon.dart @@ -11,12 +11,12 @@ import 'package:neon_framework/src/models/account.dart'; import 'package:neon_framework/src/models/app_implementation.dart'; import 'package:neon_framework/src/models/disposable.dart'; import 'package:neon_framework/src/platform/platform.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; import 'package:neon_framework/src/theme/neon.dart'; import 'package:neon_framework/src/utils/global_options.dart'; import 'package:neon_framework/src/utils/provider.dart'; import 'package:neon_framework/src/utils/request_manager.dart'; import 'package:neon_framework/src/utils/user_agent.dart'; +import 'package:neon_framework/storage.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; diff --git a/packages/neon_framework/lib/src/models/app_implementation.dart b/packages/neon_framework/lib/src/models/app_implementation.dart index 61100a277c0..8de9dd0a49e 100644 --- a/packages/neon_framework/lib/src/models/app_implementation.dart +++ b/packages/neon_framework/lib/src/models/app_implementation.dart @@ -46,7 +46,7 @@ abstract class AppImplementation nameFromLocalization(NeonLocalizations.of(context)); - /// The [SettingsStorage] for this app. + /// The storage bucket for this app. @protected late final AppStorage storage = AppStorage(StorageKeys.apps, id); diff --git a/packages/neon_framework/lib/src/settings/models/option.dart b/packages/neon_framework/lib/src/settings/models/option.dart index 7f875a6f433..e37abcfb664 100644 --- a/packages/neon_framework/lib/src/settings/models/option.dart +++ b/packages/neon_framework/lib/src/settings/models/option.dart @@ -7,6 +7,7 @@ import 'package:neon_framework/src/models/disposable.dart'; import 'package:neon_framework/src/models/label_builder.dart'; import 'package:neon_framework/src/settings/models/options_category.dart'; import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/storage.dart'; import 'package:rxdart/rxdart.dart'; /// Listenable option that is persisted in the [SettingsStorage]. diff --git a/packages/neon_framework/lib/src/settings/models/storage.dart b/packages/neon_framework/lib/src/settings/models/storage.dart index 4829cfae90a..c23d52d5a05 100644 --- a/packages/neon_framework/lib/src/settings/models/storage.dart +++ b/packages/neon_framework/lib/src/settings/models/storage.dart @@ -1,52 +1,8 @@ import 'package:meta/meta.dart'; +import 'package:neon_framework/storage.dart'; import 'package:nextcloud/ids.dart'; import 'package:shared_preferences/shared_preferences.dart'; -/// Storage interface used by `Option`s. -/// -/// Mimics the interface of [SharedPreferences]. -/// -/// See: -/// * [SingleValueStorage] for a storage that saves a single value. -/// * [AppStorage] for a storage that fully implements the [SharedPreferences] interface. -/// * [NeonStorage] that manages the storage backend. -@internal -abstract interface class SettingsStorage { - /// {@template NeonStorage.getString} - /// Reads a value from persistent storage, throwing an `Exception` if it's not a `String`. - /// {@endtemplate} - String? getString(String key); - - /// {@template NeonStorage.setString} - /// Saves a `String` [value] to persistent storage in the background. - /// - /// Note: Due to limitations in Android's SharedPreferences, - /// values cannot start with any one of the following: - /// - /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu' - /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBCaWdJbnRlZ2Vy' - /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBEb3VibGUu' - /// {@endtemplate} - Future setString(String key, String value); - - /// {@template NeonStorage.getBool} - /// Reads a value from persistent storage, throwing an `Exception` if it's not a `bool`. - /// {@endtemplate} - bool? getBool(String key); - - /// {@template NeonStorage.setBool} - /// Saves a `bool` [value] to persistent storage in the background. - /// {@endtemplate} - // ignore: avoid_positional_boolean_parameters - // ignore: avoid_positional_boolean_parameters - Future setBool(String key, bool value); - - /// {@template NeonStorage.remove} - /// Removes an entry from persistent storage. - /// {@endtemplate} - Future remove(String key); -} - /// Interface of a storable element. /// /// Usually used in enhanced enums to ensure uniqueness of the storage keys. @@ -91,55 +47,6 @@ enum StorageKeys implements Storable { final String value; } -/// Neon storage that manages the storage backend. -/// -/// [init] must be called and completed before accessing individual storages. -/// -/// See: -/// * [SingleValueStorage] for a storage that saves a single value. -/// * [AppStorage] for a storage that fully implements the [SharedPreferences] interface. -/// * [SettingsStorage] for the public interface used in `Option`s. -@internal -final class NeonStorage { - const NeonStorage._(); - - /// Shared preferences instance. - /// - /// Use [database] to access it. - /// Make sure it has been initialized with [init] before. - static SharedPreferences? _sharedPreferences; - - /// Initializes the database instance with a mocked value. - @visibleForTesting - // ignore: use_setters_to_change_properties - static void mock(SharedPreferences mock) => _sharedPreferences = mock; - - /// Sets up the [SharedPreferences] instance. - /// - /// Required to be called before accessing [database]. - static Future init() async { - if (_sharedPreferences != null) { - return; - } - - _sharedPreferences = await SharedPreferences.getInstance(); - } - - /// Returns the database instance. - /// - /// Throws a `StateError` if [init] has not completed. - @visibleForTesting - static SharedPreferences get database { - if (_sharedPreferences == null) { - throw StateError( - 'NeonStorage has not been initialized yet. Please make sure NeonStorage.init() has been called before and completed.', - ); - } - - return _sharedPreferences!; - } -} - /// A storage that saves a single value. /// /// [NeonStorage.init] must be called and completed before accessing individual values. @@ -150,36 +57,35 @@ final class NeonStorage { /// * [SettingsStorage] for the public interface used in `Option`s. @immutable @internal -final class SingleValueStorage { +final class SingleValueStorage implements KeyValueStorage { /// Creates a new storage for a single value. const SingleValueStorage(this.key); - /// The key used by the storage backend. + @override final StorageKeys key; - /// {@macro NeonStorage.containsKey} + @override bool hasValue() => NeonStorage.database.containsKey(key.value); - /// {@macro NeonStorage.remove} + @override Future remove() => NeonStorage.database.remove(key.value); - /// {@macro NeonStorage.getString} + @override String? getString() => NeonStorage.database.getString(key.value); - /// {@macro NeonStorage.setString} + @override Future setString(String value) => NeonStorage.database.setString(key.value, value); - /// {@macro NeonStorage.getBool} + @override bool? getBool() => NeonStorage.database.getBool(key.value); - /// {@macro NeonStorage.setBool} - // ignore: avoid_positional_boolean_parameters + @override Future setBool(bool value) => NeonStorage.database.setBool(key.value, value); - /// {@macro NeonStorage.getStringList} + @override List? getStringList() => NeonStorage.database.getStringList(key.value); - /// {@macro NeonStorage.setStringList} + @override Future setStringList(List value) => NeonStorage.database.setStringList(key.value, value); } diff --git a/packages/neon_framework/lib/src/storage/app_storage.dart b/packages/neon_framework/lib/src/storage/app_storage.dart new file mode 100644 index 00000000000..e50fdff8a15 --- /dev/null +++ b/packages/neon_framework/lib/src/storage/app_storage.dart @@ -0,0 +1,38 @@ +/// Storage interface used by `Option`s. +/// +/// See: +/// * `NeonStorage` to initialize and manage the storage backends. +abstract interface class SettingsStorage { + /// {@template NeonStorage.getString} + /// Reads a value from persistent storage, throwing an `Exception` if it's not a `String`. + /// {@endtemplate} + String? getString(String key); + + /// {@template NeonStorage.setString} + /// Saves a `String` [value] to persistent storage in the background. + /// + /// Note: Due to limitations in Android's SharedPreferences, + /// values cannot start with any one of the following: + /// + /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu' + /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBCaWdJbnRlZ2Vy' + /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBEb3VibGUu' + /// {@endtemplate} + Future setString(String key, String value); + + /// {@template NeonStorage.getBool} + /// Reads a value from persistent storage, throwing an `Exception` if it's not a `bool`. + /// {@endtemplate} + bool? getBool(String key); + + /// {@template NeonStorage.setBool} + /// Saves a `bool` [value] to persistent storage in the background. + /// {@endtemplate} + // ignore: avoid_positional_boolean_parameters + Future setBool(String key, bool value); + + /// {@template NeonStorage.remove} + /// Removes an entry from persistent storage. + /// {@endtemplate} + Future remove(String key); +} diff --git a/packages/neon_framework/lib/src/storage/single_value_store.dart b/packages/neon_framework/lib/src/storage/single_value_store.dart new file mode 100644 index 00000000000..e0e6583a3d2 --- /dev/null +++ b/packages/neon_framework/lib/src/storage/single_value_store.dart @@ -0,0 +1,56 @@ +import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +export 'package:neon_framework/src/storage/storage_manager.dart'; + +/// A storage that itself is a single entry of a key value store. +/// +/// Mimics the interface of [SharedPreferences]. +/// +/// See: +/// * `NeonStorage` to initialize and manage the storage backends. +abstract interface class KeyValueStorage { + /// The key used by the storage backend. + StorageKeys get key; + + /// {@macro NeonStorage.containsKey} + bool hasValue(); + + /// {@template NeonStorage.getString} + /// Reads a value from persistent storage, throwing an `Exception` if it's not a `String`. + /// {@endtemplate} + String? getString(); + + /// {@template NeonStorage.setString} + /// Saves a `String` [value] to persistent storage in the background. + /// + /// Note: Due to limitations in Android's SharedPreferences, + /// values cannot start with any one of the following: + /// + /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu' + /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBCaWdJbnRlZ2Vy' + /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBEb3VibGUu' + /// {@endtemplate} + Future setString(String value); + + /// {@template NeonStorage.getBool} + /// Reads a value from persistent storage, throwing an `Exception` if it's not a `bool`. + /// {@endtemplate} + bool? getBool(); + + /// {@template NeonStorage.setBool} + /// Saves a `bool` [value] to persistent storage in the background. + /// {@endtemplate} + // ignore: avoid_positional_boolean_parameters + Future setBool(bool value); + + /// {@template NeonStorage.remove} + /// Removes an entry from persistent storage. + /// {@endtemplate} + Future remove(); + + /// {@macro NeonStorage.getStringList} + List? getStringList(); + + /// {@macro NeonStorage.setStringList} + Future setStringList(List value); +} diff --git a/packages/neon_framework/lib/src/storage/storage_manager.dart b/packages/neon_framework/lib/src/storage/storage_manager.dart new file mode 100644 index 00000000000..fe894dbd907 --- /dev/null +++ b/packages/neon_framework/lib/src/storage/storage_manager.dart @@ -0,0 +1,46 @@ +import 'dart:async'; + +import 'package:meta/meta.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// Neon storage that manages the storage backend. +/// +/// [init] must be called and completed before accessing individual storages. +final class NeonStorage { + const NeonStorage._(); + + /// Shared preferences instance. + /// + /// Use [database] to access it. + /// Make sure it has been initialized with [init] before. + static SharedPreferences? _sharedPreferences; + + /// Initializes the database instance with a mocked value. + @visibleForTesting + // ignore: use_setters_to_change_properties + static void mock(SharedPreferences mock) => _sharedPreferences = mock; + + /// Sets up the [SharedPreferences] instance. + /// + /// Required to be called before accessing [database]. + static Future init() async { + if (_sharedPreferences != null) { + return; + } + + _sharedPreferences = await SharedPreferences.getInstance(); + } + + /// Returns the database instance. + /// + /// Throws a `StateError` if [init] has not completed. + static SharedPreferences get database { + if (_sharedPreferences == null) { + throw StateError( + 'NeonStorage has not been initialized yet. Please make sure NeonStorage.init() has been called before and completed.', + ); + } + + return _sharedPreferences!; + } +} diff --git a/packages/neon_framework/lib/src/testing/mocks.dart b/packages/neon_framework/lib/src/testing/mocks.dart index 0801a3934d7..423b6e9f436 100644 --- a/packages/neon_framework/lib/src/testing/mocks.dart +++ b/packages/neon_framework/lib/src/testing/mocks.dart @@ -12,9 +12,9 @@ import 'package:neon_framework/src/blocs/apps.dart'; import 'package:neon_framework/src/blocs/capabilities.dart'; import 'package:neon_framework/src/models/disposable.dart'; import 'package:neon_framework/src/settings/models/exportable.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; import 'package:neon_framework/src/utils/account_options.dart'; import 'package:neon_framework/src/utils/request_manager.dart'; +import 'package:neon_framework/storage.dart'; import 'package:shared_preferences/shared_preferences.dart'; class MockAccount extends Mock implements Account {} diff --git a/packages/neon_framework/lib/src/utils/push_utils.dart b/packages/neon_framework/lib/src/utils/push_utils.dart index f035fc8e6af..91a6feaf26c 100644 --- a/packages/neon_framework/lib/src/utils/push_utils.dart +++ b/packages/neon_framework/lib/src/utils/push_utils.dart @@ -20,6 +20,7 @@ import 'package:neon_framework/src/utils/findable.dart'; import 'package:neon_framework/src/utils/image_utils.dart'; import 'package:neon_framework/src/utils/localizations.dart'; import 'package:neon_framework/src/utils/request_manager.dart'; +import 'package:neon_framework/storage.dart'; import 'package:nextcloud/notifications.dart' as notifications; import 'package:rxdart/rxdart.dart'; diff --git a/packages/neon_framework/lib/storage.dart b/packages/neon_framework/lib/storage.dart new file mode 100644 index 00000000000..e5ccf87a66b --- /dev/null +++ b/packages/neon_framework/lib/storage.dart @@ -0,0 +1,8 @@ +/// Storage interfaces for the Neon app framework. +/// +/// The `NeonStorage` manages all storage backends. +library; + +export 'package:neon_framework/src/storage/app_storage.dart'; +export 'package:neon_framework/src/storage/single_value_store.dart'; +export 'package:neon_framework/src/storage/storage_manager.dart'; diff --git a/packages/neon_framework/test/storage_test.dart b/packages/neon_framework/test/storage_test.dart index 9f9d7f7413d..0ad96019884 100644 --- a/packages/neon_framework/test/storage_test.dart +++ b/packages/neon_framework/test/storage_test.dart @@ -1,6 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/storage.dart'; import 'package:neon_framework/testing.dart'; import 'package:shared_preferences/shared_preferences.dart'; From 8cd8aa1852eda1496bc81201f222165b16be6690 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 30 Jan 2024 16:11:18 +0100 Subject: [PATCH 02/11] refactor(neon_framework): add abstraction for the request manager cache Signed-off-by: Nikolas Rimikis --- .../lib/src/storage/request_cache.dart | 23 +++++++++++++++++++ .../lib/src/utils/request_manager.dart | 8 +++++-- packages/neon_framework/lib/storage.dart | 1 + 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 packages/neon_framework/lib/src/storage/request_cache.dart diff --git a/packages/neon_framework/lib/src/storage/request_cache.dart b/packages/neon_framework/lib/src/storage/request_cache.dart new file mode 100644 index 00000000000..9711daa47f7 --- /dev/null +++ b/packages/neon_framework/lib/src/storage/request_cache.dart @@ -0,0 +1,23 @@ +import 'dart:async'; + +import 'package:neon_framework/src/utils/request_manager.dart'; + +/// A storage used to cache a key value pair. +abstract interface class CacheInterface { + /// Get's the cached value for the given [key]. + /// + /// Use [getParameters] if you only need to check whether the cache is still + /// valid. + Future get(String key); + + /// Set's the cached [value] at the given [key]. + /// + /// If a value is already present it will be updated with the new one. + Future set(String key, String value, CacheParameters? parameters); + + /// Retrieves the cache parameters for the given [key]. + Future getParameters(String key); + + /// Updates the cache [parameters] for a given [key] without modifying the `value`. + Future updateParameters(String key, CacheParameters? parameters); +} diff --git a/packages/neon_framework/lib/src/utils/request_manager.dart b/packages/neon_framework/lib/src/utils/request_manager.dart index 6b2419bc7a0..7db077222d8 100644 --- a/packages/neon_framework/lib/src/utils/request_manager.dart +++ b/packages/neon_framework/lib/src/utils/request_manager.dart @@ -9,6 +9,7 @@ import 'package:meta/meta.dart'; import 'package:neon_framework/models.dart'; import 'package:neon_framework/src/bloc/result.dart'; import 'package:neon_framework/src/models/account.dart'; +import 'package:neon_framework/storage.dart'; import 'package:nextcloud/nextcloud.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; @@ -355,7 +356,7 @@ class RequestManager { } @internal -class Cache { +class Cache implements CacheInterface { factory Cache() => instance ??= Cache._(); Cache._(); @@ -405,6 +406,7 @@ class Cache { return database; } + @override Future get(String key) async { List>? result; try { @@ -417,6 +419,7 @@ class Cache { return result?.firstOrNull?['value'] as String?; } + @override Future set(String key, String value, CacheParameters? parameters) async { try { // UPSERT is only available since SQLite 3.24.0 (June 4, 2018). @@ -444,6 +447,7 @@ class Cache { } } + @override Future getParameters(String key) async { List>? result; try { @@ -462,7 +466,7 @@ class Cache { ); } - /// Updates the cache [parameters] for a given [key] without modifying the `value`. + @override Future updateParameters(String key, CacheParameters? parameters) async { try { await _requireDatabase.update( diff --git a/packages/neon_framework/lib/storage.dart b/packages/neon_framework/lib/storage.dart index e5ccf87a66b..6f85e1fec4c 100644 --- a/packages/neon_framework/lib/storage.dart +++ b/packages/neon_framework/lib/storage.dart @@ -4,5 +4,6 @@ library; export 'package:neon_framework/src/storage/app_storage.dart'; +export 'package:neon_framework/src/storage/request_cache.dart'; export 'package:neon_framework/src/storage/single_value_store.dart'; export 'package:neon_framework/src/storage/storage_manager.dart'; From c572c8c8edca09a4d3a043bbec3d9e0d401c6a61 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 30 Jan 2024 16:11:19 +0100 Subject: [PATCH 03/11] refactor(neon_framework): make NeonStorage a singleton class Signed-off-by: Nikolas Rimikis --- packages/neon_framework/lib/neon.dart | 2 +- .../lib/src/settings/models/storage.dart | 32 ++--- .../lib/src/storage/storage_manager.dart | 31 +++-- .../neon_framework/lib/src/testing/mocks.dart | 2 + .../lib/src/utils/push_utils.dart | 2 +- .../neon_framework/test/storage_test.dart | 119 ++++++++++-------- 6 files changed, 106 insertions(+), 82 deletions(-) diff --git a/packages/neon_framework/lib/neon.dart b/packages/neon_framework/lib/neon.dart index 99986041161..fa92470a692 100644 --- a/packages/neon_framework/lib/neon.dart +++ b/packages/neon_framework/lib/neon.dart @@ -36,7 +36,7 @@ Future runNeon({ await NeonPlatform.setup(); await RequestManager.instance.initCache(); - await NeonStorage.init(); + await NeonStorage().init(); final packageInfo = await PackageInfo.fromPlatform(); buildUserAgent(packageInfo); diff --git a/packages/neon_framework/lib/src/settings/models/storage.dart b/packages/neon_framework/lib/src/settings/models/storage.dart index c23d52d5a05..8e2c59d4901 100644 --- a/packages/neon_framework/lib/src/settings/models/storage.dart +++ b/packages/neon_framework/lib/src/settings/models/storage.dart @@ -65,28 +65,28 @@ final class SingleValueStorage implements KeyValueStorage { final StorageKeys key; @override - bool hasValue() => NeonStorage.database.containsKey(key.value); + bool hasValue() => NeonStorage().database.containsKey(key.value); @override - Future remove() => NeonStorage.database.remove(key.value); + Future remove() => NeonStorage().database.remove(key.value); @override - String? getString() => NeonStorage.database.getString(key.value); + String? getString() => NeonStorage().database.getString(key.value); @override - Future setString(String value) => NeonStorage.database.setString(key.value, value); + Future setString(String value) => NeonStorage().database.setString(key.value, value); @override - bool? getBool() => NeonStorage.database.getBool(key.value); + bool? getBool() => NeonStorage().database.getBool(key.value); @override - Future setBool(bool value) => NeonStorage.database.setBool(key.value, value); + Future setBool(bool value) => NeonStorage().database.setBool(key.value, value); @override - List? getStringList() => NeonStorage.database.getStringList(key.value); + List? getStringList() => NeonStorage().database.getStringList(key.value); @override - Future setStringList(List value) => NeonStorage.database.setStringList(key.value, value); + Future setStringList(List value) => NeonStorage().database.setStringList(key.value, value); } /// A storage that can save a group of values. @@ -137,31 +137,31 @@ final class AppStorage implements SettingsStorage { /// {@template NeonStorage.containsKey} /// Returns true if the persistent storage contains the given [key]. /// {@endtemplate} - bool containsKey(String key) => NeonStorage.database.containsKey(formatKey(key)); + bool containsKey(String key) => NeonStorage().database.containsKey(formatKey(key)); @override - Future remove(String key) => NeonStorage.database.remove(formatKey(key)); + Future remove(String key) => NeonStorage().database.remove(formatKey(key)); @override - String? getString(String key) => NeonStorage.database.getString(formatKey(key)); + String? getString(String key) => NeonStorage().database.getString(formatKey(key)); @override - Future setString(String key, String value) => NeonStorage.database.setString(formatKey(key), value); + Future setString(String key, String value) => NeonStorage().database.setString(formatKey(key), value); @override - bool? getBool(String key) => NeonStorage.database.getBool(formatKey(key)); + bool? getBool(String key) => NeonStorage().database.getBool(formatKey(key)); @override - Future setBool(String key, bool value) => NeonStorage.database.setBool(formatKey(key), value); + Future setBool(String key, bool value) => NeonStorage().database.setBool(formatKey(key), value); /// {@template NeonStorage.getStringList} /// Reads a set of string values from persistent storage, throwing an `Exception` if it's not a `String` set. /// {@endtemplate} - List? getStringList(String key) => NeonStorage.database.getStringList(formatKey(key)); + List? getStringList(String key) => NeonStorage().database.getStringList(formatKey(key)); /// {@template NeonStorage.setStringList} /// Saves a list of `String` [value]s to persistent storage in the background. /// {@endtemplate} Future setStringList(String key, List value) => - NeonStorage.database.setStringList(formatKey(key), value); + NeonStorage().database.setStringList(formatKey(key), value); } diff --git a/packages/neon_framework/lib/src/storage/storage_manager.dart b/packages/neon_framework/lib/src/storage/storage_manager.dart index fe894dbd907..01523c406f7 100644 --- a/packages/neon_framework/lib/src/storage/storage_manager.dart +++ b/packages/neon_framework/lib/src/storage/storage_manager.dart @@ -6,24 +6,35 @@ import 'package:shared_preferences/shared_preferences.dart'; /// Neon storage that manages the storage backend. /// /// [init] must be called and completed before accessing individual storages. -final class NeonStorage { - const NeonStorage._(); +@sealed +class NeonStorage { + /// Accesses the current instance of the storage, creating a new one if non + /// existent. + factory NeonStorage() => instance ??= NeonStorage._(); + + /// Mocks the storage for testing. + @visibleForTesting + factory NeonStorage.mocked(NeonStorage mocked) => instance = mocked; + + NeonStorage._(); + + /// The current instance of the storage. + /// + /// Setting this to null resets the singleton leading to a new storage being + /// created on the next access of the constructor. + @visibleForTesting + static NeonStorage? instance; /// Shared preferences instance. /// /// Use [database] to access it. /// Make sure it has been initialized with [init] before. - static SharedPreferences? _sharedPreferences; - - /// Initializes the database instance with a mocked value. - @visibleForTesting - // ignore: use_setters_to_change_properties - static void mock(SharedPreferences mock) => _sharedPreferences = mock; + SharedPreferences? _sharedPreferences; /// Sets up the [SharedPreferences] instance. /// /// Required to be called before accessing [database]. - static Future init() async { + Future init() async { if (_sharedPreferences != null) { return; } @@ -34,7 +45,7 @@ final class NeonStorage { /// Returns the database instance. /// /// Throws a `StateError` if [init] has not completed. - static SharedPreferences get database { + SharedPreferences get database { if (_sharedPreferences == null) { throw StateError( 'NeonStorage has not been initialized yet. Please make sure NeonStorage.init() has been called before and completed.', diff --git a/packages/neon_framework/lib/src/testing/mocks.dart b/packages/neon_framework/lib/src/testing/mocks.dart index 423b6e9f436..95a8b743252 100644 --- a/packages/neon_framework/lib/src/testing/mocks.dart +++ b/packages/neon_framework/lib/src/testing/mocks.dart @@ -45,6 +45,8 @@ class MockAppImplementationOptions extends Mock implements AppImplementationOpti class MockCache extends Mock implements Cache {} +class MockNeonStorage extends Mock implements NeonStorage {} + class MockSharedPreferences extends Mock implements SharedPreferences {} class MockCallbackFunction extends Mock { diff --git a/packages/neon_framework/lib/src/utils/push_utils.dart b/packages/neon_framework/lib/src/utils/push_utils.dart index 91a6feaf26c..d40b0762e7d 100644 --- a/packages/neon_framework/lib/src/utils/push_utils.dart +++ b/packages/neon_framework/lib/src/utils/push_utils.dart @@ -88,7 +88,7 @@ class PushUtils { } }, ); - await NeonStorage.init(); + await NeonStorage().init(); final keypair = loadRSAKeypair(); for (final message in Uri(query: utf8.decode(messages)).queryParameters.values) { diff --git a/packages/neon_framework/test/storage_test.dart b/packages/neon_framework/test/storage_test.dart index 0ad96019884..262176a5e5d 100644 --- a/packages/neon_framework/test/storage_test.dart +++ b/packages/neon_framework/test/storage_test.dart @@ -7,16 +7,31 @@ import 'package:shared_preferences/shared_preferences.dart'; void main() { test('NeonStorage', () async { - expect(() => NeonStorage.database, throwsA(isA())); + expect(() => NeonStorage().database, throwsA(isA())); SharedPreferences.setMockInitialValues({}); - await NeonStorage.init(); + await NeonStorage().init(); - expect(NeonStorage.database, isA()); + expect(NeonStorage().database, isA()); }); - group('AppStorage', () { - test('formatKey', () async { + group('Storages', () { + late SharedPreferences sharedPreferences; + + setUp(() { + sharedPreferences = MockSharedPreferences(); + final storageMock = MockNeonStorage(); + + when(() => storageMock.database).thenReturn(sharedPreferences); + + NeonStorage.mocked(storageMock); + }); + + tearDown(() { + NeonStorage.instance = null; + }); + + test('AppStorage formatKey', () async { var appStorage = const AppStorage(StorageKeys.accounts); var key = appStorage.formatKey('test-key'); expect(key, 'accounts-test-key'); @@ -28,9 +43,7 @@ void main() { expect(appStorage.id, 'test-suffix'); }); - test('interface', () async { - final sharedPreferences = MockSharedPreferences(); - NeonStorage.mock(sharedPreferences); + test('AppStorage interface', () async { const appStorage = AppStorage(StorageKeys.accounts); const key = 'key'; final formattedKey = appStorage.formatKey(key); @@ -75,52 +88,50 @@ void main() { expect(result, false); verify(() => sharedPreferences.setStringList(formattedKey, ['hi there'])).called(1); }); - }); - test('SingleValueStorage', () async { - final sharedPreferences = MockSharedPreferences(); - NeonStorage.mock(sharedPreferences); - const storage = SingleValueStorage(StorageKeys.global); - final key = StorageKeys.global.value; - - when(() => sharedPreferences.containsKey(key)).thenReturn(true); - dynamic result = storage.hasValue(); - expect(result, equals(true)); - verify(() => sharedPreferences.containsKey(key)).called(1); - - when(() => sharedPreferences.remove(key)).thenAnswer((_) => Future.value(false)); - result = await storage.remove(); - expect(result, equals(false)); - verify(() => sharedPreferences.remove(key)).called(1); - - when(() => sharedPreferences.getString(key)).thenReturn(null); - result = storage.getString(); - expect(result, isNull); - verify(() => sharedPreferences.getString(key)).called(1); - - when(() => sharedPreferences.setString(key, 'value')).thenAnswer((_) => Future.value(false)); - result = await storage.setString('value'); - expect(result, false); - verify(() => sharedPreferences.setString(key, 'value')).called(1); - - when(() => sharedPreferences.getBool(key)).thenReturn(true); - result = storage.getBool(); - expect(result, equals(true)); - verify(() => sharedPreferences.getBool(key)).called(1); - - when(() => sharedPreferences.setBool(key, true)).thenAnswer((_) => Future.value(true)); - result = await storage.setBool(true); - expect(result, true); - verify(() => sharedPreferences.setBool(key, true)).called(1); - - when(() => sharedPreferences.getStringList(key)).thenReturn(['hi there']); - result = storage.getStringList(); - expect(result, equals(['hi there'])); - verify(() => sharedPreferences.getStringList(key)).called(1); - - when(() => sharedPreferences.setStringList(key, ['hi there'])).thenAnswer((_) => Future.value(false)); - result = await storage.setStringList(['hi there']); - expect(result, false); - verify(() => sharedPreferences.setStringList(key, ['hi there'])).called(1); + test('SingleValueStorage', () async { + const storage = SingleValueStorage(StorageKeys.global); + final key = StorageKeys.global.value; + + when(() => sharedPreferences.containsKey(key)).thenReturn(true); + dynamic result = storage.hasValue(); + expect(result, equals(true)); + verify(() => sharedPreferences.containsKey(key)).called(1); + + when(() => sharedPreferences.remove(key)).thenAnswer((_) => Future.value(false)); + result = await storage.remove(); + expect(result, equals(false)); + verify(() => sharedPreferences.remove(key)).called(1); + + when(() => sharedPreferences.getString(key)).thenReturn(null); + result = storage.getString(); + expect(result, isNull); + verify(() => sharedPreferences.getString(key)).called(1); + + when(() => sharedPreferences.setString(key, 'value')).thenAnswer((_) => Future.value(false)); + result = await storage.setString('value'); + expect(result, false); + verify(() => sharedPreferences.setString(key, 'value')).called(1); + + when(() => sharedPreferences.getBool(key)).thenReturn(true); + result = storage.getBool(); + expect(result, equals(true)); + verify(() => sharedPreferences.getBool(key)).called(1); + + when(() => sharedPreferences.setBool(key, true)).thenAnswer((_) => Future.value(true)); + result = await storage.setBool(true); + expect(result, true); + verify(() => sharedPreferences.setBool(key, true)).called(1); + + when(() => sharedPreferences.getStringList(key)).thenReturn(['hi there']); + result = storage.getStringList(); + expect(result, equals(['hi there'])); + verify(() => sharedPreferences.getStringList(key)).called(1); + + when(() => sharedPreferences.setStringList(key, ['hi there'])).thenAnswer((_) => Future.value(false)); + result = await storage.setStringList(['hi there']); + expect(result, false); + verify(() => sharedPreferences.setStringList(key, ['hi there'])).called(1); + }); }); } From 9244902a16133c09b0f1021cec9d4641f49578c5 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 30 Jan 2024 16:11:19 +0100 Subject: [PATCH 04/11] refactor(neon_framework): migrate accounts list to SingleValueStorage Signed-off-by: Nikolas Rimikis --- .../neon_framework/lib/src/blocs/accounts.dart | 17 ++++++----------- .../lib/src/settings/models/storage.dart | 5 ++++- .../settings/utils/settings_export_helper.dart | 2 +- packages/neon_framework/test/storage_test.dart | 8 ++++---- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/packages/neon_framework/lib/src/blocs/accounts.dart b/packages/neon_framework/lib/src/blocs/accounts.dart index 2fafde5ddad..364a5fd45c2 100644 --- a/packages/neon_framework/lib/src/blocs/accounts.dart +++ b/packages/neon_framework/lib/src/blocs/accounts.dart @@ -22,8 +22,6 @@ import 'package:neon_framework/src/utils/global_options.dart'; import 'package:nextcloud/core.dart' as core; import 'package:rxdart/rxdart.dart'; -const _keyAccounts = 'accounts'; - /// The Bloc responsible for managing the [Account]s @sealed abstract interface class AccountsBloc implements Disposable { @@ -313,7 +311,7 @@ class _AccountsBloc extends Bloc implements AccountsBloc { @override AccountOptions getOptionsFor(Account account) => accountsOptions[account] ??= AccountOptions( - AppStorage(StorageKeys.accounts, account.id), + AppStorage(StorageKeys.accountOptions, account.id), getAppsBlocFor(account), ); @@ -369,21 +367,18 @@ class _AccountsBloc extends Bloc implements AccountsBloc { /// /// It is not checked whether the stored information is still valid. List loadAccounts() { - const storage = AppStorage(StorageKeys.accounts); + const storage = SingleValueStorage(StorageKeys.accountOptions); - if (storage.containsKey(_keyAccounts)) { - return storage - .getStringList(_keyAccounts)! - .map((a) => Account.fromJson(json.decode(a) as Map)) - .toList(); + if (storage.hasValue()) { + return storage.getStringList()!.map((a) => Account.fromJson(json.decode(a) as Map)).toList(); } return []; } /// Saves the given [accounts] to the storage. Future saveAccounts(List accounts) async { - const storage = AppStorage(StorageKeys.accounts); + const storage = SingleValueStorage(StorageKeys.accountOptions); final values = accounts.map((a) => json.encode(a.toJson())).toList(); - await storage.setStringList(_keyAccounts, values); + await storage.setStringList(values); } diff --git a/packages/neon_framework/lib/src/settings/models/storage.dart b/packages/neon_framework/lib/src/settings/models/storage.dart index 8e2c59d4901..2284ce6b00a 100644 --- a/packages/neon_framework/lib/src/settings/models/storage.dart +++ b/packages/neon_framework/lib/src/settings/models/storage.dart @@ -24,7 +24,10 @@ enum StorageKeys implements Storable { apps._('app'), /// The key for the `Account`s and their `AccountOptions`. - accounts._('accounts'), + accountOptions._('accounts'), + + /// The key for the list of logged in `Account`s. + accounts._('accounts-accounts'), /// The key for the `GlobalOptions`. global._('global'), diff --git a/packages/neon_framework/lib/src/settings/utils/settings_export_helper.dart b/packages/neon_framework/lib/src/settings/utils/settings_export_helper.dart index cc0d7e4eced..843cea9fc7b 100644 --- a/packages/neon_framework/lib/src/settings/utils/settings_export_helper.dart +++ b/packages/neon_framework/lib/src/settings/utils/settings_export_helper.dart @@ -123,7 +123,7 @@ class AccountsBlocExporter implements Exportable { final AccountsBloc accountsBloc; /// Key the exported value will be stored at. - static final _key = StorageKeys.accounts.value; + static final _key = StorageKeys.accountOptions.value; @override MapEntry export() => MapEntry(_key, Map.fromEntries(_serialize())); diff --git a/packages/neon_framework/test/storage_test.dart b/packages/neon_framework/test/storage_test.dart index 262176a5e5d..e27ba3f3f53 100644 --- a/packages/neon_framework/test/storage_test.dart +++ b/packages/neon_framework/test/storage_test.dart @@ -32,19 +32,19 @@ void main() { }); test('AppStorage formatKey', () async { - var appStorage = const AppStorage(StorageKeys.accounts); + var appStorage = const AppStorage(StorageKeys.accountOptions); var key = appStorage.formatKey('test-key'); expect(key, 'accounts-test-key'); - expect(appStorage.id, StorageKeys.accounts.value); + expect(appStorage.id, StorageKeys.accountOptions.value); - appStorage = const AppStorage(StorageKeys.accounts, 'test-suffix'); + appStorage = const AppStorage(StorageKeys.accountOptions, 'test-suffix'); key = appStorage.formatKey('test-key'); expect(key, 'accounts-test-suffix-test-key'); expect(appStorage.id, 'test-suffix'); }); test('AppStorage interface', () async { - const appStorage = AppStorage(StorageKeys.accounts); + const appStorage = AppStorage(StorageKeys.accountOptions); const key = 'key'; final formattedKey = appStorage.formatKey(key); From a63a78a4741fca751929763d28ba7c74f3aff2e8 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 30 Jan 2024 16:11:19 +0100 Subject: [PATCH 05/11] refactor(neon_framework): remove AppStorage methods not available in its interface Signed-off-by: Nikolas Rimikis --- .../lib/src/settings/models/storage.dart | 16 ---------------- .../lib/src/utils/push_utils.dart | 5 +++-- packages/neon_framework/test/storage_test.dart | 17 +---------------- 3 files changed, 4 insertions(+), 34 deletions(-) diff --git a/packages/neon_framework/lib/src/settings/models/storage.dart b/packages/neon_framework/lib/src/settings/models/storage.dart index 2284ce6b00a..8e539f66599 100644 --- a/packages/neon_framework/lib/src/settings/models/storage.dart +++ b/packages/neon_framework/lib/src/settings/models/storage.dart @@ -137,11 +137,6 @@ final class AppStorage implements SettingsStorage { return '${groupKey.value}-$key'; } - /// {@template NeonStorage.containsKey} - /// Returns true if the persistent storage contains the given [key]. - /// {@endtemplate} - bool containsKey(String key) => NeonStorage().database.containsKey(formatKey(key)); - @override Future remove(String key) => NeonStorage().database.remove(formatKey(key)); @@ -156,15 +151,4 @@ final class AppStorage implements SettingsStorage { @override Future setBool(String key, bool value) => NeonStorage().database.setBool(formatKey(key), value); - - /// {@template NeonStorage.getStringList} - /// Reads a set of string values from persistent storage, throwing an `Exception` if it's not a `String` set. - /// {@endtemplate} - List? getStringList(String key) => NeonStorage().database.getStringList(formatKey(key)); - - /// {@template NeonStorage.setStringList} - /// Saves a list of `String` [value]s to persistent storage in the background. - /// {@endtemplate} - Future setStringList(String key, List value) => - NeonStorage().database.setStringList(formatKey(key), value); } diff --git a/packages/neon_framework/lib/src/utils/push_utils.dart b/packages/neon_framework/lib/src/utils/push_utils.dart index d40b0762e7d..27494b94bd1 100644 --- a/packages/neon_framework/lib/src/utils/push_utils.dart +++ b/packages/neon_framework/lib/src/utils/push_utils.dart @@ -44,14 +44,15 @@ class PushUtils { const keyDevicePrivateKey = 'device-private-key'; final RSAKeypair keypair; - if (!storage.containsKey(keyDevicePrivateKey) || (storage.getString(keyDevicePrivateKey)!.isEmpty)) { + final privateKey = storage.getString(keyDevicePrivateKey); + if (privateKey == null || privateKey.isEmpty) { debugPrint('Generating RSA keys for push notifications'); // The key size has to be 2048, other sizes are not accepted by Nextcloud (at the moment at least) // ignore: avoid_redundant_argument_values keypair = RSAKeypair.fromRandom(keySize: 2048); unawaited(storage.setString(keyDevicePrivateKey, keypair.privateKey.toPEM())); } else { - keypair = RSAKeypair(RSAPrivateKey.fromPEM(storage.getString(keyDevicePrivateKey)!)); + keypair = RSAKeypair(RSAPrivateKey.fromPEM(privateKey)); } return keypair; diff --git a/packages/neon_framework/test/storage_test.dart b/packages/neon_framework/test/storage_test.dart index e27ba3f3f53..5c74dcbe78f 100644 --- a/packages/neon_framework/test/storage_test.dart +++ b/packages/neon_framework/test/storage_test.dart @@ -48,13 +48,8 @@ void main() { const key = 'key'; final formattedKey = appStorage.formatKey(key); - when(() => sharedPreferences.containsKey(formattedKey)).thenReturn(true); - dynamic result = appStorage.containsKey(key); - expect(result, equals(true)); - verify(() => sharedPreferences.containsKey(formattedKey)).called(1); - when(() => sharedPreferences.remove(formattedKey)).thenAnswer((_) => Future.value(false)); - result = await appStorage.remove(key); + dynamic result = await appStorage.remove(key); expect(result, equals(false)); verify(() => sharedPreferences.remove(formattedKey)).called(1); @@ -77,16 +72,6 @@ void main() { result = await appStorage.setBool(key, true); expect(result, true); verify(() => sharedPreferences.setBool(formattedKey, true)).called(1); - - when(() => sharedPreferences.getStringList(formattedKey)).thenReturn(['hi there']); - result = appStorage.getStringList(key); - expect(result, equals(['hi there'])); - verify(() => sharedPreferences.getStringList(formattedKey)).called(1); - - when(() => sharedPreferences.setStringList(formattedKey, ['hi there'])).thenAnswer((_) => Future.value(false)); - result = await appStorage.setStringList(key, ['hi there']); - expect(result, false); - verify(() => sharedPreferences.setStringList(formattedKey, ['hi there'])).called(1); }); test('SingleValueStorage', () async { From c65c5a07ec4739d5128be2c1b5f15fd290a4db6d Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 30 Jan 2024 16:11:19 +0100 Subject: [PATCH 06/11] refactor(neon_framework): let NeonStorage manage the AppStorage Signed-off-by: Nikolas Rimikis --- .../lib/src/blocs/accounts.dart | 3 +- .../lib/src/blocs/push_notifications.dart | 3 +- .../lib/src/models/app_implementation.dart | 3 +- .../settings/models/options_collection.dart | 4 +- .../lib/src/settings/models/storage.dart | 68 ----------- .../lib/src/storage/app_storage.dart | 38 ------ .../lib/src/storage/settings_store.dart | 112 ++++++++++++++++++ .../lib/src/storage/storage_manager.dart | 5 + .../lib/src/utils/global_options.dart | 3 +- .../lib/src/utils/push_utils.dart | 2 +- packages/neon_framework/lib/storage.dart | 2 +- .../test/options_collection_test.dart | 1 + .../neon_framework/test/storage_test.dart | 1 + 13 files changed, 131 insertions(+), 114 deletions(-) delete mode 100644 packages/neon_framework/lib/src/storage/app_storage.dart create mode 100644 packages/neon_framework/lib/src/storage/settings_store.dart diff --git a/packages/neon_framework/lib/src/blocs/accounts.dart b/packages/neon_framework/lib/src/blocs/accounts.dart index 364a5fd45c2..db8dc3fe09d 100644 --- a/packages/neon_framework/lib/src/blocs/accounts.dart +++ b/packages/neon_framework/lib/src/blocs/accounts.dart @@ -19,6 +19,7 @@ import 'package:neon_framework/src/settings/models/storage.dart'; import 'package:neon_framework/src/utils/account_options.dart'; import 'package:neon_framework/src/utils/findable.dart'; import 'package:neon_framework/src/utils/global_options.dart'; +import 'package:neon_framework/storage.dart'; import 'package:nextcloud/core.dart' as core; import 'package:rxdart/rxdart.dart'; @@ -311,7 +312,7 @@ class _AccountsBloc extends Bloc implements AccountsBloc { @override AccountOptions getOptionsFor(Account account) => accountsOptions[account] ??= AccountOptions( - AppStorage(StorageKeys.accountOptions, account.id), + NeonStorage().settingsStorage(StorageKeys.accountOptions, account.id), getAppsBlocFor(account), ); diff --git a/packages/neon_framework/lib/src/blocs/push_notifications.dart b/packages/neon_framework/lib/src/blocs/push_notifications.dart index 100aec50a71..0705ce5c1db 100644 --- a/packages/neon_framework/lib/src/blocs/push_notifications.dart +++ b/packages/neon_framework/lib/src/blocs/push_notifications.dart @@ -11,6 +11,7 @@ import 'package:neon_framework/src/settings/models/storage.dart'; import 'package:neon_framework/src/utils/findable.dart'; import 'package:neon_framework/src/utils/global_options.dart'; import 'package:neon_framework/src/utils/push_utils.dart'; +import 'package:neon_framework/storage.dart'; import 'package:nextcloud/notifications.dart' as notifications; import 'package:unifiedpush/unifiedpush.dart'; @@ -42,7 +43,7 @@ class _PushNotificationsBloc extends Bloc implements PushNotificationsBloc { } final AccountsBloc accountsBloc; - late final storage = const AppStorage(StorageKeys.lastEndpoint); + late final storage = NeonStorage().settingsStorage(StorageKeys.lastEndpoint); final GlobalOptions globalOptions; StreamSubscription>? accountsListener; diff --git a/packages/neon_framework/lib/src/models/app_implementation.dart b/packages/neon_framework/lib/src/models/app_implementation.dart index 8de9dd0a49e..9ecf25c7fe9 100644 --- a/packages/neon_framework/lib/src/models/app_implementation.dart +++ b/packages/neon_framework/lib/src/models/app_implementation.dart @@ -14,6 +14,7 @@ import 'package:neon_framework/src/settings/models/storage.dart'; import 'package:neon_framework/src/utils/findable.dart'; import 'package:neon_framework/src/utils/provider.dart'; import 'package:neon_framework/src/widgets/drawer_destination.dart'; +import 'package:neon_framework/storage.dart'; import 'package:nextcloud/core.dart' as core; import 'package:nextcloud/nextcloud.dart' show VersionCheck; import 'package:provider/provider.dart'; @@ -48,7 +49,7 @@ abstract class AppImplementation setStringList(List value) => NeonStorage().database.setStringList(key.value, value); } - -/// A storage that can save a group of values. -/// -/// Implements the interface of [SharedPreferences]. -/// [NeonStorage.init] must be called and completed before accessing individual values. -/// -/// See: -/// * [NeonStorage] to initialize the storage backend. -/// * [SingleValueStorage] for a storage that saves a single value. -/// * [SettingsStorage] for the public interface used in `Option`s. -@immutable -@internal -final class AppStorage implements SettingsStorage { - /// Creates a new app storage. - const AppStorage( - this.groupKey, [ - this.suffix, - ]); - - /// The group key for this app storage. - /// - /// Keys are formatted with [formatKey] - final StorageKeys groupKey; - - /// The optional suffix of the storage key. - /// - /// Used to differentiate between multiple AppStorages with the same [groupKey]. - final String? suffix; - - /// Returns the id for this app storage. - /// - /// Uses the [suffix] and falling back to the [groupKey] if not present. - /// This uniquely identifies the storage and is used in `Exportable` classes. - String get id => suffix ?? groupKey.value; - - /// Concatenates the [groupKey], [suffix] and [key] to build a unique key - /// used in the storage backend. - @visibleForTesting - String formatKey(String key) { - if (suffix != null) { - return '${groupKey.value}-$suffix-$key'; - } - - return '${groupKey.value}-$key'; - } - - @override - Future remove(String key) => NeonStorage().database.remove(formatKey(key)); - - @override - String? getString(String key) => NeonStorage().database.getString(formatKey(key)); - - @override - Future setString(String key, String value) => NeonStorage().database.setString(formatKey(key), value); - - @override - bool? getBool(String key) => NeonStorage().database.getBool(formatKey(key)); - - @override - Future setBool(String key, bool value) => NeonStorage().database.setBool(formatKey(key), value); -} diff --git a/packages/neon_framework/lib/src/storage/app_storage.dart b/packages/neon_framework/lib/src/storage/app_storage.dart deleted file mode 100644 index e50fdff8a15..00000000000 --- a/packages/neon_framework/lib/src/storage/app_storage.dart +++ /dev/null @@ -1,38 +0,0 @@ -/// Storage interface used by `Option`s. -/// -/// See: -/// * `NeonStorage` to initialize and manage the storage backends. -abstract interface class SettingsStorage { - /// {@template NeonStorage.getString} - /// Reads a value from persistent storage, throwing an `Exception` if it's not a `String`. - /// {@endtemplate} - String? getString(String key); - - /// {@template NeonStorage.setString} - /// Saves a `String` [value] to persistent storage in the background. - /// - /// Note: Due to limitations in Android's SharedPreferences, - /// values cannot start with any one of the following: - /// - /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu' - /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBCaWdJbnRlZ2Vy' - /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBEb3VibGUu' - /// {@endtemplate} - Future setString(String key, String value); - - /// {@template NeonStorage.getBool} - /// Reads a value from persistent storage, throwing an `Exception` if it's not a `bool`. - /// {@endtemplate} - bool? getBool(String key); - - /// {@template NeonStorage.setBool} - /// Saves a `bool` [value] to persistent storage in the background. - /// {@endtemplate} - // ignore: avoid_positional_boolean_parameters - Future setBool(String key, bool value); - - /// {@template NeonStorage.remove} - /// Removes an entry from persistent storage. - /// {@endtemplate} - Future remove(String key); -} diff --git a/packages/neon_framework/lib/src/storage/settings_store.dart b/packages/neon_framework/lib/src/storage/settings_store.dart new file mode 100644 index 00000000000..fba80532105 --- /dev/null +++ b/packages/neon_framework/lib/src/storage/settings_store.dart @@ -0,0 +1,112 @@ +import 'package:meta/meta.dart'; +import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/src/storage/storage_manager.dart'; + +/// Storage interface used by `Option`s. +/// +/// See: +/// * [NeonStorage] that manages the storage backend. +abstract interface class SettingsStorage { + /// The group key for this app storage. + StorageKeys get groupKey; + + /// The optional suffix of the storage key. + /// + /// Used to differentiate between multiple AppStorages with the same [groupKey]. + String? get suffix; + + /// The id for this app storage. + String get id; + + /// {@template NeonStorage.getString} + /// Reads a value from persistent storage, throwing an `Exception` if it's not a `String`. + /// {@endtemplate} + String? getString(String key); + + /// {@template NeonStorage.setString} + /// Saves a `String` [value] to persistent storage in the background. + /// + /// Note: Due to limitations in Android's SharedPreferences, + /// values cannot start with any one of the following: + /// + /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBhIGxpc3Qu' + /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBCaWdJbnRlZ2Vy' + /// - 'VGhpcyBpcyB0aGUgcHJlZml4IGZvciBEb3VibGUu' + /// {@endtemplate} + Future setString(String key, String value); + + /// {@template NeonStorage.getBool} + /// Reads a value from persistent storage, throwing an `Exception` if it's not a `bool`. + /// {@endtemplate} + bool? getBool(String key); + + /// {@template NeonStorage.setBool} + /// Saves a `bool` [value] to persistent storage in the background. + /// {@endtemplate} + // ignore: avoid_positional_boolean_parameters + Future setBool(String key, bool value); + + /// {@template NeonStorage.remove} + /// Removes an entry from persistent storage. + /// {@endtemplate} + Future remove(String key); +} + +/// A storage that can save a group of values. +/// +/// Implements the interface of `SharedPreferences`. +/// [NeonStorage.init] must be called and completed before accessing individual values. +/// +/// See: +/// * [NeonStorage] to initialize and manage the storage backends. +@immutable +@internal +final class AppStorage implements SettingsStorage { + /// Creates a new app storage. + const AppStorage( + this.groupKey, [ + this.suffix, + ]); + + /// The group key for this app storage. + /// + /// Keys are formatted with [formatKey] + @override + final StorageKeys groupKey; + + @override + final String? suffix; + + /// Returns the id for this app storage. + /// + /// Uses the [suffix] and falling back to the [groupKey] if not present. + /// This uniquely identifies the storage and is used in `Exportable` classes. + @override + String get id => suffix ?? groupKey.value; + + /// Concatenates the [groupKey], [suffix] and [key] to build a unique key + /// used in the storage backend. + @visibleForTesting + String formatKey(String key) { + if (suffix != null) { + return '${groupKey.value}-$suffix-$key'; + } + + return '${groupKey.value}-$key'; + } + + @override + Future remove(String key) => NeonStorage().database.remove(formatKey(key)); + + @override + String? getString(String key) => NeonStorage().database.getString(formatKey(key)); + + @override + Future setString(String key, String value) => NeonStorage().database.setString(formatKey(key), value); + + @override + bool? getBool(String key) => NeonStorage().database.getBool(formatKey(key)); + + @override + Future setBool(String key, bool value) => NeonStorage().database.setBool(formatKey(key), value); +} diff --git a/packages/neon_framework/lib/src/storage/storage_manager.dart b/packages/neon_framework/lib/src/storage/storage_manager.dart index 01523c406f7..1a12fcdea88 100644 --- a/packages/neon_framework/lib/src/storage/storage_manager.dart +++ b/packages/neon_framework/lib/src/storage/storage_manager.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'package:meta/meta.dart'; +import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/src/storage/settings_store.dart'; import 'package:shared_preferences/shared_preferences.dart'; /// Neon storage that manages the storage backend. @@ -54,4 +56,7 @@ class NeonStorage { return _sharedPreferences!; } + + /// Initializes a new `SettingsStorage`. + SettingsStorage settingsStorage(StorageKeys groupKey, [String? suffix]) => AppStorage(groupKey, suffix); } diff --git a/packages/neon_framework/lib/src/utils/global_options.dart b/packages/neon_framework/lib/src/utils/global_options.dart index 5ce55454573..b67ccfd6edc 100644 --- a/packages/neon_framework/lib/src/utils/global_options.dart +++ b/packages/neon_framework/lib/src/utils/global_options.dart @@ -6,6 +6,7 @@ import 'package:neon_framework/src/models/label_builder.dart'; import 'package:neon_framework/src/settings/models/option.dart'; import 'package:neon_framework/src/settings/models/options_collection.dart'; import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/storage.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:universal_io/io.dart'; @@ -20,7 +21,7 @@ class GlobalOptions extends OptionsCollection { /// Creates a new global options collection. GlobalOptions( this._packageInfo, - ) : super(const AppStorage(StorageKeys.global)) { + ) : super(NeonStorage().settingsStorage(StorageKeys.global)) { pushNotificationsEnabled.addListener(_pushNotificationsEnabledListener); rememberLastUsedAccount.addListener(_rememberLastUsedAccountListener); } diff --git a/packages/neon_framework/lib/src/utils/push_utils.dart b/packages/neon_framework/lib/src/utils/push_utils.dart index 27494b94bd1..b6ef3d417c9 100644 --- a/packages/neon_framework/lib/src/utils/push_utils.dart +++ b/packages/neon_framework/lib/src/utils/push_utils.dart @@ -40,7 +40,7 @@ class PushUtils { static Future Function(PushNotification notification)? onLocalNotificationClicked; static RSAKeypair loadRSAKeypair() { - const storage = AppStorage(StorageKeys.notifications); + final storage = NeonStorage().settingsStorage(StorageKeys.notifications); const keyDevicePrivateKey = 'device-private-key'; final RSAKeypair keypair; diff --git a/packages/neon_framework/lib/storage.dart b/packages/neon_framework/lib/storage.dart index 6f85e1fec4c..545fab2e648 100644 --- a/packages/neon_framework/lib/storage.dart +++ b/packages/neon_framework/lib/storage.dart @@ -3,7 +3,7 @@ /// The `NeonStorage` manages all storage backends. library; -export 'package:neon_framework/src/storage/app_storage.dart'; export 'package:neon_framework/src/storage/request_cache.dart'; +export 'package:neon_framework/src/storage/settings_store.dart' show SettingsStorage; export 'package:neon_framework/src/storage/single_value_store.dart'; export 'package:neon_framework/src/storage/storage_manager.dart'; diff --git a/packages/neon_framework/test/options_collection_test.dart b/packages/neon_framework/test/options_collection_test.dart index 639cbaf7f5d..a3cc89890fe 100644 --- a/packages/neon_framework/test/options_collection_test.dart +++ b/packages/neon_framework/test/options_collection_test.dart @@ -2,6 +2,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/settings.dart'; import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/src/storage/settings_store.dart'; import 'package:neon_framework/testing.dart'; class Collection extends AppImplementationOptions { diff --git a/packages/neon_framework/test/storage_test.dart b/packages/neon_framework/test/storage_test.dart index 5c74dcbe78f..edb1569cbec 100644 --- a/packages/neon_framework/test/storage_test.dart +++ b/packages/neon_framework/test/storage_test.dart @@ -1,6 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/src/storage/settings_store.dart'; import 'package:neon_framework/storage.dart'; import 'package:neon_framework/testing.dart'; import 'package:shared_preferences/shared_preferences.dart'; From 713d74893a5f2845eb454e04703c7c1c1fb82b6a Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 30 Jan 2024 16:11:19 +0100 Subject: [PATCH 07/11] refactor(neon_framework): let NeonStorage manage the SingleValueStorage Signed-off-by: Nikolas Rimikis --- .../lib/src/blocs/accounts.dart | 7 +-- .../lib/src/blocs/first_launch.dart | 3 +- .../lib/src/settings/models/storage.dart | 40 ----------------- .../lib/src/storage/single_value_store.dart | 43 ++++++++++++++++++- .../lib/src/storage/storage_manager.dart | 4 ++ packages/neon_framework/lib/storage.dart | 2 +- .../neon_framework/test/storage_test.dart | 1 + 7 files changed, 54 insertions(+), 46 deletions(-) diff --git a/packages/neon_framework/lib/src/blocs/accounts.dart b/packages/neon_framework/lib/src/blocs/accounts.dart index db8dc3fe09d..e40fcd0d72b 100644 --- a/packages/neon_framework/lib/src/blocs/accounts.dart +++ b/packages/neon_framework/lib/src/blocs/accounts.dart @@ -152,7 +152,7 @@ class _AccountsBloc extends Bloc implements AccountsBloc { this.globalOptions, this.allAppImplementations, ) { - const lastUsedStorage = SingleValueStorage(StorageKeys.lastUsedAccount); + final lastUsedStorage = NeonStorage().singleValueStore(StorageKeys.lastUsedAccount); accounts ..add(loadAccounts()) @@ -368,17 +368,18 @@ class _AccountsBloc extends Bloc implements AccountsBloc { /// /// It is not checked whether the stored information is still valid. List loadAccounts() { - const storage = SingleValueStorage(StorageKeys.accountOptions); + final storage = NeonStorage().singleValueStore(StorageKeys.accounts); if (storage.hasValue()) { return storage.getStringList()!.map((a) => Account.fromJson(json.decode(a) as Map)).toList(); } + return []; } /// Saves the given [accounts] to the storage. Future saveAccounts(List accounts) async { - const storage = SingleValueStorage(StorageKeys.accountOptions); + final storage = NeonStorage().singleValueStore(StorageKeys.accounts); final values = accounts.map((a) => json.encode(a.toJson())).toList(); await storage.setStringList(values); diff --git a/packages/neon_framework/lib/src/blocs/first_launch.dart b/packages/neon_framework/lib/src/blocs/first_launch.dart index 23a01a91e24..c2af705efab 100644 --- a/packages/neon_framework/lib/src/blocs/first_launch.dart +++ b/packages/neon_framework/lib/src/blocs/first_launch.dart @@ -4,6 +4,7 @@ import 'package:meta/meta.dart'; import 'package:neon_framework/src/bloc/bloc.dart'; import 'package:neon_framework/src/models/disposable.dart'; import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/storage.dart'; import 'package:rxdart/rxdart.dart'; /// Bloc that manages tasks that only need to run at the first launch of the app. @@ -23,7 +24,7 @@ class _FirstLaunchBloc extends Bloc implements FirstLaunchBloc { _FirstLaunchBloc({ bool disabled = false, }) { - const storage = SingleValueStorage(StorageKeys.firstLaunch); + final storage = NeonStorage().singleValueStore(StorageKeys.firstLaunch); if (!disabled && !storage.hasValue()) { onFirstLaunch.add(null); diff --git a/packages/neon_framework/lib/src/settings/models/storage.dart b/packages/neon_framework/lib/src/settings/models/storage.dart index ca17ab99a8b..1b7cdf8e1ef 100644 --- a/packages/neon_framework/lib/src/settings/models/storage.dart +++ b/packages/neon_framework/lib/src/settings/models/storage.dart @@ -44,43 +44,3 @@ enum StorageKeys implements Storable { @override final String value; } - -/// A storage that saves a single value. -/// -/// [NeonStorage.init] must be called and completed before accessing individual values. -/// -/// See: -/// * [NeonStorage] to initialize the storage backend. -@immutable -@internal -final class SingleValueStorage implements KeyValueStorage { - /// Creates a new storage for a single value. - const SingleValueStorage(this.key); - - @override - final StorageKeys key; - - @override - bool hasValue() => NeonStorage().database.containsKey(key.value); - - @override - Future remove() => NeonStorage().database.remove(key.value); - - @override - String? getString() => NeonStorage().database.getString(key.value); - - @override - Future setString(String value) => NeonStorage().database.setString(key.value, value); - - @override - bool? getBool() => NeonStorage().database.getBool(key.value); - - @override - Future setBool(bool value) => NeonStorage().database.setBool(key.value, value); - - @override - List? getStringList() => NeonStorage().database.getStringList(key.value); - - @override - Future setStringList(List value) => NeonStorage().database.setStringList(key.value, value); -} diff --git a/packages/neon_framework/lib/src/storage/single_value_store.dart b/packages/neon_framework/lib/src/storage/single_value_store.dart index e0e6583a3d2..8a5c938b9ff 100644 --- a/packages/neon_framework/lib/src/storage/single_value_store.dart +++ b/packages/neon_framework/lib/src/storage/single_value_store.dart @@ -1,6 +1,7 @@ +import 'package:meta/meta.dart'; import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/storage.dart'; import 'package:shared_preferences/shared_preferences.dart'; -export 'package:neon_framework/src/storage/storage_manager.dart'; /// A storage that itself is a single entry of a key value store. /// @@ -54,3 +55,43 @@ abstract interface class KeyValueStorage { /// {@macro NeonStorage.setStringList} Future setStringList(List value); } + +/// A storage that saves a single value. +/// +/// [NeonStorage.init] must be called and completed before accessing individual values. +/// +/// See: +/// * [NeonStorage] to initialize and manage the storage backends. +@immutable +@internal +final class SingleValueStorage implements KeyValueStorage { + /// Creates a new storage for a single value. + const SingleValueStorage(this.key); + + @override + final StorageKeys key; + + @override + bool hasValue() => NeonStorage().database.containsKey(key.value); + + @override + Future remove() => NeonStorage().database.remove(key.value); + + @override + String? getString() => NeonStorage().database.getString(key.value); + + @override + Future setString(String value) => NeonStorage().database.setString(key.value, value); + + @override + bool? getBool() => NeonStorage().database.getBool(key.value); + + @override + Future setBool(bool value) => NeonStorage().database.setBool(key.value, value); + + @override + List? getStringList() => NeonStorage().database.getStringList(key.value); + + @override + Future setStringList(List value) => NeonStorage().database.setStringList(key.value, value); +} diff --git a/packages/neon_framework/lib/src/storage/storage_manager.dart b/packages/neon_framework/lib/src/storage/storage_manager.dart index 1a12fcdea88..2628c34a37d 100644 --- a/packages/neon_framework/lib/src/storage/storage_manager.dart +++ b/packages/neon_framework/lib/src/storage/storage_manager.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:neon_framework/src/settings/models/storage.dart'; import 'package:neon_framework/src/storage/settings_store.dart'; +import 'package:neon_framework/src/storage/single_value_store.dart'; import 'package:shared_preferences/shared_preferences.dart'; /// Neon storage that manages the storage backend. @@ -59,4 +60,7 @@ class NeonStorage { /// Initializes a new `SettingsStorage`. SettingsStorage settingsStorage(StorageKeys groupKey, [String? suffix]) => AppStorage(groupKey, suffix); + + /// Initializes a new `KeyValueStorage`. + KeyValueStorage singleValueStore(StorageKeys key) => SingleValueStorage(key); } diff --git a/packages/neon_framework/lib/storage.dart b/packages/neon_framework/lib/storage.dart index 545fab2e648..d13a607a055 100644 --- a/packages/neon_framework/lib/storage.dart +++ b/packages/neon_framework/lib/storage.dart @@ -5,5 +5,5 @@ library; export 'package:neon_framework/src/storage/request_cache.dart'; export 'package:neon_framework/src/storage/settings_store.dart' show SettingsStorage; -export 'package:neon_framework/src/storage/single_value_store.dart'; +export 'package:neon_framework/src/storage/single_value_store.dart' show KeyValueStorage; export 'package:neon_framework/src/storage/storage_manager.dart'; diff --git a/packages/neon_framework/test/storage_test.dart b/packages/neon_framework/test/storage_test.dart index edb1569cbec..1338c196a80 100644 --- a/packages/neon_framework/test/storage_test.dart +++ b/packages/neon_framework/test/storage_test.dart @@ -2,6 +2,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/src/settings/models/storage.dart'; import 'package:neon_framework/src/storage/settings_store.dart'; +import 'package:neon_framework/src/storage/single_value_store.dart'; import 'package:neon_framework/storage.dart'; import 'package:neon_framework/testing.dart'; import 'package:shared_preferences/shared_preferences.dart'; From d2761b463f96c29f8e3a8f51fdd698259df99511 Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Tue, 30 Jan 2024 16:11:19 +0100 Subject: [PATCH 08/11] refactor(neon_framework): move storage keys into the storage library Signed-off-by: Nikolas Rimikis --- packages/neon/neon_files/lib/src/options.dart | 1 + packages/neon/neon_news/lib/src/options.dart | 1 + packages/neon/neon_notes/lib/src/options.dart | 1 + packages/neon_framework/lib/settings.dart | 1 - packages/neon_framework/lib/src/blocs/accounts.dart | 2 +- packages/neon_framework/lib/src/blocs/first_launch.dart | 2 +- packages/neon_framework/lib/src/blocs/push_notifications.dart | 2 +- .../neon_framework/lib/src/models/app_implementation.dart | 3 ++- packages/neon_framework/lib/src/settings/models/option.dart | 1 - .../lib/src/settings/utils/settings_export_helper.dart | 3 ++- .../src/{settings/models/storage.dart => storage/keys.dart} | 0 packages/neon_framework/lib/src/storage/settings_store.dart | 2 +- .../neon_framework/lib/src/storage/single_value_store.dart | 4 ++-- packages/neon_framework/lib/src/storage/storage_manager.dart | 2 +- packages/neon_framework/lib/src/utils/account_options.dart | 2 +- packages/neon_framework/lib/src/utils/global_options.dart | 3 ++- packages/neon_framework/lib/src/utils/push_utils.dart | 3 ++- packages/neon_framework/lib/storage.dart | 1 + packages/neon_framework/test/option_test.dart | 2 +- packages/neon_framework/test/options_collection_test.dart | 2 +- packages/neon_framework/test/settings_test.dart | 2 +- packages/neon_framework/test/storage_test.dart | 2 +- 22 files changed, 24 insertions(+), 18 deletions(-) rename packages/neon_framework/lib/src/{settings/models/storage.dart => storage/keys.dart} (100%) diff --git a/packages/neon/neon_files/lib/src/options.dart b/packages/neon/neon_files/lib/src/options.dart index b9135e9ec22..7ff34fafcc9 100644 --- a/packages/neon/neon_files/lib/src/options.dart +++ b/packages/neon/neon_files/lib/src/options.dart @@ -2,6 +2,7 @@ import 'package:filesize/filesize.dart'; import 'package:neon_files/l10n/localizations.dart'; import 'package:neon_framework/settings.dart'; import 'package:neon_framework/sort_box.dart'; +import 'package:neon_framework/storage.dart'; class FilesOptions extends AppImplementationOptions { FilesOptions(super.storage) { diff --git a/packages/neon/neon_news/lib/src/options.dart b/packages/neon/neon_news/lib/src/options.dart index 2b7d7c10f5d..7702f147635 100644 --- a/packages/neon/neon_news/lib/src/options.dart +++ b/packages/neon/neon_news/lib/src/options.dart @@ -1,6 +1,7 @@ import 'package:neon_framework/platform.dart'; import 'package:neon_framework/settings.dart'; import 'package:neon_framework/sort_box.dart'; +import 'package:neon_framework/storage.dart'; import 'package:neon_news/l10n/localizations.dart'; import 'package:neon_news/src/blocs/articles.dart'; diff --git a/packages/neon/neon_notes/lib/src/options.dart b/packages/neon/neon_notes/lib/src/options.dart index 221f7fcc726..652fa3b2885 100644 --- a/packages/neon/neon_notes/lib/src/options.dart +++ b/packages/neon/neon_notes/lib/src/options.dart @@ -1,5 +1,6 @@ import 'package:neon_framework/settings.dart'; import 'package:neon_framework/sort_box.dart'; +import 'package:neon_framework/storage.dart'; import 'package:neon_notes/l10n/localizations.dart'; class NotesOptions extends AppImplementationOptions { diff --git a/packages/neon_framework/lib/settings.dart b/packages/neon_framework/lib/settings.dart index 82e21908275..609563f07be 100644 --- a/packages/neon_framework/lib/settings.dart +++ b/packages/neon_framework/lib/settings.dart @@ -2,5 +2,4 @@ export 'package:neon_framework/src/models/label_builder.dart'; export 'package:neon_framework/src/settings/models/option.dart'; export 'package:neon_framework/src/settings/models/options_category.dart'; export 'package:neon_framework/src/settings/models/options_collection.dart'; -export 'package:neon_framework/src/settings/models/storage.dart' show Storable; export 'package:neon_framework/src/settings/widgets/settings_list.dart'; diff --git a/packages/neon_framework/lib/src/blocs/accounts.dart b/packages/neon_framework/lib/src/blocs/accounts.dart index e40fcd0d72b..ef918f1e145 100644 --- a/packages/neon_framework/lib/src/blocs/accounts.dart +++ b/packages/neon_framework/lib/src/blocs/accounts.dart @@ -15,7 +15,7 @@ import 'package:neon_framework/src/models/account.dart'; import 'package:neon_framework/src/models/account_cache.dart'; import 'package:neon_framework/src/models/app_implementation.dart'; import 'package:neon_framework/src/models/disposable.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/src/storage/keys.dart'; import 'package:neon_framework/src/utils/account_options.dart'; import 'package:neon_framework/src/utils/findable.dart'; import 'package:neon_framework/src/utils/global_options.dart'; diff --git a/packages/neon_framework/lib/src/blocs/first_launch.dart b/packages/neon_framework/lib/src/blocs/first_launch.dart index c2af705efab..78270a9d829 100644 --- a/packages/neon_framework/lib/src/blocs/first_launch.dart +++ b/packages/neon_framework/lib/src/blocs/first_launch.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:neon_framework/src/bloc/bloc.dart'; import 'package:neon_framework/src/models/disposable.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/src/storage/keys.dart'; import 'package:neon_framework/storage.dart'; import 'package:rxdart/rxdart.dart'; diff --git a/packages/neon_framework/lib/src/blocs/push_notifications.dart b/packages/neon_framework/lib/src/blocs/push_notifications.dart index 0705ce5c1db..6c944f13ba7 100644 --- a/packages/neon_framework/lib/src/blocs/push_notifications.dart +++ b/packages/neon_framework/lib/src/blocs/push_notifications.dart @@ -7,7 +7,7 @@ import 'package:neon_framework/src/bloc/bloc.dart'; import 'package:neon_framework/src/blocs/accounts.dart'; import 'package:neon_framework/src/models/account.dart'; import 'package:neon_framework/src/platform/platform.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/src/storage/keys.dart'; import 'package:neon_framework/src/utils/findable.dart'; import 'package:neon_framework/src/utils/global_options.dart'; import 'package:neon_framework/src/utils/push_utils.dart'; diff --git a/packages/neon_framework/lib/src/models/app_implementation.dart b/packages/neon_framework/lib/src/models/app_implementation.dart index 9ecf25c7fe9..1b9199ff6af 100644 --- a/packages/neon_framework/lib/src/models/app_implementation.dart +++ b/packages/neon_framework/lib/src/models/app_implementation.dart @@ -10,7 +10,8 @@ import 'package:neon_framework/src/models/account.dart'; import 'package:neon_framework/src/models/account_cache.dart'; import 'package:neon_framework/src/models/disposable.dart'; import 'package:neon_framework/src/settings/models/options_collection.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/src/storage/keys.dart'; + import 'package:neon_framework/src/utils/findable.dart'; import 'package:neon_framework/src/utils/provider.dart'; import 'package:neon_framework/src/widgets/drawer_destination.dart'; diff --git a/packages/neon_framework/lib/src/settings/models/option.dart b/packages/neon_framework/lib/src/settings/models/option.dart index e37abcfb664..8e57d6577bb 100644 --- a/packages/neon_framework/lib/src/settings/models/option.dart +++ b/packages/neon_framework/lib/src/settings/models/option.dart @@ -6,7 +6,6 @@ import 'package:meta/meta.dart'; import 'package:neon_framework/src/models/disposable.dart'; import 'package:neon_framework/src/models/label_builder.dart'; import 'package:neon_framework/src/settings/models/options_category.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; import 'package:neon_framework/storage.dart'; import 'package:rxdart/rxdart.dart'; diff --git a/packages/neon_framework/lib/src/settings/utils/settings_export_helper.dart b/packages/neon_framework/lib/src/settings/utils/settings_export_helper.dart index 843cea9fc7b..16446c5470e 100644 --- a/packages/neon_framework/lib/src/settings/utils/settings_export_helper.dart +++ b/packages/neon_framework/lib/src/settings/utils/settings_export_helper.dart @@ -8,7 +8,8 @@ import 'package:neon_framework/src/models/account.dart' show Account; import 'package:neon_framework/src/models/app_implementation.dart'; import 'package:neon_framework/src/settings/models/exportable.dart'; import 'package:neon_framework/src/settings/models/option.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/src/storage/keys.dart'; + import 'package:neon_framework/src/utils/findable.dart'; /// Helper class to export all [Option]s. diff --git a/packages/neon_framework/lib/src/settings/models/storage.dart b/packages/neon_framework/lib/src/storage/keys.dart similarity index 100% rename from packages/neon_framework/lib/src/settings/models/storage.dart rename to packages/neon_framework/lib/src/storage/keys.dart diff --git a/packages/neon_framework/lib/src/storage/settings_store.dart b/packages/neon_framework/lib/src/storage/settings_store.dart index fba80532105..cfe8b8d1747 100644 --- a/packages/neon_framework/lib/src/storage/settings_store.dart +++ b/packages/neon_framework/lib/src/storage/settings_store.dart @@ -1,5 +1,5 @@ import 'package:meta/meta.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/src/storage/keys.dart'; import 'package:neon_framework/src/storage/storage_manager.dart'; /// Storage interface used by `Option`s. diff --git a/packages/neon_framework/lib/src/storage/single_value_store.dart b/packages/neon_framework/lib/src/storage/single_value_store.dart index 8a5c938b9ff..20e341db81c 100644 --- a/packages/neon_framework/lib/src/storage/single_value_store.dart +++ b/packages/neon_framework/lib/src/storage/single_value_store.dart @@ -1,6 +1,6 @@ import 'package:meta/meta.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; -import 'package:neon_framework/storage.dart'; +import 'package:neon_framework/src/storage/keys.dart'; +import 'package:neon_framework/src/storage/storage_manager.dart'; import 'package:shared_preferences/shared_preferences.dart'; /// A storage that itself is a single entry of a key value store. diff --git a/packages/neon_framework/lib/src/storage/storage_manager.dart b/packages/neon_framework/lib/src/storage/storage_manager.dart index 2628c34a37d..6c50dbe17a5 100644 --- a/packages/neon_framework/lib/src/storage/storage_manager.dart +++ b/packages/neon_framework/lib/src/storage/storage_manager.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/src/storage/keys.dart'; import 'package:neon_framework/src/storage/settings_store.dart'; import 'package:neon_framework/src/storage/single_value_store.dart'; import 'package:shared_preferences/shared_preferences.dart'; diff --git a/packages/neon_framework/lib/src/utils/account_options.dart b/packages/neon_framework/lib/src/utils/account_options.dart index d4470c4ed75..7477a9788b9 100644 --- a/packages/neon_framework/lib/src/utils/account_options.dart +++ b/packages/neon_framework/lib/src/utils/account_options.dart @@ -3,7 +3,7 @@ import 'package:neon_framework/l10n/localizations.dart'; import 'package:neon_framework/src/blocs/apps.dart'; import 'package:neon_framework/src/settings/models/option.dart'; import 'package:neon_framework/src/settings/models/options_collection.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/storage.dart'; /// Account related options. @internal diff --git a/packages/neon_framework/lib/src/utils/global_options.dart b/packages/neon_framework/lib/src/utils/global_options.dart index b67ccfd6edc..eb662053300 100644 --- a/packages/neon_framework/lib/src/utils/global_options.dart +++ b/packages/neon_framework/lib/src/utils/global_options.dart @@ -5,7 +5,8 @@ import 'package:neon_framework/src/models/account.dart'; import 'package:neon_framework/src/models/label_builder.dart'; import 'package:neon_framework/src/settings/models/option.dart'; import 'package:neon_framework/src/settings/models/options_collection.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/src/storage/keys.dart'; + import 'package:neon_framework/storage.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; diff --git a/packages/neon_framework/lib/src/utils/push_utils.dart b/packages/neon_framework/lib/src/utils/push_utils.dart index b6ef3d417c9..0798ae5299a 100644 --- a/packages/neon_framework/lib/src/utils/push_utils.dart +++ b/packages/neon_framework/lib/src/utils/push_utils.dart @@ -14,7 +14,8 @@ import 'package:neon_framework/src/bloc/result.dart'; import 'package:neon_framework/src/blocs/accounts.dart'; import 'package:neon_framework/src/models/account.dart'; import 'package:neon_framework/src/models/push_notification.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/src/storage/keys.dart'; + import 'package:neon_framework/src/theme/colors.dart'; import 'package:neon_framework/src/utils/findable.dart'; import 'package:neon_framework/src/utils/image_utils.dart'; diff --git a/packages/neon_framework/lib/storage.dart b/packages/neon_framework/lib/storage.dart index d13a607a055..c9e89848ac9 100644 --- a/packages/neon_framework/lib/storage.dart +++ b/packages/neon_framework/lib/storage.dart @@ -3,6 +3,7 @@ /// The `NeonStorage` manages all storage backends. library; +export 'package:neon_framework/src/storage/keys.dart' show Storable; export 'package:neon_framework/src/storage/request_cache.dart'; export 'package:neon_framework/src/storage/settings_store.dart' show SettingsStorage; export 'package:neon_framework/src/storage/single_value_store.dart' show KeyValueStorage; diff --git a/packages/neon_framework/test/option_test.dart b/packages/neon_framework/test/option_test.dart index 7a9c51ece47..526b086ac3a 100644 --- a/packages/neon_framework/test/option_test.dart +++ b/packages/neon_framework/test/option_test.dart @@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/src/settings/models/option.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/storage.dart'; import 'package:neon_framework/testing.dart'; enum StorageKey implements Storable { diff --git a/packages/neon_framework/test/options_collection_test.dart b/packages/neon_framework/test/options_collection_test.dart index a3cc89890fe..707a475a05c 100644 --- a/packages/neon_framework/test/options_collection_test.dart +++ b/packages/neon_framework/test/options_collection_test.dart @@ -1,7 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/settings.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/src/storage/keys.dart'; import 'package:neon_framework/src/storage/settings_store.dart'; import 'package:neon_framework/testing.dart'; diff --git a/packages/neon_framework/test/settings_test.dart b/packages/neon_framework/test/settings_test.dart index aee39e2d0d9..df7b9afb7a7 100644 --- a/packages/neon_framework/test/settings_test.dart +++ b/packages/neon_framework/test/settings_test.dart @@ -4,8 +4,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/src/settings/models/option.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; import 'package:neon_framework/src/settings/widgets/option_settings_tile.dart'; +import 'package:neon_framework/storage.dart'; import 'package:neon_framework/testing.dart'; enum StorageKey implements Storable { diff --git a/packages/neon_framework/test/storage_test.dart b/packages/neon_framework/test/storage_test.dart index 1338c196a80..32e50e51c78 100644 --- a/packages/neon_framework/test/storage_test.dart +++ b/packages/neon_framework/test/storage_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; -import 'package:neon_framework/src/settings/models/storage.dart'; +import 'package:neon_framework/src/storage/keys.dart'; import 'package:neon_framework/src/storage/settings_store.dart'; import 'package:neon_framework/src/storage/single_value_store.dart'; import 'package:neon_framework/storage.dart'; From dfc9b5de31b7c2fa30ea1856b25da7d777345b7b Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Wed, 31 Jan 2024 11:59:48 +0100 Subject: [PATCH 09/11] refactor(neon_framework): allign storage names Signed-off-by: Nikolas Rimikis --- packages/neon_framework/lib/src/blocs/accounts.dart | 2 +- .../neon_framework/lib/src/blocs/push_notifications.dart | 2 +- .../neon_framework/lib/src/models/app_implementation.dart | 2 +- .../neon_framework/lib/src/settings/models/option.dart | 4 ++-- .../lib/src/settings/models/options_collection.dart | 2 +- .../neon_framework/lib/src/storage/request_cache.dart | 2 +- .../neon_framework/lib/src/storage/settings_store.dart | 6 +++--- .../lib/src/storage/single_value_store.dart | 6 +++--- .../neon_framework/lib/src/storage/storage_manager.dart | 4 ++-- packages/neon_framework/lib/src/testing/mocks.dart | 2 +- packages/neon_framework/lib/src/utils/global_options.dart | 2 +- packages/neon_framework/lib/src/utils/push_utils.dart | 2 +- .../neon_framework/lib/src/utils/request_manager.dart | 2 +- packages/neon_framework/lib/storage.dart | 4 ++-- packages/neon_framework/test/options_collection_test.dart | 2 +- packages/neon_framework/test/storage_test.dart | 8 ++++---- 16 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/neon_framework/lib/src/blocs/accounts.dart b/packages/neon_framework/lib/src/blocs/accounts.dart index ef918f1e145..e4ea27c9928 100644 --- a/packages/neon_framework/lib/src/blocs/accounts.dart +++ b/packages/neon_framework/lib/src/blocs/accounts.dart @@ -312,7 +312,7 @@ class _AccountsBloc extends Bloc implements AccountsBloc { @override AccountOptions getOptionsFor(Account account) => accountsOptions[account] ??= AccountOptions( - NeonStorage().settingsStorage(StorageKeys.accountOptions, account.id), + NeonStorage().settingsStore(StorageKeys.accountOptions, account.id), getAppsBlocFor(account), ); diff --git a/packages/neon_framework/lib/src/blocs/push_notifications.dart b/packages/neon_framework/lib/src/blocs/push_notifications.dart index 6c944f13ba7..1e3146db4b1 100644 --- a/packages/neon_framework/lib/src/blocs/push_notifications.dart +++ b/packages/neon_framework/lib/src/blocs/push_notifications.dart @@ -43,7 +43,7 @@ class _PushNotificationsBloc extends Bloc implements PushNotificationsBloc { } final AccountsBloc accountsBloc; - late final storage = NeonStorage().settingsStorage(StorageKeys.lastEndpoint); + late final storage = NeonStorage().settingsStore(StorageKeys.lastEndpoint); final GlobalOptions globalOptions; StreamSubscription>? accountsListener; diff --git a/packages/neon_framework/lib/src/models/app_implementation.dart b/packages/neon_framework/lib/src/models/app_implementation.dart index 1b9199ff6af..1b88abc5434 100644 --- a/packages/neon_framework/lib/src/models/app_implementation.dart +++ b/packages/neon_framework/lib/src/models/app_implementation.dart @@ -50,7 +50,7 @@ abstract class AppImplementation @@ -44,7 +44,7 @@ sealed class Option extends ChangeNotifier implements ValueListenable, Dis } /// Storage to persist the state. - final SettingsStorage storage; + final SettingsStore storage; /// Storage key to save the state at. final Storable key; diff --git a/packages/neon_framework/lib/src/settings/models/options_collection.dart b/packages/neon_framework/lib/src/settings/models/options_collection.dart index 8eeb61f4327..89e6ce29576 100644 --- a/packages/neon_framework/lib/src/settings/models/options_collection.dart +++ b/packages/neon_framework/lib/src/settings/models/options_collection.dart @@ -12,7 +12,7 @@ abstract class OptionsCollection implements Exportable, Disposable { /// Storage backend to use. @protected - final SettingsStorage storage; + final SettingsStore storage; /// Collection of options. @protected diff --git a/packages/neon_framework/lib/src/storage/request_cache.dart b/packages/neon_framework/lib/src/storage/request_cache.dart index 9711daa47f7..5051c2245b0 100644 --- a/packages/neon_framework/lib/src/storage/request_cache.dart +++ b/packages/neon_framework/lib/src/storage/request_cache.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:neon_framework/src/utils/request_manager.dart'; /// A storage used to cache a key value pair. -abstract interface class CacheInterface { +abstract interface class RequestCache { /// Get's the cached value for the given [key]. /// /// Use [getParameters] if you only need to check whether the cache is still diff --git a/packages/neon_framework/lib/src/storage/settings_store.dart b/packages/neon_framework/lib/src/storage/settings_store.dart index cfe8b8d1747..2f742b3f20e 100644 --- a/packages/neon_framework/lib/src/storage/settings_store.dart +++ b/packages/neon_framework/lib/src/storage/settings_store.dart @@ -6,7 +6,7 @@ import 'package:neon_framework/src/storage/storage_manager.dart'; /// /// See: /// * [NeonStorage] that manages the storage backend. -abstract interface class SettingsStorage { +abstract interface class SettingsStore { /// The group key for this app storage. StorageKeys get groupKey; @@ -61,9 +61,9 @@ abstract interface class SettingsStorage { /// * [NeonStorage] to initialize and manage the storage backends. @immutable @internal -final class AppStorage implements SettingsStorage { +final class DefaultSettingsStore implements SettingsStore { /// Creates a new app storage. - const AppStorage( + const DefaultSettingsStore( this.groupKey, [ this.suffix, ]); diff --git a/packages/neon_framework/lib/src/storage/single_value_store.dart b/packages/neon_framework/lib/src/storage/single_value_store.dart index 20e341db81c..88274dcf6e9 100644 --- a/packages/neon_framework/lib/src/storage/single_value_store.dart +++ b/packages/neon_framework/lib/src/storage/single_value_store.dart @@ -9,7 +9,7 @@ import 'package:shared_preferences/shared_preferences.dart'; /// /// See: /// * `NeonStorage` to initialize and manage the storage backends. -abstract interface class KeyValueStorage { +abstract interface class SingleValueStore { /// The key used by the storage backend. StorageKeys get key; @@ -64,9 +64,9 @@ abstract interface class KeyValueStorage { /// * [NeonStorage] to initialize and manage the storage backends. @immutable @internal -final class SingleValueStorage implements KeyValueStorage { +final class DefaultSingleValueStore implements SingleValueStore { /// Creates a new storage for a single value. - const SingleValueStorage(this.key); + const DefaultSingleValueStore(this.key); @override final StorageKeys key; diff --git a/packages/neon_framework/lib/src/storage/storage_manager.dart b/packages/neon_framework/lib/src/storage/storage_manager.dart index 6c50dbe17a5..2a4f824eeba 100644 --- a/packages/neon_framework/lib/src/storage/storage_manager.dart +++ b/packages/neon_framework/lib/src/storage/storage_manager.dart @@ -59,8 +59,8 @@ class NeonStorage { } /// Initializes a new `SettingsStorage`. - SettingsStorage settingsStorage(StorageKeys groupKey, [String? suffix]) => AppStorage(groupKey, suffix); + SettingsStore settingsStore(StorageKeys groupKey, [String? suffix]) => DefaultSettingsStore(groupKey, suffix); /// Initializes a new `KeyValueStorage`. - KeyValueStorage singleValueStore(StorageKeys key) => SingleValueStorage(key); + SingleValueStore singleValueStore(StorageKeys key) => DefaultSingleValueStore(key); } diff --git a/packages/neon_framework/lib/src/testing/mocks.dart b/packages/neon_framework/lib/src/testing/mocks.dart index 95a8b743252..aef5c727327 100644 --- a/packages/neon_framework/lib/src/testing/mocks.dart +++ b/packages/neon_framework/lib/src/testing/mocks.dart @@ -33,7 +33,7 @@ class MockAppsBloc extends Mock implements AppsBloc {} class MockCapabilitiesBloc extends Mock implements CapabilitiesBloc {} -class MockStorage extends Mock implements SettingsStorage {} +class MockStorage extends Mock implements SettingsStore {} class MockAccountOptions extends Mock implements AccountOptions {} diff --git a/packages/neon_framework/lib/src/utils/global_options.dart b/packages/neon_framework/lib/src/utils/global_options.dart index eb662053300..2e321b051e7 100644 --- a/packages/neon_framework/lib/src/utils/global_options.dart +++ b/packages/neon_framework/lib/src/utils/global_options.dart @@ -22,7 +22,7 @@ class GlobalOptions extends OptionsCollection { /// Creates a new global options collection. GlobalOptions( this._packageInfo, - ) : super(NeonStorage().settingsStorage(StorageKeys.global)) { + ) : super(NeonStorage().settingsStore(StorageKeys.global)) { pushNotificationsEnabled.addListener(_pushNotificationsEnabledListener); rememberLastUsedAccount.addListener(_rememberLastUsedAccountListener); } diff --git a/packages/neon_framework/lib/src/utils/push_utils.dart b/packages/neon_framework/lib/src/utils/push_utils.dart index 0798ae5299a..7c71dfe2950 100644 --- a/packages/neon_framework/lib/src/utils/push_utils.dart +++ b/packages/neon_framework/lib/src/utils/push_utils.dart @@ -41,7 +41,7 @@ class PushUtils { static Future Function(PushNotification notification)? onLocalNotificationClicked; static RSAKeypair loadRSAKeypair() { - final storage = NeonStorage().settingsStorage(StorageKeys.notifications); + final storage = NeonStorage().settingsStore(StorageKeys.notifications); const keyDevicePrivateKey = 'device-private-key'; final RSAKeypair keypair; diff --git a/packages/neon_framework/lib/src/utils/request_manager.dart b/packages/neon_framework/lib/src/utils/request_manager.dart index 7db077222d8..ed7a255d5ab 100644 --- a/packages/neon_framework/lib/src/utils/request_manager.dart +++ b/packages/neon_framework/lib/src/utils/request_manager.dart @@ -356,7 +356,7 @@ class RequestManager { } @internal -class Cache implements CacheInterface { +class Cache implements RequestCache { factory Cache() => instance ??= Cache._(); Cache._(); diff --git a/packages/neon_framework/lib/storage.dart b/packages/neon_framework/lib/storage.dart index c9e89848ac9..8fc7af6ee94 100644 --- a/packages/neon_framework/lib/storage.dart +++ b/packages/neon_framework/lib/storage.dart @@ -5,6 +5,6 @@ library; export 'package:neon_framework/src/storage/keys.dart' show Storable; export 'package:neon_framework/src/storage/request_cache.dart'; -export 'package:neon_framework/src/storage/settings_store.dart' show SettingsStorage; -export 'package:neon_framework/src/storage/single_value_store.dart' show KeyValueStorage; +export 'package:neon_framework/src/storage/settings_store.dart' show SettingsStore; +export 'package:neon_framework/src/storage/single_value_store.dart' show SingleValueStore; export 'package:neon_framework/src/storage/storage_manager.dart'; diff --git a/packages/neon_framework/test/options_collection_test.dart b/packages/neon_framework/test/options_collection_test.dart index 707a475a05c..c422faf5ea3 100644 --- a/packages/neon_framework/test/options_collection_test.dart +++ b/packages/neon_framework/test/options_collection_test.dart @@ -6,7 +6,7 @@ import 'package:neon_framework/src/storage/settings_store.dart'; import 'package:neon_framework/testing.dart'; class Collection extends AppImplementationOptions { - Collection(List> options) : super(const AppStorage(StorageKeys.apps)) { + Collection(List> options) : super(const DefaultSettingsStore(StorageKeys.apps)) { super.options = options; } } diff --git a/packages/neon_framework/test/storage_test.dart b/packages/neon_framework/test/storage_test.dart index 32e50e51c78..a047909e8b8 100644 --- a/packages/neon_framework/test/storage_test.dart +++ b/packages/neon_framework/test/storage_test.dart @@ -34,19 +34,19 @@ void main() { }); test('AppStorage formatKey', () async { - var appStorage = const AppStorage(StorageKeys.accountOptions); + var appStorage = const DefaultSettingsStore(StorageKeys.accountOptions); var key = appStorage.formatKey('test-key'); expect(key, 'accounts-test-key'); expect(appStorage.id, StorageKeys.accountOptions.value); - appStorage = const AppStorage(StorageKeys.accountOptions, 'test-suffix'); + appStorage = const DefaultSettingsStore(StorageKeys.accountOptions, 'test-suffix'); key = appStorage.formatKey('test-key'); expect(key, 'accounts-test-suffix-test-key'); expect(appStorage.id, 'test-suffix'); }); test('AppStorage interface', () async { - const appStorage = AppStorage(StorageKeys.accountOptions); + const appStorage = DefaultSettingsStore(StorageKeys.accountOptions); const key = 'key'; final formattedKey = appStorage.formatKey(key); @@ -77,7 +77,7 @@ void main() { }); test('SingleValueStorage', () async { - const storage = SingleValueStorage(StorageKeys.global); + const storage = DefaultSingleValueStore(StorageKeys.global); final key = StorageKeys.global.value; when(() => sharedPreferences.containsKey(key)).thenReturn(true); From 90c9618efa7ce9f536b8833911ef00a165f8054a Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Wed, 31 Jan 2024 14:28:46 +0100 Subject: [PATCH 10/11] refactor(neon_framework): make NeonStorage initialisation independent of the storage backends Signed-off-by: Nikolas Rimikis --- .../lib/src/storage/storage_manager.dart | 46 +++++++++++++------ .../neon_framework/lib/src/testing/mocks.dart | 8 +++- .../test/options_collection_test.dart | 16 ++++--- .../neon_framework/test/storage_test.dart | 19 +------- 4 files changed, 49 insertions(+), 40 deletions(-) diff --git a/packages/neon_framework/lib/src/storage/storage_manager.dart b/packages/neon_framework/lib/src/storage/storage_manager.dart index 2a4f824eeba..dd4aac5c5b6 100644 --- a/packages/neon_framework/lib/src/storage/storage_manager.dart +++ b/packages/neon_framework/lib/src/storage/storage_manager.dart @@ -28,39 +28,55 @@ class NeonStorage { @visibleForTesting static NeonStorage? instance; + bool _initialized = false; + + /// Whether the storages have been initialized. + bool get initialized => _initialized; + /// Shared preferences instance. - /// - /// Use [database] to access it. - /// Make sure it has been initialized with [init] before. - SharedPreferences? _sharedPreferences; + late SharedPreferences _sharedPreferences; - /// Sets up the [SharedPreferences] instance. + /// Sets the individual storages. /// - /// Required to be called before accessing [database]. + /// Required to be called before accessing any individual one. Future init() async { - if (_sharedPreferences != null) { + if (_initialized) { return; } _sharedPreferences = await SharedPreferences.getInstance(); + + _initialized = true; } /// Returns the database instance. /// /// Throws a `StateError` if [init] has not completed. SharedPreferences get database { - if (_sharedPreferences == null) { - throw StateError( - 'NeonStorage has not been initialized yet. Please make sure NeonStorage.init() has been called before and completed.', - ); - } + _assertInitialized(); - return _sharedPreferences!; + return _sharedPreferences; } /// Initializes a new `SettingsStorage`. - SettingsStore settingsStore(StorageKeys groupKey, [String? suffix]) => DefaultSettingsStore(groupKey, suffix); + SettingsStore settingsStore(StorageKeys groupKey, [String? suffix]) { + _assertInitialized(); + + return DefaultSettingsStore(groupKey, suffix); + } /// Initializes a new `KeyValueStorage`. - SingleValueStore singleValueStore(StorageKeys key) => DefaultSingleValueStore(key); + SingleValueStore singleValueStore(StorageKeys key) { + _assertInitialized(); + + return DefaultSingleValueStore(key); + } + + void _assertInitialized() { + if (!_initialized) { + throw StateError( + 'NeonStorage has not been initialized yet. Please make sure NeonStorage.init() has been called before and completed.', + ); + } + } } diff --git a/packages/neon_framework/lib/src/testing/mocks.dart b/packages/neon_framework/lib/src/testing/mocks.dart index aef5c727327..a4bfd779861 100644 --- a/packages/neon_framework/lib/src/testing/mocks.dart +++ b/packages/neon_framework/lib/src/testing/mocks.dart @@ -45,7 +45,13 @@ class MockAppImplementationOptions extends Mock implements AppImplementationOpti class MockCache extends Mock implements Cache {} -class MockNeonStorage extends Mock implements NeonStorage {} +class MockNeonStorage extends Mock implements NeonStorage { + MockNeonStorage() { + NeonStorage.mocked(this); + } +} + +class MockSettingsStore extends Mock implements SettingsStore {} class MockSharedPreferences extends Mock implements SharedPreferences {} diff --git a/packages/neon_framework/test/options_collection_test.dart b/packages/neon_framework/test/options_collection_test.dart index c422faf5ea3..231fd537e6e 100644 --- a/packages/neon_framework/test/options_collection_test.dart +++ b/packages/neon_framework/test/options_collection_test.dart @@ -2,11 +2,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/settings.dart'; import 'package:neon_framework/src/storage/keys.dart'; -import 'package:neon_framework/src/storage/settings_store.dart'; import 'package:neon_framework/testing.dart'; class Collection extends AppImplementationOptions { - Collection(List> options) : super(const DefaultSettingsStore(StorageKeys.apps)) { + Collection(List> options, MockSettingsStore storage) : super(storage) { + when(() => storage.id).thenReturn('app'); + super.options = options; } } @@ -25,10 +26,13 @@ void main() { group('OptionsCollection', () { final option1 = MockOption(); final option2 = MockOption(); - final collection = Collection([ - option1, - option2, - ]); + final collection = Collection( + [ + option1, + option2, + ], + MockSettingsStore(), + ); test('reset', () { collection.reset(); diff --git a/packages/neon_framework/test/storage_test.dart b/packages/neon_framework/test/storage_test.dart index a047909e8b8..c2174cadfc9 100644 --- a/packages/neon_framework/test/storage_test.dart +++ b/packages/neon_framework/test/storage_test.dart @@ -3,34 +3,17 @@ import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/src/storage/keys.dart'; import 'package:neon_framework/src/storage/settings_store.dart'; import 'package:neon_framework/src/storage/single_value_store.dart'; -import 'package:neon_framework/storage.dart'; import 'package:neon_framework/testing.dart'; -import 'package:shared_preferences/shared_preferences.dart'; void main() { - test('NeonStorage', () async { - expect(() => NeonStorage().database, throwsA(isA())); - - SharedPreferences.setMockInitialValues({}); - await NeonStorage().init(); - - expect(NeonStorage().database, isA()); - }); - group('Storages', () { - late SharedPreferences sharedPreferences; + late MockSharedPreferences sharedPreferences; setUp(() { sharedPreferences = MockSharedPreferences(); final storageMock = MockNeonStorage(); when(() => storageMock.database).thenReturn(sharedPreferences); - - NeonStorage.mocked(storageMock); - }); - - tearDown(() { - NeonStorage.instance = null; }); test('AppStorage formatKey', () async { From c5c04ce2d0021e60dce618b9fcaf95661c985fbe Mon Sep 17 00:00:00 2001 From: Nikolas Rimikis Date: Wed, 31 Jan 2024 12:42:08 +0100 Subject: [PATCH 11/11] refactor(neon_framework): let NeonStorage manage the RequestCache Signed-off-by: Nikolas Rimikis --- .../neon/neon_dashboard/test/widget_test.dart | 5 + packages/neon_framework/lib/neon.dart | 2 - .../lib/src/storage/request_cache.dart | 127 +++++++++++++++ .../lib/src/storage/settings_store.dart | 14 +- .../lib/src/storage/single_value_store.dart | 20 +-- .../lib/src/storage/storage_manager.dart | 20 ++- .../neon_framework/lib/src/testing/mocks.dart | 7 +- .../lib/src/utils/request_manager.dart | 152 +----------------- packages/neon_framework/lib/storage.dart | 2 +- .../test/request_manager_test.dart | 24 ++- .../neon_framework/test/storage_test.dart | 11 +- .../test/user_status_bloc_test.dart | 3 + .../test/weather_status_bloc_test.dart | 7 + 13 files changed, 195 insertions(+), 199 deletions(-) diff --git a/packages/neon/neon_dashboard/test/widget_test.dart b/packages/neon/neon_dashboard/test/widget_test.dart index dbb949c0301..6863147e4a7 100644 --- a/packages/neon/neon_dashboard/test/widget_test.dart +++ b/packages/neon/neon_dashboard/test/widget_test.dart @@ -40,6 +40,11 @@ void main() { ), ); + setUp(() { + final storage = MockNeonStorage(); + when(() => storage.requestCache).thenReturn(null); + }); + group('Widget item', () { final item = dashboard.WidgetItem( (b) => b diff --git a/packages/neon_framework/lib/neon.dart b/packages/neon_framework/lib/neon.dart index fa92470a692..bc211a2c36d 100644 --- a/packages/neon_framework/lib/neon.dart +++ b/packages/neon_framework/lib/neon.dart @@ -14,7 +14,6 @@ import 'package:neon_framework/src/platform/platform.dart'; import 'package:neon_framework/src/theme/neon.dart'; import 'package:neon_framework/src/utils/global_options.dart'; import 'package:neon_framework/src/utils/provider.dart'; -import 'package:neon_framework/src/utils/request_manager.dart'; import 'package:neon_framework/src/utils/user_agent.dart'; import 'package:neon_framework/storage.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -35,7 +34,6 @@ Future runNeon({ FlutterNativeSplash.preserve(widgetsBinding: binding); await NeonPlatform.setup(); - await RequestManager.instance.initCache(); await NeonStorage().init(); final packageInfo = await PackageInfo.fromPlatform(); diff --git a/packages/neon_framework/lib/src/storage/request_cache.dart b/packages/neon_framework/lib/src/storage/request_cache.dart index 5051c2245b0..68f264882d1 100644 --- a/packages/neon_framework/lib/src/storage/request_cache.dart +++ b/packages/neon_framework/lib/src/storage/request_cache.dart @@ -1,6 +1,11 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:meta/meta.dart'; import 'package:neon_framework/src/utils/request_manager.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; /// A storage used to cache a key value pair. abstract interface class RequestCache { @@ -21,3 +26,125 @@ abstract interface class RequestCache { /// Updates the cache [parameters] for a given [key] without modifying the `value`. Future updateParameters(String key, CacheParameters? parameters); } + +@internal +class DefaultRequestCache implements RequestCache { + DefaultRequestCache(); + + Database? _database; + + Future init() async { + if (_database != null) { + return; + } + + final cacheDir = await getApplicationCacheDirectory(); + _database = await openDatabase( + p.join(cacheDir.path, 'cache.db'), + version: 2, + onCreate: (db, version) async { + await db.execute( + 'CREATE TABLE cache (id INTEGER PRIMARY KEY, key TEXT, value TEXT, etag TEXT, expires INTEGER, UNIQUE(key))', + ); + }, + onUpgrade: (db, oldVersion, newVersion) async { + final batch = db.batch(); + if (oldVersion == 1) { + batch + ..execute('ALTER TABLE cache ADD COLUMN etag TEXT') + ..execute('ALTER TABLE cache ADD COLUMN expires INTEGER'); + } + await batch.commit(); + }, + ); + } + + Database get _requireDatabase { + final database = _database; + if (database == null) { + throw StateError( + 'Cache has not been set up yet. Please make sure DefaultRequestCache.init() has been called before and completed.', + ); + } + + return database; + } + + @override + Future get(String key) async { + List>? result; + try { + result = await _requireDatabase.rawQuery('SELECT value FROM cache WHERE key = ?', [key]); + } on DatabaseException catch (e, s) { + debugPrint(e.toString()); + debugPrintStack(stackTrace: s, maxFrames: 5); + } + + return result?.firstOrNull?['value'] as String?; + } + + @override + Future set(String key, String value, CacheParameters? parameters) async { + try { + // UPSERT is only available since SQLite 3.24.0 (June 4, 2018). + // Using a manual solution from https://stackoverflow.com/a/38463024 + final batch = _requireDatabase.batch() + ..update( + 'cache', + { + 'key': key, + 'value': value, + 'etag': parameters?.etag, + 'expires': parameters?.expires?.millisecondsSinceEpoch, + }, + where: 'key = ?', + whereArgs: [key], + ) + ..rawInsert( + 'INSERT INTO cache (key, value, etag, expires) SELECT ?, ?, ?, ? WHERE (SELECT changes() = 0)', + [key, value, parameters?.etag, parameters?.expires?.millisecondsSinceEpoch], + ); + await batch.commit(noResult: true); + } on DatabaseException catch (e, s) { + debugPrint(e.toString()); + debugPrintStack(stackTrace: s, maxFrames: 5); + } + } + + @override + Future getParameters(String key) async { + List>? result; + try { + result = await _requireDatabase.rawQuery('SELECT etag, expires FROM cache WHERE key = ?', [key]); + } on DatabaseException catch (e, s) { + debugPrint(e.toString()); + debugPrintStack(stackTrace: s, maxFrames: 5); + } + + final row = result?.firstOrNull; + + final expires = row?['expires'] as int?; + return CacheParameters( + etag: row?['etag'] as String?, + expires: expires != null ? DateTime.fromMillisecondsSinceEpoch(expires) : null, + ); + } + + @override + Future updateParameters(String key, CacheParameters? parameters) async { + try { + await _requireDatabase.update( + 'cache', + { + 'etag': parameters?.etag, + 'expires': parameters?.expires?.millisecondsSinceEpoch, + }, + where: 'key = ?', + whereArgs: [key], + ); + } on DatabaseException catch (e, s) { + debugPrint(e.toString()); + debugPrintStack(stackTrace: s, maxFrames: 5); + } + } +} diff --git a/packages/neon_framework/lib/src/storage/settings_store.dart b/packages/neon_framework/lib/src/storage/settings_store.dart index 2f742b3f20e..0b6cb08ab1b 100644 --- a/packages/neon_framework/lib/src/storage/settings_store.dart +++ b/packages/neon_framework/lib/src/storage/settings_store.dart @@ -1,6 +1,7 @@ import 'package:meta/meta.dart'; import 'package:neon_framework/src/storage/keys.dart'; import 'package:neon_framework/src/storage/storage_manager.dart'; +import 'package:shared_preferences/shared_preferences.dart'; /// Storage interface used by `Option`s. /// @@ -64,10 +65,13 @@ abstract interface class SettingsStore { final class DefaultSettingsStore implements SettingsStore { /// Creates a new app storage. const DefaultSettingsStore( + this._database, this.groupKey, [ this.suffix, ]); + final SharedPreferences _database; + /// The group key for this app storage. /// /// Keys are formatted with [formatKey] @@ -96,17 +100,17 @@ final class DefaultSettingsStore implements SettingsStore { } @override - Future remove(String key) => NeonStorage().database.remove(formatKey(key)); + Future remove(String key) => _database.remove(formatKey(key)); @override - String? getString(String key) => NeonStorage().database.getString(formatKey(key)); + String? getString(String key) => _database.getString(formatKey(key)); @override - Future setString(String key, String value) => NeonStorage().database.setString(formatKey(key), value); + Future setString(String key, String value) => _database.setString(formatKey(key), value); @override - bool? getBool(String key) => NeonStorage().database.getBool(formatKey(key)); + bool? getBool(String key) => _database.getBool(formatKey(key)); @override - Future setBool(String key, bool value) => NeonStorage().database.setBool(formatKey(key), value); + Future setBool(String key, bool value) => _database.setBool(formatKey(key), value); } diff --git a/packages/neon_framework/lib/src/storage/single_value_store.dart b/packages/neon_framework/lib/src/storage/single_value_store.dart index 88274dcf6e9..5171b5dee97 100644 --- a/packages/neon_framework/lib/src/storage/single_value_store.dart +++ b/packages/neon_framework/lib/src/storage/single_value_store.dart @@ -66,32 +66,34 @@ abstract interface class SingleValueStore { @internal final class DefaultSingleValueStore implements SingleValueStore { /// Creates a new storage for a single value. - const DefaultSingleValueStore(this.key); + const DefaultSingleValueStore(this._database, this.key); + + final SharedPreferences _database; @override final StorageKeys key; @override - bool hasValue() => NeonStorage().database.containsKey(key.value); + bool hasValue() => _database.containsKey(key.value); @override - Future remove() => NeonStorage().database.remove(key.value); + Future remove() => _database.remove(key.value); @override - String? getString() => NeonStorage().database.getString(key.value); + String? getString() => _database.getString(key.value); @override - Future setString(String value) => NeonStorage().database.setString(key.value, value); + Future setString(String value) => _database.setString(key.value, value); @override - bool? getBool() => NeonStorage().database.getBool(key.value); + bool? getBool() => _database.getBool(key.value); @override - Future setBool(bool value) => NeonStorage().database.setBool(key.value, value); + Future setBool(bool value) => _database.setBool(key.value, value); @override - List? getStringList() => NeonStorage().database.getStringList(key.value); + List? getStringList() => _database.getStringList(key.value); @override - Future setStringList(List value) => NeonStorage().database.setStringList(key.value, value); + Future setStringList(List value) => _database.setStringList(key.value, value); } diff --git a/packages/neon_framework/lib/src/storage/storage_manager.dart b/packages/neon_framework/lib/src/storage/storage_manager.dart index dd4aac5c5b6..9f2780bc8de 100644 --- a/packages/neon_framework/lib/src/storage/storage_manager.dart +++ b/packages/neon_framework/lib/src/storage/storage_manager.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:neon_framework/src/storage/keys.dart'; +import 'package:neon_framework/src/storage/request_cache.dart'; import 'package:neon_framework/src/storage/settings_store.dart'; import 'package:neon_framework/src/storage/single_value_store.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -44,32 +45,37 @@ class NeonStorage { return; } + final requestCache = DefaultRequestCache(); + await requestCache.init(); + _requestCache = requestCache; + _sharedPreferences = await SharedPreferences.getInstance(); _initialized = true; } - /// Returns the database instance. - /// - /// Throws a `StateError` if [init] has not completed. - SharedPreferences get database { + /// Request cache instance. + RequestCache? _requestCache; + + /// The current request cache if available. + RequestCache? get requestCache { _assertInitialized(); - return _sharedPreferences; + return _requestCache; } /// Initializes a new `SettingsStorage`. SettingsStore settingsStore(StorageKeys groupKey, [String? suffix]) { _assertInitialized(); - return DefaultSettingsStore(groupKey, suffix); + return DefaultSettingsStore(_sharedPreferences, groupKey, suffix); } /// Initializes a new `KeyValueStorage`. SingleValueStore singleValueStore(StorageKeys key) { _assertInitialized(); - return DefaultSingleValueStore(key); + return DefaultSingleValueStore(_sharedPreferences, key); } void _assertInitialized() { diff --git a/packages/neon_framework/lib/src/testing/mocks.dart b/packages/neon_framework/lib/src/testing/mocks.dart index a4bfd779861..fd144900fc6 100644 --- a/packages/neon_framework/lib/src/testing/mocks.dart +++ b/packages/neon_framework/lib/src/testing/mocks.dart @@ -13,7 +13,6 @@ import 'package:neon_framework/src/blocs/capabilities.dart'; import 'package:neon_framework/src/models/disposable.dart'; import 'package:neon_framework/src/settings/models/exportable.dart'; import 'package:neon_framework/src/utils/account_options.dart'; -import 'package:neon_framework/src/utils/request_manager.dart'; import 'package:neon_framework/storage.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -43,7 +42,7 @@ class MockOption extends Mock implements ToggleOption {} class MockAppImplementationOptions extends Mock implements AppImplementationOptions {} -class MockCache extends Mock implements Cache {} +class MockRequestCache extends Mock implements RequestCache {} class MockNeonStorage extends Mock implements NeonStorage { MockNeonStorage() { @@ -51,10 +50,10 @@ class MockNeonStorage extends Mock implements NeonStorage { } } -class MockSettingsStore extends Mock implements SettingsStore {} - class MockSharedPreferences extends Mock implements SharedPreferences {} +class MockSettingsStore extends Mock implements SettingsStore {} + class MockCallbackFunction extends Mock { FutureOr call(); } diff --git a/packages/neon_framework/lib/src/utils/request_manager.dart b/packages/neon_framework/lib/src/utils/request_manager.dart index ed7a255d5ab..7a30d8afedf 100644 --- a/packages/neon_framework/lib/src/utils/request_manager.dart +++ b/packages/neon_framework/lib/src/utils/request_manager.dart @@ -11,10 +11,7 @@ import 'package:neon_framework/src/bloc/result.dart'; import 'package:neon_framework/src/models/account.dart'; import 'package:neon_framework/storage.dart'; import 'package:nextcloud/nextcloud.dart'; -import 'package:path/path.dart' as p; -import 'package:path_provider/path_provider.dart'; import 'package:rxdart/rxdart.dart'; -import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:xml/xml.dart' as xml; /// A callback that unwraps elements of type [R] into [T]. @@ -52,10 +49,7 @@ final httpDateFormat = DateFormat('E, d MMM yyyy HH:mm:ss v', 'en_US'); /// Requests need to be made through the [nextcloud](https://pub.dev/packages/nextcloud) /// package. /// -/// Requests can be persisted in the local cache if enabled. The local cache -/// must be initialized through [initCache]. A network request is always made, -/// even if a value already exists in the cache. The cached value will only be -/// emitted when the network request has not yet finished. +/// Requests can be persisted in the local cache if enabled and set up by the [NeonStorage]. class RequestManager { RequestManager._(); @@ -72,17 +66,7 @@ class RequestManager { @visibleForTesting static set instance(RequestManager? requestManager) => _requestManager = requestManager; - /// Initializes the cache. - /// - /// Requests made before this method has completed will not be persisted in the cache. - Future initCache() async { - final cache = Cache(); - await cache.init(); - - _cache = cache; - } - - Cache? _cache; + final RequestCache? _cache = NeonStorage().requestCache; /// Executes a request to a Nextcloud API endpoint. Future wrapNextcloud({ @@ -355,137 +339,7 @@ class RequestManager { } } -@internal -class Cache implements RequestCache { - factory Cache() => instance ??= Cache._(); - - Cache._(); - - @visibleForTesting - factory Cache.mocked(Cache mocked) => instance = mocked; - - @visibleForTesting - static Cache? instance; - - Database? _database; - - Future init() async { - if (_database != null) { - return; - } - - final cacheDir = await getApplicationCacheDirectory(); - _database = await openDatabase( - p.join(cacheDir.path, 'cache.db'), - version: 2, - onCreate: (db, version) async { - await db.execute( - 'CREATE TABLE cache (id INTEGER PRIMARY KEY, key TEXT, value TEXT, etag TEXT, expires INTEGER, UNIQUE(key))', - ); - }, - onUpgrade: (db, oldVersion, newVersion) async { - final batch = db.batch(); - if (oldVersion == 1) { - batch - ..execute('ALTER TABLE cache ADD COLUMN etag TEXT') - ..execute('ALTER TABLE cache ADD COLUMN expires INTEGER'); - } - await batch.commit(); - }, - ); - } - - Database get _requireDatabase { - final database = _database; - if (database == null) { - throw StateError( - 'Cache has not been set up yet. Please make sure Cache.init() has been called before and completed.', - ); - } - - return database; - } - - @override - Future get(String key) async { - List>? result; - try { - result = await _requireDatabase.rawQuery('SELECT value FROM cache WHERE key = ?', [key]); - } on DatabaseException catch (e, s) { - debugPrint(e.toString()); - debugPrintStack(stackTrace: s, maxFrames: 5); - } - - return result?.firstOrNull?['value'] as String?; - } - - @override - Future set(String key, String value, CacheParameters? parameters) async { - try { - // UPSERT is only available since SQLite 3.24.0 (June 4, 2018). - // Using a manual solution from https://stackoverflow.com/a/38463024 - final batch = _requireDatabase.batch() - ..update( - 'cache', - { - 'key': key, - 'value': value, - 'etag': parameters?.etag, - 'expires': parameters?.expires?.millisecondsSinceEpoch, - }, - where: 'key = ?', - whereArgs: [key], - ) - ..rawInsert( - 'INSERT INTO cache (key, value, etag, expires) SELECT ?, ?, ?, ? WHERE (SELECT changes() = 0)', - [key, value, parameters?.etag, parameters?.expires?.millisecondsSinceEpoch], - ); - await batch.commit(noResult: true); - } on DatabaseException catch (e, s) { - debugPrint(e.toString()); - debugPrintStack(stackTrace: s, maxFrames: 5); - } - } - - @override - Future getParameters(String key) async { - List>? result; - try { - result = await _requireDatabase.rawQuery('SELECT etag, expires FROM cache WHERE key = ?', [key]); - } on DatabaseException catch (e, s) { - debugPrint(e.toString()); - debugPrintStack(stackTrace: s, maxFrames: 5); - } - - final row = result?.firstOrNull; - - final expires = row?['expires'] as int?; - return CacheParameters( - etag: row?['etag'] as String?, - expires: expires != null ? DateTime.fromMillisecondsSinceEpoch(expires) : null, - ); - } - - @override - Future updateParameters(String key, CacheParameters? parameters) async { - try { - await _requireDatabase.update( - 'cache', - { - 'etag': parameters?.etag, - 'expires': parameters?.expires?.millisecondsSinceEpoch, - }, - where: 'key = ?', - whereArgs: [key], - ); - } on DatabaseException catch (e, s) { - debugPrint(e.toString()); - debugPrintStack(stackTrace: s, maxFrames: 5); - } - } -} - -/// Parameters for values in [Cache]. +/// Parameters for values in [RequestCache]. @immutable class CacheParameters { /// Creates new cache parameters. diff --git a/packages/neon_framework/lib/storage.dart b/packages/neon_framework/lib/storage.dart index 8fc7af6ee94..6cf00b16351 100644 --- a/packages/neon_framework/lib/storage.dart +++ b/packages/neon_framework/lib/storage.dart @@ -4,7 +4,7 @@ library; export 'package:neon_framework/src/storage/keys.dart' show Storable; -export 'package:neon_framework/src/storage/request_cache.dart'; +export 'package:neon_framework/src/storage/request_cache.dart' show RequestCache; export 'package:neon_framework/src/storage/settings_store.dart' show SettingsStore; export 'package:neon_framework/src/storage/single_value_store.dart' show SingleValueStore; export 'package:neon_framework/src/storage/storage_manager.dart'; diff --git a/packages/neon_framework/test/request_manager_test.dart b/packages/neon_framework/test/request_manager_test.dart index 70ba2de1c35..323d6058080 100644 --- a/packages/neon_framework/test/request_manager_test.dart +++ b/packages/neon_framework/test/request_manager_test.dart @@ -17,16 +17,15 @@ String base64String(String value) => base64.encode(utf8.encode(value)); void main() { final account = MockAccount(); when(() => account.id).thenReturn('clientID'); + late MockNeonStorage storage; - tearDown(() { - RequestManager.instance = null; - Cache.instance = null; + setUp(() { + storage = MockNeonStorage(); + when(() => storage.requestCache).thenReturn(null); }); - group('Cache', () { - test('singleton', () { - expect(identical(Cache(), Cache()), isTrue); - }); + tearDown(() { + RequestManager.instance = null; }); group('RequestManager', () { @@ -266,11 +265,10 @@ void main() { }); group('wrap with cache', () { - late Cache cache; + late MockRequestCache cache; setUp(() async { - cache = MockCache(); - Cache.mocked(cache); + cache = MockRequestCache(); when(() => cache.get(any())).thenAnswer( (_) => Future.value('Cached value'), @@ -288,11 +286,7 @@ void main() { (_) => Future.value(), ); - when(() => cache.init()).thenAnswer( - (_) => Future.value(), - ); - - await RequestManager.instance.initCache(); + when(() => storage.requestCache).thenReturn(cache); }); test('successful request', () async { diff --git a/packages/neon_framework/test/storage_test.dart b/packages/neon_framework/test/storage_test.dart index c2174cadfc9..cca0e03cd76 100644 --- a/packages/neon_framework/test/storage_test.dart +++ b/packages/neon_framework/test/storage_test.dart @@ -11,25 +11,22 @@ void main() { setUp(() { sharedPreferences = MockSharedPreferences(); - final storageMock = MockNeonStorage(); - - when(() => storageMock.database).thenReturn(sharedPreferences); }); test('AppStorage formatKey', () async { - var appStorage = const DefaultSettingsStore(StorageKeys.accountOptions); + var appStorage = DefaultSettingsStore(sharedPreferences, StorageKeys.accountOptions); var key = appStorage.formatKey('test-key'); expect(key, 'accounts-test-key'); expect(appStorage.id, StorageKeys.accountOptions.value); - appStorage = const DefaultSettingsStore(StorageKeys.accountOptions, 'test-suffix'); + appStorage = DefaultSettingsStore(sharedPreferences, StorageKeys.accountOptions, 'test-suffix'); key = appStorage.formatKey('test-key'); expect(key, 'accounts-test-suffix-test-key'); expect(appStorage.id, 'test-suffix'); }); test('AppStorage interface', () async { - const appStorage = DefaultSettingsStore(StorageKeys.accountOptions); + final appStorage = DefaultSettingsStore(sharedPreferences, StorageKeys.accountOptions); const key = 'key'; final formattedKey = appStorage.formatKey(key); @@ -60,7 +57,7 @@ void main() { }); test('SingleValueStorage', () async { - const storage = DefaultSingleValueStore(StorageKeys.global); + final storage = DefaultSingleValueStore(sharedPreferences, StorageKeys.global); final key = StorageKeys.global.value; when(() => sharedPreferences.containsKey(key)).thenReturn(true); diff --git a/packages/neon_framework/test/user_status_bloc_test.dart b/packages/neon_framework/test/user_status_bloc_test.dart index ac9e1a412a4..0f3578af3cf 100644 --- a/packages/neon_framework/test/user_status_bloc_test.dart +++ b/packages/neon_framework/test/user_status_bloc_test.dart @@ -155,6 +155,9 @@ void main() { final platform = MockNeonPlatform(); when(() => platform.canUseWindowManager).thenReturn(false); NeonPlatform.mocked(platform); + + final storage = MockNeonStorage(); + when(() => storage.requestCache).thenReturn(null); }); setUp(() { diff --git a/packages/neon_framework/test/weather_status_bloc_test.dart b/packages/neon_framework/test/weather_status_bloc_test.dart index c2b06afbe6f..502155097c3 100644 --- a/packages/neon_framework/test/weather_status_bloc_test.dart +++ b/packages/neon_framework/test/weather_status_bloc_test.dart @@ -3,8 +3,10 @@ import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart'; import 'package:http/testing.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:neon_framework/blocs.dart'; import 'package:neon_framework/src/models/account.dart'; +import 'package:neon_framework/testing.dart'; import 'package:nextcloud/core.dart' as core; import 'package:rxdart/rxdart.dart'; @@ -152,6 +154,11 @@ void main() { late BehaviorSubject> capabilities; late WeatherStatusBloc bloc; + setUpAll(() { + final storage = MockNeonStorage(); + when(() => storage.requestCache).thenReturn(null); + }); + setUp(() { account = mockWeatherStatusAccount(); capabilities = BehaviorSubject>();