xref: /expo/docs/pages/modules/module-api.mdx (revision 7c8fe8d3)
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