xref: /expo/docs/pages/modules/module-api.mdx (revision 97e1898c)
1---
2title: Native Modules
3---
4
5import { CodeBlocksTable } from '~/components/plugins/CodeBlocksTable';
6import { APIBox } from '~/components/plugins/APIBox';
7import { PlatformTags } from '~/ui/components/Tag';
8import { APIMethod } from '~/components/plugins/api/APISectionMethods';
9
10> **warning** Expo Modules APIs are in beta and subject to breaking changes.
11
12The 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.
13
14## Definition Components
15
16As you might have noticed in the snippets on the [Get Started](./get-started.mdx) page, each module class must implement the `definition` function.
17The module definition consists of the DSL components that describe the module's functionality and behavior.
18
19<APIBox header="Name">
20
21Sets 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.
22
23```swift Swift / Kotlin
24Name("MyModuleName")
25```
26
27</APIBox>
28<APIBox header="Constants">
29
30Sets constant properties on the module. Can take a dictionary or a closure that returns a dictionary.
31
32<CodeBlocksTable>
33
34```swift
35// Created from the dictionary
36Constants([
37  "PI": Double.pi
38])
39
40// or returned by the closure
41Constants {
42  return [
43    "PI": Double.pi
44  ]
45}
46```
47
48```kotlin
49// Passed as arguments
50Constants(
51  "PI" to kotlin.math.PI
52)
53
54// or returned by the closure
55Constants {
56  return@Constants mapOf(
57    "PI" to kotlin.math.PI
58  )
59}
60```
61
62</CodeBlocksTable>
63</APIBox>
64<APIBox header="Function">
65
66Defines 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.
67
68#### Arguments
69
70- **name**: `String` — Name of the function that you'll call from JavaScript.
71- **body**: `(args...) -> ReturnType` — The closure to run when the function is called.
72
73The 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.
74
75See the [Argument Types](#argument-types) section for more details on what types can be used in the function body.
76
77<CodeBlocksTable>
78
79```swift
80Function("syncFunction") { (message: String) in
81  return message
82}
83```
84
85```kotlin
86Function("syncFunction") { message: String ->
87  return@Function message
88}
89```
90
91</CodeBlocksTable>
92
93```js JavaScript
94import { requireNativeModule } from 'expo-modules-core';
95
96// Assume that we have named the module "MyModule"
97const MyModule = requireNativeModule('MyModule');
98
99function getMessage() {
100  return MyModule.syncFunction('bar');
101}
102```
103
104</APIBox>
105<APIBox header="AsyncFunction">
106
107Defines 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.
108
109#### Arguments
110
111- **name**: `String` — Name of the function that you'll call from JavaScript.
112- **body**: `(args...) -> ReturnType` — The closure to run when the function is called.
113
114If 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.
115The function can receive up to 8 arguments (including the promise).
116
117See the [Argument Types](#argument-types) section for more details on what types can be used in the function body.
118
119It is recommended to use `AsyncFunction` over `Function` when it:
120
121- does I/O bound tasks such as sending network requests or interacting with the file system
122- needs to be run on different thread, e.g. the main UI thread for UI-related tasks
123- is an extensive or long-lasting operation that would block the JavaScript thread which in turn would reduce the responsiveness of the application
124
125<CodeBlocksTable>
126
127```swift
128AsyncFunction("asyncFunction") { (message: String, promise: Promise) in
129  DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
130    promise.resolve(message)
131  }
132}
133```
134
135```kotlin
136AsyncFunction("asyncFunction") { message: String, promise: Promise ->
137  launch(Dispatchers.Main) {
138    promise.resolve(message)
139  }
140}
141```
142
143</CodeBlocksTable>
144
145```js JavaScript
146import { requireNativeModule } from 'expo-modules-core';
147
148// Assume that we have named the module "MyModule"
149const MyModule = requireNativeModule('MyModule');
150
151async function getMessageAsync() {
152  return await MyModule.asyncFunction('bar');
153}
154```
155
156<hr />
157
158#### Kotlin coroutines <PlatformTags prefix="" platforms={['android']} />
159
160`AsyncFunction` can receive a suspendable body on Android. However, it has to be passed in the infix notation after the `Coroutine` block. You can read more about suspendable functions and coroutines on [coroutine overview](https://kotlinlang.org/docs/coroutines-overview.html).
161
162`AsyncFunction` with suspendable body can't receive `Promise` as an argument. It uses a suspension mechanism to execute asynchronous calls.
163The function is immediately resolved with the returned value of the provided suspendable block or rejected if it throws an exception. The function can receive up to 8 arguments.
164
165By default, suspend functions are dispatched on the module's coroutine scope. Moreover, every other suspendable function called from the body block is run within the same scope.
166This scope's lifecycle is bound to the module's lifecycle - all unfinished suspend functions will be canceled when the module is deallocated.
167
168```kotlin Kotlin
169AsyncFunction("suspendFunction") Coroutine { message: String ->
170  launch {
171    return@Coroutine message
172  }
173}
174```
175
176</APIBox>
177<APIBox header="Events">
178
179Defines event names that the module can send to JavaScript.
180
181> **Note**: This component can be used inside of the [`View`](#view) block to define callback names. See [`View callbacks`](#view-callbacks)
182
183<CodeBlocksTable>
184
185```swift
186Events("onCameraReady", "onPictureSaved", "onBarCodeScanned")
187```
188
189```kotlin
190Events("onCameraReady", "onPictureSaved", "onBarCodeScanned")
191```
192
193</CodeBlocksTable>
194
195See [Sending events](#sending-events) to learn how to send events from the native code to JavaScript/TypeScript.
196
197</APIBox>
198<APIBox header="ViewManager">
199
200> **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.
201
202Enables 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).
203
204<CodeBlocksTable>
205
206```swift
207ViewManager {
208  View {
209    MyNativeView()
210  }
211
212  Prop("isHidden") { (view: UIView, hidden: Bool) in
213    view.isHidden = hidden
214  }
215}
216```
217
218```kotlin
219ViewManager {
220  View { context ->
221    MyNativeView(context)
222  }
223
224  Prop("isHidden") { view: View, hidden: Bool ->
225    view.isVisible = !hidden
226  }
227}
228```
229
230</CodeBlocksTable>
231</APIBox>
232<APIBox header="View">
233
234Enables the module to be used as a native view. Definition components that are accepted as part of the view definition: [`Prop`](#prop), [`Events`](#events).
235
236#### Arguments
237
238- **viewType** — The class of the native view that will be rendered. Note: On Android, the provided class must inherit from the [`ExpoView`](#expoview), on iOS it's optional. See [`Extending ExpoView`](#extending--expoview).
239
240<CodeBlocksTable>
241
242```swift
243View(UITextView.self) {
244  Prop("text") { ... }
245}
246```
247
248```kotlin
249View(TextView::class) {
250  Prop("text") { ... }
251}
252```
253
254</CodeBlocksTable>
255
256> 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.
257
258</APIBox>
259<APIBox header="Prop">
260
261Defines a setter for the view prop of given name.
262
263#### Arguments
264
265- **name**: `String` — Name of view prop that you want to define a setter.
266- **setter**: `(view: ViewType, value: ValueType) -> ()` — Closure that is invoked when the view rerenders.
267
268This property can only be used within a [`ViewManager`](#viewmanager) closure.
269
270<CodeBlocksTable>
271
272```swift
273Prop("background") { (view: UIView, color: UIColor) in
274  view.backgroundColor = color
275}
276```
277
278```kotlin
279Prop("background") { view: View, @ColorInt color: Int ->
280  view.setBackgroundColor(color)
281}
282```
283
284</CodeBlocksTable>
285
286> **Note** Props of function type (callbacks) are not supported yet.
287
288</APIBox>
289<APIBox header="OnCreate">
290
291Defines 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.
292
293</APIBox>
294<APIBox header="OnDestroy">
295
296Defines module's lifecycle listener that is called when the module is about to be deallocated. Use it instead of module's class destructor.
297
298</APIBox>
299<APIBox header="OnStartObserving">
300
301Defines the function that is invoked when the first event listener is added.
302
303</APIBox>
304<APIBox header="OnStopObserving">
305
306Defines the function that is invoked when all event listeners are removed.
307
308</APIBox>
309<APIBox header="OnAppContextDestroys">
310
311Defines module's lifecycle listener that is called when the app context owning the module is about to be deallocated.
312
313</APIBox>
314<APIBox header="OnAppEntersForeground" platforms={["ios"]}>
315
316Defines the listener that is called when the app is about to enter the foreground mode.
317
318> **Note** This function is not available on Android — you may want to use [`OnActivityEntersForeground`](#onactivityentersforeground) instead.
319
320</APIBox>
321<APIBox header="OnAppEntersBackground" platforms={["ios"]}>
322
323Defines the listener that is called when the app enters the background mode.
324
325> **Note** This function is not available on Android — you may want to use [`OnActivityEntersBackground`](#onactivityentersbackground) instead.
326
327</APIBox>
328<APIBox header="OnAppBecomesActive" platforms={["ios"]}>
329
330Defines the listener that is called when the app becomes active again (after `OnAppEntersForeground`).
331
332> **Note** This function is not available on Android — you may want to use [`OnActivityEntersForeground`](#onactivityentersforeground) instead.
333
334</APIBox>
335<APIBox header="OnActivityEntersForeground" platforms={["android"]}>
336
337Defines the activity lifecycle listener that is called right after the activity is resumed.
338
339> **Note** This function is not available on iOS — you may want to use [`OnAppEntersForeground`](#onappentersforeground) instead.
340
341</APIBox>
342<APIBox header="OnActivityEntersBackground" platforms={["android"]}>
343
344Defines the activity lifecycle listener that is called right after the activity is paused.
345
346> **Note** This function is not available on iOS — you may want to use [`OnAppEntersBackground`](#onappentersbackground) instead.
347
348</APIBox>
349<APIBox header="OnActivityDestroys" platforms={["android"]}>
350
351Defines the activity lifecycle listener that is called when the activity owning the JavaScript context is about to be destroyed.
352
353> **Note** This function is not available on iOS — you may want to use [`OnAppEntersBackground`](#onappentersbackground) instead.
354
355</APIBox>
356
357## Argument Types
358
359Fundamentally, 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.
360
361<APIBox header="Primitives">
362
363All 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.
364
365| Language | Supported primitive types                                                                                                      |
366| -------- | ------------------------------------------------------------------------------------------------------------------------------ |
367| Swift    | `Bool`, `Int`, `Int8`, `Int16`, `Int32`, `Int64`, `UInt`, `UInt8`, `UInt16`, `UInt32`, `UInt64`, `Float32`, `Double`, `String` |
368| Kotlin   | `Boolean`, `Int`, `UInt`, `Float`, `Double`, `String`, `Pair`                                                                  |
369
370</APIBox>
371<APIBox header="Convertibles">
372
373_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.
374
375Some common iOS types from `CoreGraphics` and `UIKit` system frameworks are already made convertible.
376
377| Native iOS Type         | TypeScript                                                                                                                                                                        |
378| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
379| `URL`                   | `string` with a URL. When scheme is not provided, it's assumed to be a file URL.                                                                                                  |
380| `CGFloat`               | `number`                                                                                                                                                                          |
381| `CGPoint`               | `{ x: number, y: number }` or `number[]` with _x_ and _y_ coords                                                                                                                  |
382| `CGSize`                | `{ width: number, height: number }` or `number[]` with _width_ and _height_                                                                                                       |
383| `CGVector`              | `{ dx: number, dy: number }` or `number[]` with _dx_ and _dy_ vector differentials                                                                                                |
384| `CGRect`                | `{ x: number, y: number, width: number, height: number }` or `number[]` with _x_, _y_, _width_ and _height_ values                                                                |
385| `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"` |
386
387Similarly, some common Android types from packages like `java.io`, `java.net`, or `android.graphics` are also made convertible.
388
389| Native Android Type                     | TypeScript                                                                                                                                                                        |
390| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
391| `java.net.URL`                          | `string` with a URL. Note that the scheme has to be provided                                                                                                                      |
392| `android.net.Uri`<br/>`java.net.URI`    | `string` with a URI. Note that the scheme has to be provided                                                                                                                      |
393| `java.io.File`<br/>`java.nio.file.Path` | `string` with a path to the file                                                                                                                                                  |
394| `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"` |
395| `kotlin.Pair<A, B>`                     | Array with two values, where the first one is of type _A_ and the second is of type _B_                                                                                           |
396
397</APIBox>
398<APIBox header="Records">
399
400_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.
401It is a better way to represent a JavaScript object with the native type-safety.
402
403<CodeBlocksTable>
404
405```swift
406struct FileReadOptions: Record {
407  @Field
408  var encoding: String = "utf8"
409
410  @Field
411  var position: Int = 0
412
413  @Field
414  var length: Int?
415}
416
417// Now this record can be used as an argument of the functions or the view prop setters.
418Function("readFile") { (path: String, options: FileReadOptions) -> String in
419  // Read the file using given `options`
420}
421```
422
423```kotlin
424class FileReadOptions : Record {
425  @Field
426  val encoding: String = "utf8"
427
428  @Field
429  val position: Int = 0
430
431  @Field
432  val length: Int?
433}
434
435// Now this record can be used as an argument of the functions or the view prop setters.
436Function("readFile") { path: String, options: FileReadOptions ->
437  // Read the file using given `options`
438}
439```
440
441</CodeBlocksTable>
442</APIBox>
443<APIBox header="Enums">
444
445With 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 `Enumerable`.
446
447<CodeBlocksTable>
448
449```swift
450enum FileEncoding: String, Enumerable {
451  case utf8
452  case base64
453}
454
455struct FileReadOptions: Record {
456  @Field
457  var encoding: FileEncoding = .utf8
458  // ...
459}
460```
461
462```kotlin
463// Note: the constructor must have an argument called value.
464enum class FileEncoding(val value: String) : Enumerable {
465  utf8("utf8"),
466  base64("base64")
467}
468
469class FileReadOptions : Record {
470  @Field
471  val encoding: FileEncoding = FileEncoding.utf8
472  // ...
473}
474```
475
476</CodeBlocksTable>
477</APIBox>
478<APIBox header="Eithers">
479
480There 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.
481They act as a container for a value of one of a couple of types.
482
483<CodeBlocksTable>
484
485```swift
486Function("foo") { (bar: Either<String, Int>) in
487  if let bar: String = bar.get() {
488    // `bar` is a String
489  }
490  if let bar: Int = bar.get() {
491    // `bar` is an Int
492  }
493}
494```
495
496```kotlin
497Function("foo") { bar: Either<String, Int> ->
498  bar.get(String::class).let {
499    // `it` is a String
500  }
501  bar.get(Int::class).let {
502    // `it` is an Int
503  }
504}
505```
506
507</CodeBlocksTable>
508
509The implementation for three Either types is currently provided out of the box, allowing you to use up to four different subtypes.
510
511- `Either<FirstType, SecondType>` — A container for one of two types.
512- `EitherOfThree<FirstType, SecondType, ThirdType>` — A container for one of three types.
513- `EitherOfFour<FirstType, SecondType, ThirdType, FourthType>` — A container for one of four types.
514
515> Either types are available as of SDK 47.
516
517</APIBox>
518
519## Native Classes
520
521<APIBox header="Module">
522
523A base class for a native module.
524
525#### Properties
526
527<APIMethod
528  name="appContext"
529  comment="Provides access to the [`AppContext`](#appcontext)."
530  returnTypeName="AppContext"
531  isProperty={true}
532  isReturnTypeReference={true}
533/>
534
535#### Methods
536
537<APIMethod
538  name="sendEvent"
539  comment="Sends an event with a given name and a payload to JavaScript. See [`Sending events`](#sending-events)"
540  returnTypeName="void"
541  parameters={[
542    {
543      name: 'eventName',
544      comment: 'The name of the JavaScript event',
545      typeName: 'string',
546    },
547    {
548      name: 'payload',
549      comment: 'The event payload',
550      typeName: 'Android: Map<String, Any?> | Bundle\niOS: [String: Any?]',
551    },
552  ]}
553/>
554
555</APIBox>
556
557<APIBox header="AppContext">
558
559The app context is an interface to a single Expo app.
560
561</APIBox>
562
563<APIBox header="ExpoView">
564
565A base class that should be used by all exported views.
566
567On iOS, `ExpoView` extends the `RCTView` which handles some styles (e.g. borders) and accessibility.
568
569#### Properties
570
571<APIMethod
572  name="appContext"
573  comment="Provides access to the [`AppContext`](#appcontext)."
574  returnTypeName="AppContext"
575  isProperty={true}
576  isReturnTypeReference={true}
577/>
578
579<hr />
580
581#### Extending `ExpoView`
582
583To export your view using the [`View`](#view) component, your custom class must inherit from the `ExpoView`. By doing that you will get access to the [`AppContext`](#appcontext) object. It's the only way of communicating with other modules and the JavaScript runtime. Also, you can't change constructor parameters, becuase provided view will be initialized by `expo-modules-core`.
584
585<CodeBlocksTable>
586
587```swift
588class LinearGradientView: ExpoView {}
589
590public class LinearGradientModule: Module {
591  public func definition() -> ModuleDefinition {
592    View(LinearGradientView.self) {
593      // ...
594    }
595  }
596}
597```
598
599```kotlin
600class LinearGradientView(
601  context: Context,
602  appContext: AppContext,
603) : ExpoView(context, appContext)
604
605class LinearGradientModule : Module() {
606  override fun definition() = ModuleDefinition {
607    View(LinearGradientView::class) {
608      // ...
609    }
610  }
611}
612```
613
614</CodeBlocksTable>
615
616</APIBox>
617
618## Guides
619
620<APIBox header="Sending events">
621
622While 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.
623
624To 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:
625
626<CodeBlocksTable>
627
628```swift
629let CLIPBOARD_CHANGED_EVENT_NAME = "onClipboardChanged"
630
631public class ClipboardModule: Module {
632  public func definition() -> ModuleDefinition {
633    Events(CLIPBOARD_CHANGED_EVENT_NAME)
634
635    OnStartObserving {
636      NotificationCenter.default.addObserver(
637        self,
638        selector: #selector(self.clipboardChangedListener),
639        name: UIPasteboard.changedNotification,
640        object: nil
641      )
642    }
643
644    OnStopObserving {
645      NotificationCenter.default.removeObserver(
646        self,
647        name: UIPasteboard.changedNotification,
648        object: nil
649      )
650    }
651  }
652
653  @objc
654  private func clipboardChangedListener() {
655    sendEvent(CLIPBOARD_CHANGED_EVENT_NAME, [
656      "contentTypes": availableContentTypes()
657    ])
658  }
659}
660```
661
662```kotlin
663const val CLIPBOARD_CHANGED_EVENT_NAME = "onClipboardChanged"
664
665class ClipboardModule : Module() {
666  override fun definition() = ModuleDefinition {
667    Events(CLIPBOARD_CHANGED_EVENT_NAME)
668
669    OnStartObserving {
670      clipboardManager?.addPrimaryClipChangedListener(listener)
671    }
672
673    OnStopObserving {
674      clipboardManager?.removePrimaryClipChangedListener(listener)
675    }
676  }
677
678  private val clipboardManager: ClipboardManager?
679    get() = appContext.reactContext?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
680
681  private val listener = ClipboardManager.OnPrimaryClipChangedListener {
682    clipboardManager?.primaryClipDescription?.let { clip ->
683      [email protected](
684        CLIPBOARD_CHANGED_EVENT_NAME,
685        bundleOf(
686          "contentTypes" to availableContentTypes(clip)
687        )
688      )
689    }
690  }
691}
692```
693
694</CodeBlocksTable>
695
696To subscribe to these events in JavaScript/TypeScript, you need to wrap the native module with `EventEmitter` class as shown:
697
698```ts TypeScript
699import { requireNativeModule, EventEmitter, Subscription } from 'expo-modules-core';
700
701const ClipboardModule = requireNativeModule('Clipboard');
702const emitter = new EventEmitter(ClipboardModule);
703
704export function addClipboardListener(listener: (event) => void): Subscription {
705  return emitter.addListener('onClipboardChanged', listener);
706}
707```
708
709</APIBox>
710
711<APIBox header="View callbacks">
712
713Some events are connected to a certain view. For example, the touch event should be sent only to the underlying JavaScript view which was pressed. In that case, you can't use `sendEvent` described in [`Sending events`](#sending-events). The `expo-modules-core` introduces a view callbacks mechanism to handle view-bound events.
714
715To use it, in the view definition, you need to provide the event names that the view can send using the [Events](#events) definition component. After that, you need to declare a property of type `EventDispatcher` in your view class. The name of the declared property has to be the same as the name exported in the `Events` component. Later, you can call it as a function and pass a payload of type `[String: Any?]` on iOS and `Map<String, Any?>` on Android.
716
717> **Note**: On Android, it's possible to specify the payload type. In case of types that don't convert into objects, the payload will be encapsulated and stored under the `payload` key: `{payload: <provided value>}`.
718
719<CodeBlocksTable>
720
721```swift
722class CameraViewModule: Module {
723  public func definition() -> ModuleDefinition {
724    View(CamerView.self) {
725      Events(
726        "onCameraReady"
727      )
728
729      // ...
730    }
731  }
732}
733
734class CameraView: ExpoView {
735  let onCameraReady = EventDispatcher()
736
737  func callOnCameraReady() {
738    onCameraReady([
739      "message": "Camera was mounted"
740    ]);
741  }
742}
743```
744
745```kotlin
746class CameraViewModule : Module() {
747  override fun definition() = ModuleDefinition {
748    View(ExpoCameraView::class) {
749      Events(
750        "onCameraReady"
751      )
752
753      // ...
754    }
755  }
756}
757
758class CameraView(
759  context: Context,
760  appContext: AppContext
761) : ExpoView(context, appContext) {
762  val onCameraReady by EventDispatcher()
763
764  fun callOnCameraReady() {
765    onCameraReady(mapOf(
766      "message" to "Camera was mounted"
767    ));
768  }
769}
770```
771
772</CodeBlocksTable>
773
774To subscribe to these events in JavaScript/TypeScript, you need to pass a function to the native view as shown:
775
776```ts TypeScript
777import { requireNativeViewManager } from 'expo-modules-core';
778
779const CameraView = requireNativeViewManager('CameraView');
780
781export default function MainView() {
782  const onCameraReady = event => {
783    console.log(event.nativeEvent);
784  };
785
786  return <CameraView onCameraReady={onCameraReady} />;
787}
788```
789
790Provided payload is available under the `nativeEvent` key.
791
792</APIBox>
793
794## Examples
795
796<CodeBlocksTable>
797
798```swift
799public class MyModule: Module {
800  public func definition() -> ModuleDefinition {
801    Name("MyFirstExpoModule")
802
803    Function("hello") { (name: String) in
804      return "Hello \(name)!"
805    }
806  }
807}
808```
809
810```kotlin
811class MyModule : Module() {
812  override fun definition() = ModuleDefinition {
813    Name("MyFirstExpoModule")
814
815    Function("hello") { name: String ->
816      return "Hello $name!"
817    }
818  }
819}
820```
821
822</CodeBlocksTable>
823
824For more examples from real modules, you can refer to Expo modules that already use this API on GitHub:
825
826- `expo-battery` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-battery/ios))
827- `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))
828- `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))
829- `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))
830- `expo-haptics` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-haptics/ios))
831- `expo-image-manipulator` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-image-manipulator/ios))
832- `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))
833- `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))
834- `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))
835- `expo-store-review` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-store-review/ios))
836- `expo-system-ui` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-system-ui/ios/ExpoSystemUI))
837- `expo-video-thumbnails` ([Swift](https://github.com/expo/expo/tree/main/packages/expo-video-thumbnails/ios))
838- `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))
839