Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"eslint.workingDirectories": [
{ "pattern": "*" },
]
"eslint.workingDirectories": [{ "pattern": "*" }],
"search.exclude": {
"packages/**/build": true,
"docs/public/static/data": true
}
}
1 change: 0 additions & 1 deletion apps/bare-expo/android/.idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
package host.exp.exponent

import com.facebook.react.ReactPackage
import expo.modules.ExpoModulesPackageList
import expo.modules.apploader.AppLoaderPackagesProviderInterface
import expo.modules.core.interfaces.Package

// Needed for `react-native link`
// import com.facebook.react.ReactApplication;
class MainApplication : ExpoApplication(), AppLoaderPackagesProviderInterface<ReactPackage?> {
class MainApplication : ExpoApplication() {
override val isDebug: Boolean
get() = BuildConfig.DEBUG

// Needed for `react-native link`
override fun getPackages(): List<ReactPackage> {
return mutableListOf()
}

override fun getExpoPackages(): List<Package> {
return ExpoModulesPackageList.getPackageList()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package host.exp.exponent.notifications.managers

import android.content.Context
import com.raizlabs.android.dbflow.sql.language.SQLite
import expo.modules.core.interfaces.Function
import host.exp.exponent.kernel.ExperienceKey
import host.exp.exponent.notifications.exceptions.UnableToScheduleException
import host.exp.exponent.notifications.schedulers.*
Expand Down Expand Up @@ -82,7 +81,7 @@ internal class SchedulerManagerImpl(private val applicationContext: Context) : S
}
}

override fun addScheduler(scheduler: Scheduler, handler: Function<String, Boolean>) {
override fun addScheduler(scheduler: Scheduler, handler: (String?) -> Boolean) {
fetchSchedulersMap()

scheduler.setApplicationContext(applicationContext)
Expand All @@ -97,7 +96,7 @@ internal class SchedulerManagerImpl(private val applicationContext: Context) : S
null
}

handler.apply(idToApply)
handler.invoke(idToApply)
}

private fun fetchSchedulersMap() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package host.exp.exponent.notifications.managers

import expo.modules.core.interfaces.Function
import host.exp.exponent.kernel.ExperienceKey
import host.exp.exponent.notifications.schedulers.Scheduler

Expand All @@ -10,5 +9,5 @@ interface SchedulersManager {
fun cancelAlreadyScheduled(experienceKey: ExperienceKey?)
fun rescheduleOrDelete(id: String?)
fun removeScheduler(id: String?)
fun addScheduler(scheduler: Scheduler, handler: Function<String, Boolean>)
fun addScheduler(scheduler: Scheduler, handler: (String?) -> Boolean)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package host.exp.exponent.notifications.managers

import android.content.Context
import expo.modules.core.interfaces.Function
import host.exp.exponent.kernel.ExperienceKey
import host.exp.exponent.notifications.schedulers.*
import java.util.concurrent.Executor
Expand Down Expand Up @@ -30,7 +29,7 @@ class SchedulersManagerProxy private constructor(private val schedulersManager:
singleThreadExecutor.execute { schedulersManager.removeScheduler(id) }
}

override fun addScheduler(scheduler: Scheduler, handler: Function<String, Boolean>) {
override fun addScheduler(scheduler: Scheduler, handler: (String?) -> Boolean) {
singleThreadExecutor.execute { schedulersManager.addScheduler(scheduler, handler) }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,15 @@ package host.exp.exponent.taskManager

import android.content.Context
import android.util.Log
import expo.modules.adapters.react.apploader.HeadlessAppLoaderNotifier
import expo.modules.apploader.HeadlessAppLoader
import expo.modules.apploader.HeadlessAppLoader.AppConfigurationError
import expo.modules.core.interfaces.Consumer
import expo.modules.core.interfaces.DoNotStrip
import host.exp.exponent.headless.InternalHeadlessAppLoader
import java.util.*

@DoNotStrip
class ExpoHeadlessAppLoader @DoNotStrip constructor(context: Context?) : HeadlessAppLoader {
private val appScopeKeysToAppRecords = mutableMapOf<String, AppRecordInterface>()

@Throws(AppConfigurationError::class)
override fun loadApp(
context: Context,
params: HeadlessAppLoader.Params,
Expand All @@ -24,38 +20,36 @@ class ExpoHeadlessAppLoader @DoNotStrip constructor(context: Context?) : Headles
val appLoader = createAppLoader(context)

if (params.appUrl == null) {
throw AppConfigurationError("Cannot execute background task because application URL is invalid")
throw IllegalArgumentException("Params must be set with appScopeKey!")
}

if (appScopeKeysToAppRecords.containsKey(params.appScopeKey)) {
alreadyRunning.run()
} else {
if (appScopeKeysToAppRecords.containsKey(params.appScopeKey)) {
alreadyRunning.run()
} else {
Log.i(
TAG,
"Loading headless app '" + params.appScopeKey + "' with url '" + params.appUrl + "'."
)
val appRecord = appLoader.loadApp(
params.appUrl,
mapOf()
) { success, exception ->
if (exception != null) {
exception.printStackTrace()
Log.e(TAG, exception.message!!)
}
HeadlessAppLoaderNotifier.notifyAppLoaded(params.appScopeKey)
callback.apply(success)
if (!success) {
appScopeKeysToAppRecords.remove(params.appScopeKey)
}
Log.i(
TAG,
"Loading headless app '" + params.appScopeKey + "' with url '" + params.appUrl + "'."
)
val appRecord = appLoader.loadApp(
params.appUrl,
mapOf()
) { success, exception ->
if (exception != null) {
exception.printStackTrace()
Log.e(TAG, exception.message!!)
}
callback.apply(success)
if (!success) {
appScopeKeysToAppRecords.remove(params.appScopeKey)
}

appScopeKeysToAppRecords[params.appScopeKey] = appRecord
}

appScopeKeysToAppRecords[params.appScopeKey] = appRecord
}
}

override fun invalidateApp(appScopeKey: String): Boolean {
appScopeKeysToAppRecords.remove(appScopeKey)
HeadlessAppLoaderNotifier.notifyAppLoaded(appScopeKey)
return false
}

Expand Down
2 changes: 1 addition & 1 deletion docs/pages/eas/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: EAS CLI reference
sidebar_title: EAS CLI
description: EAS CLI is a command-line tool that allows you to interact with Expo Application Services (EAS) from your terminal.
cliVersion: 18.0.1
cliVersion: 18.0.3
---

import { EASCLIReference } from '~/ui/components/EASCLIReference';
Expand Down
4 changes: 2 additions & 2 deletions docs/pages/guides/monorepos.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -240,12 +240,12 @@ For [npm](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#overrides),

#### Deduplicating auto-linked native modules

> **important** This is an alpha feature starting in SDK 54 and later. The process will be automated and have better support in future versions.

Often, duplicate dependencies won't cause any problems. However, native modules should never be duplicated, because only one version of a native module can be compiled for an app build at a time. Unlike JavaScript dependencies, native builds cannot contain two conflicting versions of a single native module.

From **SDK 54**, you can set `experiments.autolinkingModuleResolution` to `true` in your **app.json** to apply autolinking to Expo CLI and Metro bundler automatically. This will force dependencies that Metro resolves to match the native modules that [autolinking](/modules/autolinking/) links for your native builds.

From **SDK 55**, this is enabled automatically for apps in monorepos.

### Script '...' does not exist

React Native uses packages to ship both JavaScript and native files. These native files also need to be linked, like the [**react-native/react.Gradle**](https://github.com/facebook/react-native/blob/v0.70.6/react.gradle) file from **android/app/build.Gradle**. Usually, this path is hardcoded to something like:
Expand Down
4 changes: 2 additions & 2 deletions docs/pages/modules/autolinking.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -257,14 +257,14 @@ For example, with the `--platform ios` option, it returns an object in **react-n

## Dependency resolution and conflicts

> **important** This is an alpha feature starting in SDK 54 and later.

Autolinking and Node resolution have different goals and the module resolution algorithm in Node and Metro can sometimes come into conflict. If your app contains duplicate installations of a native module that is picked up by autolinking, your JavaScript bundle may contain both versions of the native module, while autolinking and your native app will only contain one version. This might cause runtime crashes and risks incompatibilities.

This is an especially common problem with isolated dependencies or monorepos, and you should [check for and deduplicate native modules in your dependencies](/guides/monorepos/#duplicate-native-packages-within-monorepos).

From **SDK 54**, you can set `experiments.autolinkingModuleResolution` to `true` in your [app config](/workflow/configuration/) to apply autolinking to Expo CLI and Metro bundler automatically. This will force dependencies that Metro resolves to match the native modules that **autolinking** resolves.

From **SDK 55**, the `experiments.autolinkingModuleResolution` flag is enabled by default for apps in monorepos.

## Common questions

### How to set up the autolinking in my app?
Expand Down
1 change: 1 addition & 0 deletions docs/pages/more/expo-cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ From here, you can choose to generate basic project files like:
| `EXPO_WEB_DEV_HYDRATE` | **boolean** | Enable React hydration in development for a web project. This can help you identify hydration issues early. |
| `EXPO_UNSTABLE_LIVE_BINDINGS` | **boolean** | <div className="flex items-center pb-1.5"><StatusTag status="experimental" /><StatusTag status="SDK 54+" /></div>Disable live binding in experimental import export support. Enabled by default. Live bindings improve circular dependencies support, but can lead to slightly worse performance. |
| `EXPO_UNSTABLE_LOG_BOX` | **boolean** | <div className="flex items-center pb-1.5"><StatusTag status="experimental" /><StatusTag status="SDK 55+" /></div>Enable the experimental LogBox error overlay for native applications. Enabled by default for web. |
| `EXPO_NO_QR_CODE` | **boolean** | Prevents the CLI from showing the QR code on console. |

## Telemetry

Expand Down
8 changes: 8 additions & 0 deletions docs/pages/router/advanced/zoom-transition.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,14 @@ Both `Link.AppleZoom` and `Link.AppleZoomTarget` only accept a single child comp

</Collapsible>

<Collapsible summary="Noticeable delay when opening or dismissing screens">

You may experience a noticeable delay (approximately 1 second) when navigating to or dismissing screens that use zoom transitions, especially when performing rapid open/close/open gestures. This latency is higher than what you would see with native iOS apps using the same zoom transition API.

This is an upstream issue in `react-native-screens` related to how it handles transitions on iOS. The Expo team is actively working with the `react-native-screens` team to improve this. See this [GitHub Issue](https://github.com/expo/expo/issues/42797) for updates and more details.

</Collapsible>

<Collapsible summary="Supported only within router's Stack navigator">

The zoom transition feature is only supported when using the router's built-in Stack navigator.
Expand Down
4 changes: 2 additions & 2 deletions docs/ui/components/EASCLIReference/data/eas-cli-commands.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"source": {
"url": "https://raw.githubusercontent.com/expo/eas-cli/main/packages/eas-cli/README.md",
"fetchedAt": "2026-02-12T11:55:31.163Z",
"cliVersion": "18.0.1"
"fetchedAt": "2026-02-20T08:06:30.449Z",
"cliVersion": "18.0.3"
},
"totalCommands": 102,
"commands": [
Expand Down
1 change: 1 addition & 0 deletions packages/@expo/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
### 💡 Others

- Force `forceNodeFilesystemAPI` when watchman is enabled (the default) but not present ([#43251](https://github.com/expo/expo/pull/43251) by [@kitten](https://github.com/kitten))
- Add `EXPO_NO_QR_CODE` env variable to disable QR code generation ([#43187](https://github.com/expo/expo/pull/43187) by [@cortinico](https://github.com/cortinico))

## 55.0.9 — 2026-02-16

Expand Down
26 changes: 14 additions & 12 deletions packages/@expo/cli/src/start/interface/interactiveActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,21 @@ export class DevServerManagerActions {
rows--;
}

const qr = printQRCode(interstitialPageUrl ?? nativeRuntimeUrl);
rows -= qr.lines;
qr.print();

let qrMessage = '';
if (!options.devClient) {
qrMessage = `Scan the QR code above to open in ${chalk`{bold Expo Go}`}.`;
} else {
qrMessage = chalk`Scan the QR code above to open in a {bold development build}.`;
qrMessage += ` (${learnMore('https://expo.fyi/start')})`;
if (!env.EXPO_NO_QR_CODE) {
const qr = printQRCode(interstitialPageUrl ?? nativeRuntimeUrl);
rows -= qr.lines;
qr.print();

let qrMessage = '';
if (!options.devClient) {
qrMessage = `Scan the QR code above to open in ${chalk`{bold Expo Go}`}.`;
} else {
qrMessage = chalk`Scan the QR code above to open in a {bold development build}.`;
qrMessage += ` (${learnMore('https://expo.fyi/start')})`;
}
rows--;
Log.log(printItem(qrMessage, { dim: true }));
}
rows--;
Log.log(printItem(qrMessage, { dim: true }));

if (interstitialPageUrl) {
rows--;
Expand Down
4 changes: 4 additions & 0 deletions packages/@expo/cli/src/utils/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ class Env {
get EXPO_NO_REDIRECT_PAGE() {
return boolish('EXPO_NO_REDIRECT_PAGE', false);
}
/** Disable printing the QR code in the interactive Terminal UI. */
get EXPO_NO_QR_CODE(): boolean {
return boolish('EXPO_NO_QR_CODE', false);
}
/** The React Metro port that's baked into react-native scripts and tools. */
get RCT_METRO_PORT() {
return int('RCT_METRO_PORT', 0);
Expand Down
2 changes: 2 additions & 0 deletions packages/expo-audio/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

### 💡 Others

- [android] Make in-memory preload cache. ([#43293](https://github.com/expo/expo/pull/43293) by [@alanjhughes](https://github.com/alanjhughes))

## 55.0.6 — 2026-02-16

### 🎉 New features
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,21 +224,24 @@ class AudioModule : Module() {

AsyncFunction("preload") Coroutine { source: AudioSource, _: Double ->
val uri = source.uri ?: return@Coroutine
val upstreamFactory = httpDataSourceFactory(source.headers)
AudioPreloadCache.preload(context, uri, upstreamFactory)
val factory = when (uri.toUri().scheme) {
"http", "https" -> httpDataSourceFactory(source.headers)
else -> DefaultDataSource.Factory(context)
}
AudioPreloadManager.preload(uri, factory)
}

AsyncFunction("clearPreloadedSource") Coroutine { source: AudioSource ->
val uri = source.uri ?: return@Coroutine
AudioPreloadCache.clearSource(context, uri)
AudioPreloadManager.clearSource(uri)
}

AsyncFunction("clearAllPreloadedSources") Coroutine { ->
AudioPreloadCache.clearAll(context)
AudioPreloadManager.clearAll()
}

AsyncFunction("getPreloadedSources") {
AudioPreloadCache.getPreloadedSources()
AudioPreloadManager.getPreloadedSources()
}

OnActivityEntersBackground {
Expand Down Expand Up @@ -299,7 +302,7 @@ class AudioModule : Module() {
recorders.values.forEach {
it.stopRecording()
}
AudioPreloadCache.release()
AudioPreloadManager.clearAll()
}
}

Expand Down Expand Up @@ -785,11 +788,14 @@ class AudioModule : Module() {
else -> MediaItem.fromUri(uri)
}

val factory = when (uri.scheme) {
"http", "https" -> {
AudioPreloadCache.createCacheDataSourceFactory(context, httpDataSourceFactory(source.headers))
val preloadedBytes = AudioPreloadManager.get(uriString)
val factory: DataSource.Factory = if (preloadedBytes != null) {
InMemoryDataSourceFactory(preloadedBytes)
} else {
when (uri.scheme) {
"http", "https" -> httpDataSourceFactory(source.headers)
else -> DefaultDataSource.Factory(context)
}
else -> DefaultDataSource.Factory(context)
}
return buildMediaSourceFactory(factory, mediaItem)
}
Expand Down
Loading
Loading