diff --git a/packages/expo-updates-interface/CHANGELOG.md b/packages/expo-updates-interface/CHANGELOG.md index a19af1d401d2fc..a7b9e8fdb1cd54 100644 --- a/packages/expo-updates-interface/CHANGELOG.md +++ b/packages/expo-updates-interface/CHANGELOG.md @@ -10,6 +10,10 @@ ### 💡 Others +### ⚠️ Notices + +- Documentation for new native interface. ([#43230](https://github.com/expo/expo/pull/43230) by [@douglowder](https://github.com/douglowder)) + ## 55.1.2 — 2026-02-16 ### 🎉 New features diff --git a/packages/expo-updates-interface/README.md b/packages/expo-updates-interface/README.md index 87b6feef9e411a..714f1d86270330 100644 --- a/packages/expo-updates-interface/README.md +++ b/packages/expo-updates-interface/README.md @@ -1,13 +1,236 @@ # expo-updates-interface -Native interface for modules that optionally depend on expo-updates, e.g. expo-dev-launcher. +Native interface for modules that optionally depend on expo-updates. This package provides a unified native API (iOS and Android) for querying the state of the updates system and subscribing to state machine transitions, without requiring a direct dependency on `expo-updates`. + +## Overview + +`expo-updates-interface` defines two levels of interface: + +- **`UpdatesInterface`** -- implemented by all updates controllers (enabled, disabled, and dev-launcher). Provides read-only properties describing the running update and a method to subscribe to state machine changes. +- **`UpdatesDevLauncherInterface`** -- extends `UpdatesInterface` with additional methods used exclusively by `expo-dev-launcher` to fetch updates and manage the update lifecycle. + +A singleton **`UpdatesControllerRegistry`** provides access to the active controller that implements one or both of the above interfaces. ## API documentation +### UpdatesControllerRegistry + +The registry provides the active updates controller as a weak reference, in the `controller` property. The reference will be null when `expo-updates` is not installed and compiled into the app. If `expo-updates` is present, the property is set automatically at startup. + +| Platform | Access | +| ----------- | ----------------------------------------------------- | +| **iOS** | `UpdatesControllerRegistry.sharedInstance.controller` | +| **Android** | `UpdatesControllerRegistry.controller?.get()` | + +### UpdatesInterface + +All updates controllers implement this interface. It is available whether updates is enabled, disabled, or running under the dev client. + +#### Properties + +| Property | Type | Description | +| ----------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------- | +| `isEnabled` | `Bool` / `Boolean` | Whether the updates system is enabled. Defaults to `false` when updates is disabled. | +| `runtimeVersion` | `String?` | The runtime version of the running app. Set when updates is enabled or the dev client is running. | +| `updateURL` (iOS) / `updateUrl` (Android) | `URL?` / `Uri?` | The update URL configured for this app. Set when updates is enabled or the dev client is running. | +| `launchedUpdateId` | `UUID?` | The ID of the currently running update. Only set when updates is enabled. | +| `embeddedUpdateId` | `UUID?` | The ID of the update embedded in the app binary. Only set when updates is enabled. | +| `launchAssetPath` | `String?` | The local file path of the launch asset (JS bundle) for the running update. Only set when updates is enabled. | + +#### Methods + +##### `subscribeToUpdatesStateChanges` + +Registers a listener that will be called on updates state machine transitions. Returns a subscription object that can be used to unsubscribe. + +**iOS:** + +```swift +func subscribeToUpdatesStateChanges(_ listener: any UpdatesStateChangeListener) -> UpdatesStateChangeSubscription +``` + +**Android (Kotlin):** + +```kotlin +fun subscribeToUpdatesStateChanges(listener: UpdatesStateChangeListener): UpdatesStateChangeSubscription +``` + +### UpdatesStateChangeListener + +A listener protocol/interface that receives state machine transition events. + +**iOS:** + +```swift +public protocol UpdatesStateChangeListener { + func updatesStateDidChange(_ event: [String: Any]) +} +``` + +**Android (Kotlin):** + +```kotlin +interface UpdatesStateChangeListener { + fun updatesStateDidChange(event: Map) +} +``` + +The `event` dictionary contains information about the state transition, matching the structure of the updates state machine events exposed by the `expo-updates` JS API. + +### UpdatesStateChangeSubscription + +Returned by `subscribeToUpdatesStateChanges`. Call `remove()` to unsubscribe and stop receiving state change events. + +**iOS:** + +```swift +public protocol UpdatesStateChangeSubscription { + func remove() +} +``` + +**Android (Kotlin):** + +```kotlin +interface UpdatesStateChangeSubscription { + fun remove() +} +``` + +### UpdatesDevLauncherInterface + +Extends `UpdatesInterface` with methods used by `expo-dev-launcher` to fetch and manage updates. This interface is only implemented by the dev-launcher updates controller. + +See the source files for the full method signatures: + +- **iOS:** [`UpdatesInterface.swift`](ios/EXUpdatesInterface/UpdatesInterface.swift) +- **Android:** [`UpdatesInterface.kt`](android/src/main/java/expo/modules/updatesinterface/UpdatesInterface.kt) + +## Usage example + +### Reading update information (Kotlin) + +```kotlin +import expo.modules.updatesinterface.UpdatesControllerRegistry + +val controller = UpdatesControllerRegistry.controller?.get() +if (controller != null && controller.isEnabled) { + val updateId = controller.launchedUpdateId + val runtimeVersion = controller.runtimeVersion + // ... +} +``` + +### Reading update information (Swift) + +```swift +import EXUpdatesInterface + +if let controller = UpdatesControllerRegistry.sharedInstance.controller, + controller.isEnabled { + let updateId = controller.launchedUpdateId + let runtimeVersion = controller.runtimeVersion + // ... +} +``` + +### Subscribing to state changes (Kotlin) + +```kotlin +import expo.modules.updatesinterface.UpdatesControllerRegistry +import expo.modules.updatesinterface.UpdatesStateChangeListener +import expo.modules.updatesinterface.UpdatesStateChangeSubscription + +val controller = UpdatesControllerRegistry.controller?.get() ?: return + +val subscription = controller.subscribeToUpdatesStateChanges(object : UpdatesStateChangeListener { + override fun updatesStateDidChange(event: Map) { + // Handle state change event + } +}) + +// Later, to unsubscribe: +subscription.remove() +``` + +### Subscribing to state changes (Swift) + +```swift +import EXUpdatesInterface + +class MyListener: NSObject, UpdatesStateChangeListener { + func updatesStateDidChange(_ event: [String: Any]) { + // Handle state change event + } +} + +let listener = MyListener() +if let controller = UpdatesControllerRegistry.sharedInstance.controller { + let subscription = controller.subscribeToUpdatesStateChanges(listener) + + // Later, to unsubscribe: + subscription.remove() +} +``` + +## Installation in an Expo native module + +- The `expo-updates-interface` package should be added to the module's NPM dependencies. (The `expo-updates` package does not need to be added.) +- The module's iOS podspec should have "EXUpdatesInterface" added to the pod dependencies, as in this example: + +```ruby +Pod::Spec.new do |s| + s.name = 'InterfaceDemo' + s.version = '1.0.0' + s.platforms = { + :ios => '15.1', + :tvos => '15.1' + } + s.static_framework = true + + s.dependency 'ExpoModulesCore' + s.dependency 'EXUpdatesInterface' + + # Swift/Objective-C compatibility + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + } + + s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" +end +``` + +- The module's Android `build.gradle` should have this package added as a dependency, as in this example: + +```gradle +android { + namespace "expo.modules.interfacedemo" + defaultConfig { + versionCode 1 + versionName "0.7.6" + } + lintOptions { + abortOnError false + } +} + +dependencies { + implementation project(':expo-updates-interface') +} +``` + ## Installation in managed Expo projects +This package is included as a dependency of `expo-updates` and `expo-dev-client`. No separate installation is needed. + ## Installation in bare React Native projects +This package is included as a dependency of `expo-updates` and `expo-dev-client`. If you need to install it separately: + +```sh +npx expo install expo-updates-interface +``` + ## Contributing Contributions are very welcome! Please refer to guidelines described in the [contributing guide](https://github.com/expo/expo#contributing).