Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save sschottler/af860b8c2a11fc43630ea9bfeee08207 to your computer and use it in GitHub Desktop.
Save sschottler/af860b8c2a11fc43630ea9bfeee08207 to your computer and use it in GitHub Desktop.
import { NativeModules, NativeEventEmitter, EventSubscriptionVendor, Platform } from 'react-native';
import { Consumer } from '../models';
// This pattern assumes native module extends RCTEventEmitter like this tutorial demonstrates:
// https://teabreak.e-spres-oh.com/swift-in-react-native-the-ultimate-guide-part-1-modules-9bb8d054db03
// So we don't need separate object to assign event handlers to the nativemodule
// and we can just say nativeModule.addListener('someEvent', eventHandler)
// Most nativemodules won't need to emit events. In fact, we could probably just
// pass in a callback that the native layer invokes multiple times
// but if we ever need an event emitter for a native module, here's how we can unify the api:
// declare a type for the native module explicitly and assign to that variable:
const AuthenticationServiceNativeModule: AuthenticationServiceEventEmitter =
NativeModules.AuthenticationService;
// EventSubscriptionVendor must be combined with type so TypeScript
// doesn't complain in NativeEventEmitter super constructor call below
type AuthenticationServiceEventEmitter = AuthenticationService & EventSubscriptionVendor;
// all common methods that have same signature between IOS/Android:
interface AuthenticationService {
initialize(awsdkBundleId: string, awsdkUrl: string, awsdkKey: string): Promise<string>;
authenticateConsumer(userName: string, password: string): Promise<Consumer>;
}
// if we have android-specific calls, we extend interface and cast later:
interface AndroidAuthenticationService extends AuthenticationServiceEventEmitter {
androidOnlyCall(): Promise<string>;
}
// if we have ios-specific calls, we extend interface and cast later:
// TypeScript Rule complains if you start interface with "I"
// eslint-disable-next-line @typescript-eslint/interface-name-prefix
interface IOSAuthenticationService extends AuthenticationServiceEventEmitter {
iosOnlyCall(): Promise<string>;
}
// extend NativeEventEmitter so we get strongly-typed event emitter interface
// combined with our wrapper:
class AuthenticationService extends NativeEventEmitter {
// declaring TypeScript signatures of methods before assigning in constructor is necessary:
// https://stackoverflow.com/questions/12780391/typescript-implementing-interface-in-the-constructor-possible
// methods on native module:
initialize: (awsdkBundleId: string, awsdkUrl: string, awsdkKey: string) => Promise<string>;
authenticateConsumer: (userName: string, password: string) => Promise<Consumer>;
// methods not on native module:
platformAgnosticMethod: () => Promise<string>;
constructor(nativeModule: AuthenticationServiceEventEmitter) {
// TypeScript is happy because nativeModule type is part EventSubscriptionVendor
// which the super call from base NativeEventEmitter class requires:
super(nativeModule);
// set all methods that are the same across both platforms
// at top level so we don't have to re-implement function bodies that
// delegate the calls/parameters to the wrapped nativeModule
// like we do in RallyAuthServiceManager:
const { initialize, authenticateConsumer } = nativeModule;
this.initialize = initialize;
this.authenticateConsumer = authenticateConsumer;
// encapsulate platform-specific logic from calling code:
// (if we keep Kotlin/Swift code consistent, we should have very few
// of these kind of methods):
this.platformAgnosticMethod = (): Promise<string> => {
console.log('platform agnostic method');
if (Platform.OS === 'ios') {
// cast to the android service interface and make call:
return (nativeModule as IOSAuthenticationService).iosOnlyCall();
} else {
// cast to the ios service interface and make call:
return (nativeModule as AndroidAuthenticationService).androidOnlyCall();
}
};
}
}
// service will be strongly-typed interface that is combination of:
// event emitter methods
// base native module methods
// platform-agnostic methods that encapsulate conditional logic defined on class:
export const AmwellAuthenticationService = new AuthenticationService(
AuthenticationServiceNativeModule,
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment