From 1d51fe078ea0b925f67aac4562c08e37abdd57c6 Mon Sep 17 00:00:00 2001 From: Chintan Prajapati Date: Tue, 18 Feb 2025 11:05:42 +0530 Subject: [PATCH] Added: Local Storage --- .../base/base_controller.dart | 3 + lib/vaahextendflutter/env/env.dart | 4 + lib/vaahextendflutter/env/env.g.dart | 9 + lib/vaahextendflutter/env/storage.dart | 1 + .../storage/local/services/base_storage.dart | 65 +++++ .../storage/local/services/hive_storage.dart | 247 ++++++++++++++++++ .../storage/local/services/no_op_storage.dart | 112 ++++++++ .../services/storage/local/storage.dart | 124 +++++++++ .../services/storage/local/storage_error.dart | 11 + pubspec.lock | 101 +++++-- pubspec.yaml | 12 +- 11 files changed, 660 insertions(+), 29 deletions(-) create mode 100644 lib/vaahextendflutter/env/storage.dart create mode 100644 lib/vaahextendflutter/services/storage/local/services/base_storage.dart create mode 100644 lib/vaahextendflutter/services/storage/local/services/hive_storage.dart create mode 100644 lib/vaahextendflutter/services/storage/local/services/no_op_storage.dart create mode 100644 lib/vaahextendflutter/services/storage/local/storage.dart create mode 100644 lib/vaahextendflutter/services/storage/local/storage_error.dart diff --git a/lib/vaahextendflutter/base/base_controller.dart b/lib/vaahextendflutter/base/base_controller.dart index f0df6c43..ac5baf89 100644 --- a/lib/vaahextendflutter/base/base_controller.dart +++ b/lib/vaahextendflutter/base/base_controller.dart @@ -11,6 +11,7 @@ import '../env/env.dart'; import '../services/api.dart'; import '../services/notification/internal/notification.dart'; import '../services/notification/push/notification.dart'; +import '../services/storage/local/storage.dart'; import 'root_assets_controller.dart'; class BaseController extends GetxController { @@ -47,6 +48,8 @@ class BaseController extends GetxController { await InternalNotifications.init(); PushNotifications.askPermission(); + await LocalDatabaseStorage.init(); + // Sentry Initialization (And/ Or) Running main app if (null != config.sentryConfig && config.sentryConfig!.dsn.isNotEmpty) { await SentryFlutter.init( diff --git a/lib/vaahextendflutter/env/env.dart b/lib/vaahextendflutter/env/env.dart index eb09b9c0..40748f5b 100644 --- a/lib/vaahextendflutter/env/env.dart +++ b/lib/vaahextendflutter/env/env.dart @@ -9,6 +9,7 @@ import 'package:json_annotation/json_annotation.dart'; import '../services/logging_library/logging_library.dart'; import 'logging.dart'; import 'notification.dart'; +import 'storage.dart'; part 'env.g.dart'; @@ -62,6 +63,7 @@ class EnvironmentConfig { this.oneSignalConfig, this.pusherConfig, required this.showDebugPanel, + required this.localDatabaseStorageType, required this.debugPanelColor, }); @@ -82,6 +84,7 @@ class EnvironmentConfig { final OneSignalConfig? oneSignalConfig; final PusherConfig? pusherConfig; final bool showDebugPanel; + final LocalDatabaseStorageType localDatabaseStorageType; @JsonKey(fromJson: _colorFromJson, toJson: _colorToJson) final Color debugPanelColor; @@ -123,6 +126,7 @@ class EnvironmentConfig { pushNotificationsServiceType: PushNotificationsServiceType.none, internalNotificationsServiceType: InternalNotificationsServiceType.none, showDebugPanel: true, + localDatabaseStorageType: LocalDatabaseStorageType.none, debugPanelColor: Colors.black.withOpacity(0.8), ); } diff --git a/lib/vaahextendflutter/env/env.g.dart b/lib/vaahextendflutter/env/env.g.dart index 740c761e..fe08797c 100644 --- a/lib/vaahextendflutter/env/env.g.dart +++ b/lib/vaahextendflutter/env/env.g.dart @@ -38,6 +38,8 @@ EnvironmentConfig _$EnvironmentConfigFromJson(Map json) => : PusherConfig.fromJson( json['pusher_config'] as Map), showDebugPanel: json['show_debug_panel'] as bool, + localDatabaseStorageType: $enumDecode(_$LocalDatabaseStorageTypeEnumMap, + json['local_database_storage_type']), debugPanelColor: EnvironmentConfig._colorFromJson( (json['debug_panel_color'] as num).toInt()), ); @@ -64,6 +66,8 @@ Map _$EnvironmentConfigToJson(EnvironmentConfig instance) => 'one_signal_config': instance.oneSignalConfig, 'pusher_config': instance.pusherConfig, 'show_debug_panel': instance.showDebugPanel, + 'local_database_storage_type': + _$LocalDatabaseStorageTypeEnumMap[instance.localDatabaseStorageType]!, 'debug_panel_color': EnvironmentConfig._colorToJson(instance.debugPanelColor), }; @@ -81,3 +85,8 @@ const _$InternalNotificationsServiceTypeEnumMap = { InternalNotificationsServiceType.custom: 'custom', InternalNotificationsServiceType.none: 'none', }; + +const _$LocalDatabaseStorageTypeEnumMap = { + LocalDatabaseStorageType.hive: 'hive', + LocalDatabaseStorageType.none: 'none', +}; diff --git a/lib/vaahextendflutter/env/storage.dart b/lib/vaahextendflutter/env/storage.dart new file mode 100644 index 00000000..e695002f --- /dev/null +++ b/lib/vaahextendflutter/env/storage.dart @@ -0,0 +1 @@ +enum LocalDatabaseStorageType { hive, none } diff --git a/lib/vaahextendflutter/services/storage/local/services/base_storage.dart b/lib/vaahextendflutter/services/storage/local/services/base_storage.dart new file mode 100644 index 00000000..56496dd6 --- /dev/null +++ b/lib/vaahextendflutter/services/storage/local/services/base_storage.dart @@ -0,0 +1,65 @@ +abstract class LocalStorageService { + static Future init() async { + throw UnimplementedError(); + } + + Future addCollection({ + required String collectionName, + }); + + Future create({ + required String collectionName, + required String key, + required String value, + }); + + Future createMany({ + required String collectionName, + required Map values, + }); + + Future read({ + required String collectionName, + required String key, + }); + + Future> readMany({ + required String collectionName, + required List keys, + }); + + Future> readAll({ + required String collectionName, + }); + + Future update({ + required String collectionName, + required String key, + required String value, + }); + + Future updateMany({ + required String collectionName, + required Map values, + }); + + Future createOrUpdate({ + required String collectionName, + required String key, + required String value, + }); + + Future createOrUpdateMany({ + required String collectionName, + required Map values, + }); + + Future delete({required String collectionName, required String key}); + + Future deleteMany({ + required String collectionName, + List keys = const [], + }); + + Future deleteAll({required String collectionName}); +} diff --git a/lib/vaahextendflutter/services/storage/local/services/hive_storage.dart b/lib/vaahextendflutter/services/storage/local/services/hive_storage.dart new file mode 100644 index 00000000..f51305e0 --- /dev/null +++ b/lib/vaahextendflutter/services/storage/local/services/hive_storage.dart @@ -0,0 +1,247 @@ +import 'dart:io'; + +import 'package:hive/hive.dart'; +import 'package:path_provider/path_provider.dart'; + +import 'base_storage.dart'; + +/// A class implementing LocalStorageService interface using Hive as storage backend. +class LocalHiveStorage implements LocalStorageService { + static Future init() async { + final Directory docsDirectory = await getApplicationDocumentsDirectory(); + final String storagePath = '${docsDirectory.path}/vaahflutter'; + final Directory storageDirectory = await Directory(storagePath).create( + recursive: true, + ); + + Hive.init(storageDirectory.path); + return LocalHiveStorage(); + } + + final Map> _collections = { + 'vaah-flutter-box': Hive.openBox('vaah-flutter-box'), + }; + + @override + Future addCollection({ + required String collectionName, + }) async { + if (!_collections.containsKey(collectionName)) { + _collections[collectionName] = Hive.openBox(collectionName); + } + } + + @override + Future create({ + required String collectionName, + required String key, + required String value, + }) async { + if (!_collections.containsKey(collectionName)) { + return; + } + final Box box = await _collections[collectionName]!; + if (box.containsKey(key)) {} + + await box.put(key, value); + } + + @override + Future createMany({ + required String collectionName, + required Map values, + }) async { + final List errors = []; + final List success = []; + for (final String key in values.keys) { + try { + await create( + collectionName: collectionName, + key: key, + value: values[key]!, + ); + success.add(key); + } catch (e, st) { + errors.add(key); + } + } + return; + } + + @override + Future read({ + required String collectionName, + required String key, + }) async { + if (!_collections.containsKey(collectionName)) { + throw 'no collection data found'; + } + final Box box = await _collections[collectionName]!; + if (!box.containsKey(key)) { + throw 'no data found'; + } + + return box.get(key); + } + + @override + Future> readMany({ + required String collectionName, + required List keys, + }) async { + final Map success = {}; + final List errors = []; + for (final String key in keys) { + try { + final String? result = await read(collectionName: collectionName, key: key); + if (result != null) { + success[key] = result; + } else { + errors.add(key); + } + } catch (e, st) { + errors.add(key); + } + } + return {}; + } + + @override + Future> readAll({required String collectionName}) async { + if (!_collections.containsKey(collectionName)) { + throw 'no collection found'; + } + final Box box = await _collections[collectionName]!; + return box.toMap().map( + (key, value) { + return MapEntry( + key.toString(), + value.toString(), + ); + }, + ); + } + + @override + Future update({ + required String collectionName, + required String key, + required String value, + }) async { + if (!_collections.containsKey(collectionName)) { + throw 'no collection found'; + } + final Box box = await _collections[collectionName]!; + if (!box.containsKey(key)) { + throw 'no key found'; + } + return await box.put(key, value); + } + + @override + Future updateMany({ + required String collectionName, + required Map values, + }) async { + final List errors = []; + final List success = []; + for (final String key in values.keys) { + try { + await update( + collectionName: collectionName, + key: key, + value: values[key]!, + ); + success.add(key); + } catch (e, st) { + errors.add(key); + } + } + } + + @override + Future createOrUpdate({ + required String collectionName, + required String key, + required String value, + }) async { + if (!_collections.containsKey(collectionName)) { + throw 'no collection found'; + } + final Box box = await _collections[collectionName]!; + if (!box.containsKey(key)) { + return create(collectionName: collectionName, key: key, value: value); + } + return update(collectionName: collectionName, key: key, value: value); + } + + @override + Future createOrUpdateMany({ + required String collectionName, + required Map values, + }) async { + final List errors = []; + final List success = []; + for (final String key in values.keys) { + try { + await createOrUpdate( + collectionName: collectionName, + key: key, + value: values[key]!, + ); + success.add(key); + } catch (e, st) { + errors.add(key); + } + } + } + + @override + Future delete({required String collectionName, dynamic key}) async { + if (!_collections.containsKey(collectionName)) { + throw 'no collection found'; + } + final Box box = await _collections[collectionName]!; + if (!box.containsKey(key)) { + throw 'no key found'; + } + await box.delete(key); + } + + @override + Future deleteMany({ + required String collectionName, + List keys = const [], + }) async { + final List errors = []; + if (!_collections.containsKey(collectionName)) { + throw 'no collection found'; + } + final List nonExistingKeys = []; + final List existingKeys = []; + final Box box = await _collections[collectionName]!; + for (int i = 0; i < keys.length; i++) { + if (!box.containsKey(keys[i])) { + errors.add(keys[i]); + nonExistingKeys.add(keys[i]); + } else { + existingKeys.add(keys[i]); + } + } + if (nonExistingKeys.isEmpty) { + await box.deleteAll(keys); + } else { + await box.deleteAll(existingKeys); + // Notify about errors + } + } + + @override + Future deleteAll({required String collectionName}) async { + if (!_collections.containsKey(collectionName)) { + throw 'no collection found'; + } + final Box box = await _collections[collectionName]!; + await box.clear(); + } +} diff --git a/lib/vaahextendflutter/services/storage/local/services/no_op_storage.dart b/lib/vaahextendflutter/services/storage/local/services/no_op_storage.dart new file mode 100644 index 00000000..a708065c --- /dev/null +++ b/lib/vaahextendflutter/services/storage/local/services/no_op_storage.dart @@ -0,0 +1,112 @@ +import 'base_storage.dart'; + +/// A placeholder storage class when [LocalStorageType.none] is selected in env.dart. +class LocalNoOpStorage implements LocalStorageService { + static Future init() async { + return LocalNoOpStorage(); + } + + @override + Future addCollection({ + required String collectionName, + }) async { + return; + } + + @override + Future create({ + String collectionName = '', + required String key, + required String value, + }) async { + return; + } + + @override + Future createMany({ + String collectionName = '', + required Map values, + }) async { + return; + } + + @override + Future read({ + String collectionName = '', + required String key, + }) async { + return null; + } + + @override + Future> readMany({ + String collectionName = '', + required List keys, + }) async { + return {}; + } + + @override + Future> readAll({ + String collectionName = '', + int start = 1, + int itemsPerPage = 10, + }) async { + return {}; + } + + @override + Future update({ + String collectionName = '', + required String key, + required String value, + }) async { + return; + } + + @override + Future updateMany({ + String collectionName = '', + required Map values, + }) async { + return; + } + + @override + Future createOrUpdate({ + String collectionName = '', + required String key, + required String value, + }) async { + return; + } + + @override + Future createOrUpdateMany({ + String collectionName = '', + required Map values, + }) async { + return; + } + + @override + Future delete({ + String collectionName = '', + required String key, + }) async { + return; + } + + @override + Future deleteMany({ + String collectionName = '', + List keys = const [], + }) async { + return; + } + + @override + Future deleteAll({String collectionName = ''}) async { + return; + } +} diff --git a/lib/vaahextendflutter/services/storage/local/storage.dart b/lib/vaahextendflutter/services/storage/local/storage.dart new file mode 100644 index 00000000..5f5918d6 --- /dev/null +++ b/lib/vaahextendflutter/services/storage/local/storage.dart @@ -0,0 +1,124 @@ +import '../../../env/env.dart'; +import '../../../env/storage.dart'; +import 'services/base_storage.dart'; +import 'services/hive_storage.dart'; +import 'services/no_op_storage.dart'; + +abstract class LocalDatabaseStorage { + static LocalStorageService? _instanceLocal; + + static Future init() async { + final EnvironmentConfig envConfig = EnvironmentConfig.getConfig; + switch (envConfig.localDatabaseStorageType) { + case LocalDatabaseStorageType.hive: + _instanceLocal = await LocalHiveStorage.init(); + default: + _instanceLocal = await LocalNoOpStorage.init(); + } + return _instanceLocal; + } + + static const defaultCollectionName = 'vaah-flutter-box'; + + Future addCollection({ + required String collectionName, + }) async { + await _instanceLocal?.addCollection(collectionName: collectionName); + return; + } + + static Future create({ + String collectionName = defaultCollectionName, + required String key, + required String value, + }) async { + await _instanceLocal?.create(collectionName: collectionName, key: key, value: value); + return; + } + + static Future createMany({ + String collectionName = defaultCollectionName, + required Map values, + }) async { + await _instanceLocal?.createMany(collectionName: collectionName, values: values); + return; + } + + static Future read({ + String collectionName = defaultCollectionName, + required String key, + }) async { + await _instanceLocal?.read(collectionName: collectionName, key: key); + return; + } + + static Future> readMany({ + String collectionName = defaultCollectionName, + required List keys, + }) async { + return await _instanceLocal?.readMany(collectionName: collectionName, keys: keys) ?? {}; + } + + static Future> readAll({ + String collectionName = defaultCollectionName, + }) async { + return await _instanceLocal?.readAll(collectionName: collectionName) ?? {}; + } + + static Future update({ + String collectionName = defaultCollectionName, + required String key, + required String value, + }) async { + await _instanceLocal?.update(collectionName: collectionName, key: key, value: value); + return; + } + + static Future updateMany({ + String collectionName = defaultCollectionName, + required Map values, + }) async { + await _instanceLocal?.updateMany(collectionName: collectionName, values: values); + return; + } + + static Future createOrUpdate({ + String collectionName = defaultCollectionName, + required String key, + required String value, + }) async { + await _instanceLocal?.createOrUpdate(collectionName: collectionName, key: key, value: value); + return; + } + + static Future createOrUpdateMany({ + String collectionName = defaultCollectionName, + required Map values, + }) async { + await _instanceLocal?.createOrUpdateMany(collectionName: collectionName, values: values); + return; + } + + static Future delete({ + String collectionName = defaultCollectionName, + required String key, + }) async { + await _instanceLocal?.delete(collectionName: collectionName, key: key); + return; + } + + static Future deleteMany({ + String collectionName = defaultCollectionName, + List keys = const [], + }) async { + await _instanceLocal?.deleteMany(collectionName: collectionName, keys: keys); + return; + } + + static Future deleteAll({ + String collectionName = defaultCollectionName, + }) async { + await _instanceLocal?.deleteAll(collectionName: collectionName); + return; + } +} diff --git a/lib/vaahextendflutter/services/storage/local/storage_error.dart b/lib/vaahextendflutter/services/storage/local/storage_error.dart new file mode 100644 index 00000000..90c96bc4 --- /dev/null +++ b/lib/vaahextendflutter/services/storage/local/storage_error.dart @@ -0,0 +1,11 @@ +class StorageException implements Exception { + const StorageException({ + required this.key, + required this.throwable, + required this.stackTrace, + }); + + final String key; + final Object throwable; + final StackTrace stackTrace; +} diff --git a/pubspec.lock b/pubspec.lock index 3d36131e..a7fcf41d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -391,10 +391,58 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "9b78450b89f059e96c9ebb355fa6b3df1d6b330436e0b885fb49594c41721398" + sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" url: "https://pub.dev" source: hosted - version: "2.0.23" + version: "2.0.24" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: c0f402067fb0498934faa6bddd670de0a3db45222e2ca9a068c6177c9a2360a4 + url: "https://pub.dev" + source: hosted + version: "9.1.1" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "8cfa53010a294ff095d7be8fa5bb15f2252c50018d69c5104851303f3ff92510" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: "301f67ee9b87f04aef227f57f13f126fa7b13543c8e7a93f25c5d2d534c28a4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" flutter_test: dependency: "direct dev" description: flutter @@ -457,10 +505,18 @@ packages: dependency: transitive description: name: graphs - sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.1" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" http: dependency: transitive description: @@ -505,10 +561,10 @@ packages: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.6.7" json_annotation: dependency: "direct main" description: @@ -521,10 +577,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b + sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c url: "https://pub.dev" source: hosted - version: "6.8.0" + version: "6.9.0" leak_tracker: dependency: transitive description: @@ -646,21 +702,21 @@ packages: source: hosted version: "1.9.0" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: c464428172cb986b758c6d1724c603097febb8fb855aa265aeecc9280c294d4a + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d url: "https://pub.dev" source: hosted - version: "2.2.12" + version: "2.2.4" path_provider_foundation: dependency: transitive description: @@ -681,18 +737,18 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.2.1" petitparser: dependency: transitive description: @@ -744,12 +800,11 @@ packages: pusher_channels_flutter: dependency: "direct main" description: - path: "." - ref: master - resolved-ref: "4235f26339c8c0b2ec159b823fd8f1366be146cf" - url: "https://github.com/kishormainali/pusher-channels-flutter.git" - source: git - version: "2.2.1" + name: pusher_channels_flutter + sha256: "0b06194d85d1665d21e634ea091917b28670fb6d585d76903a4f72d894906000" + url: "https://pub.dev" + source: hosted + version: "2.4.0" sentry: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 585b07c8..94aa56fa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,16 +34,16 @@ dependencies: # Notifications onesignal_flutter: ^5.2.6 flutter_local_notifications: ^17.2.3 - pusher_channels_flutter: - git: - url: https://github.com/kishormainali/pusher-channels-flutter.git - ref: master + pusher_channels_flutter: ^2.4.0 timezone: ^0.9.4 # Storage get_storage: ^2.1.1 # Icons - cupertino_icons: ^1.0.8 - font_awesome_flutter: ^10.7.0 + cupertino_icons: ^1.0.5 + font_awesome_flutter: ^10.5.0 + hive: ^2.2.3 + path_provider: ^2.1.3 + flutter_secure_storage: ^9.1.1 dev_dependencies: flutter_test: