diff --git a/android/src/main/java/com/kevinresol/react_native_default_preference/RNDefaultPreferenceModule.java b/android/src/main/java/com/kevinresol/react_native_default_preference/RNDefaultPreferenceModule.java index b24d06d..2330e01 100644 --- a/android/src/main/java/com/kevinresol/react_native_default_preference/RNDefaultPreferenceModule.java +++ b/android/src/main/java/com/kevinresol/react_native_default_preference/RNDefaultPreferenceModule.java @@ -9,11 +9,13 @@ import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; +import java.util.Map; public class RNDefaultPreferenceModule extends ReactContextBaseJavaModule { private String preferencesName; private final SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener; private final ReactApplicationContext reactContext; + private boolean hasListeners = false; // Flag to track if listeners are active public RNDefaultPreferenceModule(ReactApplicationContext reactContext) { super(reactContext); @@ -21,11 +23,11 @@ public class RNDefaultPreferenceModule extends ReactContextBaseJavaModule { this.preferencesName = reactContext.getPackageName() + "_preferences"; preferenceChangeListener = (sharedPreferences, key) -> { - if (reactContext.hasActiveCatalystInstance()) { + if (hasListeners && reactContext.hasActiveCatalystInstance()) { // Only send events if listeners are active WritableMap changeInfo = Arguments.createMap(); changeInfo.putString("key", key); Object value = sharedPreferences.getAll().get(key); - + if (value instanceof String) { changeInfo.putString("value", (String) value); } else if (value instanceof Integer) { @@ -45,6 +47,7 @@ public class RNDefaultPreferenceModule extends ReactContextBaseJavaModule { .emit("onPreferenceChange", changeInfo); } }; + getPreferences().registerOnSharedPreferenceChangeListener(preferenceChangeListener); } @@ -53,49 +56,61 @@ public class RNDefaultPreferenceModule extends ReactContextBaseJavaModule { return "RNDefaultPreference"; } + @Override + public void initialize() { + super.initialize(); + hasListeners = true; // Mark that listeners are active when the module is initialized + } + @Override public void onCatalystInstanceDestroy() { super.onCatalystInstanceDestroy(); + hasListeners = false; // Mark that no listeners are active when the module is destroyed getPreferences().unregisterOnSharedPreferenceChangeListener(preferenceChangeListener); } private SharedPreferences getPreferences() { return getReactApplicationContext().getSharedPreferences(preferencesName, Context.MODE_PRIVATE); - }} - - private SharedPreferences.Editor getEditor() { - return getPreferences().edit(); } - private void resolvePreferenceValue(Object value, Promise promise) { - if (value instanceof String) { - promise.resolve((String) value); - } else if (value instanceof Integer) { - promise.resolve((Integer) value); - } else if (value instanceof Boolean) { - promise.resolve((Boolean) value); - } else if (value instanceof Float) { - promise.resolve((Float) value); - } else if (value instanceof Long) { - promise.resolve((Long) value); - } else { - promise.resolve(null); + @ReactMethod + public void setName(String name, Promise promise) { + this.preferencesName = name; + promise.resolve(null); } - } - private WritableMap resolvePreferenceToMap(String key, Object value) { - WritableMap map = Arguments.createMap(); - if (value instanceof String) { - map.putString(key, (String) value); - } else if (value instanceof Integer) { - map.putInt(key, (Integer) value); - } else if (value instanceof Boolean) { - map.putBoolean(key, (Boolean) value); - } else if (value instanceof Float) { - map.putDouble(key, (Float) value); - } else if (value instanceof Long) { - map.putDouble(key, ((Long) value).doubleValue()); + @ReactMethod + public void getName(Promise promise) { + promise.resolve(preferencesName); + } + + @ReactMethod + public void get(String key, Promise promise) { + Object value = getPreferences().getAll().get(key); + if (value instanceof String) { + promise.resolve((String) value); + } else if (value instanceof Integer) { + promise.resolve((Integer) value); + } else if (value instanceof Boolean) { + promise.resolve((Boolean) value); + } else if (value instanceof Float) { + promise.resolve((Float) value); + } else if (value instanceof Long) { + promise.resolve((Long) value); + } else { + promise.resolve(null); + } + } + + @ReactMethod + public void set(String key, String value, Promise promise) { + getPreferences().edit().putString(key, value).apply(); + promise.resolve(null); + } + + @ReactMethod + public void clear(String key, Promise promise) { + getPreferences().edit().remove(key).apply(); + promise.resolve(null); } - return map; - } } \ No newline at end of file diff --git a/ios/RNDefaultPreference.m b/ios/RNDefaultPreference.m index 53589ec..b9d71a1 100644 --- a/ios/RNDefaultPreference.m +++ b/ios/RNDefaultPreference.m @@ -1,7 +1,13 @@ #import "RNDefaultPreference.h" #import +#import -@implementation RNDefaultPreference +@interface RNDefaultPreference : RCTEventEmitter +@end + +@implementation RNDefaultPreference { + BOOL hasListeners; +} NSString* defaultSuiteName = nil; @@ -10,8 +16,8 @@ NSString* defaultSuiteName = nil; return dispatch_get_main_queue(); } -- (NSUserDefaults *) getDefaultUser{ - if(defaultSuiteName == nil){ +- (NSUserDefaults *)getDefaultUser { + if (defaultSuiteName == nil) { return [NSUserDefaults standardUserDefaults]; } else { return [[NSUserDefaults alloc] initWithSuiteName:defaultSuiteName]; @@ -25,11 +31,22 @@ RCT_EXPORT_MODULE() return YES; } +// Implement required method for RCTEventEmitter - (NSArray *)supportedEvents { return @[@"onPreferenceChange"]; } +// Called when the first listener is added +- (void)startObserving { + hasListeners = YES; +} + +// Called when the last listener is removed or when the module is deallocated +- (void)stopObserving { + hasListeners = NO; +} + - (instancetype)init { if (self = [super init]) { @@ -43,13 +60,16 @@ RCT_EXPORT_MODULE() - (void)userDefaultsDidChange:(NSNotification *)notification { - NSUserDefaults *defaults = [self getDefaultUser]; - NSDictionary *changedKeys = defaults.dictionaryRepresentation; - - [changedKeys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - NSDictionary *changeInfo = @{@"key": key, @"value": obj}; - [self sendEventWithName:@"onPreferenceChange" body:changeInfo]; - }]; + if (hasListeners) { // Only send events if there are listeners + NSUserDefaults *defaults = [self getDefaultUser]; + NSDictionary *changedKeys = defaults.dictionaryRepresentation; + + for (NSString *key in changedKeys) { + id value = [defaults objectForKey:key]; + NSDictionary *changeInfo = @{@"key": key, @"value": value ?: [NSNull null]}; + [self sendEventWithName:@"onPreferenceChange" body:changeInfo]; + } + } } - (void)dealloc @@ -57,146 +77,44 @@ RCT_EXPORT_MODULE() [[NSNotificationCenter defaultCenter] removeObserver:self]; } -RCT_EXPORT_METHOD(set:(NSString *)key value:(id)value - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) -{ - if ([value isKindOfClass:[NSString class]]) { - [[self getDefaultUser] setObject:value forKey:key]; - } else if ([value isKindOfClass:[NSNumber class]]) { - [[self getDefaultUser] setObject:value forKey:key]; - } - [[self getDefaultUser] synchronize]; - resolve([NSNull null]); -} - - RCT_EXPORT_METHOD(setName:(NSString *)name resolve:(RCTPromiseResolveBlock)resolve - reject:(__unused RCTPromiseRejectBlock)reject) + reject:(RCTPromiseRejectBlock)reject) { defaultSuiteName = name; resolve([NSNull null]); } RCT_EXPORT_METHOD(getName:(RCTPromiseResolveBlock)resolve - reject:(__unused RCTPromiseRejectBlock)reject) + reject:(RCTPromiseRejectBlock)reject) { resolve(defaultSuiteName); } RCT_EXPORT_METHOD(get:(NSString *)key resolve:(RCTPromiseResolveBlock)resolve - reject:(__unused RCTPromiseRejectBlock)reject) + reject:(RCTPromiseRejectBlock)reject) { id value = [[self getDefaultUser] objectForKey:key]; - if ([value isKindOfClass:[NSString class]]) { - resolve(value); - } else if ([value isKindOfClass:[NSNumber class]]) { - resolve([NSString stringWithFormat:@"%@", value]); - } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) { - NSError *error; - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:value options:0 error:&error]; - if (!jsonData) { - reject(@"error_serializing", @"Could not serialize JSON data", error); - } else { - NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - resolve(jsonString); - } - } else { - resolve([NSNull null]); - } + resolve(value ?: [NSNull null]); } RCT_EXPORT_METHOD(set:(NSString *)key value:(id)value resolve:(RCTPromiseResolveBlock)resolve - reject:(__unused RCTPromiseRejectBlock)reject) + reject:(RCTPromiseRejectBlock)reject) { - if ([value isKindOfClass:[NSString class]]) { - [[self getDefaultUser] setObject:value forKey:key]; - } else if ([value isKindOfClass:[NSNumber class]]) { - [[self getDefaultUser] setObject:value forKey:key]; - } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) { - [[self getDefaultUser] setObject:value forKey:key]; - } + NSUserDefaults *defaults = [self getDefaultUser]; + [defaults setObject:value forKey:key]; + [defaults synchronize]; resolve([NSNull null]); } RCT_EXPORT_METHOD(clear:(NSString *)key resolve:(RCTPromiseResolveBlock)resolve - reject:(__unused RCTPromiseRejectBlock)reject) + reject:(RCTPromiseRejectBlock)reject) { [[self getDefaultUser] removeObjectForKey:key]; resolve([NSNull null]); } -RCT_EXPORT_METHOD(getMultiple:(NSArray *)keys - resolve:(RCTPromiseResolveBlock)resolve - reject:(__unused RCTPromiseRejectBlock)reject) -{ - NSMutableArray *result = [NSMutableArray array]; - for (NSString *key in keys) { - id value = [[self getDefaultUser] objectForKey:key]; - if ([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]]) { - [result addObject:value]; - } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) { - NSError *error; - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:value options:0 error:&error]; - NSString *jsonString = jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : [NSNull null]; - [result addObject:jsonString]; - } else { - [result addObject:[NSNull null]]; - } - } - resolve(result); -} - -RCT_EXPORT_METHOD(setMultiple:(NSDictionary *)data - resolve:(RCTPromiseResolveBlock)resolve - reject:(__unused RCTPromiseRejectBlock)reject) -{ - for (id key in data) { - id value = data[key]; - [[self getDefaultUser] setObject:value forKey:key]; - } - resolve([NSNull null]); -} - -RCT_EXPORT_METHOD(clearMultiple:(NSArray *)keys - resolve:(RCTPromiseResolveBlock)resolve - reject:(__unused RCTPromiseRejectBlock)reject) -{ - for (NSString *key in keys) { - [[self getDefaultUser] removeObjectForKey:key]; - } - resolve([NSNull null]); -} - -RCT_EXPORT_METHOD(getAll:(RCTPromiseResolveBlock)resolve - reject:(__unused RCTPromiseRejectBlock)reject) -{ - NSArray *keys = [[[self getDefaultUser] dictionaryRepresentation] allKeys]; - NSMutableDictionary *result = [NSMutableDictionary dictionary]; - for (NSString *key in keys) { - id value = [[self getDefaultUser] objectForKey:key]; - if ([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]]) { - result[key] = value; - } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) { - NSError *error; - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:value options:0 error:&error]; - result[key] = jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : [NSNull null]; - } else { - result[key] = [NSNull null]; - } - } - resolve(result); -} - -RCT_EXPORT_METHOD(clearAll:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) -{ - NSArray *keys = [[[self getDefaultUser] dictionaryRepresentation] allKeys]; - [self clearMultiple:keys resolve:resolve reject:reject]; -} - @end \ No newline at end of file