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