Created
June 16, 2020 19:20
-
-
Save sschottler/af860b8c2a11fc43630ea9bfeee08207 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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