📦 deps(thirdparty): update snapshots
This commit is contained in:
+152
@@ -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.
|
||||
+4
@@ -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."
|
||||
+90
@@ -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`
|
||||
+206
@@ -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
|
||||
+127
@@ -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
|
||||
}
|
||||
}
|
||||
```
|
||||
+48
@@ -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
|
||||
+286
@@ -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);
|
||||
```
|
||||
+171
@@ -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) }
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user