I'm developing an iOS app and am planning to use an analytics service in it, like Flurry or Google Analytics. The thing is: what would be a good software design (loose coupled, highly cohesive, easy to maintain) to use these services?
This is a personal project of mine and I'm using it to study new technologies and best practices. I stumbled into this "challenge" and can't really find one best solution.
I have already developed some mobile apps that use this kind of service and, usually, I implement a combo of the Adapter + Factory design patterns:
A basic interface that represents a generic analytics service is created
public interface IAnalytics {
void LogEvent(string name, object param);
}
Each Service (Flurry/Google Analytics/etc) API is encapsulated through the use of an adapter that implements this interface
public class FlurryService : IAnalyticsService {
public LogEvent(sring name, object param) {
Flurry.Log(name, param.ToString());
}
}
A factory is implemented so that we have the whatever analytics service we need on that particular application
public static class AnalyticsServiceFactory {
public IAnalytics CreateService(string servicename) {
if(servicename == "google") {
return new GoogleAnalyticsService();
} else {
return new FlurryService();
}
}
}
At last, a "proxy" object (not by the book) is created to log application-specific events
public class MyAnalytics {
private static IAnalyticsService _Service = AnalyticsServiceFactory.CreateService("flurry");
public static void LogUserLoggedIn(string user) {
_Service.LogEvent("login", user);
}
public static void LogLoginFailed(string user) {
_Service.LogEvent("login", user);
}
}
This deals with encapsulation of each service API and works great, specially in apps that share code between different platforms.
There is however one problem left that is the logging of events (or actions done by the user) itself. In all cases I've worked on, the logging of events is hardcoded wherever such event occurs. For example:
public void LogIn(string userName, string pass) {
bool success = this.Controller.LogIn(userName, pass);
if(success) {
MyAnalytics.LogUserLoggedIn(username);
// Change view
} else {
MyAnalytics.LogLogInFailed(username);
// Alert
}
}
This seems more coupled than what I'd like it to be, so I'm searching for a better solution.
As I'm working with iOS, I thought about using NSNotificationCenter: Whenever an event happens, instead of immediately logging it, I post a notification in the NSNotificationCenter and another object observing these notifications takes care of calling MyAnalytics to log the event. This design, however, only works with iOS (without a non-trivial ammount of work, that is).
Another way to look at this problem is: How is it that games track your actions in order to reach an Xbox Achievement/Playstation Trophy?
Here's a design I typically use for a logging class that can be multi-provider. It allows for easy swaps between analytics, crash reporting and beta OTA providers and it uses preprocessor directives so that certain services are active in certain environments. This example uses CocoaLumberjack but you could make it work with your logging framework of choice.
#class CLLocation;
#interface TGLogger : NSObject
+ (void)startLogging;
+ (void)stopLogging;
+ (NSString *)getUdidKey;
+ (void)setUserID:(NSString *)userID;
+ (void)setUsername:(NSString *)username;
+ (void)setUserEmail:(NSString *)email;
+ (void)setUserAge:(NSUInteger)age;
+ (void)setUserGender:(NSString *)gender;
+ (void)setUserLocation:(CLLocation *)location;
+ (void)setUserValue:(NSString *)value forKey:(NSString *)key;
+ (void)setIntValue:(int)value forKey:(NSString *)key;
+ (void)setFloatValue:(float)value forKey:(NSString *)key;
+ (void)setBoolValue:(BOOL)value forKey:(NSString *)key;
extern void TGReportMilestone(NSString *milestone, NSDictionary *parameters);
extern void TGReportBeginTimedMilestone(NSString *milestone, NSDictionary *parameters);
extern void TGReportEndTimedMilestone(NSString *milestone, NSDictionary *parameters);
#end
Now the implementation file:
#import "TGLogger.h"
#import "TGAppDelegate.h"
#import <CocoaLumberjack/DDASLLogger.h>
#import <CocoaLumberjack/DDTTYLogger.h>
#import <AFNetworkActivityLogger/AFNetworkActivityLogger.h>
#import CoreLocation;
#ifdef USE_CRASHLYTICS
#import <Crashlytics/Crashlytics.h>
#import <CrashlyticsLumberjack/CrashlyticsLogger.h>
#endif
#ifdef USE_FLURRY
#import <FlurrySDK/Flurry.h>
#endif
#import <Flurry.h>
#implementation TGLogger
+ (void)startLogging
{
[DDLog addLogger:[DDASLLogger sharedInstance]];
[DDLog addLogger:[DDTTYLogger sharedInstance]];
[[DDTTYLogger sharedInstance] setColorsEnabled:YES];
[[DDTTYLogger sharedInstance] setForegroundColor:[UIColor blueColor] backgroundColor:nil forFlag:LOG_FLAG_INFO];
[[DDTTYLogger sharedInstance] setForegroundColor:[UIColor orangeColor] backgroundColor:nil forFlag:LOG_FLAG_WARN];
[[DDTTYLogger sharedInstance] setForegroundColor:[UIColor redColor] backgroundColor:nil forFlag:LOG_FLAG_ERROR];
[[AFNetworkActivityLogger sharedLogger] startLogging];
#ifdef DEBUG
[[AFNetworkActivityLogger sharedLogger] setLevel:AFLoggerLevelInfo];
#else
[[AFNetworkActivityLogger sharedLogger] setLevel:AFLoggerLevelWarn];
#endif
#if defined(USE_CRASHLYTICS) || defined(USE_FLURRY)
NSString *udid = [TGLogger getUdidKey];
TGLogInfo(#"Current UDID is: %#", udid);
#endif
#ifdef USE_CRASHLYTICS
// Start Crashlytics
[Crashlytics startWithAPIKey:TGCrashlyticsKey];
[Crashlytics setUserIdentifier:udid];
[DDLog addLogger:[CrashlyticsLogger sharedInstance]];
TGLogInfo(#"Crashlytics started with API Key: %#", TGCrashlyticsKey);
#endif
#ifdef USE_FLURRY
[Flurry setAppVersion:[[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleShortVersionString"]];
[Flurry setSecureTransportEnabled:YES];
[Flurry setShowErrorInLogEnabled:YES];
[Flurry setLogLevel:FlurryLogLevelCriticalOnly];
[Flurry startSession:TGFlurryApiKey];
TGLogInfo(#"Flurry started with API Key %# and for version %#", TGFlurryApiKey, [[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleShortVersionString"]);
TGLogInfo(#"Flurry Agent Version %#", [Flurry getFlurryAgentVersion]);
#endif
TGLogInfo(#"Logging services started");
}
+ (void)stopLogging
{
TGLogInfo(#"Shutting down logging services");
[DDLog removeAllLoggers];
}
+ (NSString *)getUdidKey
{
return [[UIDevice currentDevice] identifierForVendor].UUIDString;
}
+ (void)setUserID:(NSString *)userID
{
#ifdef USE_CRASHLYTICS
[Crashlytics setUserIdentifier:userID];
#endif
}
+ (void)setUsername:(NSString *)username
{
#ifdef USE_CRASHLYTICS
[Crashlytics setUserName:username];
#endif
#ifdef USE_FLURRY
[Flurry setUserID:username];
#endif
}
+ (void)setUserEmail:(NSString *)email
{
#ifdef USE_CRASHLYTICS
[Crashlytics setUserEmail:email];
#endif
}
+ (void)setUserAge:(NSUInteger)age
{
#ifdef USE_FLURRY
[Flurry setAge:(int)age];
#endif
}
+ (void)setUserGender:(NSString *)gender
{
#ifdef USE_FLURRY
[Flurry setGender:gender];
#endif
}
+ (void)setUserLocation:(CLLocation *)location
{
#ifdef USE_FLURRY
[Flurry setLatitude:location.coordinate.latitude longitude:location.coordinate.longitude horizontalAccuracy:location.horizontalAccuracy verticalAccuracy:location.verticalAccuracy];
#endif
#ifdef USE_CRASHLYTICS
[Crashlytics setObjectValue:location forKey:#"location"];
#endif
}
+ (void)setUserValue:(NSString *)value forKey:(NSString *)key
{
#ifdef USE_CRASHLYTICS
[Crashlytics setObjectValue:value forKey:key];
#endif
}
#pragma mark - Report key/values with crash logs
+ (void)setIntValue:(int)value forKey:(NSString *)key
{
#ifdef USE_CRASHLYTICS
[Crashlytics setIntValue:value forKey:key];
#endif
}
+ (void)setBoolValue:(BOOL)value forKey:(NSString *)key
{
#ifdef USE_CRASHLYTICS
[Crashlytics setBoolValue:value forKey:key];
#endif
}
+ (void)setFloatValue:(float)value forKey:(NSString *)key
{
#ifdef USE_CRASHLYTICS
[Crashlytics setFloatValue:value forKey:key];
#endif
}
void TGReportMilestone(NSString *milestone, NSDictionary *parameters)
{
NSCParameterAssert(milestone);
TGLogCInfo(#"Reporting %#", milestone);
#ifdef USE_FLURRY
[Flurry logEvent:milestone withParameters:parameters];
#endif
}
void TGReportBeginTimedMilestone(NSString *milestone, NSDictionary *parameters)
{
NSCParameterAssert(milestone);
TGLogCInfo(#"Starting timed event %#", milestone);
#ifdef USE_FLURRY
[Flurry logEvent:milestone withParameters:parameters timed:YES];
#endif
}
void TGReportEndTimedMilestone(NSString *milestone, NSDictionary *parameters)
{
NSCParameterAssert(milestone);
TGLogCInfo(#"Ending timed event %#", milestone);
#ifdef USE_FLURRY
[Flurry endTimedEvent:milestone withParameters:parameters];
#endif
}
#end
Related
I'm using the Vuforia plugin within Unity 2018.1 and I have my own override for UnityAppController. When I do that the Vuforia one wipes out mine so it never gets called.
I found many possible solutions but the only one I've managed to get working is to manually replace the Vuforia one with one that also calls my own code too. Which is a total hack and not a good way forward.
I found this possible solution //Can you have more than one subclass of UnityAppController?
but I don't understand it.
#import "UnityAppController.h"
namespace {
typedef BOOL (*ApplicationDidFinishLaunchingWithOptionsImp)(UnityAppController *appController,
SEL selector,
UIApplication *application,
NSDictionary *launchOptions);
ApplicationDidFinishLaunchingWithOptionsImp OriginalApplicationDidFinishLaunchingWithOptions;
BOOL ApplicationDidFinishLaunchingWithOptions(UnityAppController *appController,
SEL selector,
UIApplication *application,
NSDictionary *launchOptions) {
// Initialize Google Play Games, etc
return OriginalApplicationDidFinishLaunchingWithOptions(appController, selector, application, launchOptions);
}
IMP SwizzleMethod(SEL selector, Class klass, IMP newImp) {
Method method = class_getInstanceMethod(klass, selector);
if (method != nil) {
return class_replaceMethod(klass, selector, newImp, method_getTypeEncoding(method));
}
return nil;
}
} // anonymous namespace
#interface AppController : UnityAppController
#end
#implementation AppController
+ (void)load {
OriginalApplicationDidFinishLaunchingWithOptions = (ApplicationDidFinishLaunchingWithOptionsImp)
SwizzleMethod(#selector(application:didFinishLaunchingWithOptions:),
[UnityAppController class],
(IMP)&ApplicationDidFinishLaunchingWithOptions);
}
#end
Where do you add your own code once you have swizzled the original?
This is the code I used to replace the Vuforia version:
#implementation VuforiaNativeRendererController
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
//printf_console("Did Finish Launching with options\n");
NSURL *URL = [launchOptions valueForKey:UIApplicationLaunchOptionsURLKey];
if (URL)
{
const char *URLString = [URL.absoluteString UTF8String];
//printf_console("Application started with URL:\n");
//printf_console("%s\n", URLString);
UnitySendMessage("Scripts", "openURLComplete", URLString);
}
BOOL ret = [super application:application didFinishLaunchingWithOptions:launchOptions];
if (ret)
{
_unityView.backgroundColor = UIColor.clearColor;
}
return ret;
}
But how would I introduce that using a "swizzle" method as above?
I'm using Expressplay SDK for playing DRM contents. I have linked the Expressplay.framework to my iOS project. But while building its giving linking error
Below .h and .mm files
iosdrm.h file
#import <ExpressPlay/ExpressPlay.h>
// import RCTBridgeModule
#import <UIKit/UIKit.h>
#if __has_include(<React/RCTBridgeModule.h>)
#import <React/RCTBridgeModule.h>
#elif __has_include(“RCTBridgeModule.h”)
#import “RCTBridgeModule.h”
#else
#import “React/RCTBridgeModule.h” // Required when used as a Pod in a Swift project
#endif
#define EXP_INIT_ASYNC 1
typedef enum {
DRMCommandStatus_NO_RESULT = 0,
DRMCommandStatus_OK,
DRMCommandStatus_CLASS_NOT_FOUND_EXCEPTION,
DRMCommandStatus_ILLEGAL_ACCESS_EXCEPTION,
DRMCommandStatus_INSTANTIATION_EXCEPTION,
DRMCommandStatus_MALFORMED_URL_EXCEPTION,
DRMCommandStatus_IO_EXCEPTION,
DRMCommandStatus_INVALID_ACTION,
DRMCommandStatus_JSON_EXCEPTION,
DRMCommandStatus_ERROR
} DRMCommandStatus;
#interface iosdrm : NSObject <RCTBridgeModule>
{
UIAlertView* alertView;
NSMutableData *receivedData;
long responseCode;
NSMutableArray* proxies;
NSDictionary * cdvCommand;
}
// #property(nonatomic, readonly) WSB_PlaylistProxy* proxy;
#end
#pragma mark - Private methods
WSB_Result EXP_Initialize(void (^callback)(WSB_Result initialization_result))
{
// initialize the Wasabi runtime
WSB_Result result = WSB_Runtime_Initialize();
if (result != WSB_SUCCESS) {
NSLog(#"Failed to initialize Wasabi Runtime: %d", result);
return result;
}
// check if we're already personalized, without blocking
if (WSB_Runtime_IsPersonalized()) return WSB_SUCCESS;
// personalize in a separate thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// personalize and block until we're done
WSB_Result result = WSB_Runtime_Personalize(nil, 0);
NSLog(#"Wasabi Personalization result: %d", result);
dispatch_async(dispatch_get_main_queue(), ^{ callback(result); });
});
return EXP_INIT_ASYNC;
}
The
iosdrmManager.mm
#import "iosdrm.h"
#import <Foundation/Foundation.h>
#import <React/RCTLog.h>
// import RCTBridge
#import <React/RCTUtils.h>
#if __has_include(<React/RCTBridge.h>)
#import <React/RCTBridge.h>
#elif __has_include(“RCTBridge.h”)
#import "RCTBridge.h"
#else
#import "React/RCTBridge.h" // Required when used as a Pod in a Swift project
#endif
// import RCTEventDispatcher
#if __has_include(<React/RCTEventDispatcher.h>)
#import <React/RCTEventDispatcher.h>
#elif __has_include(“RCTEventDispatcher.h”)
#import "RCTEventDispatcher.h"
#else
#import "React/RCTEventDispatcher.h" // Required when used as a Pod in a Swift project
#endif
struct Node
{
WSB_PlaylistProxy* proxy;
};
static void EXP_OnPlaylistProxyEvent(void* instance, const WSB_PlaylistProxy_Event* event)
{
// instance not used in this example
switch (event->type) {
case WSB_PPET_ERROR_NOTIFICATION:
WSB_PlaylistProxy_ErrorNotificationEvent* e;
e = (WSB_PlaylistProxy_ErrorNotificationEvent*)event;
NSLog(#"Error notification from Playlist Proxy: %d, %s",
e->result, e->error_string);
break;
default:
break;
}
}
static dispatch_queue_t RCTGetMethodQueueDRM()
{
// We want all instances to share the same queue since they will be reading/writing the same database.
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("drmQueue", DISPATCH_QUEUE_SERIAL);
});
return queue;
}
#implementation iosdrm
#synthesize bridge = _bridge;
// Export a native module
// https://facebook.github.io/react-native/docs/native-modules-ios.html
RCT_EXPORT_MODULE(lstdrm);
- (dispatch_queue_t)methodQueue
{
return RCTGetMethodQueueDRM();
}
// Export constants
// https://facebook.github.io/react-native/releases/next/docs/native-modules-ios.html#exporting-constants
- (NSDictionary *)constantsToExport
{
return #{
#"EXAMPLE": #"example"
};
}
// Return the native view that represents your React component
- (UIView *)view
{
return [[UIView alloc] init];
}
#pragma mark - Private methods
WSB_Result EXP_Initialize(void (^callback)(WSB_Result initialization_result))
{
// initialize the Wasabi runtime
WSB_Result result = WSB_Runtime_Initialize();
if (result != WSB_SUCCESS) {
NSLog(#"Failed to initialize Wasabi Runtime: %d", result);
return result;
}
// check if we're already personalized, without blocking
if (WSB_Runtime_IsPersonalized()) return WSB_SUCCESS;
// personalize in a separate thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// personalize and block until we're done
WSB_Result result = WSB_Runtime_Personalize(nil, 0);
NSLog(#"Wasabi Personalization result: %d", result);
dispatch_async(dispatch_get_main_queue(), ^{ callback(result); });
});
return EXP_INIT_ASYNC;
}
while building i'm getting the following error
Undefined symbols for architecture x86_64:
"_WSB_Runtime_Initialize", referenced from:
EXP_Initialize(void (int) block_pointer) in iosdrmManager.o
"_WSB_Runtime_IsPersonalized", referenced from:
EXP_Initialize(void (int) block_pointer) in iosdrmManager.o
"_WSB_Runtime_Personalize", referenced from:
____Z14EXP_InitializeU13block_pointerFviE_block_invoke in iosdrmManager.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Undefined symbols for architecture x86_64:
Seems to suggest you are building for the emulator, instead of for an actual device. The library will likely not work on the emulator (neither does FairPlay, or any other DRM library that i know of). Make sure you are building for an actual iOS device (which should have some variant of arm architecture).
I cannot for the life of me get an event to properly send from iOS native across the bridge to the react native JS context. On the Objective-C side I want to have a module to easily send events across the bridge. I have called this class EventEmitter and its definition is as follows:
// EventEmitter.h
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
#interface EventEmitter : NSObject<RCTBridgeModule>
- (void)emitEvent:(NSString *) eventName withData:(id) eventData;
#end
and the implementation:
// EventEmitter.m
#import "EventEmitter.h"
#implementation EventEmitter
RCT_EXPORT_MODULE();
#synthesize bridge = _bridge;
- (void)emitEvent:(NSString *) eventName withData:(id) eventData
{
NSLog( #"emitting %# with data %#", eventName, [eventData description] );
[[_bridge eventDispatcher] sendDeviceEventWithName:eventName body:eventData];
[[_bridge eventDispatcher] sendAppEventWithName:eventName body:eventData];
}
#end
I'm using both sendDeviceEvent and sendAppEvent because I can't get either to work.
On the JS side of things I register to receive these events in the global namespace of one of my modules (so that I know the event subscription will happen before the event is emitted). I register like this:
console.log( 'ADDING EVENT LISTENERS' );
NativeAppEventEmitter.addListener( 'blah', test => console.log( 'TEST1', test ) );
DeviceEventEmitter.addListener( 'blah', test => console.log( 'TEST2', test ) );
In my log statements I get the following:
2016-03-19 12:26:42.501 [trace][tid:com.facebook.React.JavaScript] ADDING EVENT LISTENERS
2016-03-19 12:26:43.613 [name redacted][348:38737] emitting blah with data [data redacted]
So I can tell that I am sending both an app event and a device event with the tag blah and I have registered to listen for the blah event with both the DeviceEventEmitter and NativeAppEventEmitters but I'm not getting called back in the listeners.
What am I doing wrong?? Thanks for reading!
You can using NativeEventEmitter
// register eventEmitter
const {NGListener} = NativeModules; // NSListener is my class
this.eventEmitter = new NativeEventEmitter(NativeModules.NGListener);
this.eventEmitter.addListener('CancelEvent', (data) => {
console.log(data);
})
In ObjectiveC , you can create
#import <RCTViewManager.h>
#import <RCTEventEmitter.h>
#interface NGListener: RCTEventEmitter <RCTBridgeModule>
#end
#implementation NGListener
RCT_EXPORT_MODULE();
- (NSArray<NSString*> *)supportedEvents {
return #[#"CancelEvent", #"OKEvent"];
}
// And you sent event you want from objectC to react-native
[self sendEventWithName:#"CancelEvent" body:#"Tap`enter code here` on Cancel button from Objc"];
I wrote sample example to handle event from react-native to objectivec and opposite.
https://github.com/lengocgiang/event-listener
Hope this help!!
I've tried dispatching events and it seems bridge is not initialised when you create new EventEmitter instances manually by using [EventEmitter alloc] init]
You should let react-native create instances. I checked native components and they're using -(void)setBridge:(RCTBridge *)bridge method to do initialisation work. Please check out RCTLinkingManager to see an example. It's using NSNotificationCenter to handle events.
// registering for RCTOpenURLNotification evet when the module is initialised with a bridge
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleOpenURLNotification:)
name:RCTOpenURLNotification
object:nil];
}
// emitting openURL event to javascript
- (void)handleOpenURLNotification:(NSNotification *)notification
{
[_bridge.eventDispatcher sendDeviceEventWithName:#"openURL"
body:notification.userInfo];
}
// creating RCTOpenURLNotification event to invoke handleOpenURLNotification method
+ (BOOL)application:(UIApplication *)application
openURL:(NSURL *)URL
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation
{
NSDictionary<NSString *, id> *payload = #{#"url": URL.absoluteString};
[[NSNotificationCenter defaultCenter] postNotificationName:RCTOpenURLNotification
object:self
userInfo:payload];
return YES;
}
In my case I got this working by keeping a value of the bridge from RCTRootView and passing it to the Emitter Instance.
#implementation AppDelegate {
RCTBridge *rootBridge;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURL *jsCodeLocation;
......
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:#"MyApp"
initialProperties:nil
launchOptions:launchOptions];
rootBridge = rootView.bridge;
.......
}
- (IBAction)doAction:(id)sender {
BridgeEvents *events = [[BridgeEvents alloc] init];
[events setBridge:rootBridge];
[events doMyAction];
}
In my Emitter Class:
#import "RCTEventEmitter.h"
#interface BridgeEvents : RCTEventEmitter <RCTBridgeModule>
- (void)doMyAction;
#end
#import "BridgeEvents.h"
#implementation BridgeEvents
RCT_EXPORT_MODULE();
- (NSArray<NSString *> *)supportedEvents {
return #[#"onEvent"];
}
- (void)doMyAction {
[self sendEventWithName:#"onEvent" body:#""];
}
#end
RNNotification *notification = [RNNotification allocWithZone: nil];
[notification sendNotificationToReactNative]I tried everything above and was not able to get it work in my app.
Finally this worked for me.
#import "RNNotification.h"
#implementation RNNotification
RCT_EXPORT_MODULE();
+ (id)allocWithZone:(NSZone *)zone {
static RNNotification *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [super allocWithZone:zone];
});
return sharedInstance;
}
- (NSArray<NSString *> *)supportedEvents
{
return #[#"EventReminder"];
}
- (void)sendNotificationToReactNative
{
[self sendEventWithName:#"EventReminder" body:#{#"name": #"name"}];
}
and while initing the function
RNNotification *notification = [RNNotification allocWithZone: nil];
[notification sendNotificationToReactNative]
You have to use your emitter class like this
[[self.bridge moduleForClass:[RNNotification class]] sendNotificationToReactNative];
You can try following solution to to send event from iOS to React Native
RNEventEmitter.m
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
#interface RCT_EXTERN_MODULE(RNEventEmitter, RCTEventEmitter)
RCT_EXTERN_METHOD(supportedEvents)
#end
RNEventEmitter.swift
import Foundation
#objc(RNEventEmitter)
open class RNEventEmitter: RCTEventEmitter {
public static var emitter: RCTEventEmitter!
override init() {
super.init()
RNEventEmitter.emitter = self
}
open override func supportedEvents() -> [String] {
["onReady", "onPending", "onFailure"] // etc.
}
}
Your file from where you are going to emit event add below line
RNEventEmitter.emitter.sendEvent(withName: "onReady", body: [["status":"Connected","data":["name":dev?.name,"deviceId":dev?.identifier]]]);
React Native file
const emitter = new NativeEventEmitter(NativeModules.RNEventEmitter)
In your Bridgin-Header file import
#import <React/RCTEventEmitter.h>
In your useEffect
emitter.addListener('onReady', (data: any) => {
console.log("addListener", data);
Alert.alert(JSON.stringify(data))
});
I have an iPhone hybrid app using INTULocationManager that works well but the software is far more than I need. I have cut down to the basics as far as I can see, but I have obviously got something wrong when trying to invoke the callback to the block saved when the location request. Please can someone spot the probably pretty obvious error for me.
My .h file for this is
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
typedef void(^SBLocationRequestBlock)(CLLocation *currentLocation);
#interface SBLocationManager : NSObject
// Returns the singleton instance of this class.
+ (instancetype)sharedInstance;
// Creates a subscription for location updates
- (void)subscribeToLocationUpdatesWithBlock:(SBLocationRequestBlock)block;
// Set the minimum distance between two successive location returns
- (void)setDistanceFilter:(double)distance;
#end
My .m file is as follows
#import "SBLocationManager.h"
#import "SBLocationManager+Internal.h"
#interface SBLocationManager () <CLLocationManagerDelegate>
// The instance of CLLocationManager encapsulated by this class.
#property (nonatomic, strong) CLLocationManager *locationManager;
// Whether or not the CLLocationManager is currently sending location updates.
#property (nonatomic, assign) BOOL isUpdatingLocation ;
//Whether an error occurred during the last location update.
#property (nonatomic, assign) BOOL updateFailed;
// the code to be called when a location is available
#property (nonatomic, assign) SBLocationRequestBlock block;
#end
#implementation SBLocationManager
static id _sharedInstance;
// Create instance of this class.
- (instancetype)init
{
//self = [super init];
if (self) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.pausesLocationUpdatesAutomatically = NO; // to keep it going in background mode
#ifdef __IPHONE_8_4
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_8_4
/* iOS 9 requires setting allowsBackgroundLocationUpdates to YES in order to receive background location updates.
We only set it to YES if the location background mode is enabled for this app, as the documentation suggests it is a
fatal programmer error otherwise. */
NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"UIBackgroundModes"];
if ([backgroundModes containsObject:#"location"]) {
if ([_locationManager respondsToSelector:#selector(setAllowsBackgroundLocationUpdates:)]) {
[_locationManager setAllowsBackgroundLocationUpdates:YES];
}
}
#endif /* __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_8_4 */
#endif /* __IPHONE_8_4 */
}
self.isUpdatingLocation = NO ;
return self;
}
+ (instancetype)sharedInstance
{
static dispatch_once_t _onceToken;
dispatch_once(&_onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
- (void)subscribeToLocationUpdatesWithBlock:(SBLocationRequestBlock)block
{
self.block = block;
[self requestAuthorizationIfNeeded];
[self.locationManager startUpdatingLocation];
self.isUpdatingLocation = YES;
}
- (void)setDistanceFilter:(double)distance
{
self.locationManager.distanceFilter = distance ;
self.locationManager.desiredAccuracy = distance ;
}
#pragma mark Internal methods
//Requests permission to use location services on devices with iOS 8+.
- (void)requestAuthorizationIfNeeded
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_7_1
// As of iOS 8, apps must explicitly request location services permissions
// SBLocationManager supports both levels, "Always" and "When In Use".
// SBLocationManager determines which level of permissions to request based
// which description key is present in your app's Info.plist
// If you provide values for both description keys, the more permissive "Always
// level is requested.
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_7_1 && [CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
BOOL hasAlwaysKey = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"NSLocationAlwaysUsageDescription"] != nil;
BOOL hasWhenInUseKey = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"NSLocationWhenInUseUsageDescription"] != nil;
if (hasAlwaysKey) {
[self.locationManager requestAlwaysAuthorization];
} else if (hasWhenInUseKey) {
[self.locationManager requestWhenInUseAuthorization];
} else {
// At least one of the keys NSLocationAlwaysUsageDescription
// NSLocationWhenInUseUsageDescription MUST be present in the Info.plis
// file to use location services on iOS 8+.
NSAssert(hasAlwaysKey || hasWhenInUseKey, #"To use location services in iOS 8+, your Info.plist must provide a value for either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription.");
}
}
#endif /* __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_7_1 */
}
#pragma mark CLLocationManagerDelegate methods
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
self.updateFailed = NO;
CLLocation *mostRecentLocation = [locations lastObject];
dispatch_async(dispatch_get_main_queue(), ^{
if (self.block) {
self.block(mostRecentLocation);
}
});
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
self.updateFailed = YES;
}
- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
{
}
#end
I invoke the request as follows :
- (void)requestLocationUpdates:(double)distanceValue
{
SBLocationManager *locMgr = [SBLocationManager sharedInstance];
[locMgr subscribeToLocationUpdatesWithBlock:^(CLLocation *currentLocation) {
NSLog (#"New Location:\n%#", currentLocation);
NSString *javascriptString = [NSString stringWithFormat:#"UpdateOwnLocation('%.6f','%.6f','%.6f');", currentLocation.coordinate.latitude, currentLocation.coordinate.longitude, currentLocation.horizontalAccuracy];
[webView stringByEvaluatingJavaScriptFromString:javascriptString];
}];
[locMgr setDistanceFilter:distanceValue];
}
- (void) changeLocationUpdates:(double)distanceValue
{
SBLocationManager *locMgr = [SBLocationManager sharedInstance];
[locMgr setDistanceFilter:distanceValue];
}
However when I run this (in the simulator) I get
EXC_BAD_ACCESS in the line self.block (mostRecentLocation).
i.e. the location manager is setup and returns a location but my code for calling the requesting block fails. I have noted in the debugger that self.block is correctly pointing at my view controller code when in subscribeToLocationUpdatesWithBlock but by the time the code gets to invoking the block on the main queue it points somewhere else. Is this because self is no longer the same in this bit of code.
My apologies for bad terminology, I am a javascript programmer attempting to do something with XCode that is probably beyond my skills. Any help anyone can provide would be lovely.
I would use copy, not assign for the block property. assign is for simple things like int usually.
To narrow down if it's the block or location, add an NSLog for mostRecentLocation before calling the block.
If that's not it, check out my blog post on debugging EXC_BAD_ACCESS:
http://loufranco.com/blog/understanding-exc_bad_access
I'm looking for a good way to let each developer have a different config (e.g. server URLs, config flags) that works well with Objective-C and git and supports a default config. One idea is to have two plist files: one checked into git with all the defaults and one that is not checked in and contains custom overrides.
It'd be nice to have more flexibility than a static plist so I started thinking about conditionally loaded classes. Like:
+ (NSDictionary *)config
{
NSMutableDictionary *defaults = ...;
# if DeveloperConfig.h+m exist
// DeveloperConfig can run arbitrary code to override fields
[defaults addEntriesFromDictionary: [DeveloperConfig config]];
# endif
return defaults;
}
Is there a recommended solution for this kind of per-dev configuration?
I will do this way that MyConfigManager does not depends on anything. +[load] is used to register configs which will be called by runtime on startup.
#implementation MyConfigManager
static NSMutableDictionary *defaults;
+ (NSMutableDictionary *)mutableDefaults
{
static dispatch_once_t pred;
dispatch_once(&pred, ^{
defaults = [[NSMutableDictionary alloc] init];
});
return defaults;
}
+ (void)addDefaults:(NSDictionary *)dict
{
[[self mutableDefautls] addEntriesFromDictionary:dict];
}
+ (NSDictionary *)config
{
return [self mutableDefaults];
}
#end
#implementation DeveloperConfig
+ (void)load
{
[MyConfigManager addDefaults:#{#"key":#"value"}];
}
#end
you don't even need a new class because load is called for every categories
#interface MyConfigManager (DeveloperConfig)
#end
#implementation MyConfigManager (DeveloperConfig)
+ (void)load
{
[self addDefaults:#{#"key":#"value"}];
}
#end