1--- 2title: Native Modules 3--- 4 5import { CodeBlocksTable } from '~/components/plugins/CodeBlocksTable'; 6import { PlatformTag } from '~/ui/components/Tag'; 7import { APIBox } from '~/components/plugins/APIBox'; 8 9> **warning** Expo Modules APIs are in beta and subject to breaking changes. 10 11The native modules API is an abstraction layer on top of [JSI](https://reactnative.dev/architecture/glossary#javascript-interfaces-jsi) and other low-level primities that React Native is built upon. It is built with modern languages (Swift and Kotlin) and provides an easy to use and convenient API that is consistent across platforms where possible. 12 13## Definition Components 14 15As you might have noticed in the snippets on the [Get Started](./get-started.mdx) page, each module class must implement the `definition` function. 16The module definition consists of the DSL components that describe the module's functionality and behavior. 17 18<APIBox header="Name"> 19 20Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument. Can be inferred from module's class name, but it's recommended to set it explicitly for clarity. 21 22```swift Swift / Kotlin 23Name("MyModuleName") 24``` 25 26</APIBox> 27<APIBox header="Constants"> 28 29Sets constant properties on the module. Can take a dictionary or a closure that returns a dictionary. 30 31<CodeBlocksTable> 32 33```swift 34// Created from the dictionary 35Constants([ 36 "PI": Double.pi 37]) 38 39// or returned by the closure 40Constants { 41 return [ 42 "PI": Double.pi 43 ] 44} 45``` 46 47```kotlin 48// Passed as arguments 49Constants( 50 "PI" to kotlin.math.PI 51) 52 53// or returned by the closure 54Constants { 55 return@Constants mapOf( 56 "PI" to kotlin.math.PI 57 ) 58} 59``` 60 61</CodeBlocksTable> 62</APIBox> 63<APIBox header="Function"> 64 65Defines a native synchronous function that will be exported to JavaScript. Synchronous means that when the function is executed in JavaScript, its native code is run on the same thread and blocks further execution of the script until the native function returns. 66 67#### Arguments 68 69- **name**: `String` — Name of the function that you'll call from JavaScript. 70- **body**: `(args...) -> ReturnType` — The closure to run when the function is called. 71 72The function can receive up to 8 arguments. This is due to the limitations of generics in both Swift and Kotlin, because this component must be implemented separately for each arity. 73 74See the [Argument Types](#argument-types) section for more details on what types can be used in the function body. 75 76<CodeBlocksTable> 77 78```swift 79Function("syncFunction") { (message: String) in 80 return message 81} 82``` 83 84```kotlin 85Function("syncFunction") { message: String -> 86 return@Function message 87} 88``` 89 90</CodeBlocksTable> 91 92```js JavaScript 93import { requireNativeModule } from 'expo-modules-core'; 94 95// Assume that we have named the module "MyModule" 96const MyModule = requireNativeModule('MyModule'); 97 98function getMessage() { 99 return MyModule.syncFunction('bar'); 100} 101``` 102 103</APIBox> 104<APIBox header="AsyncFunction"> 105 106Defines a JavaScript function that always returns a `Promise` and whose native code is by default dispatched on the different thread than the JavaScript runtime runs on. 107 108#### Arguments 109 110- **name**: `String` — Name of the function that you'll call from JavaScript. 111- **body**: `(args...) -> ReturnType` — The closure to run when the function is called. 112 113If the type of the last argument is `Promise`, the function will wait for the promise to be resolved or rejected before the response is passed back to JavaScript. Otherwise, the function is immediately resolved with the returned value or rejected if it throws an exception. 114The function can receive up to 8 arguments (including the promise). 115 116See the [Argument Types](#argument-types) section for more details on what types can be used in the function body. 117 118It is recommended to use `AsyncFunction` over `Function` when it: 119 120- does I/O bound tasks such as sending network requests or interacting with the file system 121- needs to be run on different thread, e.g. the main UI thread for UI-related tasks 122- is an extensive or long-lasting operation that would block the JavaScript thread which in turn would reduce the responsiveness of the application 123 124<CodeBlocksTable> 125 126```swift 127AsyncFunction("asyncFunction") { (message: String, promise: Promise) in 128 DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { 129 promise.resolve(message) 130 } 131} 132``` 133 134```kotlin 135AsyncFunction("asyncFunction") { message: String, promise: Promise -> 136 launch(Dispatchers.Main) { 137 promise.resolve(message) 138 } 139} 140``` 141 142</CodeBlocksTable> 143 144```js JavaScript 145import { requireNativeModule } from 'expo-modules-core'; 146 147// Assume that we have named the module "MyModule" 148const MyModule = requireNativeModule('MyModule'); 149 150async function getMessageAsync() { 151 return await MyModule.asyncFunction('bar'); 152} 153``` 154 155</APIBox> 156<APIBox header="Events"> 157 158Defines event names that the module can send to JavaScript. 159 160<CodeBlocksTable> 161 162```swift 163Events("onCameraReady", "onPictureSaved", "onBarCodeScanned") 164``` 165 166```kotlin 167Events("onCameraReady", "onPictureSaved", "onBarCodeScanned") 168``` 169 170</CodeBlocksTable> 171 172See [Sending events](#sending-events) to learn how to send events from the native code to JavaScript/TypeScript. 173 174</APIBox> 175<APIBox header="ViewManager"> 176 177> **warning** **Deprecated**: To better integrate with [React Native's new architecture (Fabric)](https://reactnative.dev/architecture/fabric-renderer) and its recycling mechanism, as of SDK 47 the `ViewManager` component is deprecated in favor of [`View`](#view) with a view class passed as the first argument. This component will be removed in SDK 48. 178 179Enables the module to be used as a view manager. The view manager definition is built from the definition components used in the closure passed to `ViewManager`. Definition components that are accepted as part of the view manager definition: [`View`](#view), [`Prop`](#prop). 180 181<CodeBlocksTable> 182 183```swift 184ViewManager { 185 View { 186 MyNativeView() 187 } 188 189 Prop("isHidden") { (view: UIView, hidden: Bool) in 190 view.isHidden = hidden 191 } 192} 193``` 194 195```kotlin 196ViewManager { 197 View { context -> 198 MyNativeView(context) 199 } 200 201 Prop("isHidden") { view: View, hidden: Bool -> 202 view.isVisible = !hidden 203 } 204} 205``` 206 207</CodeBlocksTable> 208</APIBox> 209<APIBox header="View"> 210 211Enables the module to be used as a native view. Definition components that are accepted as part of the view definition: [`Prop`](#prop), [`Events`](#events). 212 213#### Arguments 214 215- **viewType** — The class of the native view that will be rendered. 216- **definition**: `() -> ViewDefinition` — A builder of the view definition. 217 218<CodeBlocksTable> 219 220```swift 221View(UITextView.self) { 222 Prop("text") { ... } 223} 224``` 225 226```kotlin 227View(TextView::class) { 228 Prop("text") { ... } 229} 230``` 231 232</CodeBlocksTable> 233 234> **info** 235> Support for rendering SwiftUI views is planned. For now, you can use [`UIHostingController`](https://developer.apple.com/documentation/swiftui/uihostingcontroller) and add its content view to your UIKit view. 236 237</APIBox> 238<APIBox header="Prop"> 239 240Defines a setter for the view prop of given name. 241 242#### Arguments 243 244- **name**: `String` — Name of view prop that you want to define a setter. 245- **setter**: `(view: ViewType, value: ValueType) -> ()` — Closure that is invoked when the view rerenders. 246 247This property can only be used within a [`ViewManager`](#viewmanager) closure. 248 249<CodeBlocksTable> 250 251```swift 252Prop("background") { (view: UIView, color: UIColor) in 253 view.backgroundColor = color 254} 255``` 256 257```kotlin 258Prop("background") { view: View, @ColorInt color: Int -> 259 view.setBackgroundColor(color) 260} 261``` 262 263</CodeBlocksTable> 264 265> **Note** Props of function type (callbacks) are not supported yet. 266 267</APIBox> 268<APIBox header="OnCreate"> 269 270Defines module's lifecycle listener that is called right after module initialization. If you need to set up something when the module gets initialized, use this instead of module's class initializer. 271 272</APIBox> 273<APIBox header="OnDestroy"> 274 275Defines module's lifecycle listener that is called when the module is about to be deallocated. Use it instead of module's class destructor. 276 277</APIBox> 278<APIBox header="OnStartObserving"> 279 280Defines the function that is invoked when the first event listener is added. 281 282</APIBox> 283<APIBox header="OnStopObserving"> 284 285Defines the function that is invoked when all event listeners are removed. 286 287</APIBox> 288<APIBox header="OnAppContextDestroys"> 289 290Defines module's lifecycle listener that is called when the app context owning the module is about to be deallocated. 291 292</APIBox> 293<APIBox header="OnAppEntersForeground" platforms={["ios"]}> 294 295Defines the listener that is called when the app is about to enter the foreground mode. 296 297> **Note** This function is not available on Android — you may want to use [`OnActivityEntersForeground`](#onactivityentersforeground) instead. 298 299</APIBox> 300<APIBox header="OnAppEntersBackground" platforms={["ios"]}> 301 302Defines the listener that is called when the app enters the background mode. 303 304> **Note** This function is not available on Android — you may want to use [`OnActivityEntersBackground`](#onactivityentersbackground) instead. 305 306</APIBox> 307<APIBox header="OnAppBecomesActive" platforms={["ios"]}> 308 309Defines the listener that is called when the app becomes active again (after `OnAppEntersForeground`). 310 311> **Note** This function is not available on Android — you may want to use [`OnActivityEntersForeground`](#onactivityentersforeground) instead. 312 313</APIBox> 314<APIBox header="OnActivityEntersForeground" platforms={["android"]}> 315 316Defines the activity lifecycle listener that is called right after the activity is resumed. 317 318> **Note** This function is not available on iOS — you may want to use [`OnAppEntersForeground`](#onappentersforeground) instead. 319 320</APIBox> 321<APIBox header="OnActivityEntersBackground" platforms={["android"]}> 322 323Defines the activity lifecycle listener that is called right after the activity is paused. 324 325> **Note** This function is not available on iOS — you may want to use [`OnAppEntersBackground`](#onappentersbackground) instead. 326 327</APIBox> 328<APIBox header="OnActivityDestroys" platforms={["android"]}> 329 330Defines the activity lifecycle listener that is called when the activity owning the JavaScript context is about to be destroyed. 331 332> **Note** This function is not available on iOS — you may want to use [`OnAppEntersBackground`](#onappentersbackground) instead. 333 334</APIBox> 335 336## Argument Types 337 338Fundamentally, only primitive and serializable data can be passed back and forth between the runtimes. However, usually native modules need to receive custom data structures — more sophisticated than just the dictionary/map where the values are of unknown (`Any`) type and so each value has to be validated and casted on its own. The Expo Modules API provides protocols to make it more convenient to work with data objects, to provide automatic validation, and finally, to ensure native type-safety on each object member. 339 340<APIBox header="Primitives"> 341 342All functions and view prop setters accept all common primitive types in Swift and Kotlin as the arguments. This includes arrays, dictionaries/maps and optionals of these primitive types. 343 344| Language | Supported primitive types | 345| -------- | ------------------------------------------------------------------------------------------------------------------------------ | 346| Swift | `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64`, `Float32`, `Double`, `String` | 347| Kotlin | `Boolean`, `Int`, `UInt`, `Float`, `Double`, `String`, `Pair` | 348 349</APIBox> 350<APIBox header="Convertibles"> 351 352_Convertibles_ are native types that can be initialized from certain specific kinds of data received from JavaScript. Such types are allowed to be used as an argument type in `Function`'s body. For example, when the `CGPoint` type is used as a function argument type, its instance can be created from an array of two numbers `(x, y)` or a JavaScript object with numeric `x` and `y` properties. 353 354Some common iOS types from `CoreGraphics` and `UIKit` system frameworks are already made convertible. 355 356| Native iOS Type | TypeScript | 357| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 358| `URL` | `string` with a URL. When scheme is not provided, it's assumed to be a file URL. | 359| `CGFloat` | `number` | 360| `CGPoint` | `{ x: number, y: number }` or `number[]` with _x_ and _y_ coords | 361| `CGSize` | `{ width: number, height: number }` or `number[]` with _width_ and _height_ | 362| `CGVector` | `{ dx: number, dy: number }` or `number[]` with _dx_ and _dy_ vector differentials | 363| `CGRect` | `{ x: number, y: number, width: number, height: number }` or `number[]` with _x_, _y_, _width_ and _height_ values | 364| `CGColor`<br/>`UIColor` | Color hex strings (`#RRGGBB`, `#RRGGBBAA`, `#RGB`, `#RGBA`), named colors following the [CSS3/SVG specification](https://www.w3.org/TR/css-color-3/#svg-color) or `"transparent"` | 365 366Similarly, some common Android types from packages like `java.io`, `java.net`, or `android.graphics` are also made convertible. 367 368| Native Android Type | TypeScript | 369| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 370| `java.net.URL` | `string` with a URL. Note that the scheme has to be provided | 371| `android.net.Uri`<br/>`java.net.URI` | `string` with a URI. Note that the scheme has to be provided | 372| `java.io.File`<br/>`java.nio.file.Path` | `string` with a path to the file | 373| `android.graphics.Color` | Color hex strings (`#RRGGBB`, `#RRGGBBAA`, `#RGB`, `#RGBA`), named colors following the [CSS3/SVG specification](https://www.w3.org/TR/css-color-3/#svg-color) or `"transparent"` | 374| `kotlin.Pair<A, B>` | Array with two values, where the first one is of type _A_ and the second is of type _B_ | 375 376</APIBox> 377<APIBox header="Records"> 378 379_Record_ is a convertible type and an equivalent of the dictionary (Swift) or map (Kotlin), but represented as a struct where each field can have its own type and provide a default value. 380It is a better way to represent a JavaScript object with the native type-safety. 381 382<CodeBlocksTable> 383 384```swift 385struct FileReadOptions: Record { 386 @Field 387 var encoding: String = "utf8" 388 389 @Field 390 var position: Int = 0 391 392 @Field 393 var length: Int? 394} 395 396// Now this record can be used as an argument of the functions or the view prop setters. 397Function("readFile") { (path: String, options: FileReadOptions) -> String in 398 // Read the file using given `options` 399} 400``` 401 402```kotlin 403class FileReadOptions : Record { 404 @Field 405 val encoding: String = "utf8" 406 407 @Field 408 val position: Int = 0 409 410 @Field 411 val length: Int? 412} 413 414// Now this record can be used as an argument of the functions or the view prop setters. 415Function("readFile") { path: String, options: FileReadOptions -> 416 // Read the file using given `options` 417} 418``` 419 420</CodeBlocksTable> 421</APIBox> 422<APIBox header="Enums"> 423 424With enums we can go even further with the above example (with `FileReadOptions` record) and limit supported encodings to `"utf8"` and `"base64"`. To use an enum as an argument or record field, it must represent a primitive value (e.g. `String`, `Int`) and conform to `EnumArgument`. 425 426<CodeBlocksTable> 427 428```swift 429enum FileEncoding: String, EnumArgument { 430 case utf8 431 case base64 432} 433 434struct FileReadOptions: Record { 435 @Field 436 var encoding: FileEncoding = .utf8 437 // ... 438} 439``` 440 441```kotlin 442// Note: the constructor must have an argument called value. 443enum class FileEncoding(val value: String) : Enumerable { 444 utf8("utf8"), 445 base64("base64") 446} 447 448class FileReadOptions : Record { 449 @Field 450 val encoding: FileEncoding = FileEncoding.utf8 451 // ... 452} 453``` 454 455</CodeBlocksTable> 456</APIBox> 457<APIBox header="Eithers"> 458 459There are some use cases where you want to pass various types for a single function argument. This is where Either types might come in handy. 460They act as a container for a value of one of a couple of types. 461 462<CodeBlocksTable> 463 464```swift 465Function("foo") { (bar: Either<String, Int>) in 466 if let bar: String = bar.get() { 467 // `bar` is a String 468 } 469 if let bar: Int = bar.get() { 470 // `bar` is an Int 471 } 472} 473``` 474 475```kotlin 476Function("foo") { bar: Either<String, Int> -> 477 bar.get(String::class).let { 478 // `it` is a String 479 } 480 bar.get(Int::class).let { 481 // `it` is an Int 482 } 483} 484``` 485 486</CodeBlocksTable> 487 488The implementation for three Either types is currently provided out of the box, allowing you to use up to four different subtypes. 489 490- `Either<FirstType, SecondType>` — A container for one of two types. 491- `EitherOfThree<FirstType, SecondType, ThirdType>` — A container for one of three types. 492- `EitherOfFour<FirstType, SecondType, ThirdType, FourthType>` — A container for one of four types. 493 494> **info** 495> Either types are available as of SDK 47. 496 497</APIBox> 498 499## Guides 500 501<APIBox header="Sending events"> 502 503While JavaScript/TypeScript to Native communication is mostly covered by native functions, you might also want to let the JavaScript/TypeScript code know about certain system events, for example, when the clipboard content changes. 504 505To do this, in the module definition, you need to provide the event names that the module can send using the [Events](#events) definition component. After that, you can use the `sendEvent(eventName, payload)` function on the module instance to send the actual event with some payload. For example, a minimal clipboard implementation that sends native events may look like this: 506 507<CodeBlocksTable> 508 509```swift 510let CLIPBOARD_CHANGED_EVENT_NAME = "onClipboardChanged" 511 512public class ClipboardModule: Module { 513 public func definition() -> ModuleDefinition { 514 Events(CLIPBOARD_CHANGED_EVENT_NAME) 515 516 OnStartObserving { 517 NotificationCenter.default.addObserver( 518 self, 519 selector: #selector(self.clipboardChangedListener), 520 name: UIPasteboard.changedNotification, 521 object: nil 522 ) 523 } 524 525 OnStopObserving { 526 NotificationCenter.default.removeObserver( 527 self, 528 name: UIPasteboard.changedNotification, 529 object: nil 530 ) 531 } 532 } 533 534 @objc 535 private func clipboardChangedListener() { 536 sendEvent(CLIPBOARD_CHANGED_EVENT_NAME, [ 537 "contentTypes": availableContentTypes() 538 ]) 539 } 540} 541``` 542 543```kotlin 544const val CLIPBOARD_CHANGED_EVENT_NAME = "onClipboardChanged" 545 546class ClipboardModule : Module() { 547 override fun definition() = ModuleDefinition { 548 Events(CLIPBOARD_CHANGED_EVENT_NAME) 549 550 OnStartObserving { 551 clipboardManager?.addPrimaryClipChangedListener(listener) 552 } 553 554 OnStopObserving { 555 clipboardManager?.removePrimaryClipChangedListener(listener) 556 } 557 } 558 559 private val clipboardManager: ClipboardManager? 560 get() = appContext.reactContext?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager 561 562 private val listener = ClipboardManager.OnPrimaryClipChangedListener { 563 clipboardManager?.primaryClipDescription?.let { clip -> 564 [email protected]( 565 CLIPBOARD_CHANGED_EVENT_NAME, 566 bundleOf( 567 "contentTypes" to availableContentTypes(clip) 568 ) 569 ) 570 } 571 } 572} 573``` 574 575</CodeBlocksTable> 576 577To subscribe to these events in JavaScript/TypeScript, you need to wrap the native module with `EventEmitter` class as shown: 578 579```ts TypeScript 580import { requireNativeModule, EventEmitter, Subscription } from 'expo-modules-core'; 581 582const ClipboardModule = requireNativeModule('Clipboard'); 583const emitter = new EventEmitter(ClipboardModule); 584 585export function addClipboardListener(listener: (event) => void): Subscription { 586 return emitter.addListener('onClipboardChanged', listener); 587} 588``` 589 590</APIBox> 591 592## Examples 593 594<CodeBlocksTable> 595 596```swift 597public class MyModule: Module { 598 public func definition() -> ModuleDefinition { 599 Name("MyFirstExpoModule") 600 601 Function("hello") { (name: String) in 602 return "Hello \(name)!" 603 } 604 } 605} 606``` 607 608```kotlin 609class MyModule : Module() { 610 override fun definition() = ModuleDefinition { 611 Name("MyFirstExpoModule") 612 613 Function("hello") { name: String -> 614 return "Hello $name!" 615 } 616 } 617} 618``` 619 620</CodeBlocksTable> 621 622For more examples from real modules, you can refer to Expo modules that already use this API on GitHub: 623 624- `expo-battery` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-battery/ios)) 625- `expo-cellular` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-cellular/ios), [Kotlin](https://github.com/expo/expo/tree/main/packages/expo-cellular/android/src/main/java/expo/modules/cellular)) 626- `expo-clipboard` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-clipboard/ios), [Kotlin](https://github.com/expo/expo/tree/main/packages/expo-clipboard/android/src/main/java/expo/modules/clipboard)) 627- `expo-crypto` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-crypto/ios), [Kotlin](https://github.com/expo/expo/tree/main/packages/expo-crypto/android/src/main/java/expo/modules/crypto)) 628- `expo-haptics` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-haptics/ios)) 629- `expo-image-manipulator` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-image-manipulator/ios)) 630- `expo-image-picker` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-image-picker/ios), [Kotlin](https://github.com/expo/expo/tree/main/packages/expo-image-picker/android/src/main/java/expo/modules/imagepicker)) 631- `expo-linear-gradient` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-linear-gradient/ios), [Kotlin](https://github.com/expo/expo/tree/main/packages/expo-linear-gradient/android/src/main/java/expo/modules/lineargradient)) 632- `expo-localization` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-localization/ios), [Kotlin](https://github.com/expo/expo/tree/main/packages/expo-localization/android/src/main/java/expo/modules/localization)) 633- `expo-store-review` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-store-review/ios)) 634- `expo-system-ui` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-system-ui/ios/ExpoSystemUI)) 635- `expo-web-browser` ([Swift](https://github.com/expo/expo/blob/main/packages/expo-web-browser/ios), [Kotlin](https://github.com/expo/expo/blob/main/packages/expo-web-browser/android/src/main/java/expo/modules/webbrowser)) 636