📦 deps(thirdparty): update snapshots
This commit is contained in:
+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