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</APIBox> 172<APIBox header="ViewManager"> 173 174> **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. 175 176Enables 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). 177 178<CodeBlocksTable> 179 180```swift 181ViewManager { 182 View { 183 MyNativeView() 184 } 185 186 Prop("isHidden") { (view: UIView, hidden: Bool) in 187 view.isHidden = hidden 188 } 189} 190``` 191 192```kotlin 193ViewManager { 194 View { context -> 195 MyNativeView(context) 196 } 197 198 Prop("isHidden") { view: View, hidden: Bool -> 199 view.isVisible = !hidden 200 } 201} 202``` 203 204</CodeBlocksTable> 205</APIBox> 206<APIBox header="View"> 207 208Enables the module to be used as a native view. Definition components that are accepted as part of the view definition: [`Prop`](#prop), [`Events`](#events). 209 210#### Arguments 211 212- **viewType** — The class of the native view that will be rendered. 213- **definition**: `() -> ViewDefinition` — A builder of the view definition. 214 215<CodeBlocksTable> 216 217```swift 218View(UITextView.self) { 219 Prop("text") { ... } 220} 221``` 222 223```kotlin 224View(TextView::class) { 225 Prop("text") { ... } 226} 227``` 228 229</CodeBlocksTable> 230 231> **info** 232> 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. 233 234</APIBox> 235<APIBox header="Prop"> 236 237Defines a setter for the view prop of given name. 238 239#### Arguments 240 241- **name**: `String` — Name of view prop that you want to define a setter. 242- **setter**: `(view: ViewType, value: ValueType) -> ()` — Closure that is invoked when the view rerenders. 243 244This property can only be used within a [`ViewManager`](#viewmanager) closure. 245 246<CodeBlocksTable> 247 248```swift 249Prop("background") { (view: UIView, color: UIColor) in 250 view.backgroundColor = color 251} 252``` 253 254```kotlin 255Prop("background") { view: View, @ColorInt color: Int -> 256 view.setBackgroundColor(color) 257} 258``` 259 260</CodeBlocksTable> 261 262> **Note** Props of function type (callbacks) are not supported yet. 263 264</APIBox> 265<APIBox header="OnCreate"> 266 267Defines 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. 268 269</APIBox> 270<APIBox header="OnDestroy"> 271 272Defines module's lifecycle listener that is called when the module is about to be deallocated. Use it instead of module's class destructor. 273 274</APIBox> 275<APIBox header="OnStartObserving"> 276 277Defines the function that is invoked when the first event listener is added. 278 279</APIBox> 280<APIBox header="OnStopObserving"> 281 282Defines the function that is invoked when all event listeners are removed. 283 284</APIBox> 285<APIBox header="OnAppContextDestroys"> 286 287Defines module's lifecycle listener that is called when the app context owning the module is about to be deallocated. 288 289</APIBox> 290<APIBox header="OnAppEntersForeground" platforms={["ios"]}> 291 292Defines the listener that is called when the app is about to enter the foreground mode. 293 294> **Note** This function is not available on Android — you may want to use [`OnActivityEntersForeground`](#onactivityentersforeground) instead. 295 296</APIBox> 297<APIBox header="OnAppEntersBackground" platforms={["ios"]}> 298 299Defines the listener that is called when the app enters the background mode. 300 301> **Note** This function is not available on Android — you may want to use [`OnActivityEntersBackground`](#onactivityentersbackground) instead. 302 303</APIBox> 304<APIBox header="OnAppBecomesActive" platforms={["ios"]}> 305 306Defines the listener that is called when the app becomes active again (after `OnAppEntersForeground`). 307 308> **Note** This function is not available on Android — you may want to use [`OnActivityEntersForeground`](#onactivityentersforeground) instead. 309 310</APIBox> 311<APIBox header="OnActivityEntersForeground" platforms={["android"]}> 312 313Defines the activity lifecycle listener that is called right after the activity is resumed. 314 315> **Note** This function is not available on iOS — you may want to use [`OnAppEntersForeground`](#onappentersforeground) instead. 316 317</APIBox> 318<APIBox header="OnActivityEntersBackground" platforms={["android"]}> 319 320Defines the activity lifecycle listener that is called right after the activity is paused. 321 322> **Note** This function is not available on iOS — you may want to use [`OnAppEntersBackground`](#onappentersbackground) instead. 323 324</APIBox> 325<APIBox header="OnActivityDestroys" platforms={["android"]}> 326 327Defines the activity lifecycle listener that is called when the activity owning the JavaScript context is about to be destroyed. 328 329> **Note** This function is not available on iOS — you may want to use [`OnAppEntersBackground`](#onappentersbackground) instead. 330 331</APIBox> 332 333## Argument Types 334 335Fundamentally, 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. 336 337<APIBox header="Primitives"> 338 339All 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. 340 341| Language | Supported primitive types | 342| -------- | ------------------------------------------------------------------------------------------------------------------------------ | 343| Swift | `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64`, `Float32`, `Double`, `String` | 344| Kotlin | `Boolean`, `Int`, `UInt`, `Float`, `Double`, `String`, `Pair` | 345 346</APIBox> 347<APIBox header="Convertibles"> 348 349_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. 350 351Some common iOS types from `CoreGraphics` and `UIKit` system frameworks are already made convertible. 352 353| Native Type | TypeScript | 354| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 355| `URL` | `string` with a URL. When scheme is not provided, it's assumed to be a file URL. | 356| `CGFloat` | `number` | 357| `CGPoint` | `{ x: number, y: number }` or `number[]` with _x_ and _y_ coords | 358| `CGSize` | `{ width: number, height: number }` or `number[]` with _width_ and _height_ | 359| `CGVector` | `{ dx: number, dy: number }` or `number[]` with _dx_ and _dy_ vector differentials | 360| `CGRect` | `{ x: number, y: number, width: number, height: number }` or `number[]` with _x_, _y_, _width_ and _height_ values | 361| `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"` | 362 363</APIBox> 364<APIBox header="Records"> 365 366_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. 367It is a better way to represent a JavaScript object with the native type-safety. 368 369<CodeBlocksTable> 370 371```swift 372struct FileReadOptions: Record { 373 @Field 374 var encoding: String = "utf8" 375 376 @Field 377 var position: Int = 0 378 379 @Field 380 var length: Int? 381} 382 383// Now this record can be used as an argument of the functions or the view prop setters. 384Function("readFile") { (path: String, options: FileReadOptions) -> String in 385 // Read the file using given `options` 386} 387``` 388 389```kotlin 390class FileReadOptions : Record { 391 @Field 392 val encoding: String = "utf8" 393 394 @Field 395 val position: Int = 0 396 397 @Field 398 val length: Int? 399} 400 401// Now this record can be used as an argument of the functions or the view prop setters. 402Function("readFile") { path: String, options: FileReadOptions -> 403 // Read the file using given `options` 404} 405``` 406 407</CodeBlocksTable> 408</APIBox> 409<APIBox header="Enums"> 410 411With 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`. 412 413<CodeBlocksTable> 414 415```swift 416enum FileEncoding: String, EnumArgument { 417 case utf8 418 case base64 419} 420 421struct FileReadOptions: Record { 422 @Field 423 var encoding: FileEncoding = .utf8 424 // ... 425} 426``` 427 428```kotlin 429// Note: the constructor must have an argument called value. 430enum class FileEncoding(val value: String) { 431 utf8("utf8"), 432 base64("base64") 433} 434 435class FileReadOptions : Record { 436 @Field 437 val encoding: FileEncoding = FileEncoding.utf8 438 // ... 439} 440``` 441 442</CodeBlocksTable> 443</APIBox> 444<APIBox header="Eithers"> 445 446There 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. 447They act as a container for a value of one of a couple of types. 448 449<CodeBlocksTable> 450 451```swift 452Function("foo") { (bar: Either<String, Int>) in 453 if let bar: String = bar.get() { 454 // `bar` is a String 455 } 456 if let bar: Int = bar.get() { 457 // `bar` is an Int 458 } 459} 460``` 461 462```kotlin 463Function("foo") { bar: Either<String, Int> -> 464 bar.get(String::class).let { 465 // `it` is a String 466 } 467 bar.get(Int::class).let { 468 // `it` is an Int 469 } 470} 471``` 472 473</CodeBlocksTable> 474 475The implementation for three Either types is currently provided out of the box, allowing you to use up to four different subtypes. 476 477- `Either<FirstType, SecondType>` — A container for one of two types. 478- `EitherOfThree<FirstType, SecondType, ThirdType>` — A container for one of three types. 479- `EitherOfFour<FirstType, SecondType, ThirdType, FourthType>` — A container for one of four types. 480 481> **info** 482> Either types are available as of SDK 47. 483 484</APIBox> 485 486## Examples 487 488<CodeBlocksTable> 489 490```swift 491public class MyModule: Module { 492 public func definition() -> ModuleDefinition { 493 Name("MyFirstExpoModule") 494 495 Function("hello") { (name: String) in 496 return "Hello \(name)!" 497 } 498 } 499} 500``` 501 502```kotlin 503class MyModule : Module() { 504 override fun definition() = ModuleDefinition { 505 Name("MyFirstExpoModule") 506 507 Function("hello") { name: String -> 508 return "Hello $name!" 509 } 510 } 511} 512``` 513 514</CodeBlocksTable> 515 516For more examples from real modules, you can refer to Expo modules that already use this API on GitHub: 517 518- `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)) 519- `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)) 520- `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)) 521- `expo-haptics` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-haptics/ios)) 522- `expo-image-manipulator` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-image-manipulator/ios)) 523- `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)) 524- `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)) 525- `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)) 526- `expo-store-review` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-store-review/ios)) 527- `expo-system-ui` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-system-ui/ios/ExpoSystemUI)) 528- `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)) 529