📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot]
2026-07-01 16:02:41 +00:00
parent 8301f01888
commit c824ba9d7b
2449 changed files with 555104 additions and 9259 deletions
@@ -0,0 +1,152 @@
---
name: expo-module
description: Guide for creating and writing Expo native modules and views using the Expo Modules API (Swift, Kotlin, TypeScript). Covers module definition DSL, native views, shared objects, config plugins, lifecycle hooks, autolinking, and type system. Use when building or modifying native modules...
risk: unknown
source: https://github.com/expo/skills/tree/main/plugins/expo/skills/expo-module
source_repo: expo/skills
source_type: official
date_added: 2026-07-01
license: MIT
license_source: https://github.com/expo/skills/blob/main/LICENSE
---
# Writing Expo Modules
Complete reference for building native modules and views using the Expo Modules API. Covers Swift (iOS), Kotlin (Android), and TypeScript.
## When to Use
- Creating a new Expo native module or native view
- Adding native functionality (camera, sensors, system APIs) to an Expo app
- Wrapping platform SDKs for React Native consumption
- Building config plugins that modify native project files
- Adding Android, Apple, or web support to an existing Expo module
- Editing `expo-module.config.json`, config plugins, or lifecycle hooks
## References
Consult these resources as needed:
```
references/
create-expo-module.md Scaffolding and add-platform-support workflow, defaults, and quirks
native-module.md Module definition DSL: Name, Function, AsyncFunction, Property, Constant, Events, type system, shared objects
native-view.md Native view components: View, Prop, EventDispatcher, view lifecycle, ref-based functions
lifecycle.md Lifecycle hooks: module, iOS app/AppDelegate, Android activity/application listeners
config-plugin.md Config plugins: modifying Info.plist, AndroidManifest.xml, reading values in native code
module-config.md expo-module.config.json fields, file placement, and autolinking behavior
```
## Quick Start
Prefer `create-expo-module` over manually creating native module files and directories. In practice, the best path is usually to create the scaffold first and then build on top of it. The scaffold sets up the expected layout, `expo-module.config.json`, podspec or Gradle files, TypeScript bindings, and the standalone example app flow.
If an existing Expo module only needs another platform, use `create-expo-module add-platform-support` instead of manually copying native directories.
See [references/create-expo-module.md](references/create-expo-module.md) before scaffolding or extending a module. It covers:
- local vs standalone modules
- `--platform`, `--features`, `--barrel`, `--package-manager`, and non-interactive mode
- `expo.autolinking.nativeModulesDir`
- `add-platform-support` behavior and quirks
## Recommended Workflow
1. Choose the scaffold type first:
- **Local module** for one app
- **Standalone module** for reuse, monorepos, or publishing
2. Determine native `expo-module` features that you will need.
- Based on the user's instructions determine which feature scaffolding will be useful.
- Available features: `Constant`, `Function`, `AsyncFunction`, `Event`, `View`, `ViewEvent`, `SharedObject`
3. Scaffold deliberately:
- pass an explicit slug or path
- choose `--platform` intentionally instead of relying on defaults
- use `--features` to choose code samples which you will modify in the next step to match the real implementation.
4. Replace generated example code with the real implementation.
5. If you add a new platform later, prefer `add-platform-support` over manual file copying.
## Practical Scaffolding Rules
- Feature examples are **opt-in**. A newly scaffolded module may be minimal if no features were selected.
- `ViewEvent` implies `View`.
- Local modules do **not** generate an `index.ts` barrel by default. Use `--barrel` only if you want one.
- In non-interactive local scaffolding, pass the positional slug or path explicitly. `--name` changes the native class name, not the folder name.
- Local modules live in `expo.autolinking.nativeModulesDir` when configured, otherwise in `modules/`.
- Standalone modules have their own package metadata, scripts, and usually an example app. Local modules use the host app's tooling instead.
## Core File Shapes
The Swift and Kotlin DSL share the same structure. Swift is usually the clearest primary example; consult the references for feature-specific details.
## Module Structure Reference
The Swift and Kotlin DSL share the same structure. Both platforms are shown here for reference — in other reference files, Swift is shown as the primary language unless the Kotlin pattern meaningfully differs.
**Swift (iOS):**
```swift
import ExpoModulesCore
public class MyModule: Module {
public func definition() -> ModuleDefinition {
Name("MyModule")
Function("hello") { (name: String) -> String in
return "Hello \(name)!"
}
}
}
```
**Kotlin (Android):**
```kotlin
package expo.modules.mymodule
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class MyModule : Module() {
override fun definition() = ModuleDefinition {
Name("MyModule")
Function("hello") { name: String ->
"Hello $name!"
}
}
}
```
**TypeScript:**
```typescript
import { requireNativeModule } from "expo";
const MyModule = requireNativeModule("MyModule");
export function hello(name: string): string {
return MyModule.hello(name);
}
```
### expo-module.config.json
```json
{
"platforms": ["android", "apple"],
"apple": {
"modules": ["MyModule"]
},
"android": {
"modules": ["expo.modules.mymodule.MyModule"]
}
}
```
Note: iOS uses just the class name; Android uses the fully-qualified class name (package + class). See `references/module-config.md` for all fields.
## Limitations
- Use this skill only when the task clearly matches its upstream product or API scope.
- Verify commands, API behavior, pricing, quotas, credentials, and deployment effects against current official documentation before making changes.
- Do not treat generated examples as a substitute for environment-specific tests, security review, or user approval for destructive or costly actions.
@@ -0,0 +1,4 @@
interface:
display_name: "Expo Module"
short_description: "Build Expo native modules and native views with Swift, Kotlin, TypeScript, autolinking, and config plugins"
default_prompt: "Use $expo-module to scaffold, implement, clean up, or review Expo native modules, native views, shared objects, lifecycle hooks, autolinking configuration, and config plugins."
@@ -0,0 +1,90 @@
# Config Plugins Reference
Config plugins customize native Android and iOS projects generated with `npx expo prebuild`. They are synchronous functions that accept an `ExpoConfig` and return a modified version.
## Plugin Structure
```
my-module/
plugin/
tsconfig.json
src/
index.ts
app.plugin.js # Entry: module.exports = require('./plugin/build');
```
## Writing a Plugin
Plugin functions follow the `with` prefix naming convention.
```typescript
import {
ConfigPlugin,
withInfoPlist,
withAndroidManifest,
AndroidConfig,
} from "expo/config-plugins";
const withMyConfig: ConfigPlugin<{ apiKey: string }> = (config, { apiKey }) => {
// iOS: modify Info.plist
config = withInfoPlist(config, (config) => {
config.modResults["MY_API_KEY"] = apiKey;
return config;
});
// Android: modify AndroidManifest.xml
config = withAndroidManifest(config, (config) => {
const mainApp =
AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
AndroidConfig.Manifest.addMetaDataItemToMainApplication(
mainApp,
"MY_API_KEY",
apiKey
);
return config;
});
return config;
};
export default withMyConfig;
```
## Using in app.json
```json
{
"expo": {
"plugins": [["my-module", { "apiKey": "secret_key" }]]
}
}
```
## Reading Config Values in Native Code
**Swift:**
```swift
Function("getApiKey") {
return Bundle.main.object(forInfoDictionaryKey: "MY_API_KEY") as? String
}
```
**Kotlin:**
```kotlin
Function("getApiKey") {
val appInfo = appContext?.reactContext?.packageManager?.getApplicationInfo(
appContext?.reactContext?.packageName.toString(),
PackageManager.GET_META_DATA
)
return@Function appInfo?.metaData?.getString("MY_API_KEY")
}
```
## Key Rules
- Plugins must be synchronous; return values must be serializable (except `mods`)
- `Mods` are async functions invoked during the prebuild "syncing" phase
- Use `npm run build plugin` to compile TypeScript plugins
- Test with `npx expo prebuild --clean`
@@ -0,0 +1,206 @@
# create-expo-module
Use `create-expo-module` to scaffold new Expo modules and `create-expo-module add-platform-support` to extend existing Expo modules.
Prefer `create-expo-module` over manually creating module files and directories. In most cases, the right move is to generate the scaffold first and then build on top of it.
## Choose the Module Type First
### Local module
Use a local module when the native code only belongs to one Expo app.
- lives inside the app
- uses the app's dependencies and tooling
- does not create an example app
- respects `package.json:expo.autolinking.nativeModulesDir`, or falls back to `modules/`
### Standalone module
Use a standalone module when the module should be reusable across apps, live in a monorepo package, or be published to npm.
- has its own `package.json`
- installs its own dependencies
- builds TypeScript during scaffolding
- usually creates an `example` app unless `--no-example` is passed
- may initialize a Git repo if not already inside one
When creating a standalone module, default to keeping the example app. Only skip it when the user explicitly asks for `--no-example` or clearly does not want the example project.
## Recommended Commands
### Local module
Use an explicit slug or path.
```bash
npx create-expo-module@latest key-value-store --local --platform apple android --features Function AsyncFunction
```
If you need deterministic non-interactive output, pass the slug or path explicitly and then pass the rest of the options:
```bash
EXPO_NONINTERACTIVE=1 npx create-expo-module@latest key-value-store \
--local \
--name KeyValueStore \
--package expo.modules.keyvaluestore \
--platform apple android \
--features Function AsyncFunction
```
Important quirk:
- in non-interactive local scaffolding, omitting the positional slug causes the CLI to fall back to `my-module`
- `--name` changes the native module class name, not the directory name
### Standalone module
```bash
npx create-expo-module@latest expo-key-value-store --platform apple android --features Function AsyncFunction
```
## Creation Options
These are the module creation options exposed by the CLI:
| Option | Applies to | Notes |
| ----------------------------- | ----------------- | --------------------------------------------------------------------------------------------------------- |
| `[path]` | local, standalone | Positional slug or target path. Use this explicitly for stable local scaffolding in non-interactive mode. |
| `--local` | local | Create a local module inside the current Expo project. |
| `--platform <platforms...>` | local, standalone | Valid values: `apple`, `android`, `web`. |
| `--features <features...>` | local, standalone | Pick generated feature examples. Use `all` to include everything. |
| `--full-example` | local, standalone | Equivalent to `--features all`. |
| `--barrel` | local | Generate a local `index.ts` barrel. Ignored for standalone modules. |
| `--source <source_dir>` | local, standalone | Use a local `expo-module-template` directory instead of downloading from npm. |
| `--name <name>` | local, standalone | Native module name, for example `KeyValueStore`. |
| `--description <description>` | standalone | Package description. |
| `--package <package>` | local, standalone | Android package name, for example `expo.modules.keyvaluestore`. |
| `--author-name <name>` | standalone | Package author name. |
| `--author-email <email>` | standalone | Package author email. |
| `--author-url <url>` | standalone | Package author profile URL. |
| `--repo <url>` | standalone | Package repository URL. |
| `--license <license>` | standalone | Package license identifier. |
| `--module-version <version>` | standalone | Initial package version. |
| `--package-manager <manager>` | standalone | One of `npm`, `pnpm`, `yarn`, `bun`. |
| `--with-readme` | standalone | Keep `README.md` in the generated package. |
| `--with-changelog` | standalone | Keep `CHANGELOG.md` in the generated package. |
| `--no-example` | standalone | Skip creating the example app. |
Notes:
- `--description`, author flags, `--repo`, `--license`, and `--module-version` only affect standalone modules because local modules do not have a standalone package manifest.
- `--with-readme`, `--with-changelog`, `--no-example`, and `--package-manager` are standalone-only concerns.
- `--barrel` only affects local modules.
- `--name` changes the native module class name. It does not rename the local module directory.
## Platforms
Valid values are:
- `apple`
- `android`
- `web`
Behavior to remember:
- standalone modules default to all platforms when `--platform` is omitted in non-interactive mode
- local modules also default to all platforms in non-interactive mode
- interactive local scaffolding preselects platforms from `app.json:expo.platforms` when available, mapping `ios` to `apple`
- invalid platform values are ignored with a warning; if all provided values are invalid, the CLI falls back to all platforms
If you do not want web support, omit `web` during scaffolding instead of removing it later.
## Feature Examples
Feature examples are generated starter snippets, not capability restrictions. They are small working examples of common Expo Modules API patterns.
Available values:
- `Constant`
- `Function`
- `AsyncFunction`
- `Event`
- `View`
- `ViewEvent`
- `SharedObject`
Important behaviors:
- no features selected means a minimal module
- in interactive mode, feature examples start unselected
- in non-interactive mode, no features are included unless `--features` or `--full-example` is passed
- `--full-example` is equivalent to `--features all`
- `ViewEvent` automatically includes `View`
Use `View` only when the module actually renders UI. For native-only modules, do not scaffold the view files unless you plan to use them.
## Local Module Quirks
- local modules do not generate an `index.ts` barrel by default
- use `--barrel` only if you want a root barrel file
- local modules skip dependency installation and do not create an `example` app
- local modules do not have a local `package.json`; they rely on the host app
If `--barrel` is not used, the CLI's follow-up instructions point to direct imports from the module's `src/` files.
## Standalone Module Quirks
- `--package-manager` is only relevant for standalone modules
- if omitted, the CLI detects the package manager from the user agent or available package managers
- the scaffold builds TypeScript after installing dependencies
- the `example` app is created only when examples are enabled; `--no-example` skips it
- `--with-readme` and `--with-changelog` opt into those files
The generated standalone scripts include `build`, `clean`, `test`, `prepare`, `open:ios`, and `open:android`.
## add-platform-support
Use this subcommand when an existing Expo module needs another supported platform.
Interactive usage from the module root:
```bash
npx create-expo-module@latest add-platform-support
```
Explicit usage:
```bash
npx create-expo-module@latest add-platform-support --platform android
```
You can also pass the module path:
```bash
npx create-expo-module@latest add-platform-support ./packages/expo-key-value-store --platform web
```
Important behaviors:
- supported values are `apple`, `android`, and `web`
- in non-interactive mode, `--platform` is required
- the command only adds platforms that are not already present in `expo-module.config.json`
- it refuses to overwrite existing `android/` or `ios/` directories
- for native modules, it only works with modules that use the Expo Modules API DSL
- older module formats are not supported
### Feature detection
`add-platform-support` tries to detect the existing module's feature examples from the native module definition. This is best effort.
Use `--features` to override the detected feature examples when:
- the module is unusual
- the module uses generated code
- the definition is spread across multiple files
- the generated files do not match the existing module's shape
If no features are detected or provided, the command creates a minimal scaffold for the new platform.
## Environment Variables
- `EXPO_BETA`: use the next template version
- `EXPO_DEBUG`: enable debug logs
- `EXPO_NO_TELEMETRY`: disable telemetry
- `EXPO_NONINTERACTIVE`: force non-interactive mode
- `CI`: same as `EXPO_NONINTERACTIVE`, used in CI environments
@@ -0,0 +1,127 @@
# Lifecycle Hooks Reference
## Module Lifecycle (in module definition)
```swift
OnCreate {
// Module initialized - preferred over class initializers
}
OnDestroy {
// Module deallocated - clean up resources
}
OnAppContextDestroys {
// App context is being deallocated
}
```
## iOS App Lifecycle (in module definition)
```swift
OnAppEntersForeground { /* UIApplication.willEnterForegroundNotification */ }
OnAppEntersBackground { /* UIApplication.didEnterBackgroundNotification */ }
OnAppBecomesActive { /* UIApplication.didBecomeActiveNotification */ }
```
## Android Activity Lifecycle (in module definition)
```kotlin
OnActivityEntersForeground { /* Activity resumed */ }
OnActivityEntersBackground { /* Activity paused */ }
OnActivityDestroys { /* Activity destroyed */ }
OnNewIntent { intent -> /* Deep link received */ }
OnActivityResult { activity, result -> /* startActivityForResult callback */ }
OnUserLeavesActivity { /* User-initiated background transition */ }
RegisterActivityContracts { /* Modern activity result contracts */ }
```
---
## iOS AppDelegate Subscribers
For hooking into AppDelegate events without editing AppDelegate directly. Requires app's AppDelegate to extend `ExpoAppDelegate`.
```swift
import ExpoModulesCore
public class MyAppDelegateSubscriber: ExpoAppDelegateSubscriber {
public func applicationDidBecomeActive(_ application: UIApplication) {}
public func applicationWillResignActive(_ application: UIApplication) {}
public func applicationDidEnterBackground(_ application: UIApplication) {}
public func applicationWillEnterForeground(_ application: UIApplication) {}
public func applicationWillTerminate(_ application: UIApplication) {}
public func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
return false // Return true if handled
}
}
```
Register in `expo-module.config.json`:
```json
{
"apple": {
"appDelegateSubscribers": ["MyAppDelegateSubscriber"]
}
}
```
Result aggregation:
- `didFinishLaunchingWithOptions`: Returns `true` if **any** subscriber returns `true`
- `didReceiveRemoteNotification`: Priority: `failed` > `newData` > `noData`
---
## Android Lifecycle Listeners
For hooking into Activity/Application lifecycle outside module definitions. Useful for handling deep links, intents, and app-level initialization.
### ReactActivityLifecycleListener
Supported callbacks: `onCreate`, `onResume`, `onPause`, `onDestroy`, `onNewIntent`, `onBackPressed`.
> Note: `onStart` and `onStop` are **not supported** — the implementation hooks into `ReactActivityDelegate` which lacks these methods.
```kotlin
class MyPackage : Package {
override fun createReactActivityLifecycleListeners(
activityContext: Context
): List<ReactActivityLifecycleListener> {
return listOf(MyActivityListener())
}
}
class MyActivityListener : ReactActivityLifecycleListener {
override fun onCreate(activity: Activity, savedInstanceState: Bundle?) { }
override fun onResume(activity: Activity) { }
override fun onPause(activity: Activity) { }
override fun onDestroy(activity: Activity) { }
override fun onNewIntent(intent: Intent?): Boolean { return false }
override fun onBackPressed(): Boolean { return false }
}
```
### ApplicationLifecycleListener
Supported callbacks: `onCreate`, `onConfigurationChanged`.
```kotlin
class MyPackage : Package {
override fun createApplicationLifecycleListeners(
context: Context
): List<ApplicationLifecycleListener> {
return listOf(MyAppListener())
}
}
class MyAppListener : ApplicationLifecycleListener {
override fun onCreate(application: Application) {
// App-level initialization
}
}
```
@@ -0,0 +1,48 @@
# Module Configuration Reference
## expo-module.config.json
Use `expo-module.config.json` for autolinking and module registration.
File placement depends on the module type:
- **standalone module**: place it at the package root, next to `package.json`
- **local module**: place it at the module root inside the app's local modules directory (`expo.autolinking.nativeModulesDir`, or `modules/` by default)
Example:
```json
{
"platforms": ["android", "apple", "web"],
"apple": {
"modules": ["MyModule"],
"appDelegateSubscribers": ["MyAppDelegateSubscriber"]
},
"android": {
"modules": ["expo.modules.mymodule.MyModule"]
}
}
```
### Fields
| Field | Description |
|-------|-------------|
| `platforms` | Array of supported platforms. Valid values include `android`, `apple`, `web`, and `devtools`. You can also use granular Apple platforms such as `ios`, `macos`, and `tvos`, but `apple` is preferred when one Swift module supports multiple Apple targets. |
| `apple.modules` | Swift module class names |
| `apple.appDelegateSubscribers` | Swift AppDelegate subscriber class names |
| `android.modules` | Fully-qualified Kotlin module class names (package + class) |
## Autolinking
Expo autolinking automatically discovers and links modules that have `expo-module.config.json`. No manual native project configuration needed — install via npm, run `pod install`.
- standalone modules are resolved from dependencies and search paths.
- local modules are resolved from the `modules` directory or `nativeModulesDir` if defined.
### Resolution Order
1. Explicit dependencies in `react-native.config.js`
2. Custom `searchPaths` directories
3. Local `nativeModulesDir` (defaults to `./modules/`)
4. Recursive npm dependency resolution
@@ -0,0 +1,286 @@
# Native Module DSL Reference
Swift is shown as the primary language. Kotlin follows the same DSL structure (see SKILL.md for both). Kotlin-specific syntax is noted where it meaningfully differs.
## Name
Sets the module identifier used in JavaScript.
```swift
Name("MyModule")
```
## Constant
Computed once on first access, then cached.
```swift
Constant("PI") { 3.14159 }
```
## Function (Synchronous)
Blocks the JS thread until completion. Supports up to 8 arguments.
```swift
Function("add") { (a: Int, b: Int) -> Int in
return a + b
}
```
## AsyncFunction
Returns a Promise. Runs on a background thread by default.
```swift
AsyncFunction("fetchData") { (url: URL) -> String in
let data = try Data(contentsOf: url)
return String(data: data, encoding: .utf8) ?? ""
}
// Force main queue execution
AsyncFunction("updateUI") { () -> Void in
// UI work
}.runOnQueue(.main)
```
**Kotlin differences:**
```kotlin
// Supports Kotlin coroutines
AsyncFunction("fetchData") Coroutine { url: java.net.URL ->
withContext(Dispatchers.IO) {
url.readText()
}
}
```
## Property
Getter/setter for JS object properties.
```swift
// Read-only
Property("version") { "1.0.0" }
// Read-write
Property("volume")
.get { () -> Float in self.volume }
.set { (newValue: Float) in self.volume = newValue }
```
## Events
Declares events the module can send to JS. Must be declared before using `sendEvent`.
```swift
// Declaration
Events("onChange", "onError")
// Sending from native (Swift)
sendEvent("onChange", ["value": newValue])
```
**Kotlin difference** — uses `bundleOf`:
```kotlin
sendEvent("onChange", bundleOf("value" to newValue))
```
**JS subscription:**
```typescript
import { useEvent } from "expo";
import MyModule from "./MyModule";
// Hook-based (recommended)
const event = useEvent(MyModule, "onChange");
// Manual subscription
const subscription = MyModule.addListener("onChange", (event) => {
console.log(event.value);
});
// Clean up: subscription.remove()
```
### OnStartObserving / OnStopObserving
Called when the first listener attaches / last listener detaches. Can be scoped to specific events.
```swift
OnStartObserving("onChange") {
// Start producing events
}
OnStopObserving("onChange") {
// Stop producing events
}
```
---
## Type System
### Primitives
| Swift | Kotlin | JS |
|-------|--------|----|
| `Bool` | `Boolean` | `boolean` |
| `Int`, `Int32` | `Int` | `number` |
| `Int64` | `Long` | `number` |
| `Float`, `Float32` | `Float` | `number` |
| `Double` | `Double` | `number` |
| `String` | `String` | `string` |
| `URL` | `java.net.URL` / `android.net.Uri` | `string` |
| `CGPoint` | - | `{ x, y }` |
| `CGSize` | - | `{ width, height }` |
| `CGRect` | - | `{ x, y, width, height }` |
| `UIColor` / `CGColor` | `android.graphics.Color` | `string` (ProcessedColorValue) |
| `Data` | `kotlin.ByteArray` | `Uint8Array` |
### Records (Struct-like types)
```swift
struct UserRecord: Record {
@Field var name: String = ""
@Field var age: Int = 0
@Field var email: String?
}
Function("createUser") { (user: UserRecord) -> Bool in
return true
}
```
**Kotlin difference** — uses `class` instead of `struct`, optional fields need explicit `= null`:
```kotlin
class UserRecord : Record {
@Field var name: String = ""
@Field var age: Int = 0
@Field var email: String? = null
}
```
### Enums (Enumerable)
```swift
enum Theme: String, Enumerable {
case light
case dark
case system
}
Function("setTheme") { (theme: Theme) in
// type-safe enum value
}
```
**Kotlin difference** — uses `enum class` with explicit `value` property:
```kotlin
enum class Theme(val value: String) : Enumerable {
LIGHT("light"),
DARK("dark"),
SYSTEM("system")
}
```
### Either Types (Union types)
```swift
Function("process") { (input: Either<String, Int>) in
if let str = input.get(String.self) {
// handle string
} else if let num = input.get(Int.self) {
// handle number
}
}
```
Also available: `EitherOfThree<A, B, C>`, `EitherOfFour<A, B, C, D>`.
### JavaScript Values (Direct JS manipulation)
For advanced use in synchronous functions running on JS thread:
```swift
Function("callback") { (fn: JavaScriptFunction<String>) in
let result = fn("arg1", "arg2")
}
```
---
## Shared Objects
Bridge native class instances to JS with automatic lifecycle management. Instances are deallocated when neither JS nor native code holds a reference.
### Defining a Shared Object
```swift
class ImageContext: SharedObject {
private var image: UIImage
init(image: UIImage) {
self.image = image
super.init()
}
func rotate(degrees: Double) {
image = image.rotated(degrees: degrees)
}
}
```
**Kotlin difference** — takes `RuntimeContext` in constructor, override `sharedObjectDidRelease()` for cleanup:
```kotlin
class ImageContext(
runtimeContext: RuntimeContext,
private var bitmap: Bitmap
) : SharedObject(runtimeContext) {
fun rotate(degrees: Float) { /* ... */ }
override fun sharedObjectDidRelease() {
if (!bitmap.isRecycled) bitmap.recycle()
}
}
```
### Exposing via Class DSL
```swift
Class("Context", ImageContext.self) {
Constructor { (path: String) -> ImageContext in
return ImageContext(image: UIImage(contentsOfFile: path)!)
}
Function("rotate") { (ctx: ImageContext, degrees: Double) -> ImageContext in
ctx.rotate(degrees: degrees)
return ctx
}
Property("width")
.get { (ctx: ImageContext) -> Int in ctx.width }
}
```
Other Class DSL components: `StaticFunction`, `StaticAsyncFunction`, `AsyncFunction`.
### SharedRef
Specialized shared reference for passing typed objects between modules:
```swift
final class ImageRef: SharedRef<UIImage> {}
```
### JS Usage
```typescript
const ctx = await ImageModule.create("/path/to/image.png");
ctx.rotate(90);
console.log(ctx.width);
```
@@ -0,0 +1,171 @@
# Native View Reference
Native views let you render platform UI components (UIView on iOS, Android View on Android) as React components.
## Defining a View
**Swift:**
```swift
public class MyViewModule: Module {
public func definition() -> ModuleDefinition {
Name("MyView")
View(MyNativeView.self) {
Prop("title") { (view: MyNativeView, title: String) in
view.titleLabel.text = title
}
Events("onPress", "onLoad")
AsyncFunction("reset") { (view: MyNativeView) in
view.reset()
}
}
}
}
class MyNativeView: ExpoView {
let titleLabel = UILabel()
required init(appContext: AppContext) {
super.init(appContext: appContext)
clipsToBounds = true
addSubview(titleLabel)
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel.frame = bounds
}
}
```
**Kotlin:**
```kotlin
class MyViewModule : Module() {
override fun definition() = ModuleDefinition {
Name("MyView")
View(MyNativeView::class) {
Prop("title") { view: MyNativeView, title: String ->
view.titleView.text = title
}
Events("onPress", "onLoad")
AsyncFunction("reset") { view: MyNativeView ->
view.reset()
}
}
}
}
class MyNativeView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
val titleView = TextView(context).also {
addView(it, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
}
}
```
**TypeScript:**
```typescript
import { requireNativeView } from "expo";
export type MyViewProps = {
title?: string;
onPress?: (event: { nativeEvent: { x: number; y: number } }) => void;
onLoad?: () => void;
} & ViewProps;
const NativeView = requireNativeView<MyViewProps>("MyView");
export function MyView(props: MyViewProps) {
return <NativeView {...props} />;
}
```
## View Event Dispatching
**Swift:**
```swift
class MyNativeView: ExpoView {
let onPress = EventDispatcher()
func handleTap(at point: CGPoint) {
onPress(["x": point.x, "y": point.y])
}
}
```
**Kotlin:**
```kotlin
class MyNativeView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
private val onPress by EventDispatcher()
fun handleTap(x: Float, y: Float) {
onPress(mapOf("x" to x, "y" to y))
}
}
```
## View Lifecycle
```swift
// Called after all props have been set
OnViewDidUpdateProps { (view: MyNativeView) in
view.applyChanges()
}
```
```kotlin
// Android only - called when view is no longer used
OnViewDestroys { view: MyNativeView ->
view.cleanup()
}
```
## AsyncFunction on Views
Functions defined inside `View` are accessible via React ref:
```typescript
const ref = useRef<MyView>(null);
// Call native function
await ref.current?.reset();
```
## PropGroup (Android)
Batch-register multiple props with shared setter logic:
```kotlin
View(MyNativeView::class) {
PropGroup("border", "width" to Float::class, "color" to Int::class) { view, index, value ->
when (index) {
0 -> view.borderWidth = value as Float
1 -> view.borderColor = value as Int
}
}
}
```
## GroupView (Android)
Enable view group functionality for managing child views:
```kotlin
View(MyContainerView::class) {
GroupView {
AddChildView { parent, child, index -> parent.addView(child, index) }
GetChildCount { parent -> parent.childCount }
GetChildViewAt { parent, index -> parent.getChildAt(index) }
RemoveChildView { parent, child -> parent.removeView(child) }
RemoveChildViewAt { parent, index -> parent.removeViewAt(index) }
}
}
```