The following build configuration is a boilerplate for android plugin in react-native:
1apply plugin:'com.android.library'23group ='expo.modules.audioconverter'4version ='0.6.3'56def expoModulesCorePlugin =newFile(project(":expo-modules-core").projectDir.absolutePath,"ExpoModulesCorePlugin.gradle")7apply from: expoModulesCorePlugin
8applyKotlinExpoModulesCorePlugin()9useCoreDependencies()10useExpoPublishing()1112// If you want to use the managed Android SDK versions from expo-modules-core, set this to true.13// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.14// Most of the time, you may like to manage the Android SDK versions yourself.15def useManagedAndroidSdkVersions =false16if(useManagedAndroidSdkVersions){17useDefaultAndroidSdkVersions()18}else{19 buildscript {20// Simple helper that allows the root project to override versions declared by this library.21 ext.safeExtGet ={ prop, fallback ->22 rootProject.ext.has(prop)? rootProject.ext.get(prop): fallback
23}24}25 project.android {26 compileSdkVersion safeExtGet("compileSdkVersion",34)27 defaultConfig {28 minSdkVersion safeExtGet("minSdkVersion",21)29 targetSdkVersion safeExtGet("targetSdkVersion",34)30}31}32}3334android {35 namespace "expo.modules.audioconverter"36 defaultConfig {37 versionCode 138 versionName "0.6.3"39}40 lintOptions {41 abortOnError false42}43}44
On starting the application, Expo will scan for the ExpoAudioConverterModule and bridge our typescript code to this kotlin instance.
Remark. Note that we provide the full classpath in the json, not the filename.
Here Module, ModuleDefinition are all provided by the dependencies injected by useCoreDependencies().
Within the scope (trailing closure) of ModuleDefinition, AsyncFunction is one of the method provided by the class ModuleDefinition via function literal (for detail, refer to my article Function Literals with Receiver).
For synchrouous operations we can implement Function in place of AsyncFunction.
1.1.3. Create TypeScript Interface and Wrapper for the Module
1import{NativeModule, requireNativeModule }from"expo"2import{Platform}from"react-native"34exportinterfaceConvertToWavResult{5 outputPath:string6}78declareclassExpoAudioConverterModuleextendsNativeModule{9convertToWav(inputPath:string, outputPath:string):Promise<ConvertToWavResult>10}1112// This call loads the native module object from the JSI.13letAudioConverterModule:ExpoAudioConverterModule|null=null14try{15// Only attempt to load on Android16if(Platform.OS==="android"){17AudioConverterModule=requireNativeModule<ExpoAudioConverterModule>("ExpoAudioConverter")18}19}catch(e){20// Module failed to load21console.warn("ExpoAudioConverter native module not available")22}23exportasyncfunctionconvertToWav(inputPath:string, outputPath:string):Promise<ConvertToWavResult>{24if(!AudioConverterModule){25thrownewError("ExpoAudioConverter native module not available")26}27returnawaitAudioConverterModule.convertToWav(inputPath, outputPath)28}
So basically we don't export the class ExpoAudioConverter, we simply instantiate it in the typescript file and export the execution convertToWav for use.
1.2. Key Expo Modules Concepts
1.2.1. AsyncFunction
Purpose: Define async native functions callable from JavaScript
Syntax: AsyncFunction("name") { param1: Type, param2: Type -> ... }
Returns: Automatically wrapped in a Promise
Errors: Thrown errors become rejected Promises
1// Native2AsyncFunction("myFunction"){ input: String ->3if(input.isEmpty())throwError("Input is empty")4return"Success"5}67// JavaScript8try{9const result = await myFunction("")// Throws error10}catch(e){11 console.error(e.message)// "Input is empty"12}
1.2.2. Type Conversion
Expo automatically converts between JavaScript and native types: