strata-storage 2.4.0 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +295 -0
  2. package/android/src/main/java/com/strata/storage/EncryptedStorage.java +27 -18
  3. package/android/src/main/java/com/strata/storage/SQLiteStorage.java +85 -58
  4. package/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +9 -2
  5. package/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +83 -10
  6. package/dist/README.md +295 -0
  7. package/dist/adapters/web/MemoryAdapter.d.ts.map +1 -1
  8. package/dist/adapters/web/MemoryAdapter.js +7 -1
  9. package/dist/adapters/web/SessionStorageAdapter.d.ts.map +1 -1
  10. package/dist/adapters/web/SessionStorageAdapter.js +3 -2
  11. package/dist/android/src/main/java/com/strata/storage/EncryptedStorage.java +27 -18
  12. package/dist/android/src/main/java/com/strata/storage/SQLiteStorage.java +85 -58
  13. package/dist/android/src/main/java/com/strata/storage/SharedPreferencesStorage.java +9 -2
  14. package/dist/android/src/main/java/com/stratastorage/StrataStoragePlugin.java +83 -10
  15. package/dist/core/Strata.d.ts.map +1 -1
  16. package/dist/core/Strata.js +4 -6
  17. package/dist/firebase.d.ts.map +1 -1
  18. package/dist/firebase.js +75 -13
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/ios/Plugin/SQLiteStorage.swift +100 -30
  22. package/dist/ios/Plugin/UserDefaultsStorage.swift +13 -2
  23. package/dist/package.json +5 -5
  24. package/dist/plugin/index.d.ts.map +1 -1
  25. package/dist/plugin/index.js +31 -6
  26. package/dist/plugin/web.d.ts +5 -1
  27. package/dist/plugin/web.d.ts.map +1 -1
  28. package/dist/plugin/web.js +55 -32
  29. package/dist/utils/index.d.ts.map +1 -1
  30. package/dist/utils/index.js +7 -2
  31. package/ios/Plugin/SQLiteStorage.swift +100 -30
  32. package/ios/Plugin/UserDefaultsStorage.swift +13 -2
  33. package/package.json +29 -19
package/README.md ADDED
@@ -0,0 +1,295 @@
1
+ # Strata Storage
2
+
3
+ > Zero-dependency universal storage plugin providing a unified API for all storage operations across web, Android, and iOS platforms.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/strata-storage.svg)](https://www.npmjs.com/package/strata-storage)
6
+ [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9+-blue.svg)](https://www.typescriptlang.org/)
8
+ [![Platform](https://img.shields.io/badge/platform-Web%20%7C%20iOS%20%7C%20Android-lightgrey.svg)](https://github.com/aoneahsan/strata-storage)
9
+
10
+ ## Features
11
+
12
+ - **🚀 Zero Dependencies** - No external runtime dependencies, pure TypeScript implementation
13
+ - **🌐 Universal API** - Single consistent API across web, iOS, and Android
14
+ - **🔒 Built-in Encryption** - Secure data storage using native crypto APIs
15
+ - **📦 Compression** - Automatic data compression for large objects
16
+ - **⏱️ TTL Support** - Automatic expiration with time-to-live
17
+ - **🔄 Cross-Tab Sync** - Real-time synchronization across browser tabs
18
+ - **🎯 Advanced Queries** - Tag-based querying and filtering
19
+ - **📱 Mobile Ready** - Native iOS and Android storage with Capacitor
20
+ - **💾 Multiple Adapters** - localStorage, IndexedDB, SQLite, Keychain, and more
21
+ - **🎨 Framework Integrations** - React hooks, Vue composables, Angular services
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install strata-storage
27
+ ```
28
+
29
+ Or using yarn:
30
+
31
+ ```bash
32
+ yarn add strata-storage
33
+ ```
34
+
35
+ Or using pnpm:
36
+
37
+ ```bash
38
+ pnpm add strata-storage
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ### Basic Usage
44
+
45
+ ```typescript
46
+ import { Strata } from 'strata-storage';
47
+
48
+ // Create and initialize storage
49
+ const storage = new Strata();
50
+ await storage.initialize();
51
+
52
+ // Store data
53
+ await storage.set('username', 'john_doe');
54
+ await storage.set('user', {
55
+ id: 123,
56
+ name: 'John Doe',
57
+ email: 'john@example.com'
58
+ });
59
+
60
+ // Retrieve data
61
+ const username = await storage.get('username');
62
+ const user = await storage.get('user');
63
+
64
+ // Remove data
65
+ await storage.remove('username');
66
+
67
+ // Clear all data
68
+ await storage.clear();
69
+ ```
70
+
71
+ ### Advanced Features
72
+
73
+ #### Encryption
74
+
75
+ ```typescript
76
+ const storage = new Strata({
77
+ encryption: {
78
+ enabled: true,
79
+ password: 'your-secure-password'
80
+ }
81
+ });
82
+
83
+ await storage.initialize();
84
+ await storage.set('sensitiveData', { token: 'secret' });
85
+ ```
86
+
87
+ #### Time-To-Live (TTL)
88
+
89
+ ```typescript
90
+ // Data expires in 1 hour
91
+ await storage.set('sessionData', data, {
92
+ ttl: 60 * 60 * 1000
93
+ });
94
+ ```
95
+
96
+ #### Compression
97
+
98
+ ```typescript
99
+ const storage = new Strata({
100
+ compression: {
101
+ enabled: true,
102
+ threshold: 1024 // Compress data larger than 1KB
103
+ }
104
+ });
105
+
106
+ await storage.initialize();
107
+ await storage.set('largeData', bigObject);
108
+ ```
109
+
110
+ #### Cross-Tab Sync
111
+
112
+ ```typescript
113
+ const storage = new Strata({
114
+ sync: { enabled: true }
115
+ });
116
+
117
+ await storage.initialize();
118
+
119
+ // Subscribe to changes from other tabs
120
+ storage.subscribe((change) => {
121
+ console.log(`Key ${change.key} changed to:`, change.newValue);
122
+ });
123
+ ```
124
+
125
+ ## Platform Support
126
+
127
+ ### Web Browsers
128
+ - **localStorage** - Simple key-value storage
129
+ - **sessionStorage** - Session-scoped storage
130
+ - **IndexedDB** - Large structured data
131
+ - **Cookies** - Cookie-based storage
132
+ - **Cache API** - HTTP cache storage
133
+ - **Memory** - In-memory storage
134
+
135
+ ### iOS (via Capacitor)
136
+ - **UserDefaults** - User preferences
137
+ - **Keychain** - Secure credential storage
138
+ - **SQLite** - Database storage
139
+ - **FileManager** - File-based storage
140
+
141
+ ### Android (via Capacitor)
142
+ - **SharedPreferences** - Simple key-value storage
143
+ - **EncryptedSharedPreferences** - Secure storage
144
+ - **SQLite** - Database storage
145
+ - **File Storage** - File-based storage
146
+
147
+ ## Framework Integrations
148
+
149
+ ### React
150
+
151
+ ```typescript
152
+ import { useStrata } from 'strata-storage/react';
153
+
154
+ function MyComponent() {
155
+ const { data, loading, set, remove } = useStrata('myKey');
156
+
157
+ return (
158
+ <div>
159
+ <p>{data}</p>
160
+ <button onClick={() => set('newValue')}>Update</button>
161
+ </div>
162
+ );
163
+ }
164
+ ```
165
+
166
+ ### Vue
167
+
168
+ ```typescript
169
+ import { useStrata } from 'strata-storage/vue';
170
+
171
+ export default {
172
+ setup() {
173
+ const { data, loading, set, remove } = useStrata('myKey');
174
+
175
+ return { data, loading, set, remove };
176
+ }
177
+ };
178
+ ```
179
+
180
+ ### Angular
181
+
182
+ ```typescript
183
+ import { StrataService } from 'strata-storage/angular';
184
+
185
+ @Component({ /* ... */ })
186
+ export class MyComponent {
187
+ constructor(private storage: StrataService) {}
188
+
189
+ async saveData() {
190
+ await this.storage.set('key', 'value');
191
+ }
192
+ }
193
+ ```
194
+
195
+ ## Documentation
196
+
197
+ - **[Getting Started](docs/getting-started/installation.md)** - Installation and setup guide
198
+ - **[Quick Start](docs/getting-started/quick-start.md)** - Get up and running in minutes
199
+ - **[API Reference](docs/api/README.md)** - Complete API documentation
200
+ - **[Platform Guides](docs/guides/platforms/web.md)** - Platform-specific information
201
+ - **[Examples](docs/examples/README.md)** - Real-world usage examples
202
+ - **[Migration Guide](docs/MIGRATION.md)** - Migrating from other storage solutions
203
+
204
+ ## Storage Adapters
205
+
206
+ | Adapter | Platform | Use Case |
207
+ |---------|----------|----------|
208
+ | `localStorage` | Web | Simple key-value, persistent |
209
+ | `sessionStorage` | Web | Session-scoped data |
210
+ | `indexedDB` | Web | Large structured data |
211
+ | `cookies` | Web | Cookie-based storage |
212
+ | `cache` | Web | HTTP cache |
213
+ | `memory` | All | Temporary in-memory |
214
+ | `preferences` | Mobile | User preferences (UserDefaults/SharedPreferences) |
215
+ | `secure` | Mobile | Encrypted storage (Keychain/EncryptedSharedPreferences) |
216
+ | `sqlite` | Mobile | Database storage |
217
+ | `filesystem` | Mobile | File-based storage |
218
+
219
+ ## Requirements
220
+
221
+ - **Node.js**: 18.0.0 or higher
222
+ - **TypeScript**: 5.0+ (optional, but recommended)
223
+ - **Capacitor**: 5.x or 6.x (for mobile platforms)
224
+
225
+ ## Browser Support
226
+
227
+ - Chrome/Edge: Latest 2 versions
228
+ - Firefox: Latest 2 versions
229
+ - Safari: Latest 2 versions
230
+ - iOS Safari: iOS 13+
231
+ - Android WebView: Android 8+
232
+
233
+ ## Why Strata Storage?
234
+
235
+ ### Zero Dependencies
236
+ Unlike other storage solutions that depend on multiple packages, Strata Storage has **zero runtime dependencies**. Everything is built from scratch, ensuring:
237
+ - Smaller bundle size
238
+ - No dependency conflicts
239
+ - Better security
240
+ - Full control over implementation
241
+
242
+ ### Universal API
243
+ One API works everywhere. No need to learn different APIs for web and mobile, or switch between libraries:
244
+
245
+ ```typescript
246
+ // Same code works on web, iOS, and Android
247
+ await storage.set('key', 'value');
248
+ const value = await storage.get('key');
249
+ ```
250
+
251
+ ### Built-in Features
252
+ Advanced features included out of the box:
253
+ - Encryption (Web Crypto API / Native Crypto)
254
+ - Compression (LZ-string algorithm)
255
+ - TTL/Expiration
256
+ - Cross-tab sync
257
+ - Advanced queries
258
+ - Data migrations
259
+
260
+ ## Contributing
261
+
262
+ Contributions are welcome! Please read the [Contributing Guide](CONTRIBUTING.md) for details.
263
+
264
+ ## License
265
+
266
+ Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
267
+
268
+ ## Author
269
+
270
+ **Ahsan Mahmood**
271
+ - Email: aoneahsan@gmail.com
272
+ - LinkedIn: [linkedin.com/in/aoneahsan](https://linkedin.com/in/aoneahsan)
273
+ - Portfolio: [aoneahsan.com](https://aoneahsan.com)
274
+ - GitHub: [@aoneahsan](https://github.com/aoneahsan)
275
+ - NPM: [npmjs.com/~aoneahsan](https://www.npmjs.com/~aoneahsan)
276
+ - Phone/WhatsApp: +923046619706
277
+
278
+ ## Links
279
+
280
+ - **NPM Package**: [npmjs.com/package/strata-storage](https://www.npmjs.com/package/strata-storage)
281
+ - **GitHub Repository**: [github.com/aoneahsan/strata-storage](https://github.com/aoneahsan/strata-storage)
282
+ - **Issue Tracker**: [github.com/aoneahsan/strata-storage/issues](https://github.com/aoneahsan/strata-storage/issues)
283
+ - **Documentation**: [github.com/aoneahsan/strata-storage/tree/main/docs](https://github.com/aoneahsan/strata-storage/tree/main/docs)
284
+
285
+ ## Support
286
+
287
+ If you encounter any issues or have questions:
288
+
289
+ 1. Check the [FAQ](docs/reference/faq.md)
290
+ 2. Search [existing issues](https://github.com/aoneahsan/strata-storage/issues)
291
+ 3. Create a [new issue](https://github.com/aoneahsan/strata-storage/issues/new)
292
+
293
+ ---
294
+
295
+ Made with ❤️ by [Ahsan Mahmood](https://aoneahsan.com)
@@ -3,8 +3,11 @@ package com.strata.storage;
3
3
  import android.content.Context;
4
4
  import android.content.SharedPreferences;
5
5
  import android.os.Build;
6
+ import android.util.Log;
6
7
  import androidx.security.crypto.EncryptedSharedPreferences;
7
8
  import androidx.security.crypto.MasterKey;
9
+ import java.security.GeneralSecurityException;
10
+ import java.io.IOException;
8
11
  import java.util.Set;
9
12
  import java.util.HashSet;
10
13
  import java.util.Map;
@@ -18,28 +21,34 @@ public class EncryptedStorage {
18
21
  private SharedPreferences.Editor editor;
19
22
  private static final String DEFAULT_NAME = "StrataSecureStorage";
20
23
 
21
- public EncryptedStorage(Context context) throws Exception {
24
+ public EncryptedStorage(Context context) throws GeneralSecurityException, IOException {
22
25
  this(context, DEFAULT_NAME);
23
26
  }
24
-
25
- public EncryptedStorage(Context context, String name) throws Exception {
27
+
28
+ public EncryptedStorage(Context context, String name) throws GeneralSecurityException, IOException {
26
29
  String fileName = name != null ? name : DEFAULT_NAME;
27
-
30
+
28
31
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
29
- MasterKey masterKey = new MasterKey.Builder(context)
30
- .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
31
- .build();
32
-
33
- encryptedPrefs = EncryptedSharedPreferences.create(
34
- context,
35
- fileName,
36
- masterKey,
37
- EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
38
- EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
39
- );
40
-
41
- editor = encryptedPrefs.edit();
32
+ try {
33
+ MasterKey masterKey = new MasterKey.Builder(context)
34
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
35
+ .build();
36
+
37
+ encryptedPrefs = EncryptedSharedPreferences.create(
38
+ context,
39
+ fileName,
40
+ masterKey,
41
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
42
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
43
+ );
44
+
45
+ editor = encryptedPrefs.edit();
46
+ } catch (GeneralSecurityException | IOException e) {
47
+ Log.e("StrataStorage", "Failed to initialize encrypted storage", e);
48
+ throw e;
49
+ }
42
50
  } else {
51
+ Log.w("StrataStorage", "API < 23, using unencrypted SharedPreferences");
43
52
  // Fallback to regular SharedPreferences for older devices
44
53
  encryptedPrefs = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
45
54
  editor = encryptedPrefs.edit();
@@ -68,7 +77,7 @@ public class EncryptedStorage {
68
77
  editor.putString(key, stringValue);
69
78
  return editor.commit();
70
79
  } catch (Exception e) {
71
- e.printStackTrace();
80
+ Log.e("StrataStorage", "Failed to set value in encrypted storage", e);
72
81
  return false;
73
82
  }
74
83
  }
@@ -5,6 +5,7 @@ import android.database.Cursor;
5
5
  import android.database.sqlite.SQLiteDatabase;
6
6
  import android.database.sqlite.SQLiteOpenHelper;
7
7
  import android.content.ContentValues;
8
+ import android.util.Log;
8
9
  import java.util.ArrayList;
9
10
  import java.util.List;
10
11
  import java.util.HashMap;
@@ -78,7 +79,7 @@ public class SQLiteStorage extends SQLiteOpenHelper {
78
79
  valueBytes = json.getBytes(StandardCharsets.UTF_8);
79
80
  }
80
81
  } catch (Exception e) {
81
- e.printStackTrace();
82
+ Log.e("StrataStorage", "Failed to set value in SQLite", e);
82
83
  return false;
83
84
  }
84
85
  SQLiteDatabase db = this.getWritableDatabase();
@@ -112,36 +113,41 @@ public class SQLiteStorage extends SQLiteOpenHelper {
112
113
 
113
114
  public Map<String, Object> get(String key) {
114
115
  SQLiteDatabase db = this.getReadableDatabase();
115
- Cursor cursor = db.query(TABLE_NAME, null, KEY_ID + "=?",
116
- new String[]{key}, null, null, null, null);
117
-
118
- Map<String, Object> result = null;
119
- if (cursor != null && cursor.moveToFirst()) {
120
- result = new HashMap<>();
121
- result.put("key", key);
122
- result.put("value", cursor.getBlob(cursor.getColumnIndex(KEY_VALUE)));
123
- result.put("created", cursor.getLong(cursor.getColumnIndex(KEY_CREATED)));
124
- result.put("updated", cursor.getLong(cursor.getColumnIndex(KEY_UPDATED)));
125
-
126
- int expiresIndex = cursor.getColumnIndex(KEY_EXPIRES);
127
- if (!cursor.isNull(expiresIndex)) {
128
- result.put("expires", cursor.getLong(expiresIndex));
129
- }
130
-
131
- int tagsIndex = cursor.getColumnIndex(KEY_TAGS);
132
- if (!cursor.isNull(tagsIndex)) {
133
- result.put("tags", cursor.getString(tagsIndex));
116
+ Cursor cursor = null;
117
+ try {
118
+ cursor = db.query(TABLE_NAME, null, KEY_ID + "=?",
119
+ new String[]{key}, null, null, null, null);
120
+
121
+ Map<String, Object> result = null;
122
+ if (cursor != null && cursor.moveToFirst()) {
123
+ result = new HashMap<>();
124
+ result.put("key", key);
125
+ result.put("value", cursor.getBlob(cursor.getColumnIndex(KEY_VALUE)));
126
+ result.put("created", cursor.getLong(cursor.getColumnIndex(KEY_CREATED)));
127
+ result.put("updated", cursor.getLong(cursor.getColumnIndex(KEY_UPDATED)));
128
+
129
+ int expiresIndex = cursor.getColumnIndex(KEY_EXPIRES);
130
+ if (!cursor.isNull(expiresIndex)) {
131
+ result.put("expires", cursor.getLong(expiresIndex));
132
+ }
133
+
134
+ int tagsIndex = cursor.getColumnIndex(KEY_TAGS);
135
+ if (!cursor.isNull(tagsIndex)) {
136
+ result.put("tags", cursor.getString(tagsIndex));
137
+ }
138
+
139
+ int metadataIndex = cursor.getColumnIndex(KEY_METADATA);
140
+ if (!cursor.isNull(metadataIndex)) {
141
+ result.put("metadata", cursor.getString(metadataIndex));
142
+ }
134
143
  }
135
-
136
- int metadataIndex = cursor.getColumnIndex(KEY_METADATA);
137
- if (!cursor.isNull(metadataIndex)) {
138
- result.put("metadata", cursor.getString(metadataIndex));
144
+ return result;
145
+ } finally {
146
+ if (cursor != null) {
147
+ cursor.close();
139
148
  }
140
-
141
- cursor.close();
149
+ db.close();
142
150
  }
143
- db.close();
144
- return result;
145
151
  }
146
152
 
147
153
  public boolean remove(String key) {
@@ -179,52 +185,73 @@ public class SQLiteStorage extends SQLiteOpenHelper {
179
185
  List<String> keys = new ArrayList<>();
180
186
  String selectQuery;
181
187
  String[] selectionArgs = null;
182
-
188
+
183
189
  if (pattern != null) {
184
190
  selectQuery = "SELECT " + KEY_ID + " FROM " + TABLE_NAME + " WHERE " + KEY_ID + " LIKE ?";
185
- selectionArgs = new String[]{"% " + pattern + "%"};
191
+ selectionArgs = new String[]{"%" + pattern + "%"};
186
192
  } else {
187
193
  selectQuery = "SELECT " + KEY_ID + " FROM " + TABLE_NAME;
188
194
  }
189
-
195
+
190
196
  SQLiteDatabase db = this.getReadableDatabase();
191
- Cursor cursor = db.rawQuery(selectQuery, selectionArgs);
192
-
193
- if (cursor.moveToFirst()) {
194
- do {
195
- keys.add(cursor.getString(0));
196
- } while (cursor.moveToNext());
197
+ Cursor cursor = null;
198
+ try {
199
+ cursor = db.rawQuery(selectQuery, selectionArgs);
200
+
201
+ if (cursor != null && cursor.moveToFirst()) {
202
+ do {
203
+ keys.add(cursor.getString(0));
204
+ } while (cursor.moveToNext());
205
+ }
206
+ return keys;
207
+ } finally {
208
+ if (cursor != null) {
209
+ cursor.close();
210
+ }
211
+ db.close();
197
212
  }
198
- cursor.close();
199
- db.close();
200
- return keys;
201
213
  }
202
214
 
203
215
  public boolean has(String key) {
204
216
  SQLiteDatabase db = this.getReadableDatabase();
205
- Cursor cursor = db.query(TABLE_NAME, new String[]{KEY_ID}, KEY_ID + "=?",
206
- new String[]{key}, null, null, null, null);
207
- boolean exists = cursor.getCount() > 0;
208
- cursor.close();
209
- db.close();
210
- return exists;
217
+ Cursor cursor = null;
218
+ try {
219
+ cursor = db.query(TABLE_NAME, new String[]{KEY_ID}, KEY_ID + "=?",
220
+ new String[]{key}, null, null, null, null);
221
+ boolean exists = cursor != null && cursor.getCount() > 0;
222
+ return exists;
223
+ } finally {
224
+ if (cursor != null) {
225
+ cursor.close();
226
+ }
227
+ db.close();
228
+ }
211
229
  }
212
230
 
213
231
  public SizeInfo size() {
214
232
  SQLiteDatabase db = this.getReadableDatabase();
215
- Cursor cursor = db.rawQuery("SELECT COUNT(*), SUM(LENGTH(" + KEY_VALUE + ")) FROM " + TABLE_NAME, null);
216
-
217
- long totalSize = 0;
218
- int count = 0;
219
-
220
- if (cursor != null && cursor.moveToFirst()) {
221
- count = cursor.getInt(0);
222
- totalSize = cursor.getLong(1);
223
- cursor.close();
233
+ Cursor cursor = null;
234
+ try {
235
+ cursor = db.rawQuery("SELECT COUNT(*), SUM(LENGTH(" + KEY_VALUE + ")) FROM " + TABLE_NAME, null);
236
+
237
+ long totalSize = 0;
238
+ int count = 0;
239
+
240
+ if (cursor != null && cursor.moveToFirst()) {
241
+ count = cursor.getInt(0);
242
+ // Check for NULL values from SQL SUM function
243
+ if (!cursor.isNull(1)) {
244
+ totalSize = cursor.getLong(1);
245
+ }
246
+ }
247
+
248
+ return new SizeInfo(totalSize, count);
249
+ } finally {
250
+ if (cursor != null) {
251
+ cursor.close();
252
+ }
253
+ db.close();
224
254
  }
225
-
226
- db.close();
227
- return new SizeInfo(totalSize, count);
228
255
  }
229
256
 
230
257
  /**
@@ -2,6 +2,7 @@ package com.strata.storage;
2
2
 
3
3
  import android.content.Context;
4
4
  import android.content.SharedPreferences;
5
+ import android.util.Log;
5
6
  import java.util.Map;
6
7
  import java.util.Set;
7
8
  import java.util.HashSet;
@@ -37,7 +38,13 @@ public class SharedPreferencesStorage {
37
38
  } else if (value instanceof Boolean) {
38
39
  editor.putBoolean(key, (Boolean) value);
39
40
  } else if (value instanceof Set) {
40
- editor.putStringSet(key, (Set<String>) value);
41
+ // Safely convert to Set<String>
42
+ Set<?> rawSet = (Set<?>) value;
43
+ Set<String> stringSet = new HashSet<>();
44
+ for (Object item : rawSet) {
45
+ stringSet.add(item != null ? item.toString() : "null");
46
+ }
47
+ editor.putStringSet(key, stringSet);
41
48
  } else {
42
49
  // Convert complex objects to JSON
43
50
  String json;
@@ -56,7 +63,7 @@ public class SharedPreferencesStorage {
56
63
  }
57
64
  return editor.commit();
58
65
  } catch (Exception e) {
59
- e.printStackTrace();
66
+ Log.e("StrataStorage", "Failed to set value in SharedPreferences", e);
60
67
  return false;
61
68
  }
62
69
  }