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).
Related
Im trying to create a plugin for unity game on ios platform
For some reason the second time i try to access the static property shared instance i get
Thread 1: EXC_BAD_ACCESS (code=1, address=0xb21c290b0) on line => auto *instance = [[AppattestPluginWrapper sharedInstance] appAttestPlugin];
Weird thing is when i use break point in the sharedInstance property,the value returned isn't nil so it looks like something in the assignment of the var *instance crashes and i can't figure out why.
#import "UnityAppController.h"
#import <Foundation/Foundation.h>
#import "UnityFramework/UnityFramework-Swift.h"
#interface AppattestPluginWrapper: NSObject
{
}
#property (nonatomic, strong) AppAttestPlugin *appAttestPlugin;
#end
#implementation AppattestPluginWrapper
+ (id)sharedInstance {
static AppattestPluginWrapper *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
sharedInstance.appAttestPlugin = [[AppAttestPlugin alloc] init];
});
return sharedInstance;
}
#end
extern "C"{
void _generateAppAttestKeyId(){
auto *instance = [[AppattestPluginWrapper sharedInstance] appAttestPlugin];
[instance generateAppAttestKeyWithCompletion: ^(NSString * response, NSError * error){
if(error){
UnitySendMessage("iOSListener", "OnAppAttestKeyGenerationFailed", [[error localizedDescription] UTF8String]);
}
else if(response){
UnitySendMessage("iOSListener","OnAppAttestKeyGenerated", [response UTF8String]);
}
}];
}
}
The cause for this behaviour was a crash that corrupted the shared instance, for some reason there was no indicator for the corruption and it took some time to detect the issue that was related to the unity code.
I'm having some issues when I build my react-native app in XCode to test on app on my iPhone.
The 4 errors are:
Expected identifier
Expected identifier or '('
Missing context for method declaration
'#end' must appear in an Object-C context
All of these errors are in my project's test .m file 3rsTests.m
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import <React/RCTLog.h>
#import <React/RCTRootView.h>
#define TIMEOUT_SECONDS 600
#define TEXT_TO_LOOK_FOR #"Welcome to React Native!"
#interface threeRsTests : XCTestCase
#end
#implementation 3RsTests //Error: Expected Identifier
- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
{
if (test(view)) {
return YES;
}
for (UIView *subview in [view subviews]) {
if ([self findSubviewInView:subview matching:test]) {
return YES;
}
}
return NO;
}
- (void)testRendersWelcomeScreen //Error: missing context for method declaration
{
UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
BOOL foundElement = NO;
__block NSString *redboxError = nil;
RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
if (level >= RCTLogLevelError) {
redboxError = message;
}
});
while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
return YES;
}
return NO;
}];
}
RCTSetLogFunction(RCTDefaultLogFunction);
XCTAssertNil(redboxError, #"RedBox error: %#", redboxError);
XCTAssertTrue(foundElement, #"Couldn't find element with text '%#' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
}
#end //Error: '#end' must appear in an Objective-C context
I'm not a Swift or Object-C programmer so these errors are quite cryptic to me. I'd appreciate if anyone can give me a hand on how to get my code to compile.
This was a test file of sort and does not affect the way the name appears to the user. I followed #David Liaw's advice and renamed the identifier at line 17 and the app completed its built 100%
I am trying to implement a simple SuperpoweredAdvancedAudioPlayer in swift. I successfully modified the SuperpoweredCrossExample project so that playerA plays the song on starting the application.
ViewController.mm now looks like this:
#import "ViewController.h"
#import "SuperpoweredAdvancedAudioPlayer.h"
#import "SuperpoweredFilter.h"
#import "SuperpoweredRoll.h"
#import "SuperpoweredFlanger.h"
#import "SuperpoweredIOSAudioIO.h"
#import "SuperpoweredSimple.h"
#import <stdlib.h>
#define HEADROOM_DECIBEL 3.0f
static const float headroom = powf(10.0f, -HEADROOM_DECIBEL * 0.025);
/*
This is a .mm file, meaning it's Objective-C++.
You can perfectly mix it with Objective-C or Swift, until you keep the member variables and C++ related includes here.
Yes, the header file (.h) isn't the only place for member variables.
*/
#implementation ViewController {
SuperpoweredAdvancedAudioPlayer *playerA;
SuperpoweredIOSAudioIO *output;
float *stereoBuffer, volA;
unsigned int lastSamplerate;
}
void playerEventCallbackA(void *clientData, SuperpoweredAdvancedAudioPlayerEvent event, void *value) {
if (event == SuperpoweredAdvancedAudioPlayerEvent_LoadSuccess) {
ViewController *self = (__bridge ViewController *)clientData;
self->playerA->setBpm(126.0f);
self->playerA->setFirstBeatMs(353);
self->playerA->setPosition(self->playerA->firstBeatMs, false, false);
};
}
// This is where the Superpowered magic happens.
static bool audioProcessing(void *clientdata, float **buffers, unsigned int inputChannels, unsigned int outputChannels, unsigned int numberOfSamples, unsigned int samplerate, uint64_t hostTime) {
__unsafe_unretained ViewController *self = (__bridge ViewController *)clientdata;
if (samplerate != self->lastSamplerate) { // Has samplerate changed?
self->lastSamplerate = samplerate;
self->playerA->setSamplerate(samplerate);
};
bool silence = !self->playerA->process(self->stereoBuffer, false, numberOfSamples, self->volA);
if (!silence) SuperpoweredDeInterleave(self->stereoBuffer, buffers[0], buffers[1], numberOfSamples); // The stereoBuffer is ready now, let's put the finished audio into the requested buffers.
return !silence;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self f];
}
- (void) f {
volA = 1.0f * headroom;
if (posix_memalign((void **)&stereoBuffer, 16, 4096 + 128) != 0) abort(); // Allocating memory, aligned to 16.
playerA = new SuperpoweredAdvancedAudioPlayer((__bridge void *)self, playerEventCallbackA, 44100, 0);
playerA->open([[[NSBundle mainBundle] pathForResource:#"lycka" ofType:#"mp3"] fileSystemRepresentation]);
output = [[SuperpoweredIOSAudioIO alloc] initWithDelegate:(id<SuperpoweredIOSAudioIODelegate>)self preferredBufferSize:12 preferredMinimumSamplerate:44100 audioSessionCategory:AVAudioSessionCategoryPlayback channels:2 audioProcessingCallback:audioProcessing clientdata:(__bridge void *)self];
[output start];
playerA->play(false);
}
- (void)dealloc {
delete playerA;
free(stereoBuffer);
#if !__has_feature(objc_arc)
[output release];
[super dealloc];
#endif
}
- (void)interruptionStarted {}
- (void)recordPermissionRefused {}
- (void)mapChannels:(multiOutputChannelMap *)outputMap inputMap:(multiInputChannelMap *)inputMap externalAudioDeviceName:(NSString *)externalAudioDeviceName outputsAndInputs:(NSString *)outputsAndInputs {}
- (void)interruptionEnded { // If a player plays Apple Lossless audio files, then we need this. Otherwise unnecessary.
playerA->onMediaserverInterrupt();
}
#end
I am trying to use the same code in swift following the same method used in SuperpoweredFrequencies project to import c++ files in swift.
Superpowered.h:
#import <UIKit/UIKit.h>
#interface Superpowered: NSObject
-(void) f;
#end
Superpowered.mm:
#import "Superpowered.h"
#import "Superpowered/Headers/SuperpoweredAdvancedAudioPlayer.h"
#import "Superpowered/Headers/SuperpoweredFilter.h"
#import "Superpowered/Headers/SuperpoweredRoll.h"
#import "Superpowered/Headers/SuperpoweredFlanger.h"
#import "Superpowered/SuperpoweredIOSAudioIO.h"
#import "Superpowered/Headers/SuperpoweredSimple.h"
#import <stdlib.h>
#define HEADROOM_DECIBEL 3.0f
static const float headroom = powf(10.0f, -HEADROOM_DECIBEL * 0.025);
/*
This is a .mm file, meaning it's Objective-C++.
You can perfectly mix it with Objective-C or Swift, until you keep the member variables and C++ related includes here.
Yes, the header file (.h) isn't the only place for member variables.
*/
#implementation Superpowered {
SuperpoweredAdvancedAudioPlayer *playerA;
SuperpoweredIOSAudioIO *output;
float *stereoBuffer, volA;
unsigned int lastSamplerate;
}
void playerEventCallbackA(void *clientData, SuperpoweredAdvancedAudioPlayerEvent event, void *value) {
if (event == SuperpoweredAdvancedAudioPlayerEvent_LoadSuccess) {
Superpowered *self = (__bridge Superpowered *)clientData;
self->playerA->setBpm(126.0f);
self->playerA->setFirstBeatMs(353);
self->playerA->setPosition(self->playerA->firstBeatMs, false, false);
};
}
// This is where the Superpowered magic happens.
static bool audioProcessing(void *clientdata, float **buffers, unsigned int inputChannels, unsigned int outputChannels, unsigned int numberOfSamples, unsigned int samplerate, uint64_t hostTime) {
__unsafe_unretained Superpowered *self = (__bridge Superpowered *)clientdata;
if (samplerate != self->lastSamplerate) { // Has samplerate changed?
self->lastSamplerate = samplerate;
self->playerA->setSamplerate(samplerate);
};
bool silence = !self->playerA->process(self->stereoBuffer, false, numberOfSamples, self->volA);
if (!silence) SuperpoweredDeInterleave(self->stereoBuffer, buffers[0], buffers[1], numberOfSamples); // The stereoBuffer is ready now, let's put the finished audio into the requested buffers.
return !silence;
}
- (void)f {
volA = 1.0f * headroom;
if (posix_memalign((void **)&stereoBuffer, 16, 4096 + 128) != 0) abort(); // Allocating memory, aligned to 16.
playerA = new SuperpoweredAdvancedAudioPlayer((__bridge void *)self, playerEventCallbackA, 44100, 0);
playerA->open([[[NSBundle mainBundle] pathForResource:#"lycka" ofType:#"mp3"] fileSystemRepresentation]);
output = [[SuperpoweredIOSAudioIO alloc] initWithDelegate:(id<SuperpoweredIOSAudioIODelegate>)self preferredBufferSize:12 preferredMinimumSamplerate:44100 audioSessionCategory:AVAudioSessionCategoryPlayback channels:2 audioProcessingCallback:audioProcessing clientdata:(__bridge void *)self];
[output start];
playerA->play(false);
}
- (void)dealloc {
delete playerA;
free(stereoBuffer);
#if !__has_feature(objc_arc)
[output release];
[super dealloc];
#endif
}
- (void)interruptionStarted {}
- (void)recordPermissionRefused {}
- (void)mapChannels:(multiOutputChannelMap *)outputMap inputMap:(multiInputChannelMap *)inputMap externalAudioDeviceName:(NSString *)externalAudioDeviceName outputsAndInputs:(NSString *)outputsAndInputs {}
- (void)interruptionEnded { // If a player plays Apple Lossless audio files, then we need this. Otherwise unnecessary.
playerA->onMediaserverInterrupt();
}
#end
Project-Bridging-Header.h:
#import "Superpowered.h"
Controller.swift:
override func viewDidLoad() {
super.viewDidLoad()
let s = Superpowered();
s.f();
}
When running the app it crashes and gives the following error:
let s = Superpowered(); should be declared outside viewDidLoad(). Declaring it as an instance variable solved the problem.
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'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