diff --git a/apps/bare-expo/ios/BareExpo/Info.plist b/apps/bare-expo/ios/BareExpo/Info.plist index 2daea3098d1b99..b09285d61cff74 100644 --- a/apps/bare-expo/ios/BareExpo/Info.plist +++ b/apps/bare-expo/ios/BareExpo/Info.plist @@ -80,6 +80,12 @@ NSAllowsLocalNetworking + NSBonjourServices + + _expo._tcp + + NSLocalNetworkUsageDescription + Expo Dev Launcher uses the local network to discover and connect to development servers running on your computer. NSCalendarsFullAccessUsageDescription Allow $(PRODUCT_NAME) to access your calendars NSCalendarsUsageDescription diff --git a/apps/bare-expo/ios/Podfile.lock b/apps/bare-expo/ios/Podfile.lock index edfab839ad918d..d551dd45987912 100644 --- a/apps/bare-expo/ios/Podfile.lock +++ b/apps/bare-expo/ios/Podfile.lock @@ -38,7 +38,7 @@ PODS: - EXManifests/Tests (55.0.9): - ExpoModulesCore - ExpoModulesTestCore - - Expo (55.0.0): + - Expo (55.0.1): - ExpoModulesCore - hermes-engine - RCTRequired @@ -63,15 +63,15 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - expo-dev-client (55.0.8): + - expo-dev-client (55.0.9): - EXManifests - expo-dev-launcher - expo-dev-menu - expo-dev-menu-interface - EXUpdatesInterface - - expo-dev-launcher (55.0.9): + - expo-dev-launcher (55.0.10): - EXManifests - - expo-dev-launcher/Main (= 55.0.9) + - expo-dev-launcher/Main (= 55.0.10) - expo-dev-menu - expo-dev-menu-interface - ExpoModulesCore @@ -100,7 +100,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - expo-dev-launcher/Main (55.0.9): + - expo-dev-launcher/Main (55.0.10): - EXManifests - expo-dev-launcher/Unsafe - expo-dev-menu @@ -131,7 +131,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - expo-dev-launcher/Tests (55.0.9): + - expo-dev-launcher/Tests (55.0.10): - EXManifests - expo-dev-menu - expo-dev-menu-interface @@ -166,7 +166,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - expo-dev-launcher/Unsafe (55.0.9): + - expo-dev-launcher/Unsafe (55.0.10): - EXManifests - expo-dev-menu - expo-dev-menu-interface @@ -196,8 +196,8 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - expo-dev-menu (55.0.8): - - expo-dev-menu/Main (= 55.0.8) + - expo-dev-menu (55.0.9): + - expo-dev-menu/Main (= 55.0.9) - hermes-engine - RCTRequired - RCTTypeSafety @@ -220,7 +220,7 @@ PODS: - ReactNativeDependencies - Yoga - expo-dev-menu-interface (55.0.1) - - expo-dev-menu/Main (55.0.8): + - expo-dev-menu/Main (55.0.9): - EXManifests - expo-dev-menu-interface - ExpoModulesCore @@ -246,7 +246,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - expo-dev-menu/Tests (55.0.8): + - expo-dev-menu/Tests (55.0.9): - ExpoModulesTestCore - hermes-engine - Nimble @@ -272,7 +272,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - expo-dev-menu/UITests (55.0.8): + - expo-dev-menu/UITests (55.0.9): - hermes-engine - RCTRequired - RCTTypeSafety @@ -297,7 +297,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - Expo/Tests (55.0.0): + - Expo/Tests (55.0.1): - ExpoModulesCore - hermes-engine - RCTRequired @@ -344,11 +344,11 @@ PODS: - ExpoModulesCore - ExpoBrightness (55.0.8): - ExpoModulesCore - - ExpoBrownfield (55.0.10): + - ExpoBrownfield (55.0.11): - ExpoModulesCore - ExpoCalendar (55.0.8): - ExpoModulesCore - - ExpoCamera (55.0.8): + - ExpoCamera (55.0.9): - ExpoModulesCore - ZXingObjC/OneD - ZXingObjC/PDF417 @@ -454,7 +454,7 @@ PODS: - React-Core - ExpoMeshGradient (55.0.8): - ExpoModulesCore - - ExpoModulesCore (55.0.11): + - ExpoModulesCore (55.0.12): - ExpoModulesJSI - hermes-engine - RCTRequired @@ -479,7 +479,7 @@ PODS: - ReactNativeDependencies - RNWorklets - Yoga - - ExpoModulesCore/Tests (55.0.11): + - ExpoModulesCore/Tests (55.0.12): - ExpoModulesJSI - ExpoModulesTestCore - hermes-engine @@ -505,12 +505,12 @@ PODS: - ReactNativeDependencies - RNWorklets - Yoga - - ExpoModulesJSI (55.0.11): + - ExpoModulesJSI (55.0.12): - hermes-engine - React-Core - React-runtimescheduler - ReactCommon - - ExpoModulesJSI/Tests (55.0.11): + - ExpoModulesJSI/Tests (55.0.12): - hermes-engine - React-Core - React-runtimescheduler @@ -529,7 +529,7 @@ PODS: - ExpoModulesTestCore - ExpoPrint (55.0.8): - ExpoModulesCore - - ExpoRouter (55.0.0): + - ExpoRouter (55.0.1): - ExpoModulesCore - RNScreens - ExpoScreenCapture (55.0.8): @@ -569,7 +569,7 @@ PODS: - ExpoModulesCore - ExpoSplashScreen (55.0.9): - ExpoModulesCore - - ExpoSQLite (55.0.9): + - ExpoSQLite (55.0.10): - ExpoModulesCore - ExpoStoreReview (55.0.8): - ExpoModulesCore @@ -582,7 +582,7 @@ PODS: - UMAppLoader - ExpoTrackingTransparency (55.0.8): - ExpoModulesCore - - ExpoUI (55.0.0): + - ExpoUI (55.0.1): - ExpoModulesCore - React-RCTFabric - ExpoVideo (55.0.9): @@ -3809,10 +3809,10 @@ SPEC CHECKSUMS: EXConstants: a16ad8db13865e97aaecf64bb92e8ad8e8ce1ae8 EXJSONUtils: 0080c14b673cfa9a6be5e3fe429768ffe3d42dfb EXManifests: 22ec6b0abf4e9b54ea22624aa955cf68d6c90590 - Expo: f9523a607da84c34e453f22bde9b744d9ee0bbae - expo-dev-client: dd9d8f321697cb514bc5c60a87bc16428af62aa0 - expo-dev-launcher: b48349b5e18651f07d21562acd6c84519eab515a - expo-dev-menu: e912616f116651fd081bc4d0f3d2374a1a67d705 + Expo: 542aada019f99dbcd3675ebe0f3e070d6eb0c349 + expo-dev-client: de1af4570c4d213e52b700f701914521270ad93d + expo-dev-launcher: 393a0d0d91dfcd7d38d9744ccb2e3648c8d5a258 + expo-dev-menu: 1eed01e48a4d17539c04dd3d5c8d68c62f64daaa expo-dev-menu-interface: bf6f816d29b45bec038080790963c635e8d588c2 ExpoAgeRange: 5e8b40e46734155b4b974d4d331ea3a156d82d94 ExpoAppIntegrity: 35b85ca2476cf66b585aa33461f23734d0223658 @@ -3825,9 +3825,9 @@ SPEC CHECKSUMS: ExpoBlob: e6d05058d87eab9fbf5e34084e09083222a1b03b ExpoBlur: 7a5722ca5c8d9e8a2fe2d3be7cb61071b585ca11 ExpoBrightness: 39a2b32389e4648fe9a687b8f59363d83b73aff3 - ExpoBrownfield: 0b78f47c2c3d8328b75597905c55f8222e236123 + ExpoBrownfield: 03cd102d392fbb31df1e344c012fc4370470ac6d ExpoCalendar: 4273a695e47855b6f5aa8a4c09d6b3c0207586cc - ExpoCamera: 10eb7b317fcf0bdf1bb3e5d6eedccd08263c9b57 + ExpoCamera: af28a6cb21de33372fc5b41fd7db101d3d692f74 ExpoCellular: f1d7a24904483cfca00659a59329ffc643602c77 ExpoClipboard: ea1c19f29543c3f84abcbc500c6f1a62d954fd5c ExpoContacts: 2899427888417b4f06eba4072ff4c3871ffbbd02 @@ -3856,13 +3856,13 @@ SPEC CHECKSUMS: ExpoMaps: cc2eaf2c1a27a7f8c816224be7bf02bc8721fedf ExpoMediaLibrary: 68ca21908d9063c823a89b05ec8354b819e6302c ExpoMeshGradient: 3cf846fe392cee87c16d3208d03326712fe1c906 - ExpoModulesCore: 25b7c92ef1893085a0b04c86eae3d394c02a929c - ExpoModulesJSI: b2ced1ebf90801522b409510480e4ae45f1735d7 + ExpoModulesCore: fff90aba5d98dcfc675968096da6780c90392756 + ExpoModulesJSI: 1733437df661254d42bc5b1f030e6ba30b758b63 ExpoModulesTestCore: 382d7b11f61dd661215fbe33d8ce6c95d6c09e99 ExpoNetwork: 018e4e16afdaff30c5002fadf64daab55bc20de0 ExpoNotifications: 0293112699b35aa26f6e9e1fcecee0323f3187dc ExpoPrint: 744a2ca8033698b749389290d96f4ec836027aed - ExpoRouter: 53b441f798a0d26cc7b02d0ac28ec42e5d7ca18b + ExpoRouter: 7976ca087cc8e88f5581bc81df29d1644ea6dfb9 ExpoScreenCapture: a4b2159b48fd2514a99f426778da31d1f0a9736f ExpoScreenOrientation: e042b7f121c3b3463f9486189785d568ff57739e ExpoSecureStore: 7837b892a89ad8d28b64d9302b657e8b6ebae250 @@ -3871,13 +3871,13 @@ SPEC CHECKSUMS: ExpoSMS: d99e4e7ea518fbbd9509a98cc7a96a2fbd5a1bbd ExpoSpeech: cedb9452193282ab67cd6c6b06e0ffa6c4e2754f ExpoSplashScreen: 1ad4b0ceabf66f739f34d92ef767283ba8d42f9c - ExpoSQLite: 859262262974f29e75d78b07fe4cf4a2586a567e + ExpoSQLite: c01882b3ce0d20ad2351213dbd081a6e0e1f8837 ExpoStoreReview: 661278660a03dfdd2c9877b9b177f777b3514262 ExpoSymbols: 237882b097b55437cf37b36b21d8a4892f07e782 ExpoSystemUI: c4c5b9ba9a5a4713c70f3d68c3bcf3221f2198f2 ExpoTaskManager: 1a197d3c489d5ed7ae824f7d6b98404e3a662ff2 ExpoTrackingTransparency: a45cd07cbab513b38c44a146f522aefbf4f6dfdf - ExpoUI: 93c458a971e6d7320a7e09c5a052f06e47fe9a11 + ExpoUI: 3724a83b66498c11f5c248e4e2bb4ad6b680cc4c ExpoVideo: 570228bc29b15071627f295afa1407ea3d196934 ExpoVideoThumbnails: 7e5f6bddec993b7930b41fd47646a27e4f8133e9 ExpoWebBrowser: 19c5d250e0c101027677970a5f2fc635d9df2e73 @@ -3885,7 +3885,7 @@ SPEC CHECKSUMS: EXUpdates: c5a64985f393cf4f8beb4463f86a885c90b4fccc EXUpdatesInterface: 26412751a0f7a7130614655929e316f684552aab FBLazyVector: 32e9ed0301d0fcbc1b2b341dd7fcbf291f51eb83 - hermes-engine: 9e7dfb4eb5867a67fb30760f9f6f8646b9f77d28 + hermes-engine: 1566042511e927d64254f2efe08ae744a5eb9a00 libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7 libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 @@ -3903,7 +3903,7 @@ SPEC CHECKSUMS: React: f4edc7518ccb0b54a6f580d89dd91471844b4990 React-callinvoker: 79ef4e3f1c021571f6d2dafbe45ca432b2f3a146 React-Core: 469995a2b6ef0ffff38ed123ccd202287703939e - React-Core-prebuilt: b5ea7a06af79de343ee95cb48e56d682a29763ab + React-Core-prebuilt: e71199b350bcaeade83eea6e463e818dcc46d718 React-CoreModules: db3b65cb984dfc7e0b00db517712cff8d938fc3f React-cxxreact: 8551bebcc6bc624ce774dccae20c383844aa9d06 React-debug: 4f6739c820d7da9c20f48caa985573b6a847e5f5 @@ -3973,7 +3973,7 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 1976cdf5076a7e34718a56ead2f2069c7f54ebe9 ReactCodegen: 4e2863f450e4aec6b66a7e91d41a209aa4601c97 ReactCommon: 696163beb1630cf1f7590dbc8bfc542e40bdbe76 - ReactNativeDependencies: 5dd7d57944d42324ba59e9c95ae58b4f7c7a9882 + ReactNativeDependencies: d804b447c01215d21137868e3b5b5a920fc9f7f4 RNCAsyncStorage: e85a99325df9eb0191a6ee2b2a842644c7eb29f4 RNCMaskedView: 3c9d7586e2b9bbab573591dcb823918bc4668005 RNCPicker: e0149590451d5eae242cf686014a6f6d808f93c7 diff --git a/apps/bare-expo/package.json b/apps/bare-expo/package.json index 302dfb51032554..ae9c1f1200d2af 100644 --- a/apps/bare-expo/package.json +++ b/apps/bare-expo/package.json @@ -53,16 +53,16 @@ "@react-native-segmented-control/segmented-control": "2.5.7", "@shopify/flash-list": "2.0.2", "@shopify/react-native-skia": "2.4.18", - "expo": "~55.0.0", - "expo-brownfield": "~55.0.10", + "expo": "~55.0.1", + "expo-brownfield": "~55.0.11", "expo-build-properties": "~55.0.9", - "expo-camera": "~55.0.8", - "expo-dev-client": "~55.0.8", + "expo-camera": "~55.0.9", + "expo-dev-client": "~55.0.9", "expo-image": "~55.0.5", "expo-insights": "~55.0.10", "expo-network-addons": "~55.0.8", "expo-notifications": "~55.0.10", - "expo-router": "~55.0.0", + "expo-router": "~55.0.1", "expo-splash-screen": "~55.0.9", "lottie-react-native": "^7.3.4", "native-component-list": "*", @@ -85,7 +85,7 @@ "devDependencies": { "@babel/core": "^7.20.0", "@types/react": "~19.2.0", - "babel-preset-expo": "~55.0.7", + "babel-preset-expo": "~55.0.8", "expo-module-scripts": "^55.0.2", "jest": "^29.3.1" }, diff --git a/apps/brownfield-tester/expo-app/package.json b/apps/brownfield-tester/expo-app/package.json index de067f66b56abf..aa55398feebf42 100644 --- a/apps/brownfield-tester/expo-app/package.json +++ b/apps/brownfield-tester/expo-app/package.json @@ -13,16 +13,16 @@ "@react-navigation/bottom-tabs": "^7.7.3", "@react-navigation/elements": "^2.8.1", "@react-navigation/native": "^7.1.28", - "expo": "~55.0.0", - "expo-brownfield": "~55.0.10", + "expo": "~55.0.1", + "expo-brownfield": "~55.0.11", "expo-constants": "~55.0.7", "expo-device": "~55.0.9", - "expo-dev-menu": "~55.0.8", + "expo-dev-menu": "~55.0.9", "expo-font": "~55.0.4", "expo-glass-effect": "~55.0.7", "expo-image": "~55.0.5", "expo-linking": "~55.0.7", - "expo-router": "~55.0.0", + "expo-router": "~55.0.1", "expo-splash-screen": "~55.0.9", "expo-status-bar": "~55.0.4", "expo-symbols": "~55.0.4", diff --git a/apps/expo-go/android/app/build.gradle b/apps/expo-go/android/app/build.gradle index d6351975ae54f8..01b042abb0f2ee 100644 --- a/apps/expo-go/android/app/build.gradle +++ b/apps/expo-go/android/app/build.gradle @@ -24,6 +24,12 @@ buildscript { apply plugin: 'com.android.application' apply plugin: 'org.jetbrains.kotlin.android' apply plugin: 'com.google.firebase.crashlytics' + +def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") +apply from: expoModulesCorePlugin +applyKotlinExpoModulesCorePlugin() +useDefaultAndroidSdkVersions() + apply plugin: 'com.facebook.react' react { @@ -44,11 +50,6 @@ react { } android { - ndkVersion rootProject.ext.ndkVersion - - buildToolsVersion rootProject.ext.buildToolsVersion - compileSdk rootProject.ext.compileSdkVersion - namespace "host.exp.exponent" buildFeatures { @@ -57,8 +58,6 @@ android { defaultConfig { applicationId 'host.exp.exponent' - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion versionCode 229 versionName '55.0.2' diff --git a/apps/expo-go/android/expoview/build.gradle b/apps/expo-go/android/expoview/build.gradle index bb5f90fc7bdd57..5095b4baf6358f 100644 --- a/apps/expo-go/android/expoview/build.gradle +++ b/apps/expo-go/android/expoview/build.gradle @@ -21,15 +21,11 @@ plugins { alias libs.plugins.download id("org.jetbrains.kotlin.plugin.compose") version "2.2.21" id("org.jetbrains.kotlin.plugin.serialization") version "2.2.21" // Use your Kotlin version - id 'expo-module-gradle-plugin' } apply plugin: 'kotlin-kapt' apply from: new File(rootDir, "versioning_linking.gradle") apply plugin: 'com.apollographql.apollo' -expoModule { - canBePublished false -} def reactProperties = new Properties() file("${project(':packages:react-native:ReactAndroid').projectDir}/gradle.properties").withInputStream { reactProperties.load(it) } @@ -40,11 +36,18 @@ group = 'host.exp.exponent' version = '45.0.0' // WHEN_VERSIONING_REMOVE_TO_HERE +def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") +apply from: expoModulesCorePlugin +applyKotlinExpoModulesCorePlugin() +useDefaultAndroidSdkVersions() +useExpoPublishing() + repositories { mavenCentral() maven { url "https://jitpack.io" } } + apply plugin: 'com.facebook.react' apply plugin: 'org.jetbrains.kotlin.plugin.compose' diff --git a/apps/expo-go/ios/Podfile.lock b/apps/expo-go/ios/Podfile.lock index 60f798eb462136..2308bebd7ab1e8 100644 --- a/apps/expo-go/ios/Podfile.lock +++ b/apps/expo-go/ios/Podfile.lock @@ -20,7 +20,7 @@ PODS: - EXManifests/Tests (55.0.9): - ExpoModulesCore - ExpoModulesTestCore - - Expo (55.0.0): + - Expo (55.0.1): - boost - DoubleConversion - ExpoModulesCore @@ -51,10 +51,10 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - expo-dev-menu (55.0.8): + - expo-dev-menu (55.0.9): - boost - DoubleConversion - - expo-dev-menu/Main (= 55.0.8) + - expo-dev-menu/Main (= 55.0.9) - fast_float - fmt - glog @@ -81,7 +81,7 @@ PODS: - SocketRocket - Yoga - expo-dev-menu-interface (55.0.1) - - expo-dev-menu/Main (55.0.8): + - expo-dev-menu/Main (55.0.9): - boost - DoubleConversion - EXManifests @@ -113,7 +113,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - Expo/Tests (55.0.0): + - Expo/Tests (55.0.1): - boost - DoubleConversion - ExpoModulesCore @@ -166,7 +166,7 @@ PODS: - ExpoModulesCore - ExpoCalendar (55.0.8): - ExpoModulesCore - - ExpoCamera (55.0.8): + - ExpoCamera (55.0.9): - ExpoModulesCore - ZXingObjC/OneD - ZXingObjC/PDF417 @@ -246,7 +246,7 @@ PODS: - React-Core - ExpoMeshGradient (55.0.8): - ExpoModulesCore - - ExpoModulesCore (55.0.11): + - ExpoModulesCore (55.0.12): - boost - DoubleConversion - ExpoModulesJSI @@ -277,7 +277,7 @@ PODS: - RNWorklets - SocketRocket - Yoga - - ExpoModulesCore/Tests (55.0.11): + - ExpoModulesCore/Tests (55.0.12): - boost - DoubleConversion - ExpoModulesJSI @@ -309,12 +309,12 @@ PODS: - RNWorklets - SocketRocket - Yoga - - ExpoModulesJSI (55.0.11): + - ExpoModulesJSI (55.0.12): - hermes-engine - React-Core - React-runtimescheduler - ReactCommon - - ExpoModulesJSI/Tests (55.0.11): + - ExpoModulesJSI/Tests (55.0.12): - hermes-engine - React-Core - React-runtimescheduler @@ -333,7 +333,7 @@ PODS: - ExpoModulesTestCore - ExpoPrint (55.0.8): - ExpoModulesCore - - ExpoRouter (55.0.0): + - ExpoRouter (55.0.1): - ExpoModulesCore - RNScreens - ExpoScreenCapture (55.0.8): @@ -377,7 +377,7 @@ PODS: - ExpoModulesCore - ExpoSpeech (55.0.8): - ExpoModulesCore - - ExpoSQLite (55.0.9): + - ExpoSQLite (55.0.10): - ExpoModulesCore - ExpoStoreReview (55.0.8): - ExpoModulesCore @@ -4677,8 +4677,8 @@ SPEC CHECKSUMS: EXConstants: a16ad8db13865e97aaecf64bb92e8ad8e8ce1ae8 EXJSONUtils: 0080c14b673cfa9a6be5e3fe429768ffe3d42dfb EXManifests: 22ec6b0abf4e9b54ea22624aa955cf68d6c90590 - Expo: 9691c82db651015f9b241a89df57bf1cd6560899 - expo-dev-menu: 23fea4ca77f26bb5623266ae06219b1245c12c28 + Expo: e544060eeb863a5ec3b8c9fb8edc98c1f57c8972 + expo-dev-menu: 5088d5e44ace01845dcb5f15d58f8a62defb4d2d expo-dev-menu-interface: bf6f816d29b45bec038080790963c635e8d588c2 ExpoAgeRange: 5e8b40e46734155b4b974d4d331ea3a156d82d94 ExpoAppleAuthentication: b1ca252ee1d79888720df7af6bbb559b3ca57a77 @@ -4691,7 +4691,7 @@ SPEC CHECKSUMS: ExpoBlur: 7a5722ca5c8d9e8a2fe2d3be7cb61071b585ca11 ExpoBrightness: 39a2b32389e4648fe9a687b8f59363d83b73aff3 ExpoCalendar: 4273a695e47855b6f5aa8a4c09d6b3c0207586cc - ExpoCamera: 10eb7b317fcf0bdf1bb3e5d6eedccd08263c9b57 + ExpoCamera: af28a6cb21de33372fc5b41fd7db101d3d692f74 ExpoCellular: f1d7a24904483cfca00659a59329ffc643602c77 ExpoClipboard: ea1c19f29543c3f84abcbc500c6f1a62d954fd5c ExpoContacts: 2899427888417b4f06eba4072ff4c3871ffbbd02 @@ -4718,13 +4718,13 @@ SPEC CHECKSUMS: ExpoMailComposer: abf514c6a2488fbb62cde3b798bee6d6bf49a88f ExpoMediaLibrary: 68ca21908d9063c823a89b05ec8354b819e6302c ExpoMeshGradient: 3cf846fe392cee87c16d3208d03326712fe1c906 - ExpoModulesCore: d86fd1b53555f9b2245a4b08f98422738ef125ed - ExpoModulesJSI: b2ced1ebf90801522b409510480e4ae45f1735d7 + ExpoModulesCore: 47a9969448d03946a00a5e4f2d46a5b9c67efac9 + ExpoModulesJSI: 1733437df661254d42bc5b1f030e6ba30b758b63 ExpoModulesTestCore: 382d7b11f61dd661215fbe33d8ce6c95d6c09e99 ExpoNetwork: 018e4e16afdaff30c5002fadf64daab55bc20de0 ExpoNotifications: 0293112699b35aa26f6e9e1fcecee0323f3187dc ExpoPrint: 744a2ca8033698b749389290d96f4ec836027aed - ExpoRouter: 53b441f798a0d26cc7b02d0ac28ec42e5d7ca18b + ExpoRouter: 7976ca087cc8e88f5581bc81df29d1644ea6dfb9 ExpoScreenCapture: a4b2159b48fd2514a99f426778da31d1f0a9736f ExpoScreenOrientation: ba181744c7ac781952da30c3c2b8d7661df21446 ExpoSecureStore: 7837b892a89ad8d28b64d9302b657e8b6ebae250 @@ -4732,7 +4732,7 @@ SPEC CHECKSUMS: ExpoSharing: 28bcf1fda775876e00ad48647b6367f0c249b6b9 ExpoSMS: d99e4e7ea518fbbd9509a98cc7a96a2fbd5a1bbd ExpoSpeech: cedb9452193282ab67cd6c6b06e0ffa6c4e2754f - ExpoSQLite: 859262262974f29e75d78b07fe4cf4a2586a567e + ExpoSQLite: c01882b3ce0d20ad2351213dbd081a6e0e1f8837 ExpoStoreReview: 661278660a03dfdd2c9877b9b177f777b3514262 ExpoSymbols: 237882b097b55437cf37b36b21d8a4892f07e782 ExpoSystemUI: c4c5b9ba9a5a4713c70f3d68c3bcf3221f2198f2 @@ -4759,7 +4759,7 @@ SPEC CHECKSUMS: GoogleAppMeasurement: 8a82b93a6400c8e6551c0bcd66a9177f2e067aed GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d - hermes-engine: ca6495d9d859ae100566305c4d0afe7a0c777a46 + hermes-engine: d59202edb9173c808259b0945bb161d1e24963f0 libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7 libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 diff --git a/apps/expo-go/package.json b/apps/expo-go/package.json index dc98966f08c510..5113bc099da40b 100644 --- a/apps/expo-go/package.json +++ b/apps/expo-go/package.json @@ -43,11 +43,11 @@ "date-fns": "^2.28.0", "dedent": "^0.7.0", "es6-error": "^4.1.1", - "expo": "~55.0.0", + "expo": "~55.0.1", "expo-application": "~55.0.8", "expo-asset": "~55.0.7", "expo-blur": "~55.0.8", - "expo-camera": "~55.0.8", + "expo-camera": "~55.0.9", "expo-constants": "~55.0.7", "expo-device": "~55.0.9", "expo-font": "~55.0.4", @@ -55,7 +55,7 @@ "expo-linking": "^55.0.7", "expo-location": "~55.1.2", "expo-notifications": "~55.0.10", - "expo-router": "~55.0.0", + "expo-router": "~55.0.1", "expo-splash-screen": "~55.0.9", "expo-store-review": "~55.0.8", "expo-task-manager": "~55.0.9", diff --git a/apps/jest-expo-mock-generator/package.json b/apps/jest-expo-mock-generator/package.json index d6ec612ad25848..4870ba40222f69 100644 --- a/apps/jest-expo-mock-generator/package.json +++ b/apps/jest-expo-mock-generator/package.json @@ -6,7 +6,7 @@ "main": "index.js", "dependencies": { "@expo/mux": "^1.0.7", - "expo": "~55.0.0", + "expo": "~55.0.1", "expo-clipboard": "~55.0.8", "react": "19.2.0", "react-native": "0.83.2" diff --git a/apps/minimal-tester/package.json b/apps/minimal-tester/package.json index 40a32ba623edd4..26a54f23cccd47 100644 --- a/apps/minimal-tester/package.json +++ b/apps/minimal-tester/package.json @@ -9,13 +9,13 @@ "eject": "expo eject" }, "dependencies": { - "expo": "~55.0.0", + "expo": "~55.0.1", "expo-apple-authentication": "~55.0.8", "expo-blur": "~55.0.8", - "expo-brownfield": "~55.0.10", + "expo-brownfield": "~55.0.11", "expo-build-properties": "~55.0.9", - "expo-camera": "~55.0.8", - "expo-dev-client": "~55.0.8", + "expo-camera": "~55.0.9", + "expo-dev-client": "~55.0.9", "expo-image": "~55.0.5", "expo-linear-gradient": "~55.0.8", "expo-splash-screen": "~55.0.9", diff --git a/apps/native-component-list/package.json b/apps/native-component-list/package.json index dc99f01ab2f038..c42f0b1a0f0603 100644 --- a/apps/native-component-list/package.json +++ b/apps/native-component-list/package.json @@ -36,7 +36,7 @@ "@expo-google-fonts/inter": "^0.2.3", "@expo/react-native-action-sheet": "^4.1.1", "@expo/styleguide-base": "^1.0.1", - "@expo/ui": "~55.0.0", + "@expo/ui": "~55.0.1", "@expo/vector-icons": "^15.0.2", "@lottiefiles/dotlottie-react": "^0.10.1", "@lottiefiles/react-lottie-player": "^3.5.4", @@ -57,7 +57,7 @@ "@shopify/react-native-skia": "2.4.18", "date-format": "^2.0.0", "deep-object-diff": "^1.1.9", - "expo": "~55.0.0", + "expo": "~55.0.1", "expo-2d-context": "^0.0.4", "expo-age-range": "~0.2.10", "expo-apple-authentication": "~55.0.8", @@ -72,7 +72,7 @@ "expo-blur": "~55.0.8", "expo-brightness": "~55.0.8", "expo-calendar": "~55.0.8", - "expo-camera": "~55.0.8", + "expo-camera": "~55.0.9", "expo-cellular": "~55.0.8", "expo-checkbox": "~55.0.3", "expo-clipboard": "~55.0.8", @@ -117,7 +117,7 @@ "expo-sms": "~55.0.8", "expo-speech": "~55.0.8", "expo-splash-screen": "^55.0.9", - "expo-sqlite": "~55.0.9", + "expo-sqlite": "~55.0.10", "expo-standard-web-crypto": "~55.0.5", "expo-status-bar": "^55.0.4", "expo-store-review": "~55.0.8", diff --git a/apps/native-tests/package.json b/apps/native-tests/package.json index 7c170309eb0fe2..02bf59a98dadc2 100644 --- a/apps/native-tests/package.json +++ b/apps/native-tests/package.json @@ -9,9 +9,9 @@ "web": "expo start --web" }, "dependencies": { - "expo": "~55.0.0", + "expo": "~55.0.1", "expo-clipboard": "55.0.8", - "expo-dev-client": "~55.0.8", + "expo-dev-client": "~55.0.9", "expo-image": "~55.0.5", "expo-media-library": "55.0.9", "expo-notifications": "~55.0.10", diff --git a/apps/notification-tester/package.json b/apps/notification-tester/package.json index ac328475c29049..02da6973fcf83f 100644 --- a/apps/notification-tester/package.json +++ b/apps/notification-tester/package.json @@ -20,15 +20,15 @@ "dependencies": { "@react-navigation/bottom-tabs": "^7.7.3", "@react-navigation/native": "^7.1.28", - "expo": "~55.0.0", + "expo": "~55.0.1", "expo-font": "55.0.4", "expo-linking": "55.0.7", "expo-localization": "55.0.8", - "expo-router": "55.0.0", + "expo-router": "55.0.1", "expo-device": "55.0.9", "expo-constants": "55.0.7", - "expo-dev-client": "~55.0.8", - "expo-sqlite": "55.0.9", + "expo-dev-client": "~55.0.9", + "expo-sqlite": "55.0.10", "expo-status-bar": "55.0.4", "expo-notifications": "55.0.10", "expo-task-manager": "55.0.9", diff --git a/apps/router-e2e/package.json b/apps/router-e2e/package.json index 372feaed848f0a..d087cf0742b36c 100644 --- a/apps/router-e2e/package.json +++ b/apps/router-e2e/package.json @@ -60,12 +60,12 @@ "dependencies": { "@expo/dom-webview": "55.0.3", "@expo/vector-icons": "^15.0.2", - "expo": "~55.0.0", + "expo": "~55.0.1", "expo-haptics": "~55.0.8", "expo-linking": "~55.0.7", - "expo-router": "^55.0.0", + "expo-router": "^55.0.1", "expo-speech": "~55.0.8", - "expo-sqlite": "~55.0.9", + "expo-sqlite": "~55.0.10", "expo-symbols": "~55.0.4", "jose": "^5", "react": "19.2.0", diff --git a/apps/sandbox/package.json b/apps/sandbox/package.json index ebb62b97ce938f..257a32283688ba 100644 --- a/apps/sandbox/package.json +++ b/apps/sandbox/package.json @@ -10,9 +10,9 @@ "dependencies": { "@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/native": "^7.1.6", - "expo": "~55.0.0", + "expo": "~55.0.1", "expo-linking": "~55.0.7", - "expo-router": "^55.0.0", + "expo-router": "^55.0.1", "expo-splash-screen": "~55.0.9", "react": "19.1.1", "react-native": "0.83.2", @@ -20,7 +20,7 @@ "react-native-screens": "4.11.1-nightly-20250611-8b82e081e" }, "devDependencies": { - "babel-preset-expo": "~55.0.7" + "babel-preset-expo": "~55.0.8" }, "private": true } diff --git a/apps/test-suite/package.json b/apps/test-suite/package.json index 6f28601f8edc52..d9e848f2c75706 100644 --- a/apps/test-suite/package.json +++ b/apps/test-suite/package.json @@ -15,14 +15,14 @@ "@react-navigation/native": "^7.1.28", "@react-navigation/stack": "^7.6.7", "async-retry": "^1.1.4", - "expo": "~55.0.0", + "expo": "~55.0.1", "expo-application": "~55.0.8", "expo-asset": "~55.0.7", "expo-background-fetch": "~55.0.8", "expo-blob": "~55.0.8", "expo-brightness": "~55.0.8", "expo-calendar": "~55.0.8", - "expo-camera": "~55.0.8", + "expo-camera": "~55.0.9", "expo-cellular": "~55.0.8", "expo-checkbox": "~55.0.3", "expo-constants": "~55.0.7", @@ -42,7 +42,7 @@ "expo-notifications": "~55.0.10", "expo-secure-store": "~55.0.8", "expo-speech": "~55.0.8", - "expo-sqlite": "~55.0.9", + "expo-sqlite": "~55.0.10", "expo-task-manager": "~55.0.9", "expo-web-browser": "~55.0.9", "firebase": "^9.3.0", diff --git a/docs/.vale/writing-styles/expo-docs/HeadingCase.yml b/docs/.vale/writing-styles/expo-docs/HeadingCase.yml index eb965aa16c723a..e8255110048035 100644 --- a/docs/.vale/writing-styles/expo-docs/HeadingCase.yml +++ b/docs/.vale/writing-styles/expo-docs/HeadingCase.yml @@ -295,6 +295,7 @@ exceptions: - 'Method 2: Use' - 'Image manipulation without disk I/O' - Live Activities + - Live Activity - 'Prerequisite: Creating widget' - 'Prerequisite: Creating Live Activity' - Hiding the Tab bar diff --git a/docs/common/client-redirects.ts b/docs/common/client-redirects.ts index 90e7eb1aa6af9a..5db976a983da97 100644 --- a/docs/common/client-redirects.ts +++ b/docs/common/client-redirects.ts @@ -379,9 +379,7 @@ const RENAMED_PAGES: Record = { // Redirects as per Algolia 404 report '/workflow/build/building-on-ci': '/build/building-on-ci/', 'versions/latest/sdk/filesystem.md': '/versions/latest/sdk/filesystem/', - '/versions/v52.0.0/sdk/taskmanager': '/versions/v52.0.0/sdk/task-manager/', '/task-manager/': '/versions/latest/sdk/task-manager', - '/versions/v50.0.0/sdk/dev-client': '/versions/latest/sdk/dev-client/', // Deprecated Webpack support '/guides/customizing-webpack': '/archive/customizing-webpack', diff --git a/docs/components/plugins/api/APIStaticData.ts b/docs/components/plugins/api/APIStaticData.ts index 65630360aeabe4..b838ca1a89b677 100644 --- a/docs/components/plugins/api/APIStaticData.ts +++ b/docs/components/plugins/api/APIStaticData.ts @@ -292,14 +292,6 @@ export const hardcodedTypeLinks: Record = { }; export const sdkVersionHardcodedTypeLinks: Record> = { - 'v52.0.0': { - EventEmitter: '/versions/v52.0.0/sdk/expo/#eventemitter', - NativeModule: '/versions/v52.0.0/sdk/expo/#nativemodule', - SharedObject: '/versions/v52.0.0/sdk/expo/#sharedobject', - SharedRef: '/versions/v52.0.0/sdk/expo/#sharedref', - BufferOptions: '/versions/v52.0.0/sdk/video/#bufferoptions-1', - CameraPosition: '/versions/v52.0.0/sdk/maps/#cameraposition-2', - }, 'v53.0.0': { EventEmitter: '/versions/v53.0.0/sdk/expo/#eventemitter', NativeModule: '/versions/v53.0.0/sdk/expo/#nativemodule', @@ -349,6 +341,8 @@ export const sdkVersionHardcodedTypeLinks: Record + This command creates a new directory named **my-project** that contains your new Expo project. While you can name the project anything, this guide uses **my-project** for consistency. The new project includes an example TypeScript application to help you get started. diff --git a/docs/pages/brownfield/isolated-approach.mdx b/docs/pages/brownfield/isolated-approach.mdx index a2c5d23724af6f..1da5b70102be07 100644 --- a/docs/pages/brownfield/isolated-approach.mdx +++ b/docs/pages/brownfield/isolated-approach.mdx @@ -35,7 +35,7 @@ Learn more from the [Set up environment guide](/get-started/set-up-your-environm Run the following command to create a new directory named **my-project** that contains your new Expo project. While you can name the project anything, this guide uses **my-project** for consistency. - + The **my-project** does not need to live inside your existing native app and can be created in a separate repository or a monorepo. The new project includes an example TypeScript application to help you get started. diff --git a/docs/pages/build/setup.mdx b/docs/pages/build/setup.mdx index e38e9fdd254538..7678a6501d78ab 100644 --- a/docs/pages/build/setup.mdx +++ b/docs/pages/build/setup.mdx @@ -25,7 +25,7 @@ Don't have a project yet? No problem. It's quick and easy to create a "Hello wor Run the following command to create a new project: - + EAS Build also works well with projects created by `npx create-react-native-app`, `npx react-native`, `ignite-cli`, and other project bootstrapping tools. diff --git a/docs/pages/develop/app-navigation.mdx b/docs/pages/develop/app-navigation.mdx index de11cedfd47bd1..1f09d864406662 100644 --- a/docs/pages/develop/app-navigation.mdx +++ b/docs/pages/develop/app-navigation.mdx @@ -31,7 +31,7 @@ The library offers platform-specific look-and-feel with smooth animations and ge Expo Router is a file-based routing library for Expo and React Native projects and is a built on top of React Navigation. By following the **app** directory convention, it turns files into routes and is integrated with Expo for [Expo CLI](/more/expo-cli/) and bundling without additional setup. The library also adds features such as typed routes, dynamic routes, lazy bundling in development, static rendering for the web, and automatic deep linking. -New Expo projects created with `npx create-expo-app@latest` include Expo Router by default so you can ship cross-platform navigation quickly while still being able to reach for React Navigation APIs when needed. +New Expo projects created with `npx create-expo-app@latest --template default@sdk-55` include Expo Router by default so you can ship cross-platform navigation quickly while still being able to reach for React Navigation APIs when needed. -The EAS Update extension provides the ability to view and load published updates in your development client. - -It's available for all development clients `v0.9.0` and above. To install it, you'll need the most recent publish of `expo-updates`: +The EAS Update extension provides the ability to view and load published updates in your development client. To install it, you'll need the most recent publish of `expo-updates`: diff --git a/docs/pages/develop/unit-testing.mdx b/docs/pages/develop/unit-testing.mdx index 6ae305b26936b1..40c3ba716629a3 100644 --- a/docs/pages/develop/unit-testing.mdx +++ b/docs/pages/develop/unit-testing.mdx @@ -153,7 +153,7 @@ A unit test checks the smallest unit of code, usually a function. To write your -Inside the **app** directory of your project, create a new file called **index.tsx**, and the following code to render a simple component: +Inside the **src/app** directory of your project, create a new file called **index.tsx**, and the following code to render a simple component: ```tsx index.tsx import { PropsWithChildren } from 'react'; @@ -187,11 +187,11 @@ const styles = StyleSheet.create({ -Create a **\_\_tests\_\_** directory at the root of your project's directory. If this directory already exists in your project, use that. Then, create a new file called **HomeScreen-test.tsx**. The `jest-expo` preset customizes the Jest configuration to also identify files with **-test.ts|tsx** extensions as tests. +Create a **\_\_tests\_\_** directory at the root of your project's directory. If this directory already exists in your project, use that. Then, create a new file called **home-screen-test.tsx**. The `jest-expo` preset customizes the Jest configuration to also identify files with **-test.ts|tsx** extensions as tests. -Add the following example code in **HomeScreen-test.tsx**: +Add the following example code in **home-screen-test.tsx**: -```tsx HomeScreen-test.tsx +```tsx home-screen-test.tsx import { render } from '@testing-library/react-native'; import HomeScreen, { CustomText } from '@/app/index'; @@ -227,9 +227,9 @@ An example structure of tests next to the **components** directory is shown belo @@ -237,10 +237,10 @@ Alternatively, you can have multiple **\_\_tests\_\_** sub-directories for diffe @@ -252,9 +252,9 @@ It's all about preferences, and it is up to you to decide how you want to organi A [snapshot test](https://jestjs.io/docs/en/snapshot-testing) is used to make sure that UI stays consistent, especially when a project is working with global styles that are potentially shared across components. -To add a snapshot test for ``, add the following code snippet in the `describe()` in **HomeScreen-test.tsx**: +To add a snapshot test for ``, add the following code snippet in the `describe()` in **home-screen-test.tsx**: -```tsx HomeScreen-test.tsx +```tsx home-screen-test.tsx describe('', () => { /* @hide ... */ /* @end */ diff --git a/docs/pages/develop/user-interface/assets.mdx b/docs/pages/develop/user-interface/assets.mdx index 6c36568e28c61c..d458aa3920ec98 100644 --- a/docs/pages/develop/user-interface/assets.mdx +++ b/docs/pages/develop/user-interface/assets.mdx @@ -16,7 +16,7 @@ When an asset is stored in your project's file system, it can be embedded in you For example, to render an image called **example.png** in **App.js**, you can use `require` to import the image from the project's **assets/images** directory and pass it to the `` component: -```tsx app/index.tsx +```tsx src/app/index.tsx ``` @@ -71,7 +71,7 @@ After embedding the asset with the config plugin, [create a new development buil For example, the **example.png** is linked by the above config plugin. You can directly import it into your component and use its resource name as the URI. Note that when rendering assets without using `require`, you need to explicitly provide a width / height. -```tsx app/index.tsx +```tsx src/app/index.tsx import { Image } from 'expo-image'; /* @hide ... */ /* @end */ @@ -100,7 +100,7 @@ Install the `expo-asset` library. Import the [`useAssets`](/versions/latest/sdk/asset/#useassetsmoduleids) hook from the `expo-asset` library in your screen component: -```tsx app/index.tsx +```tsx src/app/index.tsx import { useAssets } from 'expo-asset'; export default function HomeScreen() { diff --git a/docs/pages/develop/user-interface/color-themes.mdx b/docs/pages/develop/user-interface/color-themes.mdx index 5ededdabc6533c..54793f57709100 100644 --- a/docs/pages/develop/user-interface/color-themes.mdx +++ b/docs/pages/develop/user-interface/color-themes.mdx @@ -96,13 +96,13 @@ The `userInterfaceStyle` property supports the following values: To detect the color scheme in your project, use `Appearance` or `useColorScheme` from `react-native`: -```tsx app/index.tsx +```tsx src/app/index.tsx import { Appearance, useColorScheme } from 'react-native'; ``` Then, you can use `useColorScheme()` hook as shown below: -```tsx app/index.tsx +```tsx src/app/index.tsx function MyComponent() { let colorScheme = useColorScheme(); diff --git a/docs/pages/develop/user-interface/fonts.mdx b/docs/pages/develop/user-interface/fonts.mdx index 1ab4d16956b8f6..e2bc928b4110cb 100644 --- a/docs/pages/develop/user-interface/fonts.mdx +++ b/docs/pages/develop/user-interface/fonts.mdx @@ -187,9 +187,9 @@ The [`expo-splash-screen`](/versions/latest/sdk/splash-screen/) library provides -Map the font file using the `useFonts` hook in a top level component such as the root layout (**app/layout.tsx**) file in your project: +Map the font file using the `useFonts` hook in a top-level component such as the root layout (**src/app/\_layout.tsx**) file in your project: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx /* @info Import useFonts hook from expo-font. */ import { useFonts } from 'expo-font'; /* @end */ /* @info Import SplashScreen so that when the fonts are not loaded, we can continue to show SplashScreen. */ import * as SplashScreen from 'expo-splash-screen'; /* @end */ import {useEffect} from 'react'; @@ -321,9 +321,9 @@ The [`expo-splash-screen`](/versions/latest/sdk/splash-screen/) library provides -After installing the font package, map the font using the `useFonts` hook in a top level component such as the root layout (**app/layout.tsx**) file in your project: +After installing the font package, map the font using the `useFonts` hook in a top-level component such as the root layout (**src/app/\_layout.tsx**) file in your project: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx // Rest of the import statements /* @info Import Inter_900Black and useFonts hook from @expo-google-fonts/inter*/ import { Inter_900Black, useFonts } from '@expo-google-fonts/inter'; @@ -419,7 +419,7 @@ A platform's default font is usually easy-to-read. However, don't be surprised w When the icons from `@expo/vector-icons` library load for the first time, they appear as invisible icons in your app. Once they load, they're cached for all the app's subsequent usage. To avoid showing invisible icons on your app's first load, preload during the initial loading screen with [`useFonts`](/versions/latest/sdk/font/#usefontsmap). For example: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { useFonts } from 'expo-font'; import Ionicons from '@expo/vector-icons/Ionicons'; diff --git a/docs/pages/develop/user-interface/safe-areas.mdx b/docs/pages/develop/user-interface/safe-areas.mdx index 20ff997f62a6ca..b825527cd3ba88 100644 --- a/docs/pages/develop/user-interface/safe-areas.mdx +++ b/docs/pages/develop/user-interface/safe-areas.mdx @@ -39,7 +39,7 @@ You can skip installing `react-native-safe-area-context` if you have created a p You can directly use [`SafeAreaView`](https://appandflow.github.io/react-native-safe-area-context/api/safe-area-view) to wrap the content of your screen's component. It is a regular `` with the safe area insets applied as extra padding or margin. -```tsx app/index.tsx +```tsx src/app/index.tsx import { Text } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; @@ -74,7 +74,7 @@ Alternate to `SafeAreaView`, you can use [`useSafeAreaInsets`](https://appandflo The example below uses the `useSafeAreaInsets` hook. It applies top padding to a `` using `insets.top`. -```tsx app/index.tsx +```tsx src/app/index.tsx import { Text, View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; diff --git a/docs/pages/develop/user-interface/splash-screen-and-app-icon.mdx b/docs/pages/develop/user-interface/splash-screen-and-app-icon.mdx index 9bad8b8eabf944..c234328d892cba 100644 --- a/docs/pages/develop/user-interface/splash-screen-and-app-icon.mdx +++ b/docs/pages/develop/user-interface/splash-screen-and-app-icon.mdx @@ -141,7 +141,7 @@ If your app does not use [Expo Prebuild](/workflow/prebuild) (formerly the _mana -For SDK versions below 52, in iOS development builds, launch screens can sometimes remain cached between builds, making it harder to test new images. Apple recommends clearing the _derived data_ directory before rebuilding, this can be done with Expo CLI by running: +For SDK 52 and earlier, in iOS development builds, launch screens can sometimes remain cached between builds, making it harder to test new images. Apple recommends clearing the _derived data_ directory before rebuilding, this can be done with Expo CLI by running: diff --git a/docs/pages/develop/user-interface/system-bars.mdx b/docs/pages/develop/user-interface/system-bars.mdx index 97dedd5e2a130f..ed2f86edd68ece 100644 --- a/docs/pages/develop/user-interface/system-bars.mdx +++ b/docs/pages/develop/user-interface/system-bars.mdx @@ -47,7 +47,7 @@ System bars can be customized to match your app's design and provide better visi The status bar appears at the top of the screen on both Android and iOS. You can customize it using [`expo-status-bar`](/versions/latest/sdk/status-bar). It provides a `StatusBar` component that you can use to control the appearance of the status bar while your app is running using the [`style`](/versions/latest/sdk/status-bar/#style) property or the [`setStatusBarStyle`](/versions/latest/sdk/status-bar/#statusbarsetstatusbarstylestyle-animated) method: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { StatusBar } from 'expo-status-bar'; export default function RootLayout() { @@ -68,7 +68,7 @@ To control the `StatusBar` visibility, you can set the [`hidden`](/versions/late On Android devices, the Navigation Bar appears at the bottom of the screen. You can customize it using the [`expo-navigation-bar`](/versions/latest/sdk/navigation-bar) library. It provides a `NavigationBar` component that you can use to set the style of the navigation bar using the [`setStyle`](/versions/latest/sdk/navigation-bar/#navigationbarsetstylestyle) method: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Platform } from 'react-native'; import * as NavigationBar from 'expo-navigation-bar'; import { useEffect } from 'react'; diff --git a/docs/pages/eas-update/getting-started.mdx b/docs/pages/eas-update/getting-started.mdx index 2a64f7fe39f604..8707b177ed6305 100644 --- a/docs/pages/eas-update/getting-started.mdx +++ b/docs/pages/eas-update/getting-started.mdx @@ -32,7 +32,7 @@ Don't have a project yet? No problem. It's quick and easy to create a "Hello wor Run the following command to create a new project: - + EAS Update also works well with projects created by `npx create-react-native-app`, `npx react-native`, `ignite-cli`, and other project bootstrapping tools. @@ -261,7 +261,7 @@ Publishing an update allows: - Fixing bugs and quickly updating non-native parts of a project instead of creating a new build - [Sharing a preview version of an app](/review/overview/) using internal distribution -To publish an update with changes from your project, use the `eas update` command, and specify a name for the channel, a `message` to describe the update and if using [EAS environment variables](/eas/environment-variables/), the environment pull variables from (leave unspecified to use the **.env** files present in your project directory): +To publish an update with changes from your project, use the `eas update` command, and specify a name for the channel, a `message` to describe the update, and the `--environment` flag to specify which [EAS environment variables](/eas/environment-variables/) to use (required in SDK 55 and later): -When the `--environment` flag is used, **only the environment variables from the specified EAS environment will be used during the update process** and won't use the **.env** files present in your project. This flag allows you to use the same environment variables while creating updates as with creating builds. +When the `--environment` flag is used, **only the environment variables from the specified EAS environment will be used during the update process** and won't use the **.env** files present in your project. This ensures the same environment variables are used for both your updates and builds. Expo CLI will substitute prefixed variables in your code (for example, `process.env.EXPO_PUBLIC_VARNAME`) with the corresponding plain text and sensitive environment variable values set on EAS servers for the environment specified with the `--environment` flag. Any `EXPO_PUBLIC_` variables in your application code will be replaced inline with the corresponding values from your EAS environment whether that is your local machine or your CI/CD server. -We recommend using the `--environment` flag to ensure the same environment variables are used both for your update and build jobs. +The `--environment` flag ensures the same environment variables are used for both your update and build jobs. > **info** The secret variables will not be available during the update process as they are not readable outside of EAS servers. diff --git a/docs/pages/eas/hosting/get-started.mdx b/docs/pages/eas/hosting/get-started.mdx index 0d2e8782cd3930..4bc950b1461cd0 100644 --- a/docs/pages/eas/hosting/get-started.mdx +++ b/docs/pages/eas/hosting/get-started.mdx @@ -33,7 +33,7 @@ Don't have a project yet? No problem. It's quick and easy to create a "Hello wor Run the following command to create a new project: - + diff --git a/docs/pages/eas/workflows/get-started.mdx b/docs/pages/eas/workflows/get-started.mdx index df419a9f43268d..4ef242e3c5f206 100644 --- a/docs/pages/eas/workflows/get-started.mdx +++ b/docs/pages/eas/workflows/get-started.mdx @@ -21,7 +21,7 @@ This page walks you through the process of creating your first EAS Workflow for You'll need to create a project with the following command: - + diff --git a/docs/pages/get-started/create-a-project.mdx b/docs/pages/get-started/create-a-project.mdx index 7f5cabada5c6a3..f80c3b6390e29f 100644 --- a/docs/pages/get-started/create-a-project.mdx +++ b/docs/pages/get-started/create-a-project.mdx @@ -15,9 +15,9 @@ We recommend starting with the default project created by `create-expo-app`. The To create a new project, run the following command: - + -> You can choose a different template by adding the [`--template` option](/more/create-expo/#--template). +> **Note:** During the SDK 55 transition period, `create-expo-app@latest` without the `--template` flag creates an SDK 54 project. If you plan to use Expo Go on a physical device, use an SDK 54 project. Otherwise, use `--template default@sdk-55` to create an SDK 55 project. You can also choose a different template by adding the [`--template` option](/more/create-expo/#--template). ## Next step diff --git a/docs/pages/get-started/start-developing.mdx b/docs/pages/get-started/start-developing.mdx index b26d7a1e8e334d..45a2c779b1226e 100644 --- a/docs/pages/get-started/start-developing.mdx +++ b/docs/pages/get-started/start-developing.mdx @@ -44,22 +44,21 @@ If it still doesn't work, it may be due to the router configuration — this ## Make your first change -Open the **app/(tabs)/index.tsx** file in your code editor and make a change. +Open the **src/app/index.tsx** file in your code editor and make a change. - -- Welcome! -+ Hello World! - - - + + + +- Welcome to Expo ++ Hello World! + + `} /> @@ -69,8 +68,8 @@ Expo Go is configured by default to automatically reload the app whenever a file - Make sure you have the [development mode enabled in Expo CLI](/workflow/development-mode#development-mode). - Close the Expo app and reopen it. -- Once the app is open again, shake your device to reveal the developer menu. If you are using an emulator, press Ctrl + M for Android or Cmd ⌘ + D for iOS. -- If you see **Enable Fast Refresh**, press it. If you see **Disable Fast Refresh**, dismiss the developer menu. Now try making another change. +- Once the app is open again, shake your device to reveal the developer menu. Press Cmd ⌘ + D. +- If you see **Fast Refresh** enabled, toggle it. If you see **Disable Fast Refresh**, dismiss the developer menu. Now try making another change. ` component that you can then import everywhere you need to render text strings. -```tsx MobileText.tsx +```tsx mobile-text.tsx import { Text as RNText, TextProps as RNTextProps } from 'react-native'; const MobileText = (props: RNTextProps) => { @@ -344,7 +344,7 @@ export default MobileText; For each text tag, you need to add the `lang` property with the current locale identifier. It's best to define this style in a custom reusable component. -```tsx WebText.tsx +```tsx web-text.tsx import { getLocales } from 'expo-localization'; const deviceLanguage = getLocales()[0].languageCode; diff --git a/docs/pages/guides/monorepos.mdx b/docs/pages/guides/monorepos.mdx index a65db7fc20c89a..64de9c5089d7d6 100644 --- a/docs/pages/guides/monorepos.mdx +++ b/docs/pages/guides/monorepos.mdx @@ -99,10 +99,10 @@ Before you create your app, you have to create the **apps** directory. This dire diff --git a/docs/pages/guides/new-architecture.mdx b/docs/pages/guides/new-architecture.mdx index ae4f4d32570a73..4f97c33015642d 100644 --- a/docs/pages/guides/new-architecture.mdx +++ b/docs/pages/guides/new-architecture.mdx @@ -85,7 +85,7 @@ You can configure the React Native Directory check in your **package.json** file **As of SDK 52**, all new projects will be initialized with the New Architecture enabled by default. - + ## Enable the New Architecture in an existing project diff --git a/docs/pages/guides/progressive-web-apps.mdx b/docs/pages/guides/progressive-web-apps.mdx index cea93323561c07..dc3055adf37600 100644 --- a/docs/pages/guides/progressive-web-apps.mdx +++ b/docs/pages/guides/progressive-web-apps.mdx @@ -103,9 +103,9 @@ Then add the manifest to the `` tag: -If you're using static or server rendering, the HTML entry can be dynamically created in **app/+html.tsx**. Here we'll link the manifest by adding a `` tag to the `` component: +If you're using static or server rendering, the HTML entry can be dynamically created in **src/app/+html.tsx**. Here we'll link the manifest by adding a `` tag to the `` component: -```tsx app/+html.tsx +```tsx src/app/+html.tsx import { ScrollViewStyleReset } from 'expo-router/html'; import type { PropsWithChildren } from 'react'; @@ -205,7 +205,7 @@ Then create the service worker registration script in the `` tag: Next, create a root HTML file for the app and add the service worker registration script: -```tsx app/+html.tsx +```tsx src/app/+html.tsx import { ScrollViewStyleReset } from 'expo-router/html'; import type { PropsWithChildren } from 'react'; diff --git a/docs/pages/guides/publishing-websites.mdx b/docs/pages/guides/publishing-websites.mdx index 06b9aebff511a6..b37aea5e4b26ce 100644 --- a/docs/pages/guides/publishing-websites.mdx +++ b/docs/pages/guides/publishing-websites.mdx @@ -304,8 +304,6 @@ In case you want to change the header for hosting add the following config for ` [GitHub Pages](https://pages.github.com/) allows you to publish a website directly from a GitHub repository. -> **warning** GitHub Pages deployment uses experimental `baseUrl` functionality that may not work as intended. - Start by initializing a new git repository in your project and configuring it to push to a GitHub repository. If you are already syncing your changes with a GitHub repository, skip this step. diff --git a/docs/pages/guides/tailwind.mdx b/docs/pages/guides/tailwind.mdx index 6ed0e478680957..923d53d1d775be 100644 --- a/docs/pages/guides/tailwind.mdx +++ b/docs/pages/guides/tailwind.mdx @@ -66,7 +66,7 @@ Add paths to all of your template files inside **tailwind.config.js**. module.exports = { content: [ // Ensure this points to your source code - './app/**/*.{js,tsx,ts,jsx}', + './src/app/**/*.{js,tsx,ts,jsx}', // If you use a `src` directory, add: './src/**/*.{js,tsx,ts,jsx}' // Do the same with `components`, `hooks`, `styles`, or any other top-level directories ], @@ -96,12 +96,12 @@ Create a **global.css** file in the root of your project and directives for each -Import the **global.css** file in your **app/\_layout.tsx** (if using Expo Router) or **index.js** file: +Import the **global.css** file in your **src/app/\_layout.tsx** (if using Expo Router) or **index.js** file: - + ```tsx -import '../global.css'; +import '../../global.css'; ``` ```tsx @@ -168,13 +168,13 @@ You can choose any name for this file. Using **global.css** is common practice. -Import your CSS file in your **app/\_layout.tsx** (if using Expo Router) or **index.js** file: +Import your CSS file in your **src/app/\_layout.tsx** (if using Expo Router) or **index.js** file: - + ```tsx -// If using Expo Router, import your CSS file in the app/_layout.tsx file -import '../global.css'; +// If using Expo Router, import your CSS file in the src/app/_layout.tsx file +import '../../global.css'; ``` ```tsx @@ -205,7 +205,7 @@ You now start your project and use Tailwind CSS classes in your components. You can use Tailwind with React DOM elements as-is: {/* prettier-ignore */} -```tsx app/index.tsx +```tsx src/app/index.tsx export default function Index() { return (
@@ -217,7 +217,7 @@ export default function Index() { You can use the `{ $$css: true }` syntax to use Tailwind with React Native web elements: -```tsx app/index.tsx +```tsx src/app/index.tsx import { View, Text } from 'react-native'; export default function Index() { @@ -238,11 +238,11 @@ Tailwind does not support Android and iOS platforms. You can use a compatibility Alternatively, you can use [DOM components](/guides/dom-components) to render your Tailwind web code in a `WebView` on native. {/* prettier-ignore */} -```tsx app/index.tsx +```tsx src/app/index.tsx 'use dom'; // Remember to import the global.css file in each DOM component. -import '../global.css'; +import '../../global.css'; export default function Page() { return ( diff --git a/docs/pages/guides/typescript.mdx b/docs/pages/guides/typescript.mdx index d6fc0492f53ab3..12cd377111e0bb 100644 --- a/docs/pages/guides/typescript.mdx +++ b/docs/pages/guides/typescript.mdx @@ -19,7 +19,7 @@ This guide provides a quick way to get started for a new project and also steps To create a new project, use the default template which includes base TypeScript configuration, example code, and basic navigation structure: - + After you create a new project using the command above, make sure to follow instructions from: diff --git a/docs/pages/guides/using-eslint.mdx b/docs/pages/guides/using-eslint.mdx index c22a2978b4bee3..90b01361b8ac0e 100644 --- a/docs/pages/guides/using-eslint.mdx +++ b/docs/pages/guides/using-eslint.mdx @@ -42,7 +42,7 @@ Running the above command will run the `lint` script from **package.json**. cmd={[ '# Example output for npx expo lint command', '', - '/app/components/HelloWave.tsx', + '/src/components/hello-wave.tsx', ' 22:6 warning React Hook useEffect has a missing dependency: "rotateAnimation".', ' Either include it or remove the dependency array react-hooks/exhaustive-deps', '', @@ -52,7 +52,7 @@ Running the above command will run the `lint` script from **package.json**. ### Environment configuration -ESLint is generally configured for a single environment. However, the source code is written in JavaScript in an Expo app that runs in multiple different environments. For example, the **app.config.js**, **metro.config.js**, **babel.config.js**, and **app/+html.tsx** files are run in a Node.js environment. It means they have access to the global `__dirname` variable and can use Node.js modules such as `path`. Standard Expo project files like **app/index.js** can be run in Hermes, Node.js, or the web browser. +ESLint is generally configured for a single environment. However, the source code is written in JavaScript in an Expo app that runs in multiple different environments. For example, the **app.config.js**, **metro.config.js**, **babel.config.js**, and **src/app/+html.tsx** files are run in a Node.js environment. It means they have access to the global `__dirname` variable and can use Node.js modules such as `path`. Standard Expo project files like **src/app/index.js** can be run in Hermes, Node.js, or the web browser. The approach to configure environment-specific globals differs between Flat config and legacy config: diff --git a/docs/pages/guides/using-logrocket.mdx b/docs/pages/guides/using-logrocket.mdx index 7abd39d244c169..3bb27d5fa2732e 100644 --- a/docs/pages/guides/using-logrocket.mdx +++ b/docs/pages/guides/using-logrocket.mdx @@ -33,9 +33,9 @@ Then, in your [app config](/workflow/configuration/), include the LogRocket conf } ``` -Finally, initialize LogRocket in your app in a top-level file, like **app/\_layout.tsx**: +Finally, initialize LogRocket in your app in a top-level file, like **src/app/\_layout.tsx**: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { useEffect } from 'react'; import * as Updates from 'expo-updates'; import LogRocket from '@logrocket/react-native'; diff --git a/docs/pages/guides/using-resend.mdx b/docs/pages/guides/using-resend.mdx index 1cf12111a60b26..76a1418a32e9d0 100644 --- a/docs/pages/guides/using-resend.mdx +++ b/docs/pages/guides/using-resend.mdx @@ -64,9 +64,9 @@ To enable using API Routes in your Expo project, you need to set the web.output } ``` -Then, [create an API route](/router/web/api-routes/#create-an-api-route) to handle email submissions. Inside the **app** directory, create a new file called **api/audience+api.ts**. The `+api.ts` extension is used by Expo Router to identify the file as an API Route. To test the integration, you can add the minimal code below to send an email to a recipient using Resend SDK: +Then, [create an API route](/router/web/api-routes/#create-an-api-route) to handle email submissions. Inside the **src/app** directory, create a new file called **api/audience+api.ts**. The `+api.ts` extension is used by Expo Router to identify the file as an API Route. To test the integration, you can add the minimal code below to send an email to a recipient using Resend SDK: -```tsx app/api/audience+api.ts +```tsx src/app/api/audience+api.ts import { Resend } from 'resend'; const resend = new Resend(process.env.RESEND_API_KEY); @@ -116,9 +116,9 @@ Note that only variables prefixed with `EXPO_PUBLIC_` can be used in the fronten ## Add a form to your Expo project -The following example code shows a simple form to collect email addresses from app users. In a real-world scenario, you would want to add validation and error handling to the form. For example, the following code is added to the **app/index.tsx** file: +The following example code shows a simple form to collect email addresses from app users. In a real-world scenario, you would want to add validation and error handling to the form. For example, the following code is added to the **src/app/index.tsx** file: -```tsx app/index.tsx +```tsx src/app/index.tsx import { useRef, useState } from 'react'; import { Alert, Pressable, StyleSheet, Text, TextInput, View } from 'react-native'; @@ -227,7 +227,7 @@ The `eas deploy --prod` command will: - Automatically create an EAS project if you haven't already - Prompt you to choose the preview URL for your project. Ensure that this URL is the same as the value of `EXPO_PUBLIC_BASE_URL` in your **.env.local** file. This will make the API route accessible from your Expo app while deployed in production -> **Note:** Before deployment, you need to use the `EXPO_PUBLIC_BASE_URL` as your hosted domain in your form screen (**app/index.tsx**). +> **Note:** Before deployment, you need to use the `EXPO_PUBLIC_BASE_URL` as your hosted domain in your form screen (**src/app/index.tsx**). diff --git a/docs/pages/guides/using-vexo.mdx b/docs/pages/guides/using-vexo.mdx index 64d071d77a6c15..2fb0c415476b4f 100644 --- a/docs/pages/guides/using-vexo.mdx +++ b/docs/pages/guides/using-vexo.mdx @@ -44,9 +44,9 @@ With a two-line integration, Vexo starts collecting data automatically, giving y }} /> -4. Initialize Vexo: Add the following code in your app's entry file (for example, **index.js**, **App.js**, or **app/\_layout.tsx** if using Expo Router): +4. Initialize Vexo: Add the following code in your app's entry file (for example, **index.js**, **App.js**, or **src/app/\_layout.tsx** if using Expo Router): - ```tsx app/_layout.tsx + ```tsx src/app/_layout.tsx import { vexo } from 'vexo-analytics'; // You may want to wrap this with `if (!__DEV__) { ... }` to only run Vexo in production. diff --git a/docs/pages/linking/ios-universal-links.mdx b/docs/pages/linking/ios-universal-links.mdx index d7abc1b2d0d207..17062673d0a044 100644 --- a/docs/pages/linking/ios-universal-links.mdx +++ b/docs/pages/linking/ios-universal-links.mdx @@ -180,9 +180,9 @@ If you're having trouble setting up the banner, run the following command to aut ### Add the meta tag to your statically rendered website -If you're building a [statically rendered website with Expo Router](/router/web/static-rendering), then add the HTML tag to the `` component in your [**app/+html.js** file](/router/web/static-rendering#root-html). +If you're building a [statically rendered website with Expo Router](/router/web/static-rendering), then add the HTML tag to the `` component in your [**src/app/+html.js** file](/router/web/static-rendering#root-html). -```tsx app/+html.tsx +```tsx src/app/+html.tsx import { type PropsWithChildren } from 'react'; export default function Root({ children }: PropsWithChildren) { diff --git a/docs/pages/llms.mdx b/docs/pages/llms.mdx index f2df8ccd91d229..4da0d787b1e8a9 100644 --- a/docs/pages/llms.mdx +++ b/docs/pages/llms.mdx @@ -16,6 +16,8 @@ At Expo, we support the [llms.txt](https://llmstxt.org/) initiative to provide d +- [/llms-sdk-v54.0.0.txt](/llms-sdk-v54.0.0.txt): Documentation for the Expo SDK v54.0.0 + - [/llms-sdk-v53.0.0.txt](/llms-sdk-v53.0.0.txt): Documentation for the Expo SDK v53.0.0 - [/llms-sdk-v52.0.0.txt](/llms-sdk-v52.0.0.txt): Documentation for the Expo SDK v52.0.0 diff --git a/docs/pages/modules/use-standalone-expo-module-in-your-project.mdx b/docs/pages/modules/use-standalone-expo-module-in-your-project.mdx index 9a0327d0bac6fa..5b1e82df8ac48f 100644 --- a/docs/pages/modules/use-standalone-expo-module-in-your-project.mdx +++ b/docs/pages/modules/use-standalone-expo-module-in-your-project.mdx @@ -172,8 +172,12 @@ Apart from publishing your module to npm, you can use it in your project in the To test the published module in a new project, create a new app and install the module as a dependency by running the following command: You can now use the module in your app! To test it, edit **app/(tabs)/index.tsx** and render the text message from **expo-settings**. diff --git a/docs/pages/more/create-expo.mdx b/docs/pages/more/create-expo.mdx index e6520c98a27f6c..b0be85bead1e10 100644 --- a/docs/pages/more/create-expo.mdx +++ b/docs/pages/more/create-expo.mdx @@ -14,13 +14,15 @@ To create a new project, run the following command: +> **Note:** During the SDK 55 transition period, `create-expo-app@latest` without the `--template` flag creates an SDK 54 project. If you plan to use Expo Go on a physical device, use an SDK 54 project. Otherwise, use `--template default@sdk-55` to create an SDK 55 project. + Running the above command will prompt you to enter the app name of your project. This app name is also used in the app config's [`name`](/versions/latest/config/app/#name) property. diff --git a/docs/pages/router/advanced/apple-handoff.mdx b/docs/pages/router/advanced/apple-handoff.mdx index d59c8a2158d867..612349929d689f 100644 --- a/docs/pages/router/advanced/apple-handoff.mdx +++ b/docs/pages/router/advanced/apple-handoff.mdx @@ -105,7 +105,7 @@ In development, you must start the website **before installing the app on your d In any route that you want to support handoff, use the `Head` component from `expo-router/head`: -```tsx app/index.tsx +```tsx src/app/index.tsx import Head from 'expo-router/head'; import { Text } from 'react-native'; @@ -135,7 +135,7 @@ The `expo-router/head` component supports the following meta tags: You may want to switch the values between platforms, for that you can use **Platform.select**: {/* prettier-ignore */} -```tsx app/index.tsx +```tsx src/app/index.tsx import Head from 'expo-router/head'; export default function App() { diff --git a/docs/pages/router/advanced/authentication.mdx b/docs/pages/router/advanced/authentication.mdx index 4475b2a9ce1468..2ec959a5649aaf 100644 --- a/docs/pages/router/advanced/authentication.mdx +++ b/docs/pages/router/advanced/authentication.mdx @@ -23,20 +23,20 @@ With Expo Router, all routes are always defined and accessible. You can use runt Controls what is protected], + ['src/app/_layout.tsx', Controls what is protected], [ - 'app/sign-in.tsx', + 'src/app/sign-in.tsx', Always accessible , ], [ - 'app/(app)/_layout.tsx', + 'src/app/(app)/_layout.tsx', Requires authorization , ], - ['app/(app)/index.tsx', Should be protected by the (app)/_layout], + ['src/app/(app)/index.tsx', Should be protected by the (app)/_layout], ]} /> @@ -48,7 +48,7 @@ To follow the above example, set up a [React Context provider](https://react.dev This provider uses a mock implementation. You can replace it with your own [authentication provider](/guides/authentication/). -```tsx ctx.tsx +```tsx src/ctx.tsx import { use, createContext, type PropsWithChildren } from 'react'; import { useStorageState } from './useStorageState'; @@ -100,7 +100,7 @@ export function SessionProvider({ children }: PropsWithChildren) { The following code snippet is a basic hook that persists tokens securely on native with [`expo-secure-store`](/versions/latest/sdk/securestore) and in local storage on web. {/* prettier-ignore */} -```tsx useStorageState.ts +```tsx src/useStorageState.ts import { useEffect, useCallback, useReducer } from 'react'; import * as SecureStore from 'expo-secure-store'; import { Platform } from 'react-native'; @@ -178,7 +178,7 @@ export function useStorageState(key: string): UseStateHook { Create a **SplashScreenController** to manage the splash screen. Authentication loading is asynchronous, so keep the splash screen visible until authentication loads. -```tsx splash.tsx +```tsx src/splash.tsx import { SplashScreen } from 'expo-router'; import { useSession } from './ctx'; @@ -201,11 +201,11 @@ export function SplashScreenController() { Add the `SessionProvider` to your root layout. This gives your entire app access to the authentication context. Ensure the `SplashScreenController` is inside the `SessionProvider`. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; -import { SessionProvider } from '../ctx'; -import { SplashScreenController } from '../splash'; +import { SessionProvider } from '@/ctx'; +import { SplashScreenController } from '@/splash'; export default function Root() { // Set up the auth context and render your layout inside of it. @@ -229,11 +229,11 @@ function RootNavigator() { Create the `/sign-in` screen. This screen toggles authentication using `signIn()`. Since this screen is outside the `(app)` group, the group's layout and authentication check do not run when rendering this screen. This lets logged-out users access this screen. -```tsx app/sign-in.tsx|collapseHeight=480 +```tsx src/app/sign-in.tsx|collapseHeight=480 import { router } from 'expo-router'; import { Text, View } from 'react-native'; -import { useSession } from '../ctx'; +import { useSession } from '@/ctx'; export default function SignIn() { const { signIn } = useSession(); @@ -258,9 +258,9 @@ export default function SignIn() { Now modify the `RootNavigator` to protect routes based on your `SessionProvider`. -```tsx app/_layout.tsx|collapseHeight=400 +```tsx src/app/_layout.tsx|collapseHeight=400 // All import statements remain the same except you need to import `useSession` from your `ctx.tsx` file. -import { SessionProvider, useSession } from '../ctx'; +import { SessionProvider, useSession } from '@/ctx'; // All of the above code remains unchanged. Update the `RootNavigator` to protect routes based on your `SessionProvider` below. @@ -287,10 +287,10 @@ function RootNavigator() { Implement an authenticated screen that lets users sign out. -```tsx app/(app)/index.tsx|collapseHeight=480 +```tsx src/app/(app)/index.tsx|collapseHeight=480 import { Text, View } from 'react-native'; -import { useSession } from '../../ctx'; +import { useSession } from '@/ctx'; export default function Index() { const { signOut } = useSession(); @@ -312,9 +312,9 @@ export default function Index() { -Create the **app/(app)/\_layout.tsx**: +Create the **src/app/(app)/\_layout.tsx**: -```tsx app/(app)/_layout.tsx +```tsx src/app/(app)/_layout.tsx import { Stack } from 'expo-router'; export default function AppLayout() { @@ -333,12 +333,12 @@ Another common pattern is to render a sign-in modal over the top of the app. Thi Modal presented over the root], - ['app/(app)/(root)/_layout.tsx', Protects child routes], + ['src/app/_layout.tsx', 'Declares global session context'], + 'src/app/(app)/_layout.tsx', + ['src/app/(app)/sign-in.tsx', Modal presented over the root], + ['src/app/(app)/(root)/_layout.tsx', Protects child routes], [ - 'app/(app)/(root)/index.tsx', + 'src/app/(app)/(root)/index.tsx', Requires authorization , @@ -346,7 +346,7 @@ Another common pattern is to render a sign-in modal over the top of the app. Thi ]} /> -```tsx app/(app)/_layout.tsx|collapseHeight=480 +```tsx src/app/(app)/_layout.tsx|collapseHeight=480 import { Stack } from 'expo-router'; export const unstable_settings = { diff --git a/docs/pages/router/advanced/custom-tabs.mdx b/docs/pages/router/advanced/custom-tabs.mdx index 32da5ee493e818..62422df0b29404 100644 --- a/docs/pages/router/advanced/custom-tabs.mdx +++ b/docs/pages/router/advanced/custom-tabs.mdx @@ -44,7 +44,7 @@ There are four components offered by `expo-router/ui` to create custom tab layou A bare minimum structure of a custom tab layout would consist of a `TabList` (containing `TabTrigger` components for each tab) and a`TabSlot`, all within the `Tabs` component, as shown here: -```tsx app/(tabs)/_layout.tsx|collapseHeight=440 +```tsx src/app/(tabs)/_layout.tsx|collapseHeight=440 import { Tabs, TabList, TabTrigger, TabSlot } from 'expo-router/ui'; import { Text } from 'react-native'; @@ -105,7 +105,7 @@ A `TabTrigger` can link to a deeply nested route. ` @@ -217,7 +217,7 @@ The `TabList` is both the configuration and default appearance of the `Tabs`, bu `TabTrigger` will forward an `isFocused` prop, so you can create a separate tab button component that reacts to focused status. -```tsx TabButton.tsx +```tsx tab-button.tsx import FontAwesome from '@expo/vector-icons/FontAwesome'; import { TabTriggerSlotProps } from 'expo-router/ui'; import { ComponentProps, Ref } from 'react'; diff --git a/docs/pages/router/advanced/drawer.mdx b/docs/pages/router/advanced/drawer.mdx index 4920df506c2069..a5e6932cdc300e 100644 --- a/docs/pages/router/advanced/drawer.mdx +++ b/docs/pages/router/advanced/drawer.mdx @@ -3,11 +3,17 @@ title: Drawer description: Learn how to use the Drawer layout in Expo Router. --- +import { ContentSpotlight } from '~/ui/components/ContentSpotlight'; import { Terminal } from '~/ui/components/Snippet'; import { Tabs, Tab } from '~/ui/components/Tabs'; A navigation drawer is a common pattern in mobile apps, it allows users to swipe open a menu from a side of their screen to expose navigation options. This menu is also typically toggleable through a button in the app's header. + + ## Installation @@ -39,7 +45,7 @@ To use [drawer navigator](https://reactnavigation.org/docs/drawer-based-navigati Now you can use the `Drawer` layout to create a drawer navigator. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Drawer } from 'expo-router/drawer'; export default function Layout() { @@ -49,7 +55,7 @@ export default function Layout() { To edit the drawer navigation menu labels, titles and screen options specific screens are required as follows: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Drawer } from 'expo-router/drawer'; export default function Layout() { diff --git a/docs/pages/router/advanced/modals.mdx b/docs/pages/router/advanced/modals.mdx index d6bc140267cf5a..15e51292f0ea80 100644 --- a/docs/pages/router/advanced/modals.mdx +++ b/docs/pages/router/advanced/modals.mdx @@ -37,7 +37,7 @@ For most use cases, you can use the `Modal` component and customize it according ## Modal screen using Expo Router -A modal screen is a file created inside the **app** directory and is used as a route within the existing stack. It is used for complex interactions that need to be part of the navigation system, such as multi-step forms where you can link to a specific screen after the process completes. +A modal screen is a file created inside the **src/app** directory and is used as a route within the existing stack. It is used for complex interactions that need to be part of the navigation system, such as multi-step forms where you can link to a specific screen after the process completes. Below is an example of how a modal screen works on different platforms: @@ -45,13 +45,13 @@ Below is an example of how a modal screen works on different platforms: ### Usage -To implement a modal route, create a screen called **modal.tsx** inside the **app** directory. Here's an example file structure: +To implement a modal route, create a screen called **modal.tsx** inside the **src/app** directory. Here's an example file structure: - + -The above file structure produces a layout where the `index` is the first route in the stack. Inside the root layout file (**app/\_layout.tsx**), you can add the `modal` route in the stack. To present it as a modal, set the `presentation` option to `modal` on the route. +The above file structure produces a layout where the `index` is the first route in the stack. Inside the root layout file (**src/app/\_layout.tsx**), you can add the `modal` route in the stack. To present it as a modal, set the `presentation` option to `modal` on the route. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export default function Layout() { @@ -74,7 +74,7 @@ export default function Layout() { You can use the `Link` component to navigate to the modal screen from the **index.tsx** file. {/* prettier-ignore */} -```tsx app/index.tsx|collapseHeight=350 +```tsx src/app/index.tsx|collapseHeight=350 import { Link } from 'expo-router'; import { StyleSheet, Text, View } from 'react-native'; @@ -106,7 +106,7 @@ const styles = StyleSheet.create({ The **modal.tsx** presents the contents of the modal. -```tsx app/modal.tsx|collapseHeight=250 +```tsx src/app/modal.tsx|collapseHeight=250 import { StyleSheet, Text, View } from 'react-native'; export default function Modal() { @@ -135,7 +135,7 @@ A modal loses its previous context when it is the current screen in the navigato - On web, the modal is presented as a separate route, and the dismiss behavior has to be provided manually using [`router.canGoBack()`](/router/navigating-pages/#imperative-navigation). Here's an example of how to dismiss the modal: {/* prettier-ignore */} -```tsx app/modal.tsx +```tsx src/app/modal.tsx import { Link, /* @info Import the router object which is used to navigate imperatively. */ router /* @end */} from 'expo-router'; import { StyleSheet, Text, View } from 'react-native'; @@ -168,7 +168,7 @@ const styles = StyleSheet.create({ By default on iOS, the modal has a dark background which hides the status bar. To change the status bar appearance, you can use the `Platform` API to check if the current platform is iOS and then use the [`StatusBar`](/versions/latest/sdk/status-bar/) component to change the appearance inside the **modal.tsx** file. {/* prettier-ignore */} -```tsx app/modal.tsx|collapseHeight=250 +```tsx src/app/modal.tsx|collapseHeight=250 import { StyleSheet, Text, View, Platform } from 'react-native'; import { StatusBar } from 'expo-status-bar'; @@ -216,7 +216,7 @@ Form sheet presents a modal as a bottom sheet that app users can drag between di To use form sheet, set the `presentation` option to `formSheet` on your modal screen: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export default function Layout() { @@ -246,7 +246,7 @@ Detents define the heights where the sheet can rest. Use `sheetAllowedDetents` t > **info** Android supports a maximum of 3 detents. iOS accepts any number of detents. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export default function Layout() { @@ -279,7 +279,7 @@ export default function Layout() { | `sheetCornerRadius` | `number` | Corner radius of the sheet in pixels. | | `sheetLargestUndimmedDetentIndex` | `number \| 'none' \| 'last'` | Largest detent index that keeps the background undimmed. | -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export default function Layout() { @@ -314,7 +314,7 @@ export default function Layout() { You can add a footer to the sheet that stays visible at all detent positions using a React component: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; import { View, Button } from 'react-native'; @@ -347,7 +347,7 @@ export default function Layout() { When using numeric detents, your modal content can use `flex: 1` to fill the available space within the sheet: -```tsx app/modal.tsx +```tsx src/app/modal.tsx import { StyleSheet, Text, View } from 'react-native'; export default function Modal() { diff --git a/docs/pages/router/advanced/native-intent.mdx b/docs/pages/router/advanced/native-intent.mdx index fe7dc16456c85d..5dcba5e5d0ebde 100644 --- a/docs/pages/router/advanced/native-intent.mdx +++ b/docs/pages/router/advanced/native-intent.mdx @@ -24,13 +24,13 @@ Expo Router will always evaluate a URL with the assumption that the URL targets In such scenarios, the URL needs to be rewritten to correctly target a route. -To facilitate this, create a special file called **+native-intent.tsx** at the top level of your project's **app** directory. This file exports a special [`redirectSystemPath`](/versions/latest/sdk/router/#nativeintent) method designed to handle URL/path processing. When invoked, it receives an `options` object with two attributes: `path` and `initial`. +To facilitate this, create a special file called **+native-intent.tsx** at the top level of your project's **src/app** directory. This file exports a special [`redirectSystemPath`](/versions/latest/sdk/router/#nativeintent) method designed to handle URL/path processing. When invoked, it receives an `options` object with two attributes: `path` and `initial`. - + Here's an example the applies practices on how `redirectSystemPath` is used inside **+native-intent.tsx** file. Following this example, you can ensure the stability and reliability of your app's URL processing functionality and mitigate the risk of unexpected errors and crashes. -```ts app/+native-intent.tsx +```ts src/app/+native-intent.tsx import ThirdPartyService from 'third-party-sdk'; export function redirectSystemPath({ path, initial }) { @@ -74,7 +74,7 @@ While your app is open, you can react to URL changes within your `_layout` files - **global**: Add the logic to your root `_layout` file - **localized**: Add a `_layout` file to an existing directory (or create a new [group directory](/router/basics/notation/#parentheses)) -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Slot, Redirect } from 'expo-router'; export default function RootLayout() { @@ -99,7 +99,7 @@ In native apps, an alternative way to rewrite a URL is to handle it within the [ Below is a basic example of how to send navigation events to an external service, such as an analytics or logging service. Consult with your provider for specific instructions. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import ThirdPartyService from 'third-party-sdk'; import { Slot, usePathname } from 'expo-router'; diff --git a/docs/pages/router/advanced/native-tabs.mdx b/docs/pages/router/advanced/native-tabs.mdx index 5a30a57a26ae54..2d376cbc7b2f2d 100644 --- a/docs/pages/router/advanced/native-tabs.mdx +++ b/docs/pages/router/advanced/native-tabs.mdx @@ -45,7 +45,7 @@ For other tab layouts see: You can use file-based routing to create a tabs layout. Here's an example file structure: - + The above file structure produces a layout with a tab bar at the bottom of the screen. The tab bar will have two tabs: **Home** and **Settings**. @@ -55,7 +55,7 @@ The above file structure produces a layout with a tab bar at the bottom of the s className="max-w-[540px]" /> -You can use the **app/\_layout.tsx** file to define your app's root layout using tabs. This file is the main layout file for the tab bar and each tab. Inside it, you can control how the tab bar and each tab item look and behave. +You can use the **src/app/\_layout.tsx** file to define your app's root layout using tabs. This file is the main layout file for the tab bar and each tab. Inside it, you can control how the tab bar and each tab item look and behave. @@ -63,7 +63,7 @@ You can use the **app/\_layout.tsx** file to define your app's root layout using -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { @@ -109,9 +109,9 @@ export default function TabLayout() { -Finally, you have the two tab files that make up the content of the tabs: **app/index.tsx** and **app/settings.tsx**. +Finally, you have the two tab files that make up the content of the tabs: **src/app/index.tsx** and **src/app/settings.tsx**. -```tsx app/index.tsx and app/settings.tsx +```tsx src/app/index.tsx and src/app/settings.tsx import { View, Text, StyleSheet } from 'react-native'; export default function Tab() { @@ -155,7 +155,7 @@ Alternatively, you can pass `{default: ..., selected: ...}` to either the `sf` o -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { @@ -203,7 +203,7 @@ Liquid glass on iOS automatically changes colors based on if the background colo -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { DynamicColorIOS } from 'react-native'; import { NativeTabs } from 'expo-router/unstable-native-tabs'; @@ -294,7 +294,7 @@ When using the `src` or `xcasset` prop for custom images on iOS, you can control -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { @@ -403,7 +403,7 @@ If you don't want to display a label, you can use the `hidden` prop to hide the -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { @@ -453,7 +453,7 @@ You can use the `Badge` component to customize the badge displayed for the tab b -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { @@ -513,7 +513,7 @@ Since the native tab layout's appearance varies by platform, the customization o You can hide the tab bar using `hidden` prop on the `NativeTabs` component. To hide tab bar for specific screens, you can use context API to set the `hidden` prop dynamically. -```tsx context/TabBarContext.tsx +```tsx src/context/TabBarContext.tsx import { createContext } from 'react'; export const TabBarContext = createContext<{ @@ -523,11 +523,11 @@ export const TabBarContext = createContext<{ }); ``` -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { NativeTabs } from 'expo-router/unstable-native-tabs'; import { useState } from 'react'; -import { TabBarContext } from '../context/TabBarContext'; +import { TabBarContext } from '@/context/TabBarContext'; export default function TabLayout() { const [isTabBarHidden, setIsTabBarHidden] = useState(false); @@ -546,11 +546,11 @@ export default function TabLayout() { } ``` -```tsx app/index.tsx +```tsx src/app/index.tsx import { useFocusEffect } from 'expo-router'; import { use } from 'react'; -import { TabBarContext } from '../context/TabBarContext'; +import { TabBarContext } from '@/context/TabBarContext'; export default function HomeScreen() { const { setIsTabBarHidden } = use(TabBarContext); @@ -572,7 +572,7 @@ export default function HomeScreen() { If you want to hide a tab based on a condition, you can either remove the trigger or pass the `hidden` prop to the `NativeTabs.Trigger` component. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { @@ -597,7 +597,7 @@ By default, tapping a tab that is already active closes all screens in that tab' -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { @@ -649,7 +649,7 @@ By default, tapping a tab that is already active and showing its root screen scr -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { @@ -709,7 +709,7 @@ To add a separate search tab, assign the `role` with its value set to `search` t -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { @@ -762,10 +762,15 @@ export default function TabLayout() { To add a search field to the tab bar, wrap the screen in a Stack navigator and configure `headerSearchBarOptions`. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { @@ -782,7 +787,7 @@ export default function TabLayout() { } ``` -```tsx app/search/_layout.tsx +```tsx src/app/search/_layout.tsx import { Stack } from 'expo-router'; export default function SearchLayout() { @@ -790,7 +795,7 @@ export default function SearchLayout() { } ``` -```tsx app/search/index.tsx +```tsx src/app/search/index.tsx import { ScrollView } from 'react-native'; import { Stack } from 'expo-router'; @@ -820,7 +825,7 @@ To implement the minimized behavior on the tab bar, you can use -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { @@ -878,7 +883,7 @@ The bottom accessory can appear in two placements: `'regular'` (standard positio The following example demonstrates a mini player with state lifted to the parent component: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { NativeTabs } from 'expo-router/unstable-native-tabs'; import { useState } from 'react'; import { View, Text, Pressable, StyleSheet } from 'react-native'; @@ -969,7 +974,7 @@ If you need full control over safe area handling, you can disable automatic cont -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { @@ -1007,7 +1012,7 @@ export default function TabLayout() { When `disableAutomaticContentInsets` is set to `true`, you must manage safe area insets manually. You can use `SafeAreaView` from `react-native-screens/experimental`: -```tsx app/index.tsx +```tsx src/app/index.tsx import { SafeAreaView } from 'react-native-screens/experimental'; export default function HomeScreen() { @@ -1254,7 +1259,7 @@ On iOS 18 and earlier, the native tab bar becomes transparent when scrolling to You can use the [`disableTransparentOnScrollEdge`](/versions/latest/sdk/router-native-tabs/#disabletransparentonscrolledge) prop to disable this behavior. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { NativeTabs } from 'expo-router/unstable-native-tabs'; function TabLayout() { @@ -1270,7 +1275,7 @@ function TabLayout() { When you are using a `ScrollView` and the tab bar is transparent from the start, ensure that the `ScrollView` is a first child of the screen component. If you wrap it with another component make sure to set `collapsable` to `false` on the wrapper component. -```tsx app/index.tsx +```tsx src/app/index.tsx import { ScrollView, View } from 'react-native'; export default function HomeScreen() { @@ -1290,7 +1295,7 @@ This happens because React Navigation's default theme uses a white background co **For apps supporting both light and dark modes:** -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { ThemeProvider, DarkTheme, DefaultTheme } from '@react-navigation/native'; import { NativeTabs } from 'expo-router/unstable-native-tabs'; import { useColorScheme } from 'react-native'; @@ -1315,7 +1320,7 @@ export default function TabLayout() { **For dark-mode-only apps:** -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { ThemeProvider, DarkTheme } from '@react-navigation/native'; import { NativeTabs } from 'expo-router/unstable-native-tabs'; @@ -1344,7 +1349,7 @@ Tapping an active tab should scroll the content to the top, but this may not wor Ensure that the `ScrollView` is a direct first child of the screen component. If you wrap it with another component, make sure to set `collapsable` to `false` on the wrapper component. -```tsx app/index.tsx +```tsx src/app/index.tsx import { ScrollView, View } from 'react-native'; export default function HomeScreen() { @@ -1434,7 +1439,7 @@ Native tabs cannot be nested inside other native tabs. You can still nest [JavaS -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { NativeTabs } from 'expo-router/unstable-native-tabs'; export default function TabLayout() { diff --git a/docs/pages/router/advanced/nesting-navigators.mdx b/docs/pages/router/advanced/nesting-navigators.mdx index 732f4f9e122550..8477cd71f5c904 100644 --- a/docs/pages/router/advanced/nesting-navigators.mdx +++ b/docs/pages/router/advanced/nesting-navigators.mdx @@ -24,31 +24,31 @@ Consider the following file structure which is used as an example: -In the above example, **app/home/feed.tsx** matches `/home/feed`, and **app/home/messages.tsx** matches `/home/messages`. +In the above example, **src/app/home/feed.tsx** matches `/home/feed`, and **src/app/home/messages.tsx** matches `/home/messages`. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export default Stack; ``` -Both **app/home/\_layout.tsx** and **app/index.tsx** below are nested in the **app/\_layout.tsx** layout so that it will be rendered as a stack. +Both **src/app/home/\_layout.tsx** and **src/app/index.tsx** below are nested in the **src/app/\_layout.tsx** layout so that it will be rendered as a stack. -```tsx app/home/_layout.tsx +```tsx src/app/home/_layout.tsx import { Tabs } from 'expo-router'; export default Tabs; ``` -```tsx app/index.tsx +```tsx src/app/index.tsx import { Link } from 'expo-router'; export default function Root() { @@ -56,9 +56,9 @@ export default function Root() { } ``` -Both **app/home/feed.tsx** and **app/home/messages.tsx** below are nested in the **home/\_layout.tsx** layout, so it will be rendered as a tab. +Both **src/app/home/feed.tsx** and **src/app/home/messages.tsx** below are nested in the **home/\_layout.tsx** layout, so it will be rendered as a tab. -```tsx app/home/feed.tsx +```tsx src/app/home/feed.tsx import { View, Text } from 'react-native'; export default function Feed() { @@ -70,7 +70,7 @@ export default function Feed() { } ``` -```tsx app/home/messages.tsx +```tsx src/app/home/messages.tsx import { View, Text } from 'react-native'; export default function Messages() { @@ -82,6 +82,10 @@ export default function Messages() { } ``` +## Stack inside native tabs + +When using native tabs, you can nest a `` layout inside each tab to support headers and pushing screens. For a complete example, see [Use Stacks inside tabs](/router/advanced/native-tabs/#use-stacks-inside-tabs). + ## Navigate to a screen in a nested navigator In React Navigation, navigating to a specific nested screen can be controlled by passing the screen name in params. This renders the specified nested screen instead of the initial screen for that nested navigator. diff --git a/docs/pages/router/advanced/platform-specific-modules.mdx b/docs/pages/router/advanced/platform-specific-modules.mdx index 8be8195bc1454f..73c4ab11a51f9c 100644 --- a/docs/pages/router/advanced/platform-specific-modules.mdx +++ b/docs/pages/router/advanced/platform-specific-modules.mdx @@ -13,19 +13,19 @@ While building your app, you may want to show specific content based on the curr There are two ways to use platform-specific extensions: -### Within app directory +### Within src/app directory -Metro bundler's platform-specific extensions (for example, **.android.tsx**, **.ios.tsx**, **.native.tsx**, or **.web.tsx**) are supported in the **app** directory only if a **non-platform version** also exists. This ensures that routes are universal across platforms for deep linking. +Metro bundler's platform-specific extensions (for example, **.android.tsx**, **.ios.tsx**, **.native.tsx**, or **.web.tsx**) are supported in the **src/app** directory only if a **non-platform version** also exists. This ensures that routes are universal across platforms for deep linking. Consider the following project structure: @@ -35,34 +35,34 @@ In the above file structure: - **index.tsx** file is used as the home page for all platforms. - **about.web.tsx** file is used as the about page for the web, and the **about.tsx** file is used on all other platforms. -### Outside app directory +### Outside src/app directory -You can create platform-specific files with extensions (for example, **.android.tsx**, **.ios.tsx**, **.native.tsx**, or **.web.tsx**) outside the **app** directory and use them from within the **app** directory. +You can create platform-specific files with extensions (for example, **.android.tsx**, **.ios.tsx**, **.native.tsx**, or **.web.tsx**) outside the **src/app** directory and use them from within the **src/app** directory. Consider the following project structure: -In the above file structure, the designs require you to build different `about` screens for each platform. In that case, you can create a component for each platform in the **components** directory using platform extensions. When imported, Metro will ensure the correct component version is used based on the current platform. You can then re-export the component as a screen in the **app** directory. +In the above file structure, the designs require you to build different `about` screens for each platform. In that case, you can create a component for each platform in the **src/components** directory using platform extensions. When imported, Metro will ensure the correct component version is used based on the current platform. You can then re-export the component as a screen in the **src/app** directory. -```tsx app/about.tsx -export { default } from '../components/about'; +```tsx src/app/about.tsx +export { default } from '@/components/about'; ``` ## Platform module You can use the [`Platform`](https://reactnative.dev/docs/platform-specific-code#platform-module) module from React Native to detect the current platform and render the appropriate content based on the result. For example, you can render a `Tabs` layout on native and a custom layout on the web. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Platform } from 'react-native'; import { Link, Slot, Tabs } from 'expo-router'; diff --git a/docs/pages/router/advanced/protected.mdx b/docs/pages/router/advanced/protected.mdx index 25f8a6b5372a70..926a25456754c6 100644 --- a/docs/pages/router/advanced/protected.mdx +++ b/docs/pages/router/advanced/protected.mdx @@ -17,17 +17,17 @@ Protected screens allow you to prevent users from accessing certain routes using -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; const isLoggedIn = false; @@ -60,7 +60,7 @@ In Expo Router, a screen can **only exist in one active route group at a time**. You should only declare a screen only once, in the most appropriate group or stack. If a screen's availability depends on logic, wrap it in a conditional group instead of duplicating the screen. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; const isLoggedIn = true; @@ -82,7 +82,7 @@ export function AppLayout() { Protected screens can be nested to define hierarchical access control logic. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; const isLoggedIn = true; @@ -114,17 +114,17 @@ You can configure the navigator to fall back to a specific screen if access is d -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; const isLoggedIn = false; @@ -149,7 +149,7 @@ In the above example, since the **index** screen is protected and the `guard` is Protected routes are also available for [Tabs](/router/advanced/tabs/) and [Drawer](/router/advanced/drawer/) navigators. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Tabs } from 'expo-router'; const isLoggedIn = false; diff --git a/docs/pages/router/advanced/router-settings.mdx b/docs/pages/router/advanced/router-settings.mdx index 1cb1929c5ffd29..d713aa2a3c0aed 100644 --- a/docs/pages/router/advanced/router-settings.mdx +++ b/docs/pages/router/advanced/router-settings.mdx @@ -14,9 +14,9 @@ import { FileTree } from '~/ui/components/FileTree'; When deep linking to a route, you may want to provide a user with a "back" button. The `initialRouteName` sets the default screen of the stack and should match a valid filename (without the extension). - + -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export const unstable_settings = { diff --git a/docs/pages/router/advanced/shared-routes.mdx b/docs/pages/router/advanced/shared-routes.mdx index 2ccc6d30dfdd42..c9f41a04ffd041 100644 --- a/docs/pages/router/advanced/shared-routes.mdx +++ b/docs/pages/router/advanced/shared-routes.mdx @@ -7,17 +7,17 @@ import { FileTree } from '~/ui/components/FileTree'; To match the same URL with different layouts, use [**groups**](/router/basics/notation/#parentheses) with overlapping child routes. This pattern is very common in native apps. For example, in the X app, a profile can be viewed in every tab (such as home, search, and profile). However, there is only one URL that is required to access this route. -In the example below, **app/\_layout.tsx** is the tab bar and each route has its own header. The **app/(profile)/[user].tsx** route is shared between each tab. +In the example below, **src/app/\_layout.tsx** is the tab bar and each route has its own header. The **src/app/(profile)/[user].tsx** route is shared between each tab. @@ -29,11 +29,11 @@ Shared routes can be navigated directly by including the group name in the route > Array syntax is an advanced concept that is unique to native app development. -Instead of defining the same route multiple times with different layouts, use the array syntax `(,)` to duplicate the children of a group. For example, `app/(home,search)/[user].tsx` — creates `app/(home)/[user].tsx` and `app/(search)/[user].tsx` in memory. +Instead of defining the same route multiple times with different layouts, use the array syntax `(,)` to duplicate the children of a group. For example, `src/app/(home,search)/[user].tsx` — creates `src/app/(home)/[user].tsx` and `src/app/(search)/[user].tsx` in memory. To distinguish between the two routes use a layout's `segment` prop: -```tsx app/(home,search)/_layout.tsx +```tsx src/app/(home,search)/_layout.tsx export default function DynamicLayout({ segment }) { if (segment === '(search)') { return ; @@ -46,7 +46,7 @@ export default function DynamicLayout({ segment }) { To enable the **array syntax**, specify the [`initialRouteName`](/router/advanced/router-settings/#initialroutename) for each group using `unstable_settings` object in the dynamic layout: {/* prettier-ignore */} -```tsx app/(home,search)/_layout.tsx +```tsx src/app/(home,search)/_layout.tsx export const unstable_settings = { initialRouteName: 'home', search: { diff --git a/docs/pages/router/advanced/stack-toolbar.mdx b/docs/pages/router/advanced/stack-toolbar.mdx index 8c8af2ee0a3fd3..8a57c4e67fbdba 100644 --- a/docs/pages/router/advanced/stack-toolbar.mdx +++ b/docs/pages/router/advanced/stack-toolbar.mdx @@ -7,13 +7,13 @@ import { Collapsible } from '~/ui/components/Collapsible'; > **important** `Stack.Toolbar` is an alpha API available on **iOS only** in **Expo SDK 55** and later. The API is subject to breaking changes. -[`Stack.Toolbar`](/versions/unversioned/sdk/router/#stacktoolbar) lets you add native iOS toolbar items to your Stack screens. You can place buttons, menus, and custom views in the header (left or right side) or in the bottom toolbar area. +[`Stack.Toolbar`](/versions/latest/sdk/router/#stacktoolbar) lets you add native iOS toolbar items to your Stack screens. You can place buttons, menus, and custom views in the header (left or right side) or in the bottom toolbar area. ## Adding header buttons -Use [`Stack.Toolbar.Button`](/versions/unversioned/sdk/router/#stacktoolbarbutton) within `Stack.Toolbar` with `placement="right"` or `placement="left"` to add buttons to the navigation header. This is useful for actions like favoriting, sharing, or editing content. +Use [`Stack.Toolbar.Button`](/versions/latest/sdk/router/#stacktoolbarbutton) within `Stack.Toolbar` with `placement="right"` or `placement="left"` to add buttons to the navigation header. This is useful for actions like favoriting, sharing, or editing content. -```tsx app/notes/[id].tsx +```tsx src/app/notes/[id].tsx import { useState } from 'react'; import { Stack } from 'expo-router'; import { View, Text, Alert } from 'react-native'; @@ -104,9 +104,9 @@ export default function Page() { ## Building action menus -For screens with multiple actions, use [`Stack.Toolbar.Menu`](/versions/unversioned/sdk/router/#stacktoolbarmenu) to group them into a dropdown menu: +For screens with multiple actions, use [`Stack.Toolbar.Menu`](/versions/latest/sdk/router/#stacktoolbarmenu) to group them into a dropdown menu: -```tsx app/mail/[id].tsx +```tsx src/app/mail/[id].tsx import { useState } from 'react'; import { Stack } from 'expo-router'; import { Alert } from 'react-native'; @@ -148,7 +148,7 @@ export default function EmailScreen() { } ``` -The `isOn` prop on [`Stack.Toolbar.MenuAction`](/versions/unversioned/sdk/router/#stacktoolbarmenuaction) shows a checkmark next to the action, useful for toggle states. The `destructive` prop styles the action in red to indicate a dangerous operation. +The `isOn` prop on [`Stack.Toolbar.MenuAction`](/versions/latest/sdk/router/#stacktoolbarmenuaction) shows a checkmark next to the action, useful for toggle states. The `destructive` prop styles the action in red to indicate a dangerous operation. ### Nested submenus @@ -199,7 +199,7 @@ export default function EmailScreen() { iOS apps commonly have a bottom toolbar for primary actions. To add one, use `Stack.Toolbar` without a placement prop (it defaults to `"bottom"`): -```tsx app/photos/index.tsx +```tsx src/app/photos/index.tsx import { Stack } from 'expo-router'; import { Alert } from 'react-native'; @@ -220,15 +220,15 @@ export default function PhotosScreen() { } ``` -[`Stack.Toolbar.Spacer`](/versions/unversioned/sdk/router/#stacktoolbarspacer) creates flexible space between items, pushing them to opposite sides. This is how you achieve layouts like having buttons on both ends of the toolbar. +[`Stack.Toolbar.Spacer`](/versions/latest/sdk/router/#stacktoolbarspacer) creates flexible space between items, pushing them to opposite sides. This is how you achieve layouts like having buttons on both ends of the toolbar. > **info** Bottom toolbars can only be used inside page components, not in layout files. ## Adding badges to buttons -In header toolbars, you can add badges to indicate counts or status. Use [`Stack.Toolbar.Icon`](/versions/unversioned/sdk/router/#stacktoolbaricon), [`Stack.Toolbar.Label`](/versions/unversioned/sdk/router/#stacktoolbarlabel), and [`Stack.Toolbar.Badge`](/versions/unversioned/sdk/router/#stacktoolbarbadge) to compose the button content: +In header toolbars, you can add badges to indicate counts or status. Use [`Stack.Toolbar.Icon`](/versions/latest/sdk/router/#stacktoolbaricon), [`Stack.Toolbar.Label`](/versions/latest/sdk/router/#stacktoolbarlabel), and [`Stack.Toolbar.Badge`](/versions/latest/sdk/router/#stacktoolbarbadge) to compose the button content: -```tsx app/inbox.tsx +```tsx src/app/inbox.tsx import { Stack } from 'expo-router'; export default function InboxScreen() { @@ -253,9 +253,9 @@ export default function InboxScreen() { ## Embedding custom views -When you need something beyond buttons and menus, use [`Stack.Toolbar.View`](/versions/unversioned/sdk/router/#stacktoolbarview) to embed any React Native component: +When you need something beyond buttons and menus, use [`Stack.Toolbar.View`](/versions/latest/sdk/router/#stacktoolbarview) to embed any React Native component: -```tsx app/search.tsx +```tsx src/app/search.tsx import { Stack } from 'expo-router'; import { Pressable, Alert } from 'react-native'; import { SymbolView } from 'expo-symbols'; @@ -284,7 +284,7 @@ export default function SearchScreen() { Use the `hidden` prop to toggle toolbar items based on state: -```tsx app/document.tsx +```tsx src/app/document.tsx import { useState } from 'react'; import { Stack } from 'expo-router'; @@ -313,7 +313,7 @@ Toolbar buttons with liquid glass styling may flicker or flash their background To fix this, wrap your root layout with `` from `@react-navigation/native` using the appropriate theme: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { ThemeProvider, DarkTheme, DefaultTheme } from '@react-navigation/native'; import { Stack } from 'expo-router'; import { useColorScheme } from 'react-native'; @@ -339,7 +339,7 @@ A white flash between screen transitions usually means the navigation stack is u To fix this, wrap your root layout with React Navigation's `` and pass the appropriate theme: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { ThemeProvider, DarkTheme, DefaultTheme } from '@react-navigation/native'; import { Stack } from 'expo-router'; import { useColorScheme } from 'react-native'; @@ -365,7 +365,7 @@ When using `headerLargeTitle: true` (or ``) alongside To fix this, ensure `ScrollView` or `FlatList` is the first child rendered by your screen component. If you need a wrapper, set `collapsable={false}` on it: -```tsx app/index.tsx +```tsx src/app/index.tsx import { Stack } from 'expo-router'; import { ScrollView, View, Text } from 'react-native'; @@ -383,7 +383,7 @@ export default function Home() { If you need to wrap the `ScrollView`, set `collapsable={false}` on the wrapper: -```tsx app/index.tsx +```tsx src/app/index.tsx import { Stack } from 'expo-router'; import { ScrollView, View, Text } from 'react-native'; @@ -431,4 +431,4 @@ You cannot nest `Stack.Toolbar` components inside each other. ## Learn more -For complete API documentation, including all available props, see the [`Stack.Toolbar` API reference](/versions/unversioned/sdk/router/#stacktoolbar). +For complete API documentation, including all available props, see the [`Stack.Toolbar` API reference](/versions/latest/sdk/router/#stacktoolbar). diff --git a/docs/pages/router/advanced/stack.mdx b/docs/pages/router/advanced/stack.mdx index 6cdd8c07a8f526..1d49190357ad74 100644 --- a/docs/pages/router/advanced/stack.mdx +++ b/docs/pages/router/advanced/stack.mdx @@ -30,13 +30,13 @@ This guide provides information on how you can create a `Stack` navigator in you You can use file-based routing to create a stack navigator. Here's an example file structure: - + This file structure produces a layout where the `index` route is the first route in the stack, and the `details` route is pushed on top of the `index` route when navigated. -You can use the **app/\_layout.tsx** file to define your app's `Stack` navigator with these two routes: +You can use the **src/app/\_layout.tsx** file to define your app's `Stack` navigator with these two routes: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export default function Layout() { @@ -52,7 +52,7 @@ Starting in SDK 55, you can configure screen options and the header using either You can use the `` component in the layout component route to statically configure a route's options. -```tsx app/_layout.tsx|collapseHeight=440 +```tsx src/app/_layout.tsx|collapseHeight=440 import { Stack } from 'expo-router'; export default function Layout() { @@ -80,7 +80,7 @@ export default function Layout() { You can configure the header bar for all routes in a `Stack` navigator by using the `screenOptions` prop. This is useful for setting a common header style across all routes. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export default function Layout() { @@ -108,7 +108,7 @@ To configure a route's options dynamically, you can use either the composition c -```tsx app/details.tsx +```tsx src/app/details.tsx import { Stack, useLocalSearchParams, useRouter } from 'expo-router'; import { View, Text, StyleSheet } from 'react-native'; @@ -148,7 +148,7 @@ const styles = StyleSheet.create({ > **important** Screen composition API is in alpha and available in SDK 55 and later. -```tsx app/details.tsx +```tsx src/app/details.tsx import { Stack, useLocalSearchParams, useRouter } from 'expo-router'; import { View, Text, StyleSheet } from 'react-native'; @@ -205,7 +205,7 @@ You can add buttons to the header by using the `headerLeft` and `headerRight` op -```tsx app/index.tsx +```tsx src/app/index.tsx import { Stack } from 'expo-router'; import { Button, Text, Image, StyleSheet } from 'react-native'; import { useState } from 'react'; @@ -245,7 +245,7 @@ const styles = StyleSheet.create({ > **important** Screen composition API is in alpha and available in SDK 55 and later. -```tsx app/index.tsx +```tsx src/app/index.tsx import { Stack } from 'expo-router'; import { Button, Text, Image, StyleSheet } from 'react-native'; import { useState } from 'react'; @@ -299,9 +299,9 @@ For example, the `index` route in the following layout structure shows a list of @@ -309,7 +309,7 @@ The `Stack` navigator will push a new screen every time the app user navigates t You can use the `` component in the layout component route to modify the push behavior: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export default function Layout() { @@ -341,7 +341,7 @@ You can optionally pass a positive number to dismiss up to that specified number Dismiss is different from `back` as it targets the closest stack and not the current navigator. If you have nested navigators, calling `dismiss` will take you back multiple screens. {/* prettier-ignore */} -```tsx app/settings.tsx +```tsx src/app/settings.tsx import { Button, View } from 'react-native'; /* @info Import useRouter from Expo Router. */ import { useRouter } from 'expo-router'; @@ -377,7 +377,7 @@ Dismisses screens in the current `` until the specified `Href` is reach For example, consider the history of `/one`, `/two`, `/three` routes, where `/three` is the current route. The action `router.dismissTo('/one')` will cause the history to go back twice, while `router.dismissTo('/four')` will `push` the history forward to the `/four` route. {/* prettier-ignore */} -```tsx app/settings.tsx +```tsx src/app/settings.tsx import { Button, View, Text } from 'react-native'; /* @info Import useRouter from Expo Router. */ import { useRouter } from 'expo-router'; @@ -411,7 +411,7 @@ To return to the first screen in the closest stack. This is similar to [`popToTo For example, the `home` route is the first screen, and the `settings` is the last. To go from `settings` to `home` route you'll have to go back to `details`. However, using the `dismissAll` action, you can go from `settings` to `home` and dismiss any screen in between. {/* prettier-ignore */} -```tsx app/settings.tsx +```tsx src/app/settings.tsx import { Button, View, Text } from 'react-native'; /* @info Import useRouter from Expo Router. */ import { useRouter } from 'expo-router'; @@ -443,7 +443,7 @@ export default function Settings() { To check if it is possible to dismiss the current screen. Returns `true` if the router is within a stack with more than one screen in the stack's history. {/* prettier-ignore */} -```tsx app/settings.tsx|collapseHeight=410 +```tsx src/app/settings.tsx|collapseHeight=410 import { Button, View } from 'react-native'; /* @info Import useRouter from Expo Router. */ import { useRouter } from 'expo-router'; @@ -506,7 +506,7 @@ export const JsStack = withLayoutContext< After defining the `JsStack` component, you can use it in your app: {/* prettier-ignore */} -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { JsStack } from '../layouts/js-stack'; export default function Layout() { @@ -559,7 +559,7 @@ When using `headerLargeTitle: true` (or ``) with a `Sc To fix this, ensure `ScrollView` or `FlatList` is the first child rendered by your screen component. If you need a wrapper, set `collapsable={false}` on it: -```tsx app/index.tsx +```tsx src/app/index.tsx import { Stack } from 'expo-router'; import { ScrollView, View, Text } from 'react-native'; @@ -577,7 +577,7 @@ export default function Home() { If you need to wrap the `ScrollView`, set `collapsable={false}` on the wrapper: -```tsx app/index.tsx +```tsx src/app/index.tsx import { Stack } from 'expo-router'; import { ScrollView, View, Text } from 'react-native'; @@ -601,9 +601,9 @@ export default function Home() { A white flash between screen transitions usually means the navigation stack is using a light background while your app uses a dark theme. -To fix this, wrap your root layout with React Navigation's `ThemeProvider` and pass the appropriate theme: +To fix this, wrap your root layout with React Navigation's `` and pass the appropriate theme: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { ThemeProvider, DarkTheme, DefaultTheme } from '@react-navigation/native'; import { Stack } from 'expo-router'; import { useColorScheme } from 'react-native'; @@ -623,7 +623,7 @@ export default function RootLayout() { For apps that are always dark-themed: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { ThemeProvider, DarkTheme } from '@react-navigation/native'; import { Stack } from 'expo-router'; diff --git a/docs/pages/router/advanced/tabs.mdx b/docs/pages/router/advanced/tabs.mdx index fbbd4c90a5f9e2..5986b7b72b0883 100644 --- a/docs/pages/router/advanced/tabs.mdx +++ b/docs/pages/router/advanced/tabs.mdx @@ -51,10 +51,10 @@ You can use file-based routing to create a tabs layout. Here's an example file s @@ -66,9 +66,9 @@ This file structure produces a layout with a tab bar at the bottom of the screen className="max-w-[540px]" /> -You can use the **app/\_layout.tsx** file to define your app's root layout: +You can use the **src/app/\_layout.tsx** file to define your app's root layout: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export default function Layout() { @@ -84,7 +84,7 @@ The **(tabs)** directory is a special directory name that tells Expo Router to u From the file structure, the **(tabs)** directory has three files. The first is **(tabs)/\_layout.tsx**. This file is the main layout file for the tab bar and each tab. Inside it, you can control how the tab bar and each tab button look and behave. -```tsx app/(tabs)/_layout.tsx +```tsx src/app/(tabs)/_layout.tsx import FontAwesome from '@expo/vector-icons/FontAwesome'; import { Tabs } from 'expo-router'; @@ -110,9 +110,9 @@ export default function TabLayout() { } ``` -Finally, you have the two tab files that make up the content of the tabs: **app/(tabs)/index.tsx** and **app/(tabs)/settings.tsx**. +Finally, you have the two tab files that make up the content of the tabs: **src/app/(tabs)/index.tsx** and **src/app/(tabs)/settings.tsx**. -```tsx app/(tabs)/index.tsx & app/(tabs)/settings.tsx +```tsx src/app/(tabs)/index.tsx & src/app/(tabs)/settings.tsx import { View, Text, StyleSheet } from 'react-native'; export default function Tab() { @@ -138,7 +138,7 @@ The tab file named **index.tsx** is the default tab when the app loads. The seco The JavaScript tabs in Expo Router extend the [Bottom Tabs Navigator](https://reactnavigation.org/docs/bottom-tab-navigator) from React Navigation. The specific APIs available depend on your versions. For example, Expo Router v6 extends Bottom Tabs Navigator v7. Check your versions to ensure compatibility, then you can use the same configuration props to customize the bottom tab bar and individual tabs. For example: -```tsx app/(tabs)/_layout.tsx +```tsx src/app/(tabs)/_layout.tsx import { Tabs } from 'expo-router'; export default function TabLayout() { @@ -174,7 +174,7 @@ For additional details and navigator-specific examples, see [React Navigation's Sometimes you want a route to exist but not show up in the tab bar. You can pass `href: null` to disable the button: -```tsx app/(tabs)/_layout.tsx +```tsx src/app/(tabs)/_layout.tsx import { Tabs } from 'expo-router'; export default function TabLayout() { @@ -197,7 +197,7 @@ export default function TabLayout() { You can use a dynamic route in a tab bar. For example, you have a `[user]` tab that shows a user's profile. You can use the `href` option to link to a specific user's profile. -```tsx app/(tabs)/_layout.tsx +```tsx src/app/(tabs)/_layout.tsx import { Tabs } from 'expo-router'; export default function TabLayout() { diff --git a/docs/pages/router/advanced/web-modals.mdx b/docs/pages/router/advanced/web-modals.mdx index c99a6d3589c72a..04347de952a7fe 100644 --- a/docs/pages/router/advanced/web-modals.mdx +++ b/docs/pages/router/advanced/web-modals.mdx @@ -19,11 +19,11 @@ Modals in Expo Router are configured using `Stack.Screen` component with specifi Consider the following navigation tree, which includes a stack navigator defined in the layout file, a home screen where the modal is accessed, and the modal screen component: - + -In the layout file (**app/\_layout.tsx**), the modal screen component is added to the Stack navigator: +In the layout file (**src/app/\_layout.tsx**), the modal screen component is added to the Stack navigator: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export const unstable_settings = { @@ -48,7 +48,7 @@ export default function Layout() { The **modal.tsx** is used to display the contents of a modal: -```tsx app/modal.tsx +```tsx src/app/modal.tsx import { Text, View } from 'react-native'; export default function Modal() { @@ -58,7 +58,7 @@ export default function Modal() { Now, to open the modal from **index.tsx**, you can use `router.push('/modal')` in your index route: -```tsx app/index.tsx +```tsx src/app/index.tsx import { router } from 'expo-router'; import { Pressable, Text, View, StyleSheet } from 'react-native'; @@ -213,7 +213,7 @@ webModalStyle: { To create a full screen modal for content that covers maximum space, you can use `webModalStyle` property in your modal route's `Stack.Screen` options: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export const unstable_settings = { @@ -246,7 +246,7 @@ Here's the result of the above example: When running your web app on mobile devices, you can set `sheetAllowedDetents` to `fitToContents` or a custom value if you want to avoid showing a full screen modal: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export const unstable_settings = { @@ -284,7 +284,7 @@ The modal appears as a sheet on a mobile device: For smaller interactions, you can create a compact modal that fits its content: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export const unstable_settings = { @@ -325,7 +325,7 @@ Here's the result of the above example: You can set the `presentation` option to `transparentModal` when you want to display an overlay that should maintain the visual context of the underlying screen: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export const unstable_settings = { @@ -357,7 +357,7 @@ Here's the result of the above example: You customize the corner radius using `sheetCornerRadius`: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export const unstable_settings = { @@ -394,7 +394,7 @@ Here's the result of the above example: You can use `sheetAllowedDetents` to define the height at which the modal can rest: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export const unstable_settings = { @@ -454,9 +454,9 @@ The video above demonstrates a modal window that appears over the main content o You can achieve the above web modal behavior by using the [`transparentModal`](https://reactnavigation.org/docs/stack-navigator/#transparent-modals) presentation mode, styling the overlay and modal content, and utilizing [`react-native-reanimated`](/versions/latest/sdk/reanimated/#installation) to animate the modal's presentation. -Modify your project's root layout (**app/\_layout.tsx**) to add an `options` object to the modal route: +Modify your project's root layout (**src/app/\_layout.tsx**) to add an `options` object to the modal route: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; /* @info unstable_settings can be set in any stack's _layout.tsx file. It is used to define the initial route name for the stack, which ensures that users have a consistent starting point, especially when deep linking. */ @@ -495,7 +495,7 @@ The above example sets the `index` screen as the [`initialRouteName`](/router/ad Style the overlay and modal content in **modal.tsx** as shown below: {/* prettier-ignore */} -```tsx app/modal.tsx|collapseHeight=250 +```tsx src/app/modal.tsx|collapseHeight=250 import { Link } from 'expo-router'; import { Pressable, StyleSheet, Text } from 'react-native'; import Animated, { FadeIn, SlideInDown } from 'react-native-reanimated'; diff --git a/docs/pages/router/advanced/zoom-transition.mdx b/docs/pages/router/advanced/zoom-transition.mdx index dedfe59a24bbc4..15420ec05c1da5 100644 --- a/docs/pages/router/advanced/zoom-transition.mdx +++ b/docs/pages/router/advanced/zoom-transition.mdx @@ -24,7 +24,7 @@ To implement zoom transitions, you need to use the `Link.AppleZoom` component to To activate zoom transition for a link, wrap the source (`Image`) element with `Link.AppleZoom` in your screen: -```tsx app/index.tsx +```tsx src/app/index.tsx import { View, Text, StyleSheet, Pressable } from 'react-native'; import { Link } from 'expo-router'; import { Image } from 'expo-image'; @@ -49,7 +49,7 @@ export default function HomeScreen() { In the destination screen, define the `Image` component: -```tsx app/image.tsx +```tsx src/app/image.tsx import { View, Text, StyleSheet } from 'react-native'; import { Image } from 'expo-image'; @@ -79,7 +79,7 @@ The `Link.AppleZoom` component wraps the element you want to zoom from. It is us You can specify the alignment of the zoomed element on the destination screen by using `Link.AppleZoomTarget` element. -```tsx app/image.tsx +```tsx src/app/image.tsx export default function ImageScreen() { return ( @@ -103,9 +103,9 @@ If you need more control over the alignment rectangle, you can pass an `alignmen ## Complete example -Here's a more complex example showing a gallery grid with zoom transitions to detail views. The source screen component (**app/index.tsx**) uses `Link.AppleZoom` to wrap an `Image` component: +Here's a more complex example showing a gallery grid with zoom transitions to detail views. The source screen component (**src/app/index.tsx**) uses `Link.AppleZoom` to wrap an `Image` component: -```tsx app/index.tsx +```tsx src/app/index.tsx import { Image } from 'expo-image'; import { Link } from 'expo-router'; import { useState } from 'react'; @@ -178,7 +178,7 @@ const styles = StyleSheet.create({ In the destination screen, the `Link.AppleZoomTarget` is used to specify the alignment of the zoomed element: -```tsx app/image/[id].tsx +```tsx src/app/image/[id].tsx import { Image } from 'expo-image'; import { Link, useLocalSearchParams } from 'expo-router'; import { useMemo } from 'react'; @@ -234,13 +234,13 @@ const styles = StyleSheet.create({ ## Controlling dismissal gestures -The [`usePreventZoomTransitionDismissal`](/versions/unversioned/sdk/router/#usepreventzoomtransitiondismissal_options) hook allows you to control the interactive swipe-to-dismiss gesture on screens using zoom transitions. This is useful when you want to prevent accidental dismissals or restrict dismissal to specific screen areas. +The [`usePreventZoomTransitionDismissal`](/versions/latest/sdk/router/#usepreventzoomtransitiondismissal_options) hook allows you to control the interactive swipe-to-dismiss gesture on screens using zoom transitions. This is useful when you want to prevent accidental dismissals or restrict dismissal to specific screen areas. ### Disabling dismissal completely Call the hook without any options to completely disable the swipe-to-dismiss gesture: -```tsx app/detail.tsx +```tsx src/app/detail.tsx import { usePreventZoomTransitionDismissal } from 'expo-router'; export default function DetailScreen() { @@ -254,7 +254,7 @@ export default function DetailScreen() { Use the `unstable_dismissalBoundsRect` option to define a rectangle where dismissal gestures are allowed. This is useful for image viewers where you want dismissal only from the image area: -```tsx app/image.tsx +```tsx src/app/image.tsx import { usePreventZoomTransitionDismissal } from 'expo-router'; import { View, StyleSheet } from 'react-native'; import { Image } from 'expo-image'; diff --git a/docs/pages/router/basics/common-navigation-patterns.mdx b/docs/pages/router/basics/common-navigation-patterns.mdx index bbc0530047eb6d..3ca3305f67accc 100644 --- a/docs/pages/router/basics/common-navigation-patterns.mdx +++ b/docs/pages/router/basics/common-navigation-patterns.mdx @@ -21,19 +21,19 @@ Consider the following navigation tree: -In the **app/(tabs)/\_layout.tsx** file, return a `Tabs` component: +In the **src/app/(tabs)/\_layout.tsx** file, return a `Tabs` component: {/* prettier-ignore */} -```tsx app/(tabs)/_layout.tsx +```tsx src/app/(tabs)/_layout.tsx import { Tabs } from 'expo-router'; export default function TabLayout() { @@ -49,9 +49,9 @@ export default function TabLayout() { } ``` -In the **app/(tabs)/feed/\_layout.tsx** file, return a `Stack` component: +In the **src/app/(tabs)/feed/\_layout.tsx** file, return a `Stack` component: -```tsx app/(tabs)/feed/_layout.tsx +```tsx src/app/(tabs)/feed/_layout.tsx import { Stack } from 'expo-router'; /* @info Setting initialRouteName ensures that direct links deep into the stack still push the index route onto the stack first. */ @@ -65,11 +65,11 @@ export default function FeedLayout() { } ``` -Now, within the **app/(tabs)/feed** directory, you can have `Link` components that point to different posts (for example, `/feed/123`). Those links will push the `feed/[postId]` route onto the stack, leaving the tab navigator visible. +Now, within the **src/app/(tabs)/feed** directory, you can have `Link` components that point to different posts (for example, `/feed/123`). Those links will push the `feed/[postId]` route onto the stack, leaving the tab navigator visible. You can also navigate from any other tab to a post in the feed tab with the same URL. Use `withAnchor` in conjunction with `initialRouteName` to ensure that the `feed/index` route is always the first screen in the stack: -```tsx app/(tabs)/feed/index.tsx +```tsx src/app/(tabs)/feed/index.tsx Go to post @@ -84,23 +84,42 @@ You can also nest tabs inside of an outer stack navigator. That is often more us Icon={BookOpen02Icon} /> +## Different tabs per platform: platform-specific tabs + +When building a cross-platform app, you may want to use [native tabs](/router/advanced/native-tabs/) on Android and iOS for a platform-native look and feel, while using [custom tabs](/router/advanced/custom-tabs/) on web for full control over styling. You can achieve this using [platform-specific file extensions](/router/advanced/platform-specific-modules/). + + + +The root layout renders an `AppTabs` component. Expo's module resolution automatically picks **app-tabs.native.tsx** on Android and iOS, and **app-tabs.tsx** on web, allowing each platform to use a tab implementation suited to its conventions. + +For a complete example of this pattern with code, see [Platform-specific tabs](/router/basics/layout/#platform-specific-tabs) in the Layout guide. + ## One screen, two tabs: sharing routes Route groups can be used to share a single screen between two different tabs. Consider a navigation tree that has a Feed tab and a Search tab, and they both share pages for viewing a user profile: -Each of the tabs is put in a group so you can define a third directory that shares routes between two groups (**app/(tabs)/(feed,search)/**). Even with the extra layer, **app/(tabs)/(feed)/index.tsx** is still the nearest index, so it will be the default route. +Each of the tabs is put in a group so you can define a third directory that shares routes between two groups (**src/app/(tabs)/(feed,search)/**). Even with the extra layer, **src/app/(tabs)/(feed)/index.tsx** is still the nearest index, so it will be the default route. -```tsx app/(tabs)/_layout.tsx +```tsx src/app/(tabs)/_layout.tsx import { Tabs } from 'expo-router'; export default function TabLayout() { @@ -115,7 +134,7 @@ export default function TabLayout() { Both the `(feed)` and `(search)` route groups contain stacks, so they can also share a single layout: -```tsx app/(tabs)/(feed,search)/_layout.tsx +```tsx src/app/(tabs)/(feed,search)/_layout.tsx import { Stack } from 'expo-router'; export default function SharedLayout() { @@ -144,24 +163,24 @@ For example, consider the following navigation tree in which you have a bottom t Protected , ], [ - 'app/(tabs)/settings.tsx', + 'src/app/(tabs)/settings.tsx', Protected , ], - ['app/sign-in.tsx'], - ['app/create-account.tsx'], + ['src/app/sign-in.tsx'], + ['src/app/create-account.tsx'], [ - 'app/modal.tsx', + 'src/app/modal.tsx', Protected , @@ -169,9 +188,9 @@ For example, consider the following navigation tree in which you have a bottom t ]} /> -When your app is first launched, the router will try to open the root index, **app/(tabs)/index.tsx**. If you wrap this screen in a `Stack.Protected` with the `guard={false}`, the screen will become inaccessible and the next available screen will be opened instead. In this example, the `sign-in` screen will be opened, since it is the next available route. +When your app is first launched, the router will try to open the root index, **src/app/(tabs)/index.tsx**. If you wrap this screen in a `Stack.Protected` with the `guard={false}`, the screen will become inaccessible and the next available screen will be opened instead. In this example, the `sign-in` screen will be opened, since it is the next available route. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; import { useAuthState } from '@/utils/authState'; @@ -200,7 +219,7 @@ Another benefit of protected routes is that they are checked even if you deep li Protected routes can also be used to conditionally show bottom tabs. In this example, the `vip` tab will only be shown to authenticated users who are VIP members: -```tsx app/(tabs)/_layout.tsx +```tsx src/app/(tabs)/_layout.tsx import { Stack } from 'expo-router'; import { useAuthState } from '@/utils/authState'; @@ -234,7 +253,7 @@ Separating your navigation states into distinct routes is meant to serve you and Thinking back to authentication, the protected route setup works great if the user should simply not be able to visit certain pages without logging in. But what about when unauthenticated users can browse an app in read-only mode? In that case, you might want to show a login modal over the app, rather than redirecting the user to a login page: -```tsx app/(logged-in)/_layout.tsx +```tsx src/app/(logged-in)/_layout.tsx import { Modal } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Stack } from 'expo-router'; diff --git a/docs/pages/router/basics/core-concepts.mdx b/docs/pages/router/basics/core-concepts.mdx index b4ea082d31d9cf..933e0883dbcfdd 100644 --- a/docs/pages/router/basics/core-concepts.mdx +++ b/docs/pages/router/basics/core-concepts.mdx @@ -11,31 +11,35 @@ Before diving into how to construct your app's navigation tree with Expo Router, ## The rules of Expo Router -### 1. All screens/pages are files inside of app directory +### 1. All screens/pages are files inside the src/app directory -All navigation routes in your app are defined by the files and sub-directories inside the **app** directory. Every file inside the **app** directory has a default export that defines a distinct page in your app (except for the special **\_layout** files). +All navigation routes in your app are defined by the files and sub-directories inside the [**src/app**](/router/reference/src-directory/) directory. Every file inside the **src/app** directory has a default export that defines a distinct page in your app (except for the special **\_layout** files). -Accordingly, directories inside **app** define groups of related screens together as stacks, tabs, or in other arrangements. +Accordingly, directories inside **src/app** define groups of related screens together. ### 2. All pages have a URL -All pages have a URL path that matches the file's location in the **app** directory, which can be used to navigate to that page in the address bar on the web, or as an app-specific deep link in a native mobile app. This is what is meant by Expo Router supporting "universal deep-linking". All pages in your app can be navigated to with a URL, regardless of the platform. +All pages have a URL path that matches the file's location in the **src/app** directory, which can be used to navigate to that page in the address bar on the web, or as an app-specific deep link in a native mobile app. This is what is meant by Expo Router supporting [universal deep-linking](/linking/overview/). All pages in your app can be navigated to with a URL, regardless of the platform. ### 3. First index.tsx is the initial route -With Expo Router, you do not define an initial route or first screen in code. Rather, when you open your app, Expo Router will look for the first **index.tsx** file matching the `/` URL. This could be an **app/index.tsx** file, but it doesn't have to be. If the user should start by default in a deeper part of your navigation tree, you can use a [route group](/router/basics/notation/#parentheses) (a directory where the name is surrounded in parenthesis), and that will not count as part of the URL. If you want your first screen to be a group of tabs, you might put all of the tab pages inside the **app/(tabs)** directory and define the default tab as **index.tsx**. With this arrangement, the `/` URL will take the user directly to **app/(tabs)/index.tsx** file. +With Expo Router, you do not define an initial route or first screen in code. Rather, when you open your app, Expo Router will look for the first **index.tsx** file matching the `/` URL. In the [default template](/router/installation/#quick-start), this is **src/app/index.tsx**. If the app user should start by default in a deeper part of your navigation tree, you can use a [route group](/router/basics/notation/#parentheses) (a directory where the name is surrounded in parentheses), and that will not count as part of the URL. If you want your first screen to be a group of tabs, you might put all of the tab pages inside the **src/app/(tabs)** directory and define the default tab as **index.tsx**. With this arrangement, the `/` URL will take the user directly to **src/app/(tabs)/index.tsx** file. ### 4. Root \_layout.tsx replaces App.jsx/tsx -Every project should have a **\_layout.tsx** file directly inside the **app** directory. This file is rendered before any other route in your app and is where you would put the initialization code that may have previously gone inside an **App.jsx** file, such as loading fonts or interacting with the splash screen. +Every project should have a **\_layout.tsx** file directly inside the **src/app** directory. This file is rendered before any other route in your app and is where you would put the initialization code that may have previously gone inside an **App.jsx** file, such as loading fonts, setting up theme providers, or interacting with the splash screen. For example, the default template wraps the app with a `ThemeProvider` for dark and light mode support and renders the `AppTabs` component from this file. -### 5. Non-navigation components live outside of app directory +### 5. Default template uses platform-specific tabs -In Expo Router, the **app** directory is exclusively for defining your app's routes. Other parts of your app, like components, hooks, utilities, and so on, should be placed in other top-level directories. If you put a non-route inside of the **app** directory, Expo Router will attempt to treat it like a route. +The default template uses two different tab implementations depending on the platform. On Android and iOS, tabs are rendered using [native tabs](/router/advanced/native-tabs/), which use the platform's built-in tab bar for a native look and feel. On web, tabs are rendered using [custom tabs](/router/advanced/custom-tabs/) from `expo-router/ui`, which are unstyled and flexible components that allow full control over the tab bar's appearance. -Alternatively, you can create a [top-level **src** directory](/router/reference/src-directory/) and put your routes inside the **src/app** directory, with non-route components going in folders like **src/components**, **src/utils**, and so on. This is the only other directory structure that Expo Router will recognize. +This is achieved through [platform-specific file extensions](/router/advanced/platform-specific-modules/). The tab component is defined in two files: **src/components/app-tabs.native.tsx** for Android and iOS, and **src/components/app-tabs.tsx** for web. Expo's module resolution automatically picks the correct file based on the platform. This pattern is used because native platforms have a system tab bar that provides expected behaviors like scroll-to-top on tap and native animations, while web requires a custom-styled tab bar that fits typical website navigation patterns. -### 6. It's still React Navigation under the hood +### 6. Non-navigation components live outside the src/app directory + +In Expo Router, the **src/app** directory is exclusively for defining your app's routes. Other parts of your app, like components, hooks, utilities, and so on, should be placed in other directories such as **src/components**, **src/hooks**, and **src/constants**. If you put a non-route inside the **src/app** directory, Expo Router will attempt to treat it like a route. + +### 7. It's still React Navigation under the hood While this may sound quite a bit different from React Navigation, Expo Router is actually built on top of React Navigation. You can think of Expo Router as an Expo CLI optimization that translates your file structure into React Navigation components that you previously defined in your own code. @@ -47,17 +51,20 @@ Let's apply these foundational rules of Expo Router to quickly identify key elem -- **app/index.tsx** is the initial route, and will appear first when you open the app or navigate to your web app's root URL. -- **app/home.tsx** is a page with the route `/home`, so you can navigate to it with a URL like `yourapp.com/home` in the browser, or `myapp://home` in a native app. -- **app/\_layout.tsx** is the root layout. Any initialization code you may have previously put in **App.jsx** should go here. -- **app/profile/friends.tsx** is a page with the route `/profile/friends`. -- **TextField.tsx** and **Toolbar.tsx** are not in the **app** directory, so they will not be considered pages. They will not have a URL, and they cannot be the target of a navigation action. However, they can be used as components in the pages inside of the **app** directory. +- **src/app/index.tsx** is the initial route, and will appear first when you open the app or navigate to your web app's root URL. +- **src/app/home.tsx** is a page with the route `/home`, so you can navigate to it with a URL like `yourapp.com/home` in the browser, or `yourapp://home` in a native app. +- **src/app/\_layout.tsx** is the root layout. Any initialization code you may have previously put in **App.jsx** should go here. +- **src/app/profile/friends.tsx** is a page with the route `/profile/friends`. +- **src/components/app-tabs.native.tsx** and **src/components/app-tabs.tsx** are [platform-specific](/router/advanced/platform-specific-modules/) tab components. The **.native.tsx** file is used on Android and iOS, while the **.tsx** file is used on web. The root layout imports these to render the tab navigator. +- **src/components/text-field.tsx** and **src/components/toolbar.tsx** are not in the **src/app** directory, so they will not be considered pages. They will not have a URL, and they cannot be the target of a navigation action. However, they can be used as components in the pages inside the **src/app** directory. diff --git a/docs/pages/router/basics/layout.mdx b/docs/pages/router/basics/layout.mdx index 247e2074b0a845..291c7828a517c9 100644 --- a/docs/pages/router/basics/layout.mdx +++ b/docs/pages/router/basics/layout.mdx @@ -16,17 +16,17 @@ import { VideoBoxLink } from '~/ui/components/VideoBoxLink'; className="mb-6" /> -Each directory within the **app** directory (including **app** itself) can define a layout in the form of a **\_layout.tsx** file inside that directory. This file defines how all the pages within that directory are arranged. This is where you would define a stack navigator, tab navigator, drawer navigator, or any other layout that you want to use for the pages in that directory. The layout file exports a default component that is rendered before whatever page you are navigating to within that directory. +Each directory within the **src/app** directory (including **src/app** itself) can define a layout in the form of a **\_layout.tsx** file inside that directory. This file defines how all the pages within that directory are arranged. This is where you would define a stack navigator, tab navigator, drawer navigator, or any other layout that you want to use for the pages in that directory. The layout file exports a default component that is rendered before whatever page you are navigating to within that directory. Let's look at a few common layout scenarios. ## Root layout -Virtually every app will have a **\_layout.tsx** file directly inside the **app** directory. This is the root layout and represents the entry point for your navigation. In addition to describing the top-level navigator for your app, this file is where you would put initialization code that may have previously gone inside an **App.jsx** file, such as loading fonts, interacting with the splash screen, or adding context providers. +Virtually every app will have a **\_layout.tsx** file directly inside the **src/app** directory. This is the root layout and represents the entry point for your navigation. In addition to describing the top-level navigator for your app, this file is where you would put initialization code that may have previously gone inside an **App.jsx** file, such as loading fonts, interacting with the splash screen, or adding context providers. Here's an example root layout: -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { useFonts } from 'expo-font'; import { Stack } from 'expo-router'; import * as SplashScreen from 'expo-splash-screen'; @@ -36,7 +36,7 @@ SplashScreen.preventAutoHideAsync(); export default function RootLayout() { const [loaded] = useFonts({ - SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'), + SpaceMono: require('@/assets/fonts/SpaceMono-Regular.ttf'), }); useEffect(() => { @@ -61,16 +61,16 @@ You can implement a stack navigator in your root layout, as shown above, or in a -If you want everything inside of the **app/products** directory to be arranged in a stack relationship, inside the **\_layout.tsx** file, return a `Stack` component: +If you want everything inside the **src/app/products** directory to be arranged in a stack relationship, inside the **\_layout.tsx** file, return a `Stack` component: -```tsx app/products/_layout.tsx +```tsx src/app/products/_layout.tsx import { Stack } from 'expo-router'; export default function StackLayout() { @@ -82,7 +82,7 @@ When you navigate to `/products`, it will first go to the default route, which i The `Stack` component implements [React Navigation's native stack](https://reactnavigation.org/docs/native-stack-navigator/) and can use the same screen options. However, you do not have to define the pages specifically inside the navigator. The files inside the directory will be automatically treated as eligible routes in the stack. However, if you want to define screen options, you can add a `Stack.Screen` component inside the `Stack` component. The `name` prop should match the route name, but you do not need to supply a `component` prop; Expo Router will map this automatically: -```tsx app/products/_layout.tsx +```tsx src/app/products/_layout.tsx import { Stack } from 'expo-router'; export default function StackLayout() { @@ -98,20 +98,24 @@ While it is possible to nest navigators, be sure to only do so when it is truly ## Tabs -Much like a stack, you can implement a tab navigator in your layout file, and all the routes directly inside that directory will be treated as tabs. Consider the following file structure: +Expo Router provides multiple ways to implement tab navigation depending on your needs. + +### JavaScript tabs + +You can implement a JavaScript-based tab navigator in a layout file using the `Tabs` component. All the routes directly inside that directory will be treated as tabs. Consider the following file structure: In the **\_layout.tsx** file, return a `Tabs` component: -```tsx app/(tabs)/_layout.tsx +```tsx src/app/(tabs)/_layout.tsx|collapseHeight=480 import { Tabs } from 'expo-router'; import MaterialIcons from '@expo/vector-icons/MaterialIcons'; @@ -125,7 +129,8 @@ export default function TabLayout() { tabBarIcon: ({ color }) => , }} /> - + + ); } @@ -135,6 +140,111 @@ This will cause the **index.tsx**, **feed.tsx**, and **profile.tsx** files to ap In the case of `Tabs`, you will likely want to define the tabs in the navigator, as this influences the order in which tabs appear, the title, and the icon inside the tab. The index route will be the default selected tab. +### Native tabs + +On Android and iOS, you can use [native tabs](/router/advanced/native-tabs/) to render the platform's built-in tab bar. Native tabs provide expected platform behaviors like scroll-to-top on tap, native animations, and a native look and feel. + +Like JavaScript tabs, native tabs can be used in a layout file inside a route group directory: + + + +```tsx src/app/(tabs)/_layout.tsx +import { NativeTabs } from 'expo-router/unstable-native-tabs'; + +export default function TabLayout() { + return ( + + + Home + + + + Feed + + + + Profile + + + + ); +} +``` + +### Platform-specific tabs + +Since native tabs are only available on Android and iOS, a common pattern is to use [platform-specific file extensions](/router/advanced/platform-specific-modules/) to provide different tab implementations for native and web. The root layout renders a tab component, and Expo's module resolution automatically picks the correct file based on the platform. + + + +The root layout imports and renders the `AppTabs` component. **app-tabs.native.tsx** is used on Android and iOS, and **app-tabs.tsx** on web: + +```tsx src/app/_layout.tsx +import AppTabs from '@/components/app-tabs'; + +export default function RootLayout() { + return ; +} +``` + +On Android and iOS, **app-tabs.native.tsx** uses [native tabs](/router/advanced/native-tabs/): + +```tsx src/components/app-tabs.native.tsx +import { NativeTabs } from 'expo-router/native-tabs'; + +export default function AppTabs() { + return ( + + + Home + + + + Explore + + + + ); +} +``` + +On web, **app-tabs.tsx** uses [custom tabs](/router/advanced/custom-tabs/) from `expo-router/ui`, which are unstyled and flexible components: + +```tsx src/components/app-tabs.tsx +import { Tabs, TabList, TabTrigger, TabSlot } from 'expo-router/ui'; + +export default function AppTabs() { + return ( + + + + + Home + + + Explore + + + + ); +} +``` + ## Slot In some cases, you may want a layout without a navigator. This is helpful for adding a header or footer around the current route, or for displaying a modal over any route inside a directory. In this case, you can use the `Slot` component, which serves as a placeholder for the current child route. @@ -143,16 +253,16 @@ Consider the following file structure: For example, you may want to wrap any route inside the **social** directory with a header and footer, but you want navigating between the pages to simply replace the current page rather than pushing new pages onto a stack, which can then later be popped off with a "back" navigation action. In the **\_layout.tsx** file, return a `Slot` component surrounded by your header and footer: -```tsx app/social/_layout.tsx +```tsx src/app/social/_layout.tsx import { Slot } from 'expo-router'; export default function Layout() { @@ -171,6 +281,7 @@ export default function Layout() { These are just a few examples of common layouts to give you an idea of how it works. There's much more you can do with layout: - Implement a [Drawer navigator](/router/advanced/drawer) +- Use [native tabs](/router/advanced/native-tabs) for a platform-native tab bar on Android and iOS - Replace the default tabs with [fully customized tabs](/router/advanced/custom-tabs) - Use a [modal](/router/advanced/modals) to display a page with transparency, such that the parent navigator is still visible underneath - [Adapt any navigator that is compatible with React Navigation](/versions/latest/sdk/router/#withlayoutcontextnav-processor), including top tabs, bottom sheets, and more diff --git a/docs/pages/router/basics/navigation.mdx b/docs/pages/router/basics/navigation.mdx index 6fd1845eee437c..01d2bb3d22b39a 100644 --- a/docs/pages/router/basics/navigation.mdx +++ b/docs/pages/router/basics/navigation.mdx @@ -30,14 +30,14 @@ export default function Home() { Expo Router apps default to stack navigation, where navigating to a new route pushes a screen onto a stack, and backing out of that route pops it off the stack. Usually, you would want to use the `router.navigate` function. This will either push a new page onto the stack or unwind to an existing route on the stack. However, you can also call `router.push` to explicitly push a new page onto the stack, `router.back` to go back to the previous page, or `router.replace` to replace the current page on the stack. -With Expo Router, you refer to pages by their URL, or their position relative to the **app** directory. Check out the following file structure and how you would navigate to each page: +With Expo Router, you refer to pages by their URL, or their position relative to the **src/app** directory. Check out the following file structure and how you would navigate to each page: @@ -53,7 +53,7 @@ With Expo Router, you refer to pages by their URL, or their position relative to The typical way to link to a page in Expo Router is to use links like web apps. Expo Router has a `Link` component for navigating between pages, where the `href` is the same route you would use in `router.navigate`: {/* prettier-ignore */} -```tsx app/index.tsx +```tsx src/app/index.tsx import { View } from 'react-native'; /* @info Import the Link React component from expo-router. */ import { Link } from 'expo-router'; @@ -132,12 +132,12 @@ Dynamic routes can be linked to with their full URL, or by passing a `params` ob Consider the following file structure: - + Each of these links will navigate to the same page: {/* prettier-ignore */} -```tsx app/index.tsx +```tsx src/app/index.tsx /* @info Import the Link React component and router to navigate imperatively from expo-router. */ import { Link, router } from 'expo-router'; /* @end */ @@ -311,9 +311,9 @@ Assuming your `scheme` is `myapp`, here are some examples of how you would link @@ -327,16 +327,16 @@ Consider the following file structure: `stack` is a stack navigator, and `/stack/index` is always the first route in the stack. -To ensure that `/stack/index` is always loaded first, even if the user deep links to `/stack/second`, you can set the `initialRouteName` in **app/stack/\_layout.tsx**: +To ensure that `/stack/index` is always loaded first, even if the user deep links to `/stack/second`, you can set the `initialRouteName` in **src/app/stack/\_layout.tsx**: ```tsx export const unstable_settings = { @@ -347,7 +347,7 @@ export const unstable_settings = { By default, the `initialRouteName` is only considered when deep linking and not during navigation within your app. However, you can use the `withAnchor` prop on `Link` to force the initial route to be loaded when navigating directly into another stack inside your app. -So, if **app/index.tsx** contained a link to `/stack/second`, add the `withAnchor` prop to ensure that `/stack/index` is loaded first, which will cause the user to go back to `/stack/index` when they press the back button from `/stack/second`: +So, if **src/app/index.tsx** contained a link to `/stack/second`, add the `withAnchor` prop to ensure that `/stack/index` is loaded first, which will cause the user to go back to `/stack/index` when they press the back button from `/stack/second`: ```tsx diff --git a/docs/pages/router/basics/notation.mdx b/docs/pages/router/basics/notation.mdx index 5459853754d947..dd8285ed6e8c1e 100644 --- a/docs/pages/router/basics/notation.mdx +++ b/docs/pages/router/basics/notation.mdx @@ -7,52 +7,54 @@ searchRank: 8 import { FileTree } from '~/ui/components/FileTree'; -When you look inside the **app** directory in a typical Expo Router project, you'll see a lot more than some simple file and directory names. What do the parentheses and brackets mean? Let's learn the significance of file-based routing notation and how it allows you to define complex navigation patterns. +When you look inside the **src/app** directory in a typical Expo Router project, you'll see a lot more than some simple file and directory names. What do the parentheses and brackets mean? Let's learn the significance of file-based routing notation and how it allows you to define complex navigation patterns. ## Types of route notation ### Simple names/no notation - + Regular file and directory names without any notation signify _static routes_. Their URL matches exactly as they appear in your file tree. So, a file named **favorites.tsx** inside the **feed** directory will have a URL of `/feed/favorites`. ### Square brackets - + If you see square brackets in a file or directory name, you are looking at a _dynamic route_. The name of the route includes a parameter that can be used when rendering the page. The parameter could be either in a directory name or a file name. For example, a file named **[userName].tsx** will match `/evanbacon`, `/expo`, or another username. Then, you can access that parameter with the `useLocalSearchParams` hook inside the page, using that to load the data for that specific user. ### Parentheses - + -A directory with its name surrounded in parentheses indicates a _route group_. These directories are useful for grouping routes together without affecting the URL. For example, a file named **app/(tabs)/settings.tsx** will have `/settings` for its URL, even though it is not directly in the **app** directory. +A directory with its name surrounded in parentheses indicates a _route group_. These directories are useful for grouping routes together without affecting the URL. For example, a file named **src/app/(home)/settings.tsx** will have `/settings` for its URL, even though it is not directly in the **src/app** directory. Route groups can be useful for simple organization purposes, but often become more important for defining complex relationships between routes. ### index.tsx files - + -Just like on the web, an **index.tsx** file indicates the default route for a directory. For example, a file named **profile/index.tsx** will match `/profile`. A file named **(tabs)/index.tsx** will match `/`, effectively becoming the default route for your entire app. +Just like on the web, an **index.tsx** file indicates the default route for a directory. For example, a file named **profile/index.tsx** will match `/profile`. A file named **(home)/index.tsx** will match `/`, effectively becoming the default route for your entire app. ### \_layout.tsx files - + **\_layout.tsx** files are special files that are not pages themselves but define how groups of routes inside a directory relate to each other. If a directory of routes is arranged as a stack or tabs, the layout route is where you would define that relationship by using a stack navigator or tab navigator component. -Layout routes are rendered before the actual page routes inside their directory. This means that the **\_layout.tsx** directly inside the **app** directory is rendered before anything else in the app, and is where you would put the initialization code that may have previously gone inside an **App.jsx** file. +Layout routes are rendered before the actual page routes inside their directory. This means that the **\_layout.tsx** directly inside the **src/app** directory is rendered before anything else in the app, and is where you would put the initialization code that may have previously gone inside an **App.jsx** file. ### Plus sign @@ -71,21 +73,21 @@ Consider the following project file structure to identify the different types of -- **app/about.tsx** is a static route that matches `/about`. -- **app/users/[userId].tsx** is a dynamic route that matches `/users/123`, `/users/456`, and so on. -- **app/(tabs)** is a route group. It will not factor into the URL, so `/feed` will match **app/(tabs)/feed.tsx**. -- **app/(tabs)/index.tsx** is the default route for the **(tabs)** directory, so it will be the initially-focused tab, and will match the `/` URL. -- **app/(tabs)/\_layout.tsx** is a layout file defining how the three pages inside **app/(tabs)/** relate to each other. If you use a tab navigator component inside of this file, then those screens will be arranged as tabs. -- **app/\_layout.tsx** is the root layout file, and is rendered before any other route in the app. -- **+not-found.tsx** is a special route that will be displayed if the user navigates to a route that doesn't exist in your app. +- **src/app/about.tsx** is a static route that matches `/about`. +- **src/app/users/[userId].tsx** is a dynamic route that matches `/users/123`, `/users/456`, and so on. +- **src/app/(home)** is a route group. It will not factor into the URL, so `/feed` will match **src/app/(home)/feed.tsx**. +- **src/app/(home)/index.tsx** is the default route for the **(home)** directory, and will match the `/` URL. +- **src/app/(home)/\_layout.tsx** is a layout file defining how the pages inside **src/app/(home)/** relate to each other. +- **src/app/\_layout.tsx** is the root layout file, and is rendered before any other route in the app. +- **src/app/+not-found.tsx** is a special route that will be displayed if the user navigates to a route that doesn't exist in your app. diff --git a/docs/pages/router/error-handling.mdx b/docs/pages/router/error-handling.mdx index 622e8d81910b35..d8c2a80eac091b 100644 --- a/docs/pages/router/error-handling.mdx +++ b/docs/pages/router/error-handling.mdx @@ -17,7 +17,7 @@ This guide specifies how to handle unmatched routes and errors in your app when Native apps don't have a server so there are technically no 404s. However, if you're implementing a router universally, then it makes sense to handle missing routes. This is done automatically for each app, but you can also customize it. -```tsx app/+not-found.tsx +```tsx src/app/+not-found.tsx import { Unmatched } from 'expo-router'; export default Unmatched; ``` @@ -46,7 +46,7 @@ Expo Router enables fine-tuned error handling to enable a more opinionated data- You can export a nested [`ErrorBoundary`](/versions/latest/sdk/router/#errorboundary) component from any route to intercept and format component-level errors using [React Error Boundaries](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary): {/* prettier-ignore */} -```tsx app/home.tsx +```tsx src/app/home.tsx import { View, Text } from 'react-native'; import { type ErrorBoundaryProps } from 'expo-router'; diff --git a/docs/pages/router/installation.mdx b/docs/pages/router/installation.mdx index b1e37be92c8234..3ec71d1da2ab74 100644 --- a/docs/pages/router/installation.mdx +++ b/docs/pages/router/installation.mdx @@ -36,7 +36,7 @@ The above command will install versions of these libraries that are compatible w ### Setup entry point -For the property `main`, use the `expo-router/entry` as its value in the **package.json**. The initial client file is [**app/\_layout.tsx**](/router/basics/layout/#root-layout). +For the property `main`, use the `expo-router/entry` as its value in the **package.json**. The initial client file is [**src/app/\_layout.tsx**](/router/reference/src-directory/) (or [**app/\_layout.tsx**](/router/basics/layout/#root-layout) if not using the **src** directory). ```json package.json { @@ -46,7 +46,7 @@ For the property `main`, use the `expo-router/entry` as its value in the **packa -You can create a custom entry point in your Expo Router project to initialize and load side-effects before your app loads the root layout (**app/\_layout.tsx**). Below are some of the common cases for a custom entry point: +You can create a custom entry point in your Expo Router project to initialize and load side-effects before your app loads the root layout (**src/app/\_layout.tsx**). Below are some of the common cases for a custom entry point: - Initializing global services like analytics, error reporting, and so on. - Setting up polyfills @@ -54,7 +54,7 @@ You can create a custom entry point in your Expo Router project to initialize an 1. Create a new file in the root of your project, such as **index.js**. After creating this file, the project structure should look like this: - + 2. Import or add your custom configuration to the file. Then, import `expo-router/entry` to register the app entry. Remember to always import it last to ensure all configurations are properly set up before the app renders. @@ -83,11 +83,14 @@ You can create a custom entry point in your Expo Router project to initialize an ### Modify project configuration -Add a deep linking `scheme` in your [app config](/workflow/configuration/): +Add a deep linking `scheme` and enable [typed routes](/router/reference/typed-routes/) in your [app config](/workflow/configuration/): ```json app.json { - "scheme": "your-app-scheme" + "scheme": "your-app-scheme", + "experiments": { + "typedRoutes": true + } } ``` @@ -111,7 +114,7 @@ Then, enable [Metro web](/guides/customizing-metro/#adding-web-support-to-metro) ### Modify babel.config.js -Ensure you use `babel-preset-expo` as the `preset`, in the **babel.config.js** file or delete the file: +If your project has a **babel.config.js** file, ensure you use `babel-preset-expo` as the `preset`. If you don't need any custom Babel configuration, you can delete the file entirely: ```js babel.config.js module.exports = function (api) { @@ -126,15 +129,38 @@ module.exports = function (api) { +### Configure path aliases + +If you are using the [`src` directory](/router/reference/src-directory/), add path aliases to your **tsconfig.json** so you can use short import paths like `@/components/button` instead of relative paths: + +```json tsconfig.json +{ + "extends": "expo/tsconfig.base", + "compilerOptions": { + "strict": true, + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"] +} +``` + +The `@/*` alias maps to the **src** directory in the above example. + + + + + ### Clear bundler cache -After updating the Babel config file, run the following command to clear the bundler cache: +After updating the configuration, run the following command to clear the bundler cache: - + ### Update resolutions diff --git a/docs/pages/router/introduction.mdx b/docs/pages/router/introduction.mdx index 0d9e3c03a4a6a4..8bef955f75931c 100644 --- a/docs/pages/router/introduction.mdx +++ b/docs/pages/router/introduction.mdx @@ -28,13 +28,15 @@ We recommend creating a new Expo app using `create-expo-app` to create a project +> **Note:** During the SDK 55 transition period, `create-expo-app@latest` without the `--template` flag creates an SDK 54 project. If you plan to use Expo Go on a physical device, use an SDK 54 project. Otherwise, use `--template default@sdk-55` to create an SDK 55 project. + diff --git a/docs/pages/router/migrate/from-expo-webpack.mdx b/docs/pages/router/migrate/from-expo-webpack.mdx index f1d9ff0fb71e88..48506e8670889f 100644 --- a/docs/pages/router/migrate/from-expo-webpack.mdx +++ b/docs/pages/router/migrate/from-expo-webpack.mdx @@ -59,7 +59,7 @@ In `@expo/webpack-config` all routes shared a single HTML file. This file was ba In Expo Router, there are two different rendering patterns: -- **Recommended**: `web.output: "static"` which outputs a new HTML file for each route in the app. This approach lets you [dynamically generate the entire HTML template using the **app/+html.js** file](/router/web/static-rendering#root-html). +- **Recommended**: `web.output: "static"` which outputs a new HTML file for each route in the app. This approach lets you [dynamically generate the entire HTML template using the **src/app/+html.tsx** file](/router/web/static-rendering#root-html). - **Not recommended**: `web.output: "single"` which outputs a single-page application. This approach lets you use `public/index.html` as the template HTML file. ## Static resources @@ -176,7 +176,7 @@ For example, here's a possible flow for setting up Workbox. Create a new project Next, create a root HTML file for the app and add the service worker registration script: -```tsx app/+html.tsx +```tsx src/app/+html.tsx import { ScrollViewStyleReset } from 'expo-router/html'; import type { PropsWithChildren } from 'react'; @@ -283,7 +283,7 @@ Unlike `@expo/webpack-config`, Expo Router does not automatically attempt to gen You can link this in your HTML file using the `link` tag: -```tsx app/+html.tsx +```tsx src/app/+html.tsx import { ScrollViewStyleReset } from 'expo-router/html'; import type { PropsWithChildren } from 'react'; diff --git a/docs/pages/router/migrate/from-react-navigation.mdx b/docs/pages/router/migrate/from-react-navigation.mdx index 844d6bfd74068d..592a54f7319ba8 100644 --- a/docs/pages/router/migrate/from-react-navigation.mdx +++ b/docs/pages/router/migrate/from-react-navigation.mdx @@ -70,7 +70,35 @@ Expo Router **does not** add `react-native-gesture-handler` (as of v3), so you'l ### Copy screens to the app directory -Create an **app** directory at the root of your repo, or in a root **src** directory. +Create an **app** directory inside a root **src** directory. Expo Router automatically detects the **src/app** directory as the root of your routes. + +Ensure your **tsconfig.json** and **app.json** are properly configured + +```json tsconfig.json +{ + "extends": "expo/tsconfig.base", + "compilerOptions": { + "strict": true, + "paths": { + "@/*": ["./src/*"], + "@/assets/*": ["./assets/*"] + } + }, + "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"] +} +``` + +Also ensure your **app.json** has the `expo-router` plugin configured: + +```json app.json +{ + "expo": { + /* @hide ... */ + /* @end */ + "plugins": ["expo-router"] + } +} +``` Layout the structure of your app by creating files according to the [application of Expo Router rules](/router/basics/core-concepts/#the-rules-of-expo-router-applied). Kebab-case and lowercase letters are considered best practice for route filenames. @@ -127,16 +155,16 @@ function App() { -```jsx app/_layout.js +```tsx src/app/_layout.tsx import { Stack } from 'expo-router'; export default function RootLayout() { @@ -157,7 +185,7 @@ export default function RootLayout() { The tab navigator will be moved to a subdirectory. -```jsx app/(home)/_layout.js +```tsx src/app/(home)/_layout.tsx import { Tabs } from 'expo-router'; export default function HomeLayout() { @@ -307,7 +335,7 @@ The [`fallback`](https://reactnavigation.org/docs/navigation-container/#fallback In React Navigation, you set the theme for the entire app using the [``](https://reactnavigation.org/docs/navigation-container/#theme) component. Expo Router manages the root container for you, so instead you should set the theme using the `ThemeProvider` directly. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { ThemeProvider, DarkTheme, DefaultTheme, useTheme } from '@react-navigation/native'; import { Slot } from 'expo-router'; @@ -434,7 +462,7 @@ You can use the [`reset`](https://reactnavigation.org/docs/navigation-actions/#r In the below example, the `navigation` prop is accessible from the `useNavigation` hook and the `CommonActions.reset` action from `@react-navigation/native`. The object specified in the `reset` action replaces the existing navigation state with the new one. {/* prettier-ignore */} -```jsx app/screen.js +```tsx src/app/screen.tsx import { useNavigation } from 'expo-router' import { CommonActions } from '@react-navigation/native' @@ -466,7 +494,7 @@ Expo Router can automatically generate [statically typed routes](/router/referen React Navigation navigators ``, ``, and `` use a shared appearance provider. In React Navigation, you set the theme for the entire app using the `` component. Expo Router manages the root container so that you can set the theme using the `ThemeProvider` directly. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx /* @info Import theme APIs from React Navigation directly. */ import { ThemeProvider, DarkTheme, DefaultTheme, useTheme } from '@react-navigation/native'; /* @end */ diff --git a/docs/pages/router/reference/screen-tracking.mdx b/docs/pages/router/reference/screen-tracking.mdx index da886b371320d7..3fcfe1e0ea75be 100644 --- a/docs/pages/router/reference/screen-tracking.mdx +++ b/docs/pages/router/reference/screen-tracking.mdx @@ -11,9 +11,9 @@ Unlike React Navigation, Expo Router always has access to a URL. This means scre 1. Create a higher-order component that observes the currently selected URL 2. Track the URL in your analytics provider - + -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx import { useEffect } from 'react'; import { usePathname, useGlobalSearchParams, Slot } from 'expo-router'; diff --git a/docs/pages/router/reference/src-directory.mdx b/docs/pages/router/reference/src-directory.mdx index e4b6bbcfc9210e..7729b6666fe741 100644 --- a/docs/pages/router/reference/src-directory.mdx +++ b/docs/pages/router/reference/src-directory.mdx @@ -7,7 +7,9 @@ import { FileTree } from '~/ui/components/FileTree'; import { Terminal } from '~/ui/components/Snippet'; import { Step } from '~/ui/components/Step'; -As your project grows, it can be helpful to move all the directories containing application code into a single **src** directory. Expo Router supports this out of the box. +Projects created with the [default template](/get-started/create-a-project/) on SDK 55 and later already include a top-level **src** directory that contains the **app**, **components**, **constants**, and **hooks** directories. No extra configuration is needed. + +If you are using a [custom template](/more/create-expo/#--template) or an existing project that doesn't include a **src** directory, follow the steps below to set it up. ## Using a top-level src directory diff --git a/docs/pages/router/reference/troubleshooting.mdx b/docs/pages/router/reference/troubleshooting.mdx index 21ddfe3b667821..86268e123cef8e 100644 --- a/docs/pages/router/reference/troubleshooting.mdx +++ b/docs/pages/router/reference/troubleshooting.mdx @@ -63,7 +63,7 @@ This can happen when using a custom version of `@expo/metro-config` that does no If you set up a modal or another screen that is expected to have a back button, then you'll need to add [`unstable_settings`](/router/advanced/router-settings/) to the route's layout to ensure the initial route is configured. Initial routes are somewhat unique to mobile apps and fit awkwardly in the system — improvements pending. -```tsx app/_layout.tsx +```tsx src/app/_layout.tsx export const unstable_settings = { initialRouteName: 'index', }; diff --git a/docs/pages/router/reference/typed-routes.mdx b/docs/pages/router/reference/typed-routes.mdx index fdfa71a02b29cd..05b0c7a523bb13 100644 --- a/docs/pages/router/reference/typed-routes.mdx +++ b/docs/pages/router/reference/typed-routes.mdx @@ -81,12 +81,12 @@ You can leverage the `useSegments()` hooks from `expo-router` to create complex @@ -105,7 +105,7 @@ export function Button() { } ``` -Now, you can leverage `` + +_See also: [official Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/components/button)_ + +### CircularProgress + +`import { CircularProgress } from '@expo/ui/jetpack-compose'; ` + +_See also: [official Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/components/progress)_ + +### ContextMenu + +> Note: Also known as DropdownMenu. + +`import { ContextMenu } from '@expo/ui/jetpack-compose'; setSelectedIndex(index)} /> ` + +_See also: [official Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/components/menu)_ + +### Chip + +`import { Chip } from '@expo/ui/jetpack-compose'; // Assist chip with icon console.log('Opening flight booking...')} /> // Filter chip with selection handleFilterToggle('Images')} /> // Input chip with dismiss handleInputDismiss('Work')} /> // Suggestion chip console.log('Searching nearby...')} />` + +_See also: [official Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/components/chip)_ + +### DateTimePicker (date) + +`import { DateTimePicker } from '@expo/ui/jetpack-compose'; { setSelectedDate(date); }} displayedComponents='date' initialDate={selectedDate.toISOString()} variant='picker' />` + +_See also: [official Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/components/datepickers)_ + +### DateTimePicker (time) + +`import { DateTimePicker } from '@expo/ui/jetpack-compose'; { setSelectedDate(date); }} displayedComponents='hourAndMinute' initialDate={selectedDate.toISOString()} variant='picker' />` + +_See also: [official Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/components/time-pickers)_ + +### LinearProgress + +### Picker (radio) + +`import { Picker } from '@expo/ui/jetpack-compose'; { setSelectedIndex(index); }} variant="radio" />` + +_See also: [official Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/components/radio-button)_ + +### Picker (segmented) + +`import { Picker } from '@expo/ui/jetpack-compose'; { setSelectedIndex(index); }} variant="segmented" />` + +_See also: [official Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/components/segmented-button)_ + +### Slider + +`import { Slider } from '@expo/ui/jetpack-compose'; { setValue(value); }} />` + +_See also: [official Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/components/slider)_ + +### Switch (toggle) + +> Note: also known as Toggle. + +`import { Switch } from '@expo/ui/jetpack-compose'; { setChecked(checked); }} color="#ff0000" label="Play music" variant="switch" />` + +_See also: [official Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/components/switch)_ + +### Switch (checkbox) + +`import { Switch } from '@expo/ui/jetpack-compose'; { setChecked(checked); }} label="Play music" color="#ff0000" variant="checkbox" />` + +_See also: [official Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/components/checkbox)_ + +### TextInput + +`import { TextInput } from '@expo/ui/jetpack-compose'; ` + +_See also: [official Jetpack Compose documentation](https://developer.android.com/develop/ui/compose/text/user-input)_ + +## API + +Full documentation is not yet available. Use TypeScript types to explore the API. + +`// Import from the Jetpack Compose package import { Button } from '@expo/ui/jetpack-compose';` + +--- + +# swift-ui + +SwiftUI components for building native iOS interfaces with @expo/ui. + +Bundled version: + +~0.2.0-beta.9 + +> This library is currently in beta and subject to breaking changes. It is not available in the Expo Go app — use [development builds](https://docs.expo.dev/develop/development-builds/introduction) to try it out. + +The SwiftUI components in `@expo/ui/swift-ui` allow you to build fully native iOS interfaces using SwiftUI from React Native. + +## Installation + +`npx expo install @expo/ui` + +If you are installing this in an [existing React Native app](https://docs.expo.dev/bare/overview), make sure to [install `expo`](https://docs.expo.dev/bare/installing-expo-modules) in your project. + +## Usage + +Using a component from `@expo/ui/swift-ui` requires wrapping it in a [`Host`](https://docs.expo.dev/versions/latest/sdk/ui/swift-ui#host) component. The `Host` is a container for SwiftUI views. + +`import { Host, Button } from '@expo/ui/swift-ui'; export function SaveButton() { return ( ); }` + +For an in-depth explanation of how `Host` works, see the following resources: + +[ + +Expo UI guide for Swift UI + +Learn about the basics of `@expo/ui/swift-ui` + +](https://docs.expo.dev/guides/expo-ui-swift-ui)[ + +Expo UI iOS Liquid Glass Tutorial + +Learn how to build real SwiftUI views in your React Native app with the new Expo UI. + +](https://www.youtube.com/watch?v=2wXYLWz3YEQ) + +## Components + +### BottomSheet + +`import { BottomSheet, Host, Text } from '@expo/ui/swift-ui'; import { useWindowDimensions } from 'react-native'; const { width } = useWindowDimensions(); setIsOpened(e)}> Hello, world! ` + +_See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/view/sheet\(ispresented:ondismiss:content:\))_ + +### Button + +> The borderless variant is not available on Apple TV. + +`import { Button, Host } from '@expo/ui/swift-ui'; ` + +_See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/button)_ + +### CircularProgress + +`import { CircularProgress, Host } from '@expo/ui/swift-ui'; ` + +_See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/progressview)_ + +### ColorPicker + +> This component is not available on Apple TV. + +`import { ColorPicker, Host } from '@expo/ui/swift-ui'; ` + +_See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/colorpicker)_ + +> Note: Also known as DropdownMenu. + +`import { ContextMenu, Host } from '@expo/ui/swift-ui'; setSelectedIndex(index)} /> ` + +_See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/view/contextmenu\(menuitems:\))_ + +### DateTimePicker (date) + +> This component is not available on Apple TV. + +`import { DateTimePicker, Host } from '@expo/ui/swift-ui'; { setSelectedDate(date); }} displayedComponents='date' initialDate={selectedDate.toISOString()} variant='wheel' /> ` + +_See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/datepicker)_ + +### DateTimePicker (time) + +> This component is not available on Apple TV. + +`import { DateTimePicker, Host } from '@expo/ui/swift-ui'; { setSelectedDate(date); }} displayedComponents='hourAndMinute' initialDate={selectedDate.toISOString()} variant='wheel' /> ` + +_See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/datepicker)_ + +### Gauge + +> This component is not available on Apple TV. + +`import { Gauge, Host } from "@expo/ui/swift-ui"; ` + +_See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/gauge)_ + +### Host + +A component that allows you to put the other `@expo/ui/swift-ui` components in React Native. It acts like [``](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/svg) for DOM, [``](https://shopify.github.io/react-native-skia/docs/canvas/overview/) for [`react-native-skia`](https://shopify.github.io/react-native-skia/), which underlying uses [`UIHostingController`](https://developer.apple.com/documentation/swiftui/uihostingcontroller) to render the SwiftUI views in UIKit. + +Since the `Host` component is a React Native [`View`](https://reactnative.dev/docs/view), you can pass the [`style`](https://reactnative.dev/docs/style) prop to it or `matchContents` prop to make the `Host` component match the contents' size. + +`import { Button, Host } from '@expo/ui/swift-ui'; function Example() { return ( ); }` + +`import { Button, Host, VStack, Text } from '@expo/ui/swift-ui'; function Example() { return ( Hello, world! ); }` + +### LinearProgress + +`import { LinearProgress, Host } from '@expo/ui/swift-ui'; ` + +_See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/progressview)_ + +### List + +``import { Host, List } from '@expo/ui/swift-ui'; alert(`indexes of selected items: ${items.join(', ')}`)} moveEnabled={moveEnabled} onMoveItem={(from, to) => alert(`moved item at index ${from} to index ${to}`)} onDeleteItem={(item) => alert(`deleted item at index: ${item}`)} listStyle='automatic' deleteEnabled={deleteEnabled} selectEnabled={selectEnabled}> {data.map((item, index) => ( ))} `` + +_See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/list)_ + +### Picker (segmented) + +`import { Host, Picker } from '@expo/ui/swift-ui'; { setSelectedIndex(index); }} variant="segmented" /> ` + +_See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/picker#Styling-pickers)_ + +### Picker (wheel) + +> The wheel variant is not available on Apple TV. + +`import { Host, Picker } from '@expo/ui/swift-ui'; { setSelectedIndex(index); }} variant="wheel" /> ` + +_See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/pickerstyle/wheel)_ + +### Slider + +> This component is not available on Apple TV. + +`import { Host, Slider } from '@expo/ui/swift-ui'; { setValue(value); }} /> ` + +_See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/slider)_ + +### Switch (toggle) + +> Note: Also known as Toggle. + +`import { Host, Switch } from '@expo/ui/swift-ui'; { setChecked(checked); }} color="#ff0000" label="Play music" variant="switch" /> ` + +_See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/toggle)_ + +### Switch (checkbox) + +`import { Host, Switch } from '@expo/ui/swift-ui'; { setChecked(checked); }} label="Play music" variant="checkbox" /> ` + +_See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/toggle)_ + +### TextField + +`import { Host, TextField } from '@expo/ui/swift-ui'; ` + +_See also: [official SwiftUI documentation](https://developer.apple.com/documentation/swiftui/textfield)_ + +### More + +Expo UI is still in active development. We continue to add more functionality and may change the API. Some examples in the docs may not be up to date. If you want to see the latest changes, check the [examples](https://github.com/expo/expo/tree/main/apps/native-component-list/src/screens/UI). + +## API + +Full documentation is not yet available. Use TypeScript types to explore the API. + +`// Import from the SwiftUI package import { BottomSheet } from '@expo/ui/swift-ui';` + +--- + +# updates + +## Expo Updates + +A library that enables your app to manage remote updates to your application code. + +`expo-updates` is a library that enables your app to manage remote updates to your application code. It communicates with the configured remote update service to get information about available updates. + +## Installation + +The `expo-updates` library can be automatically configured using [EAS Update](https://docs.expo.dev/eas-update/introduction), which is a hosted service that manages and serves updates to your app. To get started with EAS Update, follow the instructions in the [Get started](https://docs.expo.dev/eas-update/getting-started) guide. + +Alternatively, it is also possible to configure the `expo-updates` library manually in cases where a different remote update service is required or configuration is only specified in native files. + +Manual installation, configuration, and custom remote update services + +## Configuration + +There are build-time configuration options that control the behavior of the library. For most apps, these configuration values are set in the [app config](https://docs.expo.dev/workflow/configuration) under the [`updates` property](https://docs.expo.dev/versions/latest/config/app#updates). + +The two core required configuration options are: + +* [`updates.url`](https://docs.expo.dev/versions/latest/config/app#updates): the URL at which the library fetches remote updates +* [`runtimeVersion`](https://docs.expo.dev/versions/latest/config/app#runtimeversion): a [runtime version](https://docs.expo.dev/versions/latest/sdk/updates#runtime-version) + +These are configured automatically when following the EAS Update [Get started](https://docs.expo.dev/eas-update/getting-started) guide. + +#### Runtime version + +Each time you build a binary for your app it includes the native code and configuration present at the time of the build as well as native configuration, and this unique combination is represented by a string called a runtime version. A remote update targets one runtime version, indicating that only binaries with a matching runtime version can load the remote update. + +Manual configuration Automatic configuration using runtime version policies + +#### Native configuration and overriding + +If your project does not use Continuous Native Generation, these configuration values may also be set in your app's native configuration files or overridden at during initialization in native code. + +Native configuration instructions + +## Usage + +By default, `expo-updates` checks for updates when the app launches. If an update is available, it downloads the update and applies it the next time the app is restarted. You can tune this startup behavior using the `checkAutomatically` and `fallbackToCacheTimeout` configuration options above. + +The library also provides a variety of constants to inspect the current update and functions to customize update behavior from your application code (after startup). For example, one common alternative usage pattern is to manually check for updates after the app has started instead of doing the default check on launch. + +Example: Check for updates manually + +## Testing + +Most of the methods and constants in this library can be used or tested only in release builds. In debug builds, the default behavior is to always load the latest JavaScript from a development server. It is possible to [build a debug version of your app with the same updates behavior as a release build](https://docs.expo.dev/eas-update/debug-advanced#debugging-of-native-code-while-loading-the-app-through-expo-updates). Such an app will not open the latest JavaScript from your development server — it will load published updates just as a release build does. This may be useful for debugging the behavior of your app when it is not connected to a development server. + +To test the content of an update in a development build, run [`eas update`](https://docs.expo.dev/eas-update/getting-started) and then browse to the update in your development build. Note that this only simulates what an update will look like in your app, and most of the [Updates API](https://docs.expo.dev/versions/latest/sdk/updates#api) is unavailable when running in a development build. + +To test updates in a release build, you can create a [.apk](https://docs.expo.dev/build-reference/apk) or a [simulator build](https://docs.expo.dev/build-reference/simulators), or make a release build locally with `npx expo run:android --variant release` and `npx expo run:ios --configuration Release` (you don't need to submit this build to the store to test). The full [Updates API](https://docs.expo.dev/versions/latest/sdk/updates#api) is available in a release build. + +To test the content of an update in Expo Go, run [`eas update`](https://docs.expo.dev/eas-update/getting-started) and then browse to the update in Expo Go. Note that this only simulates what an update will look like in your app, and most of the [Updates API](https://docs.expo.dev/versions/latest/sdk/updates#api) is unavailable when running in Expo Go. Also note that only updates using [Expo Go-compatible libraries](https://docs.expo.dev/workflow/using-libraries#determining-third-party-library-compatibility) are supported. + +## API + +`import * as Updates from 'expo-updates';` + +## Constants + +### `Updates.channel` + +Type: `string | null` + +The channel name of the current build, if configured for use with EAS Update. `null` otherwise. + +Expo Go and development builds are not set to a specific channel and can run any updates compatible with their native runtime. Therefore, this value will always be `null` when running an update on Expo Go or a development build. + +### `Updates.checkAutomatically` + +Type: `[UpdatesCheckAutomaticallyValue](#updatescheckautomaticallyvalue) | null` + +Determines if and when `expo-updates` checks for and downloads updates automatically on startup. + +### `Updates.createdAt` + +Type: `[Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) | null` + +If `expo-updates` is enabled, this is a `Date` object representing the creation time of the update that's currently running (whether it was embedded or downloaded at runtime). + +In development mode, or any other environment in which `expo-updates` is disabled, this value is null. + +### `Updates.emergencyLaunchReason` + +Type: `null | string` + +If `isEmergencyLaunch` is set to true, this will contain a string error message describing what failed during initialization. + +### `Updates.isEmbeddedLaunch` + +Type: `boolean` + +This will be true if the currently running update is the one embedded in the build, and not one downloaded from the updates server. + +### `Updates.isEmergencyLaunch` + +Type: `boolean` + +`expo-updates` does its very best to always launch monotonically newer versions of your app so you don't need to worry about backwards compatibility when you put out an update. In very rare cases, it's possible that `expo-updates` may need to fall back to the update that's embedded in the app binary, even after newer updates have been downloaded and run (an "emergency launch"). This boolean will be `true` if the app is launching under this fallback mechanism and `false` otherwise. If you are concerned about backwards compatibility of future updates to your app, you can use this constant to provide special behavior for this rare case. + +### `Updates.isEnabled` + +Type: `boolean` + +Whether `expo-updates` is enabled. This may be false in a variety of cases including: + +* enabled set to false in configuration +* missing or invalid URL in configuration +* missing runtime version or SDK version in configuration +* error accessing storage on device during initialization + +When false, the embedded update is loaded. + +### `Updates.latestContext` + +Type: `[UpdatesNativeStateMachineContext](#updatesnativestatemachinecontext)` + +### `Updates.launchDuration` + +Type: `null | number` + +Number of milliseconds it took to launch. + +### `Updates.manifest` + +Type: `[Partial](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)<[Manifest](#manifest)>` + +If `expo-updates` is enabled, this is the [manifest](https://docs.expo.dev/versions/latest/sdk/constants#manifest) (or [classic manifest](https://docs.expo.dev/versions/latest/sdk/constants#appmanifest)) object for the update that's currently running. + +In development mode, or any other environment in which `expo-updates` is disabled, this object is empty. + +### `Updates.runtimeVersion` + +Type: `string | null` + +The runtime version of the current build. + +### `Updates.updateId` + +Type: `string | null` + +The UUID that uniquely identifies the currently running update. The UUID is represented in its canonical string form and will always use lowercase letters. This value is `null` when running in a local development environment or any other environment where `expo-updates` is disabled. + +Example + +`"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"` + +## Hooks + +### `useUpdates()` + +Hook that obtains information on available updates and on the currently running update. + +the structures with information on currently running and available updates. + +Example + +`import { StatusBar } from 'expo-status-bar'; import * as Updates from 'expo-updates'; import { useEffect } from 'react'; import { Button, Text, View } from 'react-native'; export default function UpdatesDemo() { const { currentlyRunning, isUpdateAvailable, isUpdatePending } = Updates.useUpdates(); useEffect(() => { if (isUpdatePending) { // Update has successfully downloaded; apply it now Updates.reloadAsync(); } }, [isUpdatePending]); // If true, we show the button to download and run the update const showDownloadButton = isUpdateAvailable; // Show whether or not we are running embedded code or an update const runTypeMessage = currentlyRunning.isEmbeddedLaunch ? 'This app is running from built-in code' : 'This app is running an update'; return ( Updates Demo {runTypeMessage} ); }` + +## Classes + +## Methods + +### `Updates.checkForUpdateAsync()` + +Checks the server to see if a newly deployed update to your project is available. Does not actually download the update. This method cannot be used in development mode, and the returned promise will be rejected if you try to do so. + +Checking for an update uses a device's bandwidth and battery life like any network call. Additionally, updates served by Expo may be rate limited. A good rule of thumb to check for updates judiciously is to check when the user launches or foregrounds the app. Avoid polling for updates in a frequent loop. + +A promise that fulfills with an [`UpdateCheckResult`](#updatecheckresult) object. + +The promise rejects in Expo Go or if the app is in development mode, or if there is an unexpected error or timeout communicating with the server. It also rejects when `expo-updates` is not enabled. + +### `Updates.clearLogEntriesAsync()` + +Clears existing `expo-updates` log entries. + +> For now, this operation does nothing on the client. Once log persistence has been implemented, this operation will actually remove existing logs. + +A promise that fulfills if the clear operation was successful. + +The promise rejects if there is an unexpected error in clearing the logs. + +### `Updates.fetchUpdateAsync()` + +Downloads the most recently deployed update to your project from server to the device's local storage. This method cannot be used in development mode, and the returned promise will be rejected if you try to do so. + +> Note: [`reloadAsync()`](#updatesreloadasync) can be called after promise resolution to reload the app using the most recently downloaded version. Otherwise, the update will be applied on the next app cold start. + +A promise that fulfills with an [`UpdateFetchResult`](#updatefetchresult) object. + +The promise rejects in Expo Go or if the app is in development mode, or if there is an unexpected error or timeout communicating with the server. It also rejects when `expo-updates` is not enabled. + +Retrieves the current extra params. + +This method cannot be used in Expo Go or development mode. It also rejects when `expo-updates` is not enabled. + +### `Updates.readLogEntriesAsync(maxAge)` + + + +Retrieves the most recent `expo-updates` log entries. + +A promise that fulfills with an array of [`UpdatesLogEntry`](#updateslogentry) objects; + +The promise rejects if there is an unexpected error in retrieving the logs. + +### `Updates.reloadAsync(options)` + + + +Instructs the app to reload using the most recently downloaded version. This is useful for triggering a newly downloaded update to launch without the user needing to manually restart the app. Unlike `Expo.reloadAppAsync()` provided by the `expo` package, this function not only reloads the app but also changes the loaded JavaScript bundle to that of the most recently downloaded update. + +It is not recommended to place any meaningful logic after a call to `await Updates.reloadAsync()`. This is because the promise is resolved after verifying that the app can be reloaded, and immediately before posting an asynchronous task to the main thread to actually reload the app. It is unsafe to make any assumptions about whether any more JS code will be executed after the `Updates.reloadAsync` method call resolves, since that depends on the OS and the state of the native module and main threads. + +This method cannot be used in Expo Go or development mode, and the returned promise will be rejected if you try to do so. It also rejects when `expo-updates` is not enabled. + +A promise that fulfills right before the reload instruction is sent to the JS runtime, or rejects if it cannot find a reference to the JS runtime. If the promise is rejected in production mode, it most likely means you have installed the module incorrectly. Double check you've followed the installation instructions. In particular, on iOS ensure that you set the `bridge` property on `EXUpdatesAppController` with a pointer to the `RCTBridge` you want to reload, and on Android ensure you either call `UpdatesController.initialize` with the instance of `ReactApplication` you want to reload, or call `UpdatesController.setReactNativeHost` with the proper instance of `ReactNativeHost`. + + + +Sets an extra param if value is non-null, otherwise unsets the param. Extra params are sent as an [Expo Structured Field Value Dictionary](https://docs.expo.dev/technical-specs/expo-sfv-0) in the `Expo-Extra-Params` header of update requests. A compliant update server may use these params when selecting an update to serve. + +This method cannot be used in Expo Go or development mode. It also rejects when `expo-updates` is not enabled. + + + +Overrides updates request headers in runtime from build time. This method allows you to load specific updates with custom request headers. Use this method at your own risk, as it may cause unexpected behavior. [Learn more about use cases and limitations](https://docs.expo.dev/eas-update/override/). + +### `Updates.setUpdateURLAndRequestHeadersOverride(configOverride)` + + + +Overrides updates URL and reuqest headers in runtime from build time. This method allows you to load specific updates from a URL that you provide. Use this method at your own risk, as it may cause unexpected behavior. Because of the risk, this method requires `disableAntiBrickingMeasures` to be set to `true` in the app.json file. [Learn more about use cases and limitations](https://docs.expo.dev/eas-update/override/). + +## Interfaces + +### `ReloadScreenImageSource` + +Image source that can be used for the reload screen. + +### `ReloadScreenOptions` + +Configuration options for customizing the reload screen appearance. + +### `UpdatesModuleInterface` + +Common interface for all native module implementations (android, ios, web). + +## Types + +### `CurrentlyRunningInfo` + +Structure encapsulating information on the currently running app (either the embedded bundle or a downloaded update). + +### `UpdateCheckResult` + +Literal Type: `union` + +The result of checking for a new update. + +Acceptable values are: `[UpdateCheckResultRollBack](#updatecheckresultrollback)` | `[UpdateCheckResultAvailable](#updatecheckresultavailable)` | `[UpdateCheckResultNotAvailable](#updatecheckresultnotavailable)` + +### `UpdateCheckResultAvailable` + +The update check result when a new update is found on the server. + +### `UpdateCheckResultNotAvailable` + +The update check result if no new update was found. + +### `UpdateCheckResultRollBack` + +The update check result when a rollback directive is received. + +### `UpdateFetchResult` + +Literal Type: `union` + +The result of fetching a new update. + +Acceptable values are: `[UpdateFetchResultSuccess](#updatefetchresultsuccess)` | `[UpdateFetchResultFailure](#updatefetchresultfailure)` | `[UpdateFetchResultRollBackToEmbedded](#updatefetchresultrollbacktoembedded)` + +### `UpdateFetchResultFailure` + +The failed result of fetching a new update. + +### `UpdateFetchResultRollBackToEmbedded` + +The roll back to embedded result of fetching a new update. + +### `UpdateFetchResultSuccess` + +The successful result of fetching a new update. + +### `UpdateInfo` + +Literal Type: `union` + +Combined structure representing any type of update. + +Acceptable values are: `[UpdateInfoNew](#updateinfonew)` | `[UpdateInfoRollback](#updateinforollback)` + +### `UpdateInfoNew` + +Structure representing a new update. + +### `UpdateInfoRollback` + +Structure representing a rollback directive. + +### `UpdatesCheckAutomaticallyNativeValue` + +Literal Type: `string` + +Acceptable values are: `'ALWAYS'` | `'ERROR_RECOVERY_ONLY'` | `'NEVER'` | `'WIFI_ONLY'` + +### `UpdatesEvents` + +### `UpdatesLogEntry` + +An object representing a single log entry from `expo-updates` logging on the client. + +### `UseUpdatesReturnType` + +The type returned by [`useUpdates()`](#useupdates). + +## Enums + +### `UpdateCheckResultNotAvailableReason` + +#### `NO_UPDATE_AVAILABLE_ON_SERVER` + +`UpdateCheckResultNotAvailableReason.NO_UPDATE_AVAILABLE_ON_SERVER ļ¼ "noUpdateAvailableOnServer"` + +No update manifest or rollback directive received from the update server. + +#### `ROLLBACK_NO_EMBEDDED` + +`UpdateCheckResultNotAvailableReason.ROLLBACK_NO_EMBEDDED ļ¼ "rollbackNoEmbeddedConfiguration"` + +A rollback directive was received from the update server, but this app has no embedded update. + +#### `ROLLBACK_REJECTED_BY_SELECTION_POLICY` + +`UpdateCheckResultNotAvailableReason.ROLLBACK_REJECTED_BY_SELECTION_POLICY ļ¼ "rollbackRejectedBySelectionPolicy"` + +A rollback directive was received from the update server, but the directive does not pass the configured selection policy. + +#### `UPDATE_PREVIOUSLY_FAILED` + +`UpdateCheckResultNotAvailableReason.UPDATE_PREVIOUSLY_FAILED ļ¼ "updatePreviouslyFailed"` + +An update manifest was received from the update server, but the update has been previously launched on this device and never successfully launched. + +#### `UPDATE_REJECTED_BY_SELECTION_POLICY` + +`UpdateCheckResultNotAvailableReason.UPDATE_REJECTED_BY_SELECTION_POLICY ļ¼ "updateRejectedBySelectionPolicy"` + +An update manifest was received from the update server, but the update is not launchable, or does not pass the configured selection policy. + +### `UpdateInfoType` + +The different possible types of updates. Currently, the only supported type is `UpdateInfoType.NEW`, indicating a new update that can be downloaded and launched on the device. In the future, other types of updates may be added to this list. + +#### `NEW` + +`UpdateInfoType.NEW ļ¼ "new"` + +This is the type for new updates found on or downloaded from the update server, that are launchable on the device. + +#### `ROLLBACK` + +`UpdateInfoType.ROLLBACK ļ¼ "rollback"` + +This type is used when an update is a directive to roll back to the embedded bundle. + +### `UpdatesCheckAutomaticallyValue` + +The possible settings that determine if `expo-updates` will check for updates on app startup. By default, Expo will check for updates every time the app is loaded. Set this to `ON_ERROR_RECOVERY` to disable automatic checking unless recovering from an error. Set this to `NEVER` to completely disable automatic checking. + +#### `NEVER` + +`UpdatesCheckAutomaticallyValue.NEVER ļ¼ "NEVER"` + +Automatic update checks are off, and update checks must be done through the JS API. + +#### `ON_ERROR_RECOVERY` + +`UpdatesCheckAutomaticallyValue.ON_ERROR_RECOVERY ļ¼ "ON_ERROR_RECOVERY"` + +Only checks for updates when the app starts up after an error recovery. + +#### `ON_LOAD` + +`UpdatesCheckAutomaticallyValue.ON_LOAD ļ¼ "ON_LOAD"` + +Checks for updates whenever the app is loaded. This is the default setting. + +#### `WIFI_ONLY` + +`UpdatesCheckAutomaticallyValue.WIFI_ONLY ļ¼ "WIFI_ONLY"` + +Only checks for updates when the app starts and has a Wi-Fi connection. + +### `UpdatesLogEntryCode` + +The possible code values for `expo-updates` log entries + +#### `ASSETS_FAILED_TO_LOAD` + +`UpdatesLogEntryCode.ASSETS_FAILED_TO_LOAD ļ¼ "AssetsFailedToLoad"` + +#### `INITIALIZATION_ERROR` + +`UpdatesLogEntryCode.INITIALIZATION_ERROR ļ¼ "InitializationError"` + +#### `JS_RUNTIME_ERROR` + +`UpdatesLogEntryCode.JS_RUNTIME_ERROR ļ¼ "JSRuntimeError"` + +#### `NONE` + +`UpdatesLogEntryCode.NONE ļ¼ "None"` + +#### `NO_UPDATES_AVAILABLE` + +`UpdatesLogEntryCode.NO_UPDATES_AVAILABLE ļ¼ "NoUpdatesAvailable"` + +#### `UNKNOWN` + +`UpdatesLogEntryCode.UNKNOWN ļ¼ "Unknown"` + +#### `UPDATE_ASSETS_NOT_AVAILABLE` + +`UpdatesLogEntryCode.UPDATE_ASSETS_NOT_AVAILABLE ļ¼ "UpdateAssetsNotAvailable"` + +#### `UPDATE_CODE_SIGNING_ERROR` + +`UpdatesLogEntryCode.UPDATE_CODE_SIGNING_ERROR ļ¼ "UpdateCodeSigningError"` + +#### `UPDATE_FAILED_TO_LOAD` + +`UpdatesLogEntryCode.UPDATE_FAILED_TO_LOAD ļ¼ "UpdateFailedToLoad"` + +#### `UPDATE_HAS_INVALID_SIGNATURE` + +`UpdatesLogEntryCode.UPDATE_HAS_INVALID_SIGNATURE ļ¼ "UpdateHasInvalidSignature"` + +#### `UPDATE_SERVER_UNREACHABLE` + +`UpdatesLogEntryCode.UPDATE_SERVER_UNREACHABLE ļ¼ "UpdateServerUnreachable"` + +### `UpdatesLogEntryLevel` + +The possible log levels for `expo-updates` log entries + +#### `DEBUG` + +`UpdatesLogEntryLevel.DEBUG ļ¼ "debug"` + +#### `ERROR` + +`UpdatesLogEntryLevel.ERROR ļ¼ "error"` + +#### `FATAL` + +`UpdatesLogEntryLevel.FATAL ļ¼ "fatal"` + +#### `INFO` + +`UpdatesLogEntryLevel.INFO ļ¼ "info"` + +#### `TRACE` + +`UpdatesLogEntryLevel.TRACE ļ¼ "trace"` + +#### `WARN` + +`UpdatesLogEntryLevel.WARN ļ¼ "warn"` + +Error codes +--------------------------- + +--- + +# video + +A library that provides an API to implement video playback in apps. + +`expo-video` is a cross-platform, performant video component for React Native and Expo with Web support. + +#### Known issues  + +When two [`VideoView`](https://docs.expo.dev/versions/latest/sdk/video#videoview) components are overlapping and have the [`contentFit`](https://docs.expo.dev/versions/latest/sdk/video#contentfit) prop set to [`cover`](https://docs.expo.dev/versions/latest/sdk/video#videocontentfit), one of the videos may be displayed out of bounds. This is a [known upstream issue](https://github.com/androidx/media/issues/1107). To work around this issue, use the [`surfaceType`](https://docs.expo.dev/versions/latest/sdk/video#surfacetype) prop and set it to [`textureView`](https://docs.expo.dev/versions/latest/sdk/video#surfacetype-1). + +## Installation + +`npx expo install expo-video` + +If you are installing this in an [existing React Native app](https://docs.expo.dev/bare/overview), make sure to [install `expo`](https://docs.expo.dev/bare/installing-expo-modules) in your project. + +## Configuration in app config + +You can configure `expo-video` using its built-in [config plugin](https://docs.expo.dev/config-plugins/introduction) if you use config plugins in your project ([Continuous Native Generation (CNG)](https://docs.expo.dev/workflow/continuous-native-generation)). The plugin allows you to configure various properties that cannot be set at runtime and require building a new app binary to take effect. If your app does not use CNG, then you'll need to manually configure the library. + +### Example app.json with config plugin + +`{ "expo": { "plugins": [ [ "expo-video", { "supportsBackgroundPlayback": true, "supportsPictureInPicture": true } ] ], } }` + +### Configurable properties + +## Usage + +Here's a simple example of a video with a play and pause button. + +`import { useEvent } from 'expo'; import { useVideoPlayer, VideoView } from 'expo-video'; import { StyleSheet, View, Button } from 'react-native'; const videoSource = 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'; export default function VideoScreen() { const player = useVideoPlayer(videoSource, player => { player.loop = true; player.play(); }); const { isPlaying } = useEvent(player, 'playingChange', { isPlaying: player.playing }); return ( ); } const styles = StyleSheet.create({ contentContainer: { flex: 1, padding: 10, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 50, }, video: { width: 350, height: 275, }, controlsContainer: { padding: 10, }, });` + +### Receiving events + +The changes in properties of the [`VideoPlayer`](https://docs.expo.dev/versions/latest/sdk/video#videoplayer) do not update the React state. Therefore, to display the information about the current state of the `VideoPlayer`, it is necessary to listen to the [events](https://docs.expo.dev/versions/latest/sdk/video#videoplayerevents) it emits. The event system is based on the [`EventEmitter`](https://docs.expo.dev/versions/latest/sdk/expo#eventemitter) class and [hooks](https://docs.expo.dev/versions/latest/sdk/expo#hooks) from the [`expo`](https://docs.expo.dev/versions/latest/sdk/expo) package. There are a few ways to listen to events: + +#### `useEvent` hook + +Creates a listener that will return a stateful value that can be used in a component. It also cleans up automatically when the component unmounts. + +`import { useEvent } from 'expo'; // ... Other imports, definition of the component, creating the player etc. const { status, error } = useEvent(player, 'statusChange', { status: player.status }); // Rest of the component...` + +#### `useEventListener` hook + +Built around the `Player.addListener` and `Player.removeListener` methods, creates an event listener with automatic cleanup. + +`import { useEventListener } from 'expo'; // ...Other imports, definition of the component, creating the player etc. useEventListener(player, 'statusChange', ({ status, error }) => { setPlayerStatus(status); setPlayerError(error); console.log('Player status changed: ', status); }); // Rest of the component...` + +#### `Player.addListener` method + +Most flexible way to listen to events, but requires manual cleanup and more boilerplate code. + +`// ...Imports, definition of the component, creating the player etc. useEffect(() => { const subscription = player.addListener('statusChange', ({ status, error }) => { setPlayerStatus(status); setPlayerError(error); console.log('Player status changed: ', status); }); return () => { subscription.remove(); }; }, []); // Rest of the component...` + +### Playing local media from the assets directory + +`expo-video` supports playing local media loaded using the `require` function. You can use the result as a source directly, or assign it to the `assetId` parameter of a [`VideoSource`](https://docs.expo.dev/versions/latest/sdk/video#videosource) if you also want to configure other properties. + +``import { VideoSource } from 'expo-video'; const assetId = require('./assets/bigbuckbunny.mp4'); const videoSource: VideoSource = { assetId, metadata: { title: 'Big Buck Bunny', artist: 'The Open Movie Project', }, }; const player1 = useVideoPlayer(assetId); // You can use the `asset` directly as a video source const player2 = useVideoPlayer(videoSource);`` + +### Preloading videos + +While another video is playing, a video can be loaded before showing it in the view. This allows for quicker transitions between subsequent videos and a better user experience. + +To preload a video, you have to create a `VideoPlayer` with a video source. Even when the player is not connected to a `VideoView`, it will fill the buffers. Once it is connected to the `VideoView`, it will be able to start playing without buffering. + +In some cases, it is beneficial to preload a video later in the screen lifecycle. In that case, a `VideoPlayer` with a `null` source should be created. To start preloading, replace the player source with a video source using the `replace()` function. + +Here is an example of how to preload a video: + +`import { useVideoPlayer, VideoView, VideoSource } from 'expo-video'; import { useState, useCallback } from 'react'; import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; const bigBuckBunnySource: VideoSource = 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'; const elephantsDreamSource: VideoSource = 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4'; export default function PreloadingVideoPlayerScreen() { const player1 = useVideoPlayer(bigBuckBunnySource, player => { player.play(); }); const player2 = useVideoPlayer(elephantsDreamSource, player => { player.currentTime = 20; }); const [currentPlayer, setCurrentPlayer] = useState(player1); const replacePlayer = useCallback(async () => { currentPlayer.pause(); if (currentPlayer === player1) { setCurrentPlayer(player2); player1.pause(); player2.play(); } else { setCurrentPlayer(player1); player2.pause(); player1.play(); } }, [player1, currentPlayer]); return ( Replace Player ); } const styles = StyleSheet.create({ contentContainer: { flex: 1, padding: 10, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 50, }, button: { alignItems: 'center', justifyContent: 'center', borderRadius: 3, paddingVertical: 8, paddingHorizontal: 12, backgroundColor: '#4630ec', }, buttonText: { fontSize: 12, fontWeight: 'bold', color: '#eeeeee', textAlign: 'center', }, video: { width: 300, height: 168.75, marginVertical: 20, }, });` + +### Using the VideoPlayer directly + +In most cases, the [`useVideoPlayer`](https://docs.expo.dev/versions/latest/sdk/video#usevideoplayersource-setup) hook should be used to create a `VideoPlayer` instance. It manages the player's lifecycle and ensures that it is properly disposed of when the component is unmounted. However, in some advanced use cases, it might be necessary to create a `VideoPlayer` that does not get automatically destroyed when the component is unmounted. In those cases, the `VideoPlayer` can be created using the [`createVideoPlayer`](https://docs.expo.dev/versions/latest/sdk/video#videocreatevideoplayersource) function. You need be aware of the risks that come with this approach, as it is your responsibility to call the [`release()`](https://docs.expo.dev/versions/latest/sdk/expo#release) method when the player is no longer needed. If not handled properly, this approach may lead to memory leaks. + +`import { createVideoPlayer } from 'expo-video'; const player = createVideoPlayer(videoSource);` + +> On Android, mounting multiple `VideoView` components at the same time with the same `VideoPlayer` instance will not work due to a [platform limitation](https://github.com/expo/expo/issues/35012). + +### Caching videos + +If your app frequently replays the same video, caching can be utilized to minimize network usage and enhance user experience, albeit at the cost of increased device storage usage. `expo-video` supports video caching on `Android` and `iOS` platforms. This feature can be activated by setting the [`useCaching`](https://docs.expo.dev/versions/latest/sdk/video#videosource) property of a [`VideoSource`](https://docs.expo.dev/versions/latest/sdk/video#videosource) object to `true`. + +The cache is persistent and will be cleared on a least-recently-used basis once the preferred size is exceeded. Furthermore, the system can clear the cache due to low storage availability, so it's not advisable to depend on the cache to store critical data. + +The cache functions offline. If a portion or the entirety of a video is cached, it can be played from the cache even when the device is offline until the cached data is exhausted. + +> Due to platform limitations, the cache cannot be used with HLS video sources on iOS. Caching DRM-protected videos is not supported on Android and iOS. + +### Managing the cache + +* The preferred cache size in bytes can be defined using the [`setVideoCacheSizeAsync`](https://docs.expo.dev/versions/latest/sdk/video#videosetvideocachesizeasyncsizebytes) function. The default cache size is 1GB. +* The [`getCurrentVideoCacheSize`](https://docs.expo.dev/versions/latest/sdk/video#videogetcurrentvideocachesize) can be used to get the current storage occupied by the cache in bytes. +* All cached videos can be cleared using the [`clearVideoCacheAsync`](https://docs.expo.dev/versions/latest/sdk/video#videoclearvideocacheasync) function. + +## API + +`import { VideoView, useVideoPlayer } from 'expo-video';` + +## Components + +### `VideoView` + +Type: `React.[PureComponent](https://react.dev/reference/react/PureComponent)<[VideoViewProps](#videoviewprops)>` + +VideoViewProps + +### `allowsFullscreen` + +Optionalā€ƒType: + +`boolean`ā€ƒDefault: `true` + +Determines whether fullscreen mode is allowed or not. + +> Note: This option has been deprecated in favor of the `fullscreenOptions` prop and will be disabled in the future. + +### `allowsPictureInPicture` + +Determines whether the player allows Picture in Picture (PiP) mode. + +> Note: The `supportsPictureInPicture` property of the [config plugin](#configuration-in-app-config) has to be configured for the PiP to work. + +### `allowsVideoFrameAnalysis` + +Optionalā€ƒType: + +`boolean`ā€ƒDefault: `true` + +Specifies whether to perform video frame analysis (Live Text in videos). Check official [Apple documentation](https://developer.apple.com/documentation/avkit/avplayerviewcontroller/allowsvideoframeanalysis) for more details. + +### `contentFit` + +Optionalā€ƒType: + +`[VideoContentFit](#videocontentfit)`ā€ƒDefault: `'contain'` + +Describes how the video should be scaled to fit in the container. Options are `'contain'`, `'cover'`, and `'fill'`. + +### `contentPosition` + +Optionalā€ƒType: + +`{ dx: number, dy: number }` + +Determines the position offset of the video inside the container. + +### `crossOrigin` + +Optionalā€ƒLiteral type: `string`ā€ƒDefault: `undefined` + +Determines the [cross origin policy](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/crossorigin) used by the underlying native view on web. If `undefined` (default), does not use CORS at all. If set to `'anonymous'`, the video will be loaded with CORS enabled. Note that some videos may not play if CORS is enabled, depending on the CDN settings. If you encounter issues, consider adjusting the `crossOrigin` property. + +Acceptable values are: `'anonymous'` | `'use-credentials'` + +### `fullscreenOptions` + +Optionalā€ƒType: + +`[FullscreenOptions](#fullscreenoptions)` + +Determines the fullscreen mode options. + +### `nativeControls` + +Optionalā€ƒType: + +`boolean`ā€ƒDefault: `true` + +Determines whether native controls should be displayed or not. + +### `onFirstFrameRender` + +Optionalā€ƒType: + +`() => void` + +A callback to call after the mounted `VideoPlayer` has rendered the first frame into the `VideoView`. This event can be used to hide any cover images that conceal the initial loading of the player. + +> Note: This event may also be called during playback when the current video track changes (for example when the player switches video quality). + +### `onFullscreenEnter` + +Optionalā€ƒType: + +`() => void` + +A callback to call after the video player enters fullscreen mode. + +### `onFullscreenExit` + +Optionalā€ƒType: + +`() => void` + +A callback to call after the video player exits fullscreen mode. + +### `onPictureInPictureStart` + +Optionalā€ƒType: + +`() => void` + +A callback to call after the video player enters Picture in Picture (PiP) mode. + +### `onPictureInPictureStop` + +Optionalā€ƒType: + +`() => void` + +A callback to call after the video player exits Picture in Picture (PiP) mode. + +### `player` + +A video player instance. Use [`useVideoPlayer()`](#usevideoplayersource-setup) hook to create one. + +### `playsInline` + +Determines whether a video should be played "inline", that is, within the element's playback area. + +### `requiresLinearPlayback` + +Optionalā€ƒType: + +`boolean`ā€ƒDefault: `false` + +Determines whether the player allows the user to skip media content. + +### `showsTimecodes` + +Optionalā€ƒType: + +`boolean`ā€ƒDefault: `true` + +Determines whether the timecodes should be displayed or not. + +### `startsPictureInPictureAutomatically` + +Optionalā€ƒType: + +`boolean`ā€ƒDefault: `false` + +Determines whether the player should start Picture in Picture (PiP) automatically when the app is in the background. + +> Note: Only one player can be in Picture in Picture (PiP) mode at a time. + +> Note: The `supportsPictureInPicture` property of the [config plugin](#configuration-in-app-config) has to be configured for the PiP to work. + +### `surfaceType` + +Optionalā€ƒType: + +`[SurfaceType](#surfacetype)`ā€ƒDefault: `'surfaceView'` + +Determines the type of the surface used to render the video. + +> This prop should not be changed at runtime. + +### `useExoShutter` + +Optionalā€ƒType: + +`boolean`ā€ƒDefault: `false` + +Determines whether the player should use the default ExoPlayer shutter that covers the `VideoView` before the first video frame is rendered. Setting this property to `false` makes the Android behavior the same as iOS. + +#### Inherited Props + +### `VideoAirPlayButton` + +Type: `React.[Element](https://www.typescriptlang.org/docs/handbook/jsx.html#function-component)<[VideoAirPlayButtonProps](#videoairplaybuttonprops)>` + +A view displaying the [`AVRoutePickerView`](https://developer.apple.com/documentation/avkit/avroutepickerview). Shows a button, when pressed, an AirPlay device picker shows up, allowing users to stream the currently playing video to any available AirPlay sink. + +> When using this view, make sure that the [`allowsExternalPlayback`](#allowsexternalplayback) player property is set to `true`. + +VideoAirPlayButtonProps + +### `activeTint` + +Optionalā€ƒType: + +`[ColorValue](https://reactnative.dev/docs/colors)`ā€ƒDefault: `undefined` + +The color of the button icon while AirPlay sharing is active. + +### `onBeginPresentingRoutes` + +Optionalā€ƒType: + +`() => void` + +A callback called when the AirPlay route selection popup is about to show. + +### `onEndPresentingRoutes` + +Optionalā€ƒType: + +`() => void` + +A callback called when the AirPlay route selection popup has disappeared. + +### `prioritizeVideoDevices` + +Optionalā€ƒType: + +`boolean`ā€ƒDefault: `true` + +Determines whether the AirPlay device selection popup should show video outputs first. + +### `tint` + +Optionalā€ƒType: + +`[ColorValue](https://reactnative.dev/docs/colors)`ā€ƒDefault: `undefined` + +The color of the button icon while AirPlay sharing is not active. + +## Component Methods + +### `enterFullscreen()` + +### `exitFullscreen()` + +### `startPictureInPicture()` + +Enters Picture in Picture (PiP) mode. Throws an exception if the device does not support PiP. + +> Note: Only one player can be in Picture in Picture (PiP) mode at a time. + +> Note: The `supportsPictureInPicture` property of the [config plugin](#configuration-in-app-config) has to be configured for the PiP to work. + +### `stopPictureInPicture()` + +Exits Picture in Picture (PiP) mode. + +## Hooks + +### `useVideoPlayer(source, setup)` + + + +Creates a `VideoPlayer`, which will be automatically cleaned up when the component is unmounted. + +## Classes + +### `VideoPlayer` + +Type: Class extends `[SharedObject](https://docs.expo.dev/versions/v54.0.0/sdk/expo#sharedobjecttype)<[VideoPlayerEvents](#videoplayerevents)>` + +A class that represents an instance of the video player. + +VideoPlayer Properties + +### `allowsExternalPlayback` + +Type: + +`boolean`ā€ƒDefault: `true` + +Determines whether the player should allow external playback. + +### `audioMixingMode` + +Type: + +`[AudioMixingMode](#audiomixingmode)`ā€ƒDefault: `'auto'` + +Determines how the player will interact with other audio playing in the system. + +### `audioTrack` + +Literal type: `union`ā€ƒDefault: `null` + +Specifies the audio track currently played by the player. `null` when no audio is played. + +Acceptable values are: `null` | `[AudioTrack](#audiotrack)` + +### `availableAudioTracks` + +Read Onlyā€ƒType: + +`[AudioTrack[]](#audiotrack)` + +An array of audio tracks available for the current video. + +### `availableSubtitleTracks` + +Read Onlyā€ƒType: + +`[SubtitleTrack[]](#subtitletrack)` + +An array of subtitle tracks available for the current video. + +### `availableVideoTracks` + +Read Onlyā€ƒType: + +`[VideoTrack[]](#videotrack)` + +An array of video tracks available for the current video. + +> On iOS, when using a HLS source, make sure that the uri contains `.m3u8` extension or that the [`contentType`](#contenttype) property of the [`VideoSource`](#videosource) has been set to `'hls'`. Otherwise, the video tracks will not be available. + +### `bufferedPosition` + +Float value indicating how far the player has buffered the video in seconds. + +This value is 0 when the player has not buffered up to the current playback time. When it's impossible to determine the buffer state (for example, when the player isn't playing any media), this value is -1. + +### `bufferOptions` + +Specifies buffer options which will be used by the player when buffering the video. + +> You should provide a `BufferOptions` object when setting this property. Setting individual buffer properties is not supported. + +### `currentLiveTimestamp` + +Read Onlyā€ƒLiteral type: `union` + +The exact timestamp when the currently displayed video frame was sent from the server, based on the `EXT-X-PROGRAM-DATE-TIME` tag in the livestream metadata. If this metadata is missing, this property will return `null`. + +Acceptable values are: `null` | `number` + +### `currentOffsetFromLive` + +Read Onlyā€ƒLiteral type: `union` + +Float value indicating the latency of the live stream in seconds. If a livestream doesn't have the required metadata, this will return `null`. + +Acceptable values are: `null` | `number` + +### `currentTime` + +Float value indicating the current playback time in seconds. + +If the player is not yet playing, this value indicates the time position at which playback will begin once the `play()` method is called. + +Setting `currentTime` to a new value seeks the player to the given time. Note that frame accurate seeking may incur additional decoding delay which can impact seeking performance. Consider using the [`seekBy`](#seekbyseconds) function if the time does not have to be set precisely. + +### `duration` + +Float value indicating the duration of the current video in seconds. + +### `isExternalPlaybackActive` + +Read Onlyā€ƒType: + +`boolean` + +Indicates whether the player is currently playing back the media to an external device via AirPlay. + +### `isLive` + +Read Onlyā€ƒType: + +`boolean` + +Boolean value indicating whether the player is currently playing a live stream. + +### `loop` + +Type: + +`boolean`ā€ƒDefault: `false` + +Determines whether the player should automatically replay after reaching the end of the video. + +### `muted` + +Type: + +`boolean`ā€ƒDefault: `false` + +Boolean value whether the player is currently muted. Setting this property to `true`/`false` will mute/unmute the player. + +### `playbackRate` + +Type: + +`number`ā€ƒDefault: `1.0` + +Float value between `0` and `16.0` indicating the current playback speed of the player. + +### `playing` + +Read Onlyā€ƒType: + +`boolean` + +Boolean value whether the player is currently playing. + +> Use `play` and `pause` methods to control the playback. + +### `preservesPitch` + +Type: + +`boolean`ā€ƒDefault: `true` + +Boolean value indicating if the player should correct audio pitch when the playback speed changes. + +### `showNowPlayingNotification` + +Type: + +`boolean`ā€ƒDefault: `false` + +Boolean value determining whether the player should show the now playing notification. + +### `status` + +Read Onlyā€ƒType: + +`[VideoPlayerStatus](#videoplayerstatus)` + +Indicates the current status of the player. + +### `staysActiveInBackground` + +Type: + +`boolean`ā€ƒDefault: `false` + +Determines whether the player should continue playing after the app enters the background. + +### `subtitleTrack` + +Literal type: `union`ā€ƒDefault: `null` + +Specifies the subtitle track which is currently displayed by the player. `null` when no subtitles are displayed. + +> To ensure a valid subtitle track, always assign one of the subtitle tracks from the [`availableSubtitleTracks`](#availablesubtitletracks) array. + +Acceptable values are: `null` | `[SubtitleTrack](#subtitletrack)` + +### `targetOffsetFromLive` + +Float value indicating the time offset from the live in seconds. + +### `timeUpdateEventInterval` + +Type: + +`number`ā€ƒDefault: `0` + +Float value indicating the interval in seconds at which the player will emit the [`timeUpdate`](#videoplayerevents) event. When the value is equal to `0`, the event will not be emitted. + +### `videoTrack` + +Read Onlyā€ƒLiteral type: `union`ā€ƒDefault: `null` + +Specifies the video track currently played by the player. `null` when no video is displayed. + +Acceptable values are: `null` | `[VideoTrack](#videotrack)` + +### `volume` + +Type: + +`number`ā€ƒDefault: `1.0` + +Float value between `0` and `1.0` representing the current volume. Muting the player doesn't affect the volume. In other words, when the player is muted, the volume is the same as when unmuted. Similarly, setting the volume doesn't unmute the player. + +VideoPlayer Methods + +### `generateThumbnailsAsync(times, options)` + + + +Generates thumbnails from the currently played asset. The thumbnails are references to native images, thus they can be used as a source of the `Image` component from `expo-image`. + +### `pause()` + +### `play()` + +### `replace(source, disableWarning)` + + + +Replaces the current source with a new one. + +> On iOS, this method loads the asset data synchronously on the UI thread and can block it for extended periods of time. Use `replaceAsync` to load the asset asynchronously and avoid UI lags. + +> This method will be deprecated in the future. + +### `replaceAsync(source)` + + + +Replaces the current source with a new one, while offloading loading of the asset to a different thread. + +> On Android and Web, this method is equivalent to `replace`. + +### `replay()` + +Seeks the playback to the beginning. + +### `seekBy(seconds)` + + + +Seeks the playback by the given number of seconds. The time to which the player seeks may differ from the specified requested time for efficiency, depending on the encoding and what is currently buffered by the player. Use this function to implement playback controls that seek by specific amount of time, in which case, the actual time usually does not have to be precise. For frame accurate seeking, use the [`currentTime`](#currenttime) property. + +### `VideoThumbnail` + +Type: Class extends `[SharedRef](https://docs.expo.dev/versions/v54.0.0/sdk/expo#sharedreftype)<'image'>` + +Represents a video thumbnail that references a native image. Instances of this class can be passed as a source to the `Image` component from `expo-image`. + +VideoThumbnail Properties + +### `actualTime` + +The time in seconds at which the thumbnail was actually generated. + +### `height` + +Height of the created thumbnail. + +### `nativeRefType` + +The type of the native reference. + +### `requestedTime` + +The time in seconds at which the thumbnail was to be created. + +### `width` + +Width of the created thumbnail. + +## Methods + +### `Video.clearVideoCacheAsync()` + +Clears all video cache. + +> This function can be called only if there are no existing `VideoPlayer` instances. + +A promise that fulfills after the cache has been cleaned. + +### `Video.createVideoPlayer(source)` + + + +Creates a direct instance of `VideoPlayer` that doesn't release automatically. + +> For most use cases you should use the [`useVideoPlayer`](#usevideoplayer) hook instead. See the [Using the VideoPlayer Directly](#using-the-videoplayer-directly) section for more details. + +### `Video.getCurrentVideoCacheSize()` + +Returns the space currently occupied by the video cache in bytes. + +### `Video.isPictureInPictureSupported()` + +Returns whether the current device supports Picture in Picture (PiP) mode. + +A `boolean` which is `true` if the device supports PiP mode, and `false` otherwise. + +### `Video.setVideoCacheSizeAsync(sizeBytes)` + + + +Sets desired video cache size in bytes. The default video cache size is 1GB. Value set by this function is persistent. The cache size is not guaranteed to be exact and the actual cache size may be slightly larger. The cache is evicted on a least-recently-used basis. + +> This function can be called only if there are no existing `VideoPlayer` instances. + +A promise that fulfills after the cache size has been set. + +## Types + +### `AudioMixingMode` + +Literal Type: `string` + +Specifies the audio mode that the player should use. Audio mode is set on per-app basis, if there are multiple players playing and have different a `AudioMode` specified, the highest priority mode will be used. Priority order: 'doNotMix' > 'auto' > 'duckOthers' > 'mixWithOthers'. + +* `mixWithOthers`: The player will mix its audio output with other apps. +* `duckOthers`: The player will lower the volume of other apps if any of the active players is outputting audio. +* `auto`: The player will allow other apps to keep playing audio only when it is muted. On iOS it will always interrupt other apps when `showNowPlayingNotification` is `true` due to system requirements. +* `doNotMix`: The player will pause playback in other apps, even when it's muted. + +> On iOS, the Now Playing notification is dependent on the audio mode. If the audio mode is different from `doNotMix` or `auto` this feature will not work. + +Acceptable values are: `'mixWithOthers'` | `'duckOthers'` | `'auto'` | `'doNotMix'` + +### `AudioTrack` + +### `BufferOptions` + +Specifies buffer options which will be used by the player when buffering the video. + +### `ContentType` + +Literal Type: `string` + +Specifies the content type of the source. + +* `auto`: The player will automatically determine the content type of the video. +* `progressive`: The player will use progressive download content type. This is the default `ContentType` when the uri does not contain an extension. +* `hls`: The player will use HLS content type. +* `dash`: The player will use DASH content type (Android-only). +* `smoothStreaming`: The player will use SmoothStreaming content type (Android-only). + +Acceptable values are: `'auto'` | `'progressive'` | `'hls'` | `'dash'` | `'smoothStreaming'` + +### `DRMOptions` + +Specifies DRM options which will be used by the player while loading the video. + +### `DRMType` + +Literal Type: `string` + +Specifies which type of DRM to use: + +* Android supports ClearKey, PlayReady and Widevine. +* iOS supports FairPlay. + +Acceptable values are: `'clearkey'` | `'fairplay'` | `'playready'` | `'widevine'` + +### `IsExternalPlaybackActiveChangeEventPayload` + +### `MutedChangeEventPayload` + +Data delivered with the [`mutedChange`](#videoplayerevents) event. + +### `PlaybackRateChangeEventPayload` + +Data delivered with the [`playbackRateChange`](#videoplayerevents) event. + +### `PlayerError` + +Contains information about any errors that the player encountered during the playback + +### `PlayingChangeEventPayload` + +Data delivered with the [`playingChange`](#videoplayerevents) event. + +### `SourceChangeEventPayload` + +Data delivered with the [`sourceChange`](#videoplayerevents) event. + +### `SourceLoadEventPayload` + +Data delivered with the [`sourceLoad`](#videoplayerevents) event, contains information about the video source that has finished loading. + +### `StatusChangeEventPayload` + +Data delivered with the [`statusChange`](#videoplayerevents) event. + +### `SubtitleTrack` + +### `SubtitleTrackChangeEventPayload` + +### `SurfaceType` + +Literal Type: `string` + +Describes the type of the surface used to render the video. + +* `surfaceView`: Uses the `SurfaceView` to render the video. This value should be used in the majority of cases. Provides significantly lower power consumption, better performance, and more features. +* `textureView`: Uses the `TextureView` to render the video. Should be used in cases where the SurfaceView is not supported or causes issues (for example, overlapping video views). + +You can learn more about surface types in the official [ExoPlayer documentation](https://developer.android.com/media/media3/ui/playerview#surfacetype). + +Acceptable values are: `'textureView'` | `'surfaceView'` + +### `TimeUpdateEventPayload` + +Data delivered with the [`timeUpdate`](#videoplayerevents) event, contains information about the current playback progress. + +### `VideoContentFit` + +Literal Type: `string` + +Describes how a video should be scaled to fit in a container. + +* `contain`: The video maintains its aspect ratio and fits inside the container, with possible letterboxing/pillarboxing. +* `cover`: The video maintains its aspect ratio and covers the entire container, potentially cropping some portions. +* `fill`: The video stretches/squeezes to completely fill the container, potentially causing distortion. + +Acceptable values are: `'contain'` | `'cover'` | `'fill'` + +### `VideoMetadata` + +Contains information that will be displayed in the now playing notification when the video is playing. + +### `VideoPlayerEvents` + +Handlers for events which can be emitted by the player. + +### `VideoPlayerStatus` + +Literal Type: `string` + +Describes the current status of the player. + +* `idle`: The player is not playing or loading any videos. +* `loading`: The player is loading video data from the provided source +* `readyToPlay`: The player has loaded enough data to start playing or to continue playback. +* `error`: The player has encountered an error while loading or playing the video. + +Acceptable values are: `'idle'` | `'loading'` | `'readyToPlay'` | `'error'` + +### `VideoSize` + +Specifies the size of a video track. + +### `VideoSource` + +Type: `string` or `number` or `null` or `object` shaped as below: + +### `VideoThumbnailOptions` + +Additional options for video thumbnails generation. + +### `VideoTrack` + +Specifies a VideoTrack loaded from a [`VideoSource`](#videosource). + +### `VideoTrackChangeEventPayload` + +Data delivered with the [`videoTrackChange`](#videoplayerevents) event, contains information about the video track which is currently being played. + +### `VolumeChangeEventPayload` + +Data delivered with the [`volumeChange`](#videoplayerevents) event. + +--- + +# video-av + +`import { useState, useRef } from 'react'; import { View, StyleSheet, Button } from 'react-native'; import { Video, ResizeMode } from 'expo-av'; export default function App() { const video = useRef(null); const [status, setStatus] = useState({}); return ( ); } %%placeholder-start%%const styles = StyleSheet.create({ ... }); %%placeholder-end%%const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', backgroundColor: '#ecf0f1', }, video: { alignSelf: 'center', width: 320, height: 200, }, buttons: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', }, });` + +--- + +# video-thumbnails + +`import { useState } from 'react'; import { StyleSheet, Button, View, Image, Text } from 'react-native'; import * as VideoThumbnails from 'expo-video-thumbnails'; export default function App() { const [image, setImage] = useState(null); const generateThumbnail = async () => { try { const { uri } = await VideoThumbnails.getThumbnailAsync( 'https://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4', { time: 15000, } ); setImage(uri); } catch (e) { console.warn(e); } }; return ( {image && } {image} ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, image: { width: 200, height: 200, }, });` + +--- + +# view-pager + +`import { StyleSheet, View, Text } from 'react-native'; import PagerView from 'react-native-pager-view'; export default function MyPager() { return ( First page Swipe āž”ļø Second page Third page ); } const styles = StyleSheet.create({ container: { flex: 1, }, page: { justifyContent: 'center', alignItems: 'center', }, });` + +--- + +# webbrowser + +## Expo WebBrowser + +A library that provides access to the system's web browser and supports handling redirects. + +`expo-web-browser` provides access to the system's web browser and supports handling redirects. On Android, it uses `ChromeCustomTabs` and on iOS, it uses `SFSafariViewController` or `ASWebAuthenticationSession`, depending on the method you call. As of iOS 11, `SFSafariViewController` no longer shares cookies with Safari, so if you are using `WebBrowser` for authentication you will want to use `WebBrowser.openAuthSessionAsync`, and if you just want to open a webpage (such as your app privacy policy), then use `WebBrowser.openBrowserAsync`. + +## Installation + +`npx expo install expo-web-browser` + +If you are installing this in an [existing React Native app](https://docs.expo.dev/bare/overview), make sure to [install `expo`](https://docs.expo.dev/bare/installing-expo-modules) in your project. + +## Configuration in app config + +You can configure `expo-web-browser` using its built-in [config plugin](https://docs.expo.dev/config-plugins/introduction) if you use config plugins in your project ([Continuous Native Generation (CNG)](https://docs.expo.dev/workflow/continuous-native-generation)). The plugin allows you to configure various properties that cannot be set at runtime and require building a new app binary to take effect. If your app does not use CNG, then you'll need to manually configure the library. + +### Example app.json with config plugin + +`{ "expo": { "plugins": [ [ "expo-web-browser", { "experimentalLauncherActivity": true } ] ] } }` + +### Configurable properties + +## Usage + +`import { useState } from 'react'; import { Button, Text, View, StyleSheet } from 'react-native'; import * as WebBrowser from 'expo-web-browser'; %%placeholder-start%%%%placeholder-end%%import Constants from 'expo-constants'; export default function App() { const [result, setResult] = useState(null); const _handlePressButtonAsync = async () => { let result = await WebBrowser.openBrowserAsync('https://expo.dev'); setResult(result); }; return ( {result && JSON.stringify(result)} ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingTop: Constants.statusBarHeight, backgroundColor: '#ecf0f1', }, });` + +### Handling deep links from the WebBrowser + +If your project uses Expo Router, deep links are handled automatically. + +If you use the `WebBrowser` window for authentication or another use case where you want to pass information back into your app through a deep link, add a handler with `Linking.addEventListener` before opening the browser. When the listener fires, you should call [`dismissBrowser`](https://docs.expo.dev/versions/latest/sdk/webbrowser#webbrowserdismissbrowser). It will not automatically be dismissed when a deep link is handled. Aside from that, redirects from `WebBrowser` work the same as other deep links. Read more about it in [Linking](https://docs.expo.dev/linking/into-your-app#handle-urls). + +## API + +`import * as WebBrowser from 'expo-web-browser';` + +## Methods + +### `WebBrowser.coolDownAsync(browserPackage)` + + + +This methods removes all bindings to services created by [`warmUpAsync`](#webbrowserwarmupasyncbrowserpackage) or [`mayInitWithUrlAsync`](#webbrowsermayinitwithurlasyncurl-browserpackage). You should call this method once you don't need them to avoid potential memory leaks. However, those binding would be cleared once your application is destroyed, which might be sufficient in most cases. + +The promise which fulfils with `WebBrowserCoolDownResult` when cooling is performed, or an empty object when there was no connection to be dismissed. + +### `WebBrowser.dismissAuthSession()` + +Dismisses the current authentication session. On web, it will close the popup window associated with auth process. + +The `void` on the successful attempt or throws an error if dismiss functionality is not available. + +### `WebBrowser.dismissBrowser()` + +Dismisses the presented web browser. + +`[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<{ type: WebBrowserResultType.DISMISS }>` + +The promise that resolves with `{ type: 'dismiss' }` on the successful attempt or throws an error if dismiss functionality is not available. + +### `WebBrowser.getCustomTabsSupportingBrowsersAsync()` + +Returns a list of applications package names supporting Custom Tabs, Custom Tabs service, user chosen and preferred one. This may not be fully reliable, since it uses `PackageManager.getResolvingActivities` under the hood. (For example, some browsers might not be present in browserPackages list once another browser is set to default.) + +The promise which fulfils with [`WebBrowserCustomTabsResults`](#webbrowsercustomtabsresults) object. + +### `WebBrowser.maybeCompleteAuthSession(options)` + + + +Possibly completes an authentication session on web in a window popup. The method should be invoked on the page that the window redirects to. + +Returns an object with message about why the redirect failed or succeeded: + +If `type` is set to `failed`, the reason depends on the message: + +* `Not supported on this platform`: If the platform doesn't support this method (Android, iOS). +* `Cannot use expo-web-browser in a non-browser environment`: If the code was executed in an SSR or node environment. +* `No auth session is currently in progress`: (the cached state wasn't found in local storage). This can happen if the window redirects to an origin (website) that is different to the initial website origin. If this happens in development, it may be because the auth started on localhost and finished on your computer port (Ex: `128.0.0.*`). This is controlled by the `redirectUrl` and `returnUrl`. +* `Current URL "" and original redirect URL "" do not match`: This can occur when the redirect URL doesn't match what was initial defined as the `returnUrl`. You can skip this test in development by passing `{ skipRedirectCheck: true }` to the function. + +If `type` is set to `success`, the parent window will attempt to close the child window immediately. + +If the error `ERR_WEB_BROWSER_REDIRECT` was thrown, it may mean that the parent window was reloaded before the auth was completed. In this case you'll need to close the child window manually. + +### `WebBrowser.mayInitWithUrlAsync(url, browserPackage)` + + + +This method initiates (if needed) [CustomTabsSession](https://developer.android.com/reference/android/support/customtabs/CustomTabsSession.html#maylaunchurl) and calls its `mayLaunchUrl` method for browser specified by the package. + +A promise which fulfils with `WebBrowserMayInitWithUrlResult` object. + +### `WebBrowser.openAuthSessionAsync(url, redirectUrl, options)` + + + +#### On Android: + +This will be done using a "custom Chrome tabs" browser, [AppState](https://reactnative.dev/docs/appstate), and [Linking](https://docs.expo.dev/versions/latest/sdk/linking) APIs. + +#### On iOS: + +Opens the url with Safari in a modal using `ASWebAuthenticationSession`. The user will be asked whether to allow the app to authenticate using the given url. To handle redirection back to the mobile application, the redirect URI set in the authentication server has to use the protocol provided as the scheme in app.json [`expo.scheme`](https://docs.expo.dev/versions/latest/config/app#scheme). For example, `demo://` not `https://` protocol. Using `Linking.addEventListener` is not needed and can have side effects. + +#### On web: + +> This API can only be used in a secure environment (localhost/https). to test this. Otherwise, an error with code [`ERR_WEB_BROWSER_CRYPTO`](#err_web_browser_crypto) will be thrown. This will use the browser's [`window.open()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) API. + +* _Desktop_: This will create a new web popup window in the browser that can be closed later using `WebBrowser.maybeCompleteAuthSession()`. +* _Mobile_: This will open a new tab in the browser which can be closed using `WebBrowser.maybeCompleteAuthSession()`. + +How this works on web: + +* A crypto state will be created for verifying the redirect. +``` +* This means you need to run with `npx expo start --https` +``` +* The state will be added to the window's `localstorage`. This ensures that auth cannot complete unless it's done from a page running with the same origin as it was started. Ex: if `openAuthSessionAsync` is invoked on `https://localhost:19006`, then `maybeCompleteAuthSession` must be invoked on a page hosted from the origin `https://localhost:19006`. Using a different website, or even a different host like `https://128.0.0.*:19006` for example will not work. +* A timer will be started to check for every 1000 milliseconds (1 second) to detect if the window has been closed by the user. If this happens then a promise will resolve with `{ type: 'dismiss' }`. + +> On mobile web, Chrome and Safari will block any call to [`window.open()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) which takes too long to fire after a user interaction. This method must be invoked immediately after a user interaction. If the event is blocked, an error with code [`ERR_WEB_BROWSER_BLOCKED`](#err_web_browser_blocked) will be thrown. + +* If the user does not permit the application to authenticate with the given url, the Promise fulfills with `{ type: 'cancel' }` object. +* If the user closed the web browser, the Promise fulfills with `{ type: 'cancel' }` object. +* If the browser is closed using [`dismissBrowser`](#webbrowserdismissbrowser), the Promise fulfills with `{ type: 'dismiss' }` object. + +### `WebBrowser.openBrowserAsync(url, browserParams)` + + + +Opens the url with Safari in a modal on iOS using [`SFSafariViewController`](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller), and Chrome in a new [custom tab](https://developer.chrome.com/multidevice/android/customtabs) on Android. On iOS, the modal Safari will not share cookies with the system Safari. If you need this, use [`openAuthSessionAsync`](#webbrowseropenauthsessionasyncurl-redirecturl-options). + +The promise behaves differently based on the platform. On Android promise resolves with `{ type: 'opened' }` if we were able to open browser. On iOS: + +* If the user closed the web browser, the Promise resolves with `{ type: 'cancel' }`. +* If the browser is closed using [`dismissBrowser`](#webbrowserdismissbrowser), the Promise resolves with `{ type: 'dismiss' }`. + +### `WebBrowser.warmUpAsync(browserPackage)` + + + +This method calls `warmUp` method on [CustomTabsClient](https://developer.android.com/reference/android/support/customtabs/CustomTabsClient.html#warmup\(long\)) for specified package. + +A promise which fulfils with `WebBrowserWarmUpResult` object. + +## Types + +### `AuthSessionOpenOptions` + +If there is no native AuthSession implementation available (which is the case on Android) the params inherited from [`WebBrowserOpenOptions`](#webbrowseropenoptions) will be used in the browser polyfill. Otherwise, the browser parameters will be ignored. + +Type: `[WebBrowserOpenOptions](https://docs.expo.dev/versions/latest/sdk/webbrowser#webbrowseropenoptions)` extended by: + +### `WebBrowserAuthSessionResult` + +Literal Type: `union` + +Acceptable values are: `[WebBrowserRedirectResult](#webbrowserredirectresult)` | `[WebBrowserResult](#webbrowserresult)` + +### `WebBrowserCompleteAuthSessionOptions` + +### `WebBrowserCompleteAuthSessionResult` + +### `WebBrowserCoolDownResult` + +Type: `ServiceActionResult` + +### `WebBrowserCustomTabsResults` + +### `WebBrowserMayInitWithUrlResult` + +Type: `ServiceActionResult` + +### `WebBrowserOpenOptions` + +### `WebBrowserRedirectResult` + +### `WebBrowserResult` + +### `WebBrowserWarmUpResult` + +Type: `ServiceActionResult` + +### `WebBrowserWindowFeatures` + +Type: `Record` + +## Enums + +### `WebBrowserPresentationStyle` + +#### `AUTOMATIC` + +`WebBrowserPresentationStyle.AUTOMATIC ļ¼ "automatic"` + +The default presentation style chosen by the system. On older iOS versions, falls back to `WebBrowserPresentationStyle.FullScreen`. + +#### `CURRENT_CONTEXT` + +`WebBrowserPresentationStyle.CURRENT_CONTEXT ļ¼ "currentContext"` + +A presentation style where the browser is displayed over the app's content. + +#### `FORM_SHEET` + +`WebBrowserPresentationStyle.FORM_SHEET ļ¼ "formSheet"` + +A presentation style that displays the browser centered in the screen. + +#### `FULL_SCREEN` + +`WebBrowserPresentationStyle.FULL_SCREEN ļ¼ "fullScreen"` + +A presentation style in which the presented browser covers the screen. + +#### `OVER_CURRENT_CONTEXT` + +`WebBrowserPresentationStyle.OVER_CURRENT_CONTEXT ļ¼ "overCurrentContext"` + +A presentation style where the browser is displayed over the app's content. + +#### `OVER_FULL_SCREEN` + +`WebBrowserPresentationStyle.OVER_FULL_SCREEN ļ¼ "overFullScreen"` + +A presentation style in which the browser view covers the screen. + +#### `PAGE_SHEET` + +`WebBrowserPresentationStyle.PAGE_SHEET ļ¼ "pageSheet"` + +A presentation style that partially covers the underlying content. + +#### `POPOVER` + +`WebBrowserPresentationStyle.POPOVER ļ¼ "popover"` + +A presentation style where the browser is displayed in a popover view. + +### `WebBrowserResultType` + +#### `CANCEL` + +`WebBrowserResultType.CANCEL ļ¼ "cancel"` + +#### `DISMISS` + +`WebBrowserResultType.DISMISS ļ¼ "dismiss"` + +#### `LOCKED` + +`WebBrowserResultType.LOCKED ļ¼ "locked"` + +#### `OPENED` + +`WebBrowserResultType.OPENED ļ¼ "opened"` + +## Error codes + +### `ERR_WEB_BROWSER_REDIRECT` + +Web only: The window cannot complete the redirect request because the invoking window doesn't have a reference to its parent. This can happen if the parent window was reloaded. + +### `ERR_WEB_BROWSER_BLOCKED` + +Web only: The popup window was blocked by the browser or failed to open. This can happen in mobile browsers when the `window.open()` method was invoked too long after a user input was fired. + +Mobile browsers do this to prevent malicious websites from opening many unwanted popups on mobile. + +You're method can still run in an async function but there cannot be any long running tasks before it. You can use hooks to disable user-inputs until any other processes have finished loading. + +### `ERR_WEB_BROWSER_CRYPTO` + +Web only: The current environment doesn't support crypto. Ensure you are running from a secure origin (localhost/https). + +--- + +# webview + +A library that provides a WebView component. + +`react-native-webview` provides a `WebView` component that renders web content in a native view. + +## Installation + +`npx expo install react-native-webview` + +If you are installing this in an [existing React Native app](https://docs.expo.dev/bare/overview), make sure to [install `expo`](https://docs.expo.dev/bare/installing-expo-modules) in your project. Then, follow the [installation instructions](https://github.com/react-native-webview/react-native-webview/blob/master/docs/Getting-Started.md#react-native-webview-getting-started-guide) provided in the library's README or documentation. + +## Usage + +`import { WebView } from 'react-native-webview'; import Constants from 'expo-constants'; import { StyleSheet } from 'react-native'; export default function App() { return ( ); } const styles = StyleSheet.create({ container: { flex: 1, marginTop: Constants.statusBarHeight, }, });` + +### With inline HTML + +`import { WebView } from 'react-native-webview'; import Constants from 'expo-constants'; import { StyleSheet } from 'react-native'; export default function App() { return (
Hello world
' }} /> ); } const styles = StyleSheet.create({ container: { flex: 1, marginTop: Constants.statusBarHeight, }, });` + +## Learn more + +[ + +Visit official documentation + +Get full information on API and its usage. + +](https://github.com/react-native-webview/react-native-webview/blob/master/docs/Guide.md) + +--- + diff --git a/docs/public/static/data/unversioned/expo-accelerometer.json b/docs/public/static/data/unversioned/expo-accelerometer.json index 7b948bb724bc43..042abbc7c92261 100644 --- a/docs/public/static/data/unversioned/expo-accelerometer.json +++ b/docs/public/static/data/unversioned/expo-accelerometer.json @@ -1 +1 @@ -{"schemaVersion":"2.0","name":"expo-accelerometer","variant":"project","kind":1,"children":[{"name":"AccelerometerMeasurement","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Each of these keys represents the acceleration along that particular axis in g-force (measured in "},{"kind":"code","text":"`g`"},{"kind":"text","text":"s).\n\nA "},{"kind":"code","text":"`g`"},{"kind":"text","text":" is a unit of gravitational force equal to that exerted by the earth’s gravitational field ("},{"kind":"code","text":"`9.81 m/s^2`"},{"kind":"text","text":")."}]},"children":[{"name":"timestamp","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Timestamp of the measurement in seconds."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"x","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Value of "},{"kind":"code","text":"`g`"},{"kind":"text","text":"s device reported in X axis."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"y","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Value of "},{"kind":"code","text":"`g`"},{"kind":"text","text":"s device reported in Y axis."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"z","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Value of "},{"kind":"code","text":"`g`"},{"kind":"text","text":"s device reported in Z axis."}]},"type":{"type":"intrinsic","name":"number"}}]},{"name":"AccelerometerSensor","variant":"declaration","kind":128,"comment":{"summary":[{"kind":"text","text":"A base class for subscribable sensors. The events emitted by this class are measurements\nspecified by the parameter type "},{"kind":"code","text":"`Measurement`"},{"kind":"text","text":"."}]},"children":[{"name":"constructor","variant":"declaration","kind":512,"signatures":[{"name":"AccelerometerSensor","variant":"signature","kind":16384,"parameters":[{"name":"nativeSensorModule","variant":"param","kind":32768,"type":{"type":"intrinsic","name":"any"}},{"name":"nativeEventName","variant":"param","kind":32768,"type":{"type":"intrinsic","name":"string"}}],"type":{"type":"reference","name":"AccelerometerSensor","package":"expo-sensors"},"inheritedFrom":{"type":"reference","name":"default.constructor"}}],"inheritedFrom":{"type":"reference","name":"default.constructor"}},{"name":"_nativeEventName","variant":"declaration","kind":1024,"flags":{"isInherited":true},"type":{"type":"intrinsic","name":"string"},"inheritedFrom":{"type":"reference","name":"default._nativeEventName"}},{"name":"_nativeModule","variant":"declaration","kind":1024,"flags":{"isInherited":true},"type":{"type":"intrinsic","name":"any"},"inheritedFrom":{"type":"reference","name":"default._nativeModule"}},{"name":"addListener","variant":"declaration","kind":2048,"signatures":[{"name":"addListener","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Subscribe for updates to the accelerometer."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"A subscription that you can call "},{"kind":"code","text":"`remove()`"},{"kind":"text","text":" on when you would like to unsubscribe the listener."}]}]},"parameters":[{"name":"listener","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"A callback that is invoked when an accelerometer update is available. When invoked,\nthe listener is provided a single argument that is an "},{"kind":"code","text":"`AccelerometerMeasurement`"},{"kind":"text","text":" object."}]},"type":{"type":"reference","typeArguments":[{"type":"reference","name":"AccelerometerMeasurement","package":"expo-sensors"}],"name":"Listener","package":"expo-sensors"}}],"type":{"type":"reference","name":"EventSubscription","package":"expo-modules-core"},"overwrites":{"type":"reference","name":"default.addListener"}}],"overwrites":{"type":"reference","name":"default.addListener"}},{"name":"getListenerCount","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"getListenerCount","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Returns the registered listeners count."}]},"type":{"type":"intrinsic","name":"number"},"inheritedFrom":{"type":"reference","name":"default.getListenerCount"}}],"inheritedFrom":{"type":"reference","name":"default.getListenerCount"}},{"name":"getPermissionsAsync","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"getPermissionsAsync","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Checks user's permissions for accessing sensor."}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"reference","name":"PermissionResponse","package":"expo-modules-core"}],"name":"Promise","package":"typescript"},"inheritedFrom":{"type":"reference","name":"default.getPermissionsAsync"}}],"inheritedFrom":{"type":"reference","name":"default.getPermissionsAsync"}},{"name":"hasListeners","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"hasListeners","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Returns boolean which signifies if sensor has any listeners registered."}]},"type":{"type":"intrinsic","name":"boolean"},"inheritedFrom":{"type":"reference","name":"default.hasListeners"}}],"inheritedFrom":{"type":"reference","name":"default.hasListeners"}},{"name":"isAvailableAsync","variant":"declaration","kind":2048,"signatures":[{"name":"isAvailableAsync","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"> **info** You should always check the sensor availability before attempting to use it.\n\nReturns whether the accelerometer is enabled on the device.\n\nOn mobile web, you must first invoke "},{"kind":"code","text":"`Accelerometer.requestPermissionsAsync()`"},{"kind":"text","text":" in a user interaction (i.e. touch event) before you can use this module.\nIf the "},{"kind":"code","text":"`status`"},{"kind":"text","text":" is not equal to "},{"kind":"code","text":"`granted`"},{"kind":"text","text":" then you should inform the end user that they may have to open settings.\n\nOn **web** this starts a timer and waits to see if an event is fired. This should predict if the iOS device has the **device orientation** API disabled in\n**Settings > Safari > Motion & Orientation Access**. Some devices will also not fire if the site isn't hosted with **HTTPS** as "},{"kind":"code","text":"`DeviceMotion`"},{"kind":"text","text":" is now considered a secure API.\nThere is no formal API for detecting the status of "},{"kind":"code","text":"`DeviceMotion`"},{"kind":"text","text":" so this API can sometimes be unreliable on web."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"A promise that resolves to a "},{"kind":"code","text":"`boolean`"},{"kind":"text","text":" denoting the availability of the accelerometer."}]}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"boolean"}],"name":"Promise","package":"typescript"},"overwrites":{"type":"reference","name":"default.isAvailableAsync"}}],"overwrites":{"type":"reference","name":"default.isAvailableAsync"}},{"name":"removeAllListeners","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"removeAllListeners","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Removes all registered listeners."}]},"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"default.removeAllListeners"}}],"inheritedFrom":{"type":"reference","name":"default.removeAllListeners"}},{"name":"removeSubscription","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"removeSubscription","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Removes the given subscription."}]},"parameters":[{"name":"subscription","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"A subscription to remove."}]},"type":{"type":"reference","name":"EventSubscription","package":"expo-modules-core"}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"default.removeSubscription"}}],"inheritedFrom":{"type":"reference","name":"default.removeSubscription"}},{"name":"requestPermissionsAsync","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"requestPermissionsAsync","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Asks the user to grant permissions for accessing sensor."}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"reference","name":"PermissionResponse","package":"expo-modules-core"}],"name":"Promise","package":"typescript"},"inheritedFrom":{"type":"reference","name":"default.requestPermissionsAsync"}}],"inheritedFrom":{"type":"reference","name":"default.requestPermissionsAsync"}},{"name":"setUpdateInterval","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"setUpdateInterval","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Set the sensor update interval."}]},"parameters":[{"name":"intervalMs","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Desired interval in milliseconds between sensor updates.\n> Starting from Android 12 (API level 31), the system has a 200Hz limit for each sensor updates.\n>\n> If you need an update interval of greater than 200Hz, you should\n> * add "},{"kind":"code","text":"`android.permission.HIGH_SAMPLING_RATE_SENSORS`"},{"kind":"text","text":" to [**app.json** "},{"kind":"code","text":"`permissions`"},{"kind":"text","text":" field](/versions/latest/config/app/#permissions)\n> * or if you are using an existing React Native project, add\n> "},{"kind":"code","text":"``"},{"kind":"text","text":" to **AndroidManifest.xml**."}]},"type":{"type":"intrinsic","name":"number"}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"default.setUpdateInterval"}}],"inheritedFrom":{"type":"reference","name":"default.setUpdateInterval"}}],"extendedTypes":[{"type":"reference","typeArguments":[{"type":"reference","name":"AccelerometerMeasurement","package":"expo-sensors"}],"name":"default","package":"expo-sensors"}]},{"name":"default","variant":"declaration","kind":32,"type":{"type":"reference","name":"AccelerometerSensor","package":"expo-sensors"}},{"name":"default","variant":"declaration","kind":128,"comment":{"summary":[{"kind":"text","text":"A base class for subscribable sensors. The events emitted by this class are measurements\nspecified by the parameter type "},{"kind":"code","text":"`Measurement`"},{"kind":"text","text":"."}]},"children":[{"name":"constructor","variant":"declaration","kind":512,"signatures":[{"name":"default","variant":"signature","kind":16384,"typeParameters":[{"name":"Measurement","variant":"typeParam","kind":131072}],"parameters":[{"name":"nativeSensorModule","variant":"param","kind":32768,"type":{"type":"intrinsic","name":"any"}},{"name":"nativeEventName","variant":"param","kind":32768,"type":{"type":"intrinsic","name":"string"}}],"type":{"type":"reference","typeArguments":[{"type":"reference","name":"Measurement","package":"expo-sensors","qualifiedName":"default.Measurement","refersToTypeParameter":true}],"name":"DeviceSensor","package":"expo-sensors","qualifiedName":"default"}}]},{"name":"_nativeEventName","variant":"declaration","kind":1024,"type":{"type":"intrinsic","name":"string"}},{"name":"_nativeModule","variant":"declaration","kind":1024,"type":{"type":"intrinsic","name":"any"}},{"name":"addListener","variant":"declaration","kind":2048,"signatures":[{"name":"addListener","variant":"signature","kind":4096,"parameters":[{"name":"listener","variant":"param","kind":32768,"type":{"type":"reference","typeArguments":[{"type":"reference","name":"Measurement","package":"expo-sensors","qualifiedName":"default.Measurement","refersToTypeParameter":true}],"name":"Listener","package":"expo-sensors"}}],"type":{"type":"reference","name":"EventSubscription","package":"expo-modules-core"}}]},{"name":"getListenerCount","variant":"declaration","kind":2048,"signatures":[{"name":"getListenerCount","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Returns the registered listeners count."}]},"type":{"type":"intrinsic","name":"number"}}]},{"name":"getPermissionsAsync","variant":"declaration","kind":2048,"signatures":[{"name":"getPermissionsAsync","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Checks user's permissions for accessing sensor."}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"reference","name":"PermissionResponse","package":"expo-modules-core"}],"name":"Promise","package":"typescript"}}]},{"name":"hasListeners","variant":"declaration","kind":2048,"signatures":[{"name":"hasListeners","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Returns boolean which signifies if sensor has any listeners registered."}]},"type":{"type":"intrinsic","name":"boolean"}}]},{"name":"isAvailableAsync","variant":"declaration","kind":2048,"signatures":[{"name":"isAvailableAsync","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"> **info** You should always check the sensor availability before attempting to use it."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"A promise that resolves to a "},{"kind":"code","text":"`boolean`"},{"kind":"text","text":" denoting the availability of the sensor."}]}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"boolean"}],"name":"Promise","package":"typescript"}}]},{"name":"removeAllListeners","variant":"declaration","kind":2048,"signatures":[{"name":"removeAllListeners","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Removes all registered listeners."}]},"type":{"type":"intrinsic","name":"void"}}]},{"name":"removeSubscription","variant":"declaration","kind":2048,"signatures":[{"name":"removeSubscription","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Removes the given subscription."}]},"parameters":[{"name":"subscription","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"A subscription to remove."}]},"type":{"type":"reference","name":"EventSubscription","package":"expo-modules-core"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"requestPermissionsAsync","variant":"declaration","kind":2048,"signatures":[{"name":"requestPermissionsAsync","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Asks the user to grant permissions for accessing sensor."}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"reference","name":"PermissionResponse","package":"expo-modules-core"}],"name":"Promise","package":"typescript"}}]},{"name":"setUpdateInterval","variant":"declaration","kind":2048,"signatures":[{"name":"setUpdateInterval","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Set the sensor update interval."}]},"parameters":[{"name":"intervalMs","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Desired interval in milliseconds between sensor updates.\n> Starting from Android 12 (API level 31), the system has a 200Hz limit for each sensor updates.\n>\n> If you need an update interval of greater than 200Hz, you should\n> * add "},{"kind":"code","text":"`android.permission.HIGH_SAMPLING_RATE_SENSORS`"},{"kind":"text","text":" to [**app.json** "},{"kind":"code","text":"`permissions`"},{"kind":"text","text":" field](/versions/latest/config/app/#permissions)\n> * or if you are using an existing React Native project, add\n> "},{"kind":"code","text":"``"},{"kind":"text","text":" to **AndroidManifest.xml**."}]},"type":{"type":"intrinsic","name":"number"}}],"type":{"type":"intrinsic","name":"void"}}]}],"typeParameters":[{"name":"Measurement","variant":"typeParam","kind":131072}],"extendedBy":[{"type":"reference","name":"AccelerometerSensor"}]},{"name":"PermissionExpiration","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Permission expiration time. Currently, all permissions are granted permanently."}]},"type":{"type":"union","types":[{"type":"literal","value":"never"},{"type":"intrinsic","name":"number"}]}},{"name":"PermissionResponse","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"An object obtained by permissions get and request functions."}]},"children":[{"name":"canAskAgain","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Indicates if user can be asked again for specific permission.\nIf not, one should be directed to the Settings app\nin order to enable/disable the permission."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"expires","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Determines time when the permission expires."}]},"type":{"type":"reference","name":"PermissionExpiration","package":"expo-modules-core"}},{"name":"granted","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"A convenience boolean that indicates if the permission is granted."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"status","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Determines the status of the permission."}]},"type":{"type":"reference","name":"PermissionStatus","package":"expo-modules-core"}}]},{"name":"PermissionStatus","variant":"declaration","kind":8,"children":[{"name":"DENIED","variant":"declaration","kind":16,"comment":{"summary":[{"kind":"text","text":"User has denied the permission."}]},"type":{"type":"literal","value":"denied"}},{"name":"GRANTED","variant":"declaration","kind":16,"comment":{"summary":[{"kind":"text","text":"User has granted the permission."}]},"type":{"type":"literal","value":"granted"}},{"name":"UNDETERMINED","variant":"declaration","kind":16,"comment":{"summary":[{"kind":"text","text":"User hasn't granted or denied the permission yet."}]},"type":{"type":"literal","value":"undetermined"}}]},{"name":"Subscription","variant":"declaration","kind":256,"comment":{"summary":[{"kind":"text","text":"A subscription object that allows to conveniently remove an event listener from the emitter."}]},"children":[{"name":"remove","variant":"declaration","kind":2048,"signatures":[{"name":"remove","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Removes an event listener for which the subscription has been created.\nAfter calling this function, the listener will no longer receive any events from the emitter."}]},"type":{"type":"intrinsic","name":"void"}}]}]}],"packageName":"expo-sensors"} \ No newline at end of file +{"schemaVersion":"2.0","name":"expo-accelerometer","variant":"project","kind":1,"children":[{"name":"AccelerometerMeasurement","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Each of these keys represents the acceleration along that particular axis in g-force (measured in "},{"kind":"code","text":"`g`"},{"kind":"text","text":"s).\n\nA "},{"kind":"code","text":"`g`"},{"kind":"text","text":" is a unit of gravitational force equal to that exerted by the earth’s gravitational field ("},{"kind":"code","text":"`9.81 m/s^2`"},{"kind":"text","text":")."}]},"children":[{"name":"timestamp","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Timestamp of the measurement in seconds."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"x","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Value of "},{"kind":"code","text":"`g`"},{"kind":"text","text":"s device reported in X axis."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"y","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Value of "},{"kind":"code","text":"`g`"},{"kind":"text","text":"s device reported in Y axis."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"z","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Value of "},{"kind":"code","text":"`g`"},{"kind":"text","text":"s device reported in Z axis."}]},"type":{"type":"intrinsic","name":"number"}}]},{"name":"AccelerometerSensor","variant":"declaration","kind":128,"comment":{"summary":[{"kind":"text","text":"A base class for subscribable sensors. The events emitted by this class are measurements\nspecified by the parameter type "},{"kind":"code","text":"`Measurement`"},{"kind":"text","text":"."}]},"children":[{"name":"constructor","variant":"declaration","kind":512,"signatures":[{"name":"AccelerometerSensor","variant":"signature","kind":16384,"parameters":[{"name":"nativeSensorModule","variant":"param","kind":32768,"type":{"type":"intrinsic","name":"any"}},{"name":"nativeEventName","variant":"param","kind":32768,"type":{"type":"intrinsic","name":"string"}}],"type":{"type":"reference","name":"AccelerometerSensor","package":"expo-sensors"},"inheritedFrom":{"type":"reference","name":"default.constructor"}}],"inheritedFrom":{"type":"reference","name":"default.constructor"}},{"name":"_nativeEventName","variant":"declaration","kind":1024,"flags":{"isInherited":true},"type":{"type":"intrinsic","name":"string"},"inheritedFrom":{"type":"reference","name":"default._nativeEventName"}},{"name":"_nativeModule","variant":"declaration","kind":1024,"flags":{"isInherited":true},"type":{"type":"intrinsic","name":"any"},"inheritedFrom":{"type":"reference","name":"default._nativeModule"}},{"name":"addListener","variant":"declaration","kind":2048,"signatures":[{"name":"addListener","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Subscribe for updates to the accelerometer."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"A subscription that you can call "},{"kind":"code","text":"`remove()`"},{"kind":"text","text":" on when you would like to unsubscribe the listener."}]}]},"parameters":[{"name":"listener","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"A callback that is invoked when an accelerometer update is available. When invoked,\nthe listener is provided a single argument that is an "},{"kind":"code","text":"`AccelerometerMeasurement`"},{"kind":"text","text":" object."}]},"type":{"type":"reference","typeArguments":[{"type":"reference","name":"AccelerometerMeasurement","package":"expo-sensors"}],"name":"Listener","package":"expo-sensors"}}],"type":{"type":"reference","name":"EventSubscription","package":"expo-modules-core"},"overwrites":{"type":"reference","name":"default.addListener"}}],"overwrites":{"type":"reference","name":"default.addListener"}},{"name":"getListenerCount","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"getListenerCount","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Returns the registered listeners count."}]},"type":{"type":"intrinsic","name":"number"},"inheritedFrom":{"type":"reference","name":"default.getListenerCount"}}],"inheritedFrom":{"type":"reference","name":"default.getListenerCount"}},{"name":"getPermissionsAsync","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"getPermissionsAsync","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Checks user's permissions for accessing sensor."}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"reference","name":"PermissionResponse","package":"expo-modules-core"}],"name":"Promise","package":"typescript"},"inheritedFrom":{"type":"reference","name":"default.getPermissionsAsync"}}],"inheritedFrom":{"type":"reference","name":"default.getPermissionsAsync"}},{"name":"hasListeners","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"hasListeners","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Returns boolean which signifies if sensor has any listeners registered."}]},"type":{"type":"intrinsic","name":"boolean"},"inheritedFrom":{"type":"reference","name":"default.hasListeners"}}],"inheritedFrom":{"type":"reference","name":"default.hasListeners"}},{"name":"isAvailableAsync","variant":"declaration","kind":2048,"signatures":[{"name":"isAvailableAsync","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"> **info** You should always check the sensor availability before attempting to use it.\n\nReturns whether the accelerometer is enabled on the device.\n\nOn mobile web, you must first invoke "},{"kind":"code","text":"`Accelerometer.requestPermissionsAsync()`"},{"kind":"text","text":" in a user interaction (i.e. touch event) before you can use this module.\nIf the "},{"kind":"code","text":"`status`"},{"kind":"text","text":" is not equal to "},{"kind":"code","text":"`granted`"},{"kind":"text","text":" then you should inform the end user that they may have to open settings.\n\nOn **web** this starts a timer and waits to see if an event is fired. This should predict if the iOS device has the **device orientation** API disabled in\n**Settings > Safari > Motion & Orientation Access**. Some devices will also not fire if the site isn't hosted with **HTTPS** as "},{"kind":"code","text":"`DeviceMotion`"},{"kind":"text","text":" is now considered a secure API.\nThere is no formal API for detecting the status of "},{"kind":"code","text":"`DeviceMotion`"},{"kind":"text","text":" so this API can sometimes be unreliable on web."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"A promise that resolves to a "},{"kind":"code","text":"`boolean`"},{"kind":"text","text":" denoting the availability of the accelerometer."}]}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"boolean"}],"name":"Promise","package":"typescript"},"overwrites":{"type":"reference","name":"default.isAvailableAsync"}}],"overwrites":{"type":"reference","name":"default.isAvailableAsync"}},{"name":"removeAllListeners","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"removeAllListeners","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Removes all registered listeners."}]},"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"default.removeAllListeners"}}],"inheritedFrom":{"type":"reference","name":"default.removeAllListeners"}},{"name":"removeSubscription","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"removeSubscription","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[],"blockTags":[{"tag":"@deprecated","content":[{"kind":"text","text":"use subscription.remove() instead."}]}]},"parameters":[{"name":"subscription","variant":"param","kind":32768,"type":{"type":"reference","name":"EventSubscription","package":"expo-modules-core"}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"default.removeSubscription"}}],"inheritedFrom":{"type":"reference","name":"default.removeSubscription"}},{"name":"requestPermissionsAsync","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"requestPermissionsAsync","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Asks the user to grant permissions for accessing sensor."}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"reference","name":"PermissionResponse","package":"expo-modules-core"}],"name":"Promise","package":"typescript"},"inheritedFrom":{"type":"reference","name":"default.requestPermissionsAsync"}}],"inheritedFrom":{"type":"reference","name":"default.requestPermissionsAsync"}},{"name":"setUpdateInterval","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"setUpdateInterval","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Set the sensor update interval."}]},"parameters":[{"name":"intervalMs","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Desired interval in milliseconds between sensor updates.\n> Starting from Android 12 (API level 31), the system has a 200Hz limit for each sensor updates.\n>\n> If you need an update interval of greater than 200Hz, you should\n> * add "},{"kind":"code","text":"`android.permission.HIGH_SAMPLING_RATE_SENSORS`"},{"kind":"text","text":" to [**app.json** "},{"kind":"code","text":"`permissions`"},{"kind":"text","text":" field](/versions/latest/config/app/#permissions)\n> * or if you are using an existing React Native project, add\n> "},{"kind":"code","text":"``"},{"kind":"text","text":" to **AndroidManifest.xml**."}]},"type":{"type":"intrinsic","name":"number"}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"default.setUpdateInterval"}}],"inheritedFrom":{"type":"reference","name":"default.setUpdateInterval"}}],"extendedTypes":[{"type":"reference","typeArguments":[{"type":"reference","name":"AccelerometerMeasurement","package":"expo-sensors"}],"name":"default","package":"expo-sensors"}]},{"name":"default","variant":"declaration","kind":32,"type":{"type":"reference","name":"AccelerometerSensor","package":"expo-sensors"}},{"name":"default","variant":"declaration","kind":128,"comment":{"summary":[{"kind":"text","text":"A base class for subscribable sensors. The events emitted by this class are measurements\nspecified by the parameter type "},{"kind":"code","text":"`Measurement`"},{"kind":"text","text":"."}]},"children":[{"name":"constructor","variant":"declaration","kind":512,"signatures":[{"name":"default","variant":"signature","kind":16384,"typeParameters":[{"name":"Measurement","variant":"typeParam","kind":131072}],"parameters":[{"name":"nativeSensorModule","variant":"param","kind":32768,"type":{"type":"intrinsic","name":"any"}},{"name":"nativeEventName","variant":"param","kind":32768,"type":{"type":"intrinsic","name":"string"}}],"type":{"type":"reference","typeArguments":[{"type":"reference","name":"Measurement","package":"expo-sensors","qualifiedName":"default.Measurement","refersToTypeParameter":true}],"name":"DeviceSensor","package":"expo-sensors","qualifiedName":"default"}}]},{"name":"_nativeEventName","variant":"declaration","kind":1024,"type":{"type":"intrinsic","name":"string"}},{"name":"_nativeModule","variant":"declaration","kind":1024,"type":{"type":"intrinsic","name":"any"}},{"name":"addListener","variant":"declaration","kind":2048,"signatures":[{"name":"addListener","variant":"signature","kind":4096,"parameters":[{"name":"listener","variant":"param","kind":32768,"type":{"type":"reference","typeArguments":[{"type":"reference","name":"Measurement","package":"expo-sensors","qualifiedName":"default.Measurement","refersToTypeParameter":true}],"name":"Listener","package":"expo-sensors"}}],"type":{"type":"reference","name":"EventSubscription","package":"expo-modules-core"}}]},{"name":"getListenerCount","variant":"declaration","kind":2048,"signatures":[{"name":"getListenerCount","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Returns the registered listeners count."}]},"type":{"type":"intrinsic","name":"number"}}]},{"name":"getPermissionsAsync","variant":"declaration","kind":2048,"signatures":[{"name":"getPermissionsAsync","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Checks user's permissions for accessing sensor."}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"reference","name":"PermissionResponse","package":"expo-modules-core"}],"name":"Promise","package":"typescript"}}]},{"name":"hasListeners","variant":"declaration","kind":2048,"signatures":[{"name":"hasListeners","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Returns boolean which signifies if sensor has any listeners registered."}]},"type":{"type":"intrinsic","name":"boolean"}}]},{"name":"isAvailableAsync","variant":"declaration","kind":2048,"signatures":[{"name":"isAvailableAsync","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"> **info** You should always check the sensor availability before attempting to use it."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"A promise that resolves to a "},{"kind":"code","text":"`boolean`"},{"kind":"text","text":" denoting the availability of the sensor."}]}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"boolean"}],"name":"Promise","package":"typescript"}}]},{"name":"removeAllListeners","variant":"declaration","kind":2048,"signatures":[{"name":"removeAllListeners","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Removes all registered listeners."}]},"type":{"type":"intrinsic","name":"void"}}]},{"name":"removeSubscription","variant":"declaration","kind":2048,"signatures":[{"name":"removeSubscription","variant":"signature","kind":4096,"comment":{"summary":[],"blockTags":[{"tag":"@deprecated","content":[{"kind":"text","text":"use subscription.remove() instead."}]}]},"parameters":[{"name":"subscription","variant":"param","kind":32768,"type":{"type":"reference","name":"EventSubscription","package":"expo-modules-core"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"requestPermissionsAsync","variant":"declaration","kind":2048,"signatures":[{"name":"requestPermissionsAsync","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Asks the user to grant permissions for accessing sensor."}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"reference","name":"PermissionResponse","package":"expo-modules-core"}],"name":"Promise","package":"typescript"}}]},{"name":"setUpdateInterval","variant":"declaration","kind":2048,"signatures":[{"name":"setUpdateInterval","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Set the sensor update interval."}]},"parameters":[{"name":"intervalMs","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Desired interval in milliseconds between sensor updates.\n> Starting from Android 12 (API level 31), the system has a 200Hz limit for each sensor updates.\n>\n> If you need an update interval of greater than 200Hz, you should\n> * add "},{"kind":"code","text":"`android.permission.HIGH_SAMPLING_RATE_SENSORS`"},{"kind":"text","text":" to [**app.json** "},{"kind":"code","text":"`permissions`"},{"kind":"text","text":" field](/versions/latest/config/app/#permissions)\n> * or if you are using an existing React Native project, add\n> "},{"kind":"code","text":"``"},{"kind":"text","text":" to **AndroidManifest.xml**."}]},"type":{"type":"intrinsic","name":"number"}}],"type":{"type":"intrinsic","name":"void"}}]}],"typeParameters":[{"name":"Measurement","variant":"typeParam","kind":131072}],"extendedBy":[{"type":"reference","name":"AccelerometerSensor"}]},{"name":"PermissionExpiration","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Permission expiration time. Currently, all permissions are granted permanently."}]},"type":{"type":"union","types":[{"type":"literal","value":"never"},{"type":"intrinsic","name":"number"}]}},{"name":"PermissionResponse","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"An object obtained by permissions get and request functions."}]},"children":[{"name":"canAskAgain","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Indicates if user can be asked again for specific permission.\nIf not, one should be directed to the Settings app\nin order to enable/disable the permission."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"expires","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Determines time when the permission expires."}]},"type":{"type":"reference","name":"PermissionExpiration","package":"expo-modules-core"}},{"name":"granted","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"A convenience boolean that indicates if the permission is granted."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"status","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Determines the status of the permission."}]},"type":{"type":"reference","name":"PermissionStatus","package":"expo-modules-core"}}]},{"name":"PermissionStatus","variant":"declaration","kind":8,"children":[{"name":"DENIED","variant":"declaration","kind":16,"comment":{"summary":[{"kind":"text","text":"User has denied the permission."}]},"type":{"type":"literal","value":"denied"}},{"name":"GRANTED","variant":"declaration","kind":16,"comment":{"summary":[{"kind":"text","text":"User has granted the permission."}]},"type":{"type":"literal","value":"granted"}},{"name":"UNDETERMINED","variant":"declaration","kind":16,"comment":{"summary":[{"kind":"text","text":"User hasn't granted or denied the permission yet."}]},"type":{"type":"literal","value":"undetermined"}}]},{"name":"Subscription","variant":"declaration","kind":256,"comment":{"summary":[{"kind":"text","text":"A subscription object that allows to conveniently remove an event listener from the emitter."}]},"children":[{"name":"remove","variant":"declaration","kind":2048,"signatures":[{"name":"remove","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Removes an event listener for which the subscription has been created.\nAfter calling this function, the listener will no longer receive any events from the emitter."}]},"type":{"type":"intrinsic","name":"void"}}]}]}],"packageName":"expo-sensors"} \ No newline at end of file diff --git a/docs/public/static/data/unversioned/expo-audio.json b/docs/public/static/data/unversioned/expo-audio.json index f1a984f29c21f7..67199254500aae 100644 --- a/docs/public/static/data/unversioned/expo-audio.json +++ b/docs/public/static/data/unversioned/expo-audio.json @@ -1 +1 @@ -{"schemaVersion":"2.0","name":"expo-audio","variant":"project","kind":1,"children":[{"name":"AudioQuality","variant":"declaration","kind":8,"comment":{"summary":[{"kind":"text","text":"Audio quality levels for recording.\n\nPredefined quality levels that balance file size and audio fidelity.\nHigher quality levels produce better sound but larger files and require more processing power."}]},"children":[{"name":"HIGH","variant":"declaration","kind":16,"comment":{"summary":[{"kind":"text","text":"High quality: good fidelity, larger file size."}]},"type":{"type":"literal","value":96}},{"name":"LOW","variant":"declaration","kind":16,"comment":{"summary":[{"kind":"text","text":"Low quality: good for voice recordings where file size matters."}]},"type":{"type":"literal","value":32}},{"name":"MAX","variant":"declaration","kind":16,"comment":{"summary":[{"kind":"text","text":"Maximum quality: best fidelity, largest file size."}]},"type":{"type":"literal","value":127}},{"name":"MEDIUM","variant":"declaration","kind":16,"comment":{"summary":[{"kind":"text","text":"Medium quality: balanced option for most use cases."}]},"type":{"type":"literal","value":64}},{"name":"MIN","variant":"declaration","kind":16,"comment":{"summary":[{"kind":"text","text":"Minimum quality: smallest file size, lowest fidelity."}]},"type":{"type":"literal","value":0}}]},{"name":"IOSOutputFormat","variant":"declaration","kind":8,"comment":{"summary":[{"kind":"text","text":"Audio output format options for iOS recording.\n\nComprehensive enum of audio formats supported by iOS for recording.\nEach format has different characteristics in terms of quality, file size, and compatibility.\nSome formats like LINEARPCM offer the highest quality but larger file sizes,\nwhile compressed formats like AAC provide good quality with smaller files."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"ios"}]}]},"children":[{"name":"60958AC3","variant":"declaration","kind":16,"type":{"type":"literal","value":"cac3"}},{"name":"AC3","variant":"declaration","kind":16,"type":{"type":"literal","value":"ac-3"}},{"name":"AES3","variant":"declaration","kind":16,"type":{"type":"literal","value":"aes3"}},{"name":"ALAW","variant":"declaration","kind":16,"type":{"type":"literal","value":"alaw"}},{"name":"AMR","variant":"declaration","kind":16,"type":{"type":"literal","value":"samr"}},{"name":"AMR_WB","variant":"declaration","kind":16,"type":{"type":"literal","value":"sawb"}},{"name":"APPLEIMA4","variant":"declaration","kind":16,"type":{"type":"literal","value":"ima4"}},{"name":"APPLELOSSLESS","variant":"declaration","kind":16,"type":{"type":"literal","value":"alac"}},{"name":"AUDIBLE","variant":"declaration","kind":16,"type":{"type":"literal","value":"AUDB"}},{"name":"DVIINTELIMA","variant":"declaration","kind":16,"type":{"type":"literal","value":1836253201}},{"name":"ENHANCEDAC3","variant":"declaration","kind":16,"type":{"type":"literal","value":"ec-3"}},{"name":"ILBC","variant":"declaration","kind":16,"type":{"type":"literal","value":"ilbc"}},{"name":"LINEARPCM","variant":"declaration","kind":16,"type":{"type":"literal","value":"lpcm"}},{"name":"MACE3","variant":"declaration","kind":16,"type":{"type":"literal","value":"MAC3"}},{"name":"MACE6","variant":"declaration","kind":16,"type":{"type":"literal","value":"MAC6"}},{"name":"MICROSOFTGSM","variant":"declaration","kind":16,"type":{"type":"literal","value":1836253233}},{"name":"MPEG4AAC","variant":"declaration","kind":16,"type":{"type":"literal","value":"aac "}},{"name":"MPEG4AAC_ELD","variant":"declaration","kind":16,"type":{"type":"literal","value":"aace"}},{"name":"MPEG4AAC_ELD_SBR","variant":"declaration","kind":16,"type":{"type":"literal","value":"aacf"}},{"name":"MPEG4AAC_ELD_V2","variant":"declaration","kind":16,"type":{"type":"literal","value":"aacg"}},{"name":"MPEG4AAC_HE","variant":"declaration","kind":16,"type":{"type":"literal","value":"aach"}},{"name":"MPEG4AAC_HE_V2","variant":"declaration","kind":16,"type":{"type":"literal","value":"aacp"}},{"name":"MPEG4AAC_LD","variant":"declaration","kind":16,"type":{"type":"literal","value":"aacl"}},{"name":"MPEG4AAC_SPATIAL","variant":"declaration","kind":16,"type":{"type":"literal","value":"aacs"}},{"name":"MPEG4CELP","variant":"declaration","kind":16,"type":{"type":"literal","value":"celp"}},{"name":"MPEG4HVXC","variant":"declaration","kind":16,"type":{"type":"literal","value":"hvxc"}},{"name":"MPEG4TWINVQ","variant":"declaration","kind":16,"type":{"type":"literal","value":"twvq"}},{"name":"MPEGLAYER1","variant":"declaration","kind":16,"type":{"type":"literal","value":".mp1"}},{"name":"MPEGLAYER2","variant":"declaration","kind":16,"type":{"type":"literal","value":".mp2"}},{"name":"MPEGLAYER3","variant":"declaration","kind":16,"type":{"type":"literal","value":".mp3"}},{"name":"QDESIGN","variant":"declaration","kind":16,"type":{"type":"literal","value":"QDMC"}},{"name":"QDESIGN2","variant":"declaration","kind":16,"type":{"type":"literal","value":"QDM2"}},{"name":"QUALCOMM","variant":"declaration","kind":16,"type":{"type":"literal","value":"Qclp"}},{"name":"ULAW","variant":"declaration","kind":16,"type":{"type":"literal","value":"ulaw"}}]},{"name":"PermissionStatus","variant":"declaration","kind":8,"children":[{"name":"DENIED","variant":"declaration","kind":16,"comment":{"summary":[{"kind":"text","text":"User has denied the permission."}]},"type":{"type":"literal","value":"denied"}},{"name":"GRANTED","variant":"declaration","kind":16,"comment":{"summary":[{"kind":"text","text":"User has granted the permission."}]},"type":{"type":"literal","value":"granted"}},{"name":"UNDETERMINED","variant":"declaration","kind":16,"comment":{"summary":[{"kind":"text","text":"User hasn't granted or denied the permission yet."}]},"type":{"type":"literal","value":"undetermined"}}]},{"name":"AudioPlayer","variant":"declaration","kind":128,"children":[{"name":"currentTime","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The current position through the audio item in seconds."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"duration","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The total duration of the audio in seconds."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"id","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Unique identifier for the player object."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"isAudioSamplingSupported","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Boolean value indicating whether audio sampling is supported on the platform."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"isBuffering","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Boolean value indicating whether the player is buffering."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"isLoaded","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Boolean value indicating whether the player is finished loading."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"loop","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Boolean value indicating whether the player is currently looping."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"muted","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Boolean value indicating whether the player is currently muted."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"paused","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Boolean value indicating whether the player is currently paused."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"playbackRate","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The current playback rate of the audio. It accepts different values depending on the platform:\n- **Android**: "},{"kind":"code","text":"`0.1`"},{"kind":"text","text":" to "},{"kind":"code","text":"`2.0`"},{"kind":"text","text":"\n- **iOS**: "},{"kind":"code","text":"`0.0`"},{"kind":"text","text":" to "},{"kind":"code","text":"`2.0`"},{"kind":"text","text":"\n- **Web**: Follows browser implementation"}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```tsx\nimport { useAudioPlayer } from 'expo-audio';\n\nexport default function App() {\n const player = useAudioPlayer(source);\n\n // Normal playback speed\n player.playbackRate = 1.0;\n\n // Slow motion (half speed)\n player.playbackRate = 0.5;\n\n // Fast playback (1.5x speed)\n player.playbackRate = 1.5;\n\n // Maximum speed on mobile\n player.playbackRate = 2.0;\n}\n```"}]}]},"type":{"type":"intrinsic","name":"number"}},{"name":"playing","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Boolean value indicating whether the player is currently playing."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"shouldCorrectPitch","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"A boolean describing if we are correcting the pitch for a changed rate."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"volume","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The current volume of the audio.\n\n**Range:** "},{"kind":"code","text":"`0.0`"},{"kind":"text","text":" to "},{"kind":"code","text":"`1.0`"},{"kind":"text","text":". For example, "},{"kind":"code","text":"`0.0`"},{"kind":"text","text":" is completely silent (0%), "},{"kind":"code","text":"`0.5`"},{"kind":"text","text":" is half volume (50%), and "},{"kind":"code","text":"`1.0`"},{"kind":"text","text":" is full volume (100%)."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```tsx\nimport { useAudioPlayer } from 'expo-audio';\n\nexport default function App() {\n const player = useAudioPlayer(source);\n\n // Mute the audio\n player.volume = 0.0;\n\n // Set volume to 50%\n player.volume = 0.5;\n\n // Set to full volume\n player.volume = 1.0;\n}\n```"}]}]},"type":{"type":"intrinsic","name":"number"}},{"name":"addListener","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"addListener","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Adds a listener for the given event name."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"AudioEvents","package":"expo-audio"}}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}},{"name":"listener","variant":"param","kind":32768,"type":{"type":"indexedAccess","indexType":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true},"objectType":{"type":"reference","name":"AudioEvents","package":"expo-audio"}}}],"type":{"type":"reference","target":{"packageName":"expo-modules-core","packagePath":"src/ts-declarations/EventEmitter.ts","qualifiedName":"EventSubscription"},"name":"EventSubscription","package":"expo-modules-core"},"inheritedFrom":{"type":"reference","name":"SharedObject.addListener","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.addListener","package":"expo-modules-core"}},{"name":"clearLockScreenControls","variant":"declaration","kind":2048,"signatures":[{"name":"clearLockScreenControls","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Removes this player from lock screen controls if it's currently active.\nThis will clear the lock screen's now playing info."}]},"type":{"type":"intrinsic","name":"void"}}]},{"name":"emit","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"emit","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Synchronously calls all the listeners attached to that specific event.\nThe event can include any number of arguments that will be passed to the listeners."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"AudioEvents","package":"expo-audio"}}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}},{"name":"args","variant":"param","kind":32768,"flags":{"isRest":true},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Parameters"},"typeArguments":[{"type":"indexedAccess","indexType":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true},"objectType":{"type":"reference","name":"AudioEvents","package":"expo-audio"}}],"name":"Parameters","package":"typescript"}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.emit","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.emit","package":"expo-modules-core"}},{"name":"listenerCount","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"listenerCount","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Returns a number of listeners added to the given event."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"AudioEvents","package":"expo-audio"}}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}}],"type":{"type":"intrinsic","name":"number"},"inheritedFrom":{"type":"reference","name":"SharedObject.listenerCount","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.listenerCount","package":"expo-modules-core"}},{"name":"pause","variant":"declaration","kind":2048,"signatures":[{"name":"pause","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Pauses the player."}]},"type":{"type":"intrinsic","name":"void"}}]},{"name":"play","variant":"declaration","kind":2048,"signatures":[{"name":"play","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Start playing audio."}]},"type":{"type":"intrinsic","name":"void"}}]},{"name":"release","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"release","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"A function that detaches the JS and native objects to let the native object deallocate\nbefore the JS object gets deallocated by the JS garbage collector. Any subsequent calls to native\nfunctions of the object will throw an error as it is no longer associated with its native counterpart.\n\nIn most cases, you should never need to use this function, except some specific performance-critical cases when\nmanual memory management makes sense and the native object is known to exclusively retain some native memory\n(such as binary data or image bitmap). Before calling this function, you should ensure that nothing else will use\nthis object later on. Shared objects created by React hooks are usually automatically released in the effect's cleanup phase,\nfor example: "},{"kind":"code","text":"`useVideoPlayer()`"},{"kind":"text","text":" from "},{"kind":"code","text":"`expo-video`"},{"kind":"text","text":" and "},{"kind":"code","text":"`useImage()`"},{"kind":"text","text":" from "},{"kind":"code","text":"`expo-image`"},{"kind":"text","text":"."}]},"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.release","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.release","package":"expo-modules-core"}},{"name":"remove","variant":"declaration","kind":2048,"signatures":[{"name":"remove","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Remove the player from memory to free up resources."}]},"type":{"type":"intrinsic","name":"void"}}]},{"name":"removeAllListeners","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"removeAllListeners","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Removes all listeners for the given event name."}]},"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"AudioEvents","package":"expo-audio"}}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.removeAllListeners","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.removeAllListeners","package":"expo-modules-core"}},{"name":"removeListener","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"removeListener","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Removes a listener for the given event name."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"AudioEvents","package":"expo-audio"}}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}},{"name":"listener","variant":"param","kind":32768,"type":{"type":"indexedAccess","indexType":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true},"objectType":{"type":"reference","name":"AudioEvents","package":"expo-audio"}}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.removeListener","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.removeListener","package":"expo-modules-core"}},{"name":"replace","variant":"declaration","kind":2048,"signatures":[{"name":"replace","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Replaces the current audio source with a new one."}]},"parameters":[{"name":"source","variant":"param","kind":32768,"type":{"type":"reference","name":"AudioSource","package":"expo-audio"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"seekTo","variant":"declaration","kind":2048,"signatures":[{"name":"seekTo","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Seeks the playback by the given number of seconds."}]},"parameters":[{"name":"seconds","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The number of seconds to seek by."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"toleranceMillisBefore","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The tolerance allowed before the requested seek time, in milliseconds. iOS only."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"toleranceMillisAfter","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The tolerance allowed after the requested seek time, in milliseconds. iOS only."}]},"type":{"type":"intrinsic","name":"number"}}],"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]},{"name":"setActiveForLockScreen","variant":"declaration","kind":2048,"signatures":[{"name":"setActiveForLockScreen","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Sets or removes this audio player as the active player for lock screen controls.\nOnly one player can control the lock screen at a time.\n\n> **Note:** For lock screen controls to work correctly, ["},{"kind":"code","text":"`interruptionMode`"},{"kind":"text","text":"](#interruptionmode) must be set to "},{"kind":"code","text":"`doNotMix`"},{"kind":"text","text":" using ["},{"kind":"code","text":"`setAudioModeAsync`"},{"kind":"text","text":"](#audiosetaudiomodeasyncmode).\n> Without this, the OS might not associate lock screen controls with your player."}]},"parameters":[{"name":"active","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"Whether this player should be active for lock screen controls."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"metadata","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional metadata to display on the lock screen (title, artist, album, artwork)."}]},"type":{"type":"reference","name":"AudioMetadata","package":"expo-audio"}},{"name":"options","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional configuration to configure the lock screen controls."}]},"type":{"type":"reference","name":"AudioLockScreenOptions","package":"expo-audio"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"setPlaybackRate","variant":"declaration","kind":2048,"signatures":[{"name":"setPlaybackRate","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Sets the current playback rate of the audio."}]},"parameters":[{"name":"rate","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The playback rate of the audio. See ["},{"kind":"code","text":"`playbackRate`"},{"kind":"text","text":"](#playbackrate) property for detailed range information."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"pitchCorrectionQuality","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The quality of the pitch correction."}]},"type":{"type":"reference","name":"PitchCorrectionQuality","package":"expo-audio"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"startObserving","variant":"declaration","kind":2048,"flags":{"isOptional":true,"isInherited":true},"signatures":[{"name":"startObserving","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Function that is automatically invoked when the first listener for an event with the given name is added.\nOverride it in a subclass to perform some additional setup once the event started being observed."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"AudioEvents","package":"expo-audio"}}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.startObserving","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.startObserving","package":"expo-modules-core"}},{"name":"stopObserving","variant":"declaration","kind":2048,"flags":{"isOptional":true,"isInherited":true},"signatures":[{"name":"stopObserving","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Function that is automatically invoked when the last listener for an event with the given name is removed.\nOverride it in a subclass to perform some additional cleanup once the event is no longer observed."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"AudioEvents","package":"expo-audio"}}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.stopObserving","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.stopObserving","package":"expo-modules-core"}},{"name":"updateLockScreenMetadata","variant":"declaration","kind":2048,"signatures":[{"name":"updateLockScreenMetadata","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Updates the metadata displayed on the lock screen for this player.\nThis method only has an effect if this player is currently active for lock screen controls."}]},"parameters":[{"name":"metadata","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The metadata to display (title, artist, album, artwork)."}]},"type":{"type":"reference","name":"AudioMetadata","package":"expo-audio"}}],"type":{"type":"intrinsic","name":"void"}}]}],"extendedTypes":[{"type":"reference","target":{"packageName":"expo-modules-core","packagePath":"src/SharedObject.ts","qualifiedName":"SharedObject"},"typeArguments":[{"type":"reference","name":"AudioEvents","package":"expo-audio"}],"name":"SharedObject","package":"expo-modules-core"}]},{"name":"AudioPlaylist","variant":"declaration","kind":128,"children":[{"name":"currentIndex","variant":"declaration","kind":1024,"flags":{"isReadonly":true},"comment":{"summary":[{"kind":"text","text":"Index of the currently playing track in the playlist."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"currentTime","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Current playback position in seconds."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"duration","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Duration of the current track in seconds."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"id","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Unique identifier for the playlist instance."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"isBuffering","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Boolean value indicating whether the playlist is buffering."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"isLoaded","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Boolean value indicating whether the current track has finished loading."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"loop","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Current loop mode."}]},"type":{"type":"reference","name":"AudioPlaylistLoopMode","package":"expo-audio"}},{"name":"muted","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Boolean value indicating whether the playlist is currently muted."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"playbackRate","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Current playback rate (1.0 = normal speed)."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"playing","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Boolean value indicating whether the playlist is currently playing."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"sources","variant":"declaration","kind":1024,"flags":{"isReadonly":true},"comment":{"summary":[{"kind":"text","text":"The audio sources currently in the playlist."}]},"type":{"type":"array","elementType":{"type":"reference","name":"AudioSourceInfo","package":"expo-audio"}}},{"name":"trackCount","variant":"declaration","kind":1024,"flags":{"isReadonly":true},"comment":{"summary":[{"kind":"text","text":"Total number of tracks in the playlist."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"volume","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Current volume (0.0 to 1.0)."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"add","variant":"declaration","kind":2048,"signatures":[{"name":"add","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Add a track to the end of the playlist."}]},"parameters":[{"name":"source","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The audio source to add."}]},"type":{"type":"reference","name":"AudioSource","package":"expo-audio"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"addListener","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"addListener","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Adds a listener for the given event name."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"AudioPlaylistEvents","package":"expo-audio"}}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}},{"name":"listener","variant":"param","kind":32768,"type":{"type":"indexedAccess","indexType":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true},"objectType":{"type":"reference","name":"AudioPlaylistEvents","package":"expo-audio"}}}],"type":{"type":"reference","target":{"packageName":"expo-modules-core","packagePath":"src/ts-declarations/EventEmitter.ts","qualifiedName":"EventSubscription"},"name":"EventSubscription","package":"expo-modules-core"},"inheritedFrom":{"type":"reference","name":"SharedObject.addListener","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.addListener","package":"expo-modules-core"}},{"name":"clear","variant":"declaration","kind":2048,"signatures":[{"name":"clear","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Clear all tracks from the playlist."}]},"type":{"type":"intrinsic","name":"void"}}]},{"name":"destroy","variant":"declaration","kind":2048,"signatures":[{"name":"destroy","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Destroy the playlist and free up resources."}]},"type":{"type":"intrinsic","name":"void"}}]},{"name":"emit","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"emit","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Synchronously calls all the listeners attached to that specific event.\nThe event can include any number of arguments that will be passed to the listeners."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"AudioPlaylistEvents","package":"expo-audio"}}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}},{"name":"args","variant":"param","kind":32768,"flags":{"isRest":true},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Parameters"},"typeArguments":[{"type":"indexedAccess","indexType":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true},"objectType":{"type":"reference","name":"AudioPlaylistEvents","package":"expo-audio"}}],"name":"Parameters","package":"typescript"}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.emit","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.emit","package":"expo-modules-core"}},{"name":"insert","variant":"declaration","kind":2048,"signatures":[{"name":"insert","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Insert a track at a specific position in the playlist."}]},"parameters":[{"name":"source","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The audio source to insert."}]},"type":{"type":"reference","name":"AudioSource","package":"expo-audio"}},{"name":"index","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The position to insert at."}]},"type":{"type":"intrinsic","name":"number"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"listenerCount","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"listenerCount","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Returns a number of listeners added to the given event."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"AudioPlaylistEvents","package":"expo-audio"}}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}}],"type":{"type":"intrinsic","name":"number"},"inheritedFrom":{"type":"reference","name":"SharedObject.listenerCount","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.listenerCount","package":"expo-modules-core"}},{"name":"next","variant":"declaration","kind":2048,"signatures":[{"name":"next","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Skip to the next track in the playlist.\nIf at the end of the playlist and loop mode is 'all', wraps to the first track.\nIf loop mode is 'none' and at the end, does nothing."}]},"type":{"type":"intrinsic","name":"void"}}]},{"name":"pause","variant":"declaration","kind":2048,"signatures":[{"name":"pause","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Pause playback."}]},"type":{"type":"intrinsic","name":"void"}}]},{"name":"play","variant":"declaration","kind":2048,"signatures":[{"name":"play","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Start playing the current track in the playlist."}]},"type":{"type":"intrinsic","name":"void"}}]},{"name":"previous","variant":"declaration","kind":2048,"signatures":[{"name":"previous","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Skip to the previous track in the playlist.\nIf at the beginning of the playlist and loop mode is 'all', wraps to the last track.\nIf loop mode is 'none' and at the beginning, does nothing."}]},"type":{"type":"intrinsic","name":"void"}}]},{"name":"release","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"release","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"A function that detaches the JS and native objects to let the native object deallocate\nbefore the JS object gets deallocated by the JS garbage collector. Any subsequent calls to native\nfunctions of the object will throw an error as it is no longer associated with its native counterpart.\n\nIn most cases, you should never need to use this function, except some specific performance-critical cases when\nmanual memory management makes sense and the native object is known to exclusively retain some native memory\n(such as binary data or image bitmap). Before calling this function, you should ensure that nothing else will use\nthis object later on. Shared objects created by React hooks are usually automatically released in the effect's cleanup phase,\nfor example: "},{"kind":"code","text":"`useVideoPlayer()`"},{"kind":"text","text":" from "},{"kind":"code","text":"`expo-video`"},{"kind":"text","text":" and "},{"kind":"code","text":"`useImage()`"},{"kind":"text","text":" from "},{"kind":"code","text":"`expo-image`"},{"kind":"text","text":"."}]},"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.release","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.release","package":"expo-modules-core"}},{"name":"remove","variant":"declaration","kind":2048,"signatures":[{"name":"remove","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Remove a track from the playlist by index."}]},"parameters":[{"name":"index","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The index of the track to remove."}]},"type":{"type":"intrinsic","name":"number"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"removeAllListeners","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"removeAllListeners","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Removes all listeners for the given event name."}]},"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"AudioPlaylistEvents","package":"expo-audio"}}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.removeAllListeners","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.removeAllListeners","package":"expo-modules-core"}},{"name":"removeListener","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"removeListener","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Removes a listener for the given event name."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"AudioPlaylistEvents","package":"expo-audio"}}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}},{"name":"listener","variant":"param","kind":32768,"type":{"type":"indexedAccess","indexType":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true},"objectType":{"type":"reference","name":"AudioPlaylistEvents","package":"expo-audio"}}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.removeListener","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.removeListener","package":"expo-modules-core"}},{"name":"seekTo","variant":"declaration","kind":2048,"signatures":[{"name":"seekTo","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Seeks the playback to a specific position in seconds."}]},"parameters":[{"name":"seconds","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The position to seek to."}]},"type":{"type":"intrinsic","name":"number"}}],"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]},{"name":"skipTo","variant":"declaration","kind":2048,"signatures":[{"name":"skipTo","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Skip to a specific track in the playlist by index."}]},"parameters":[{"name":"index","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The index of the track to skip to."}]},"type":{"type":"intrinsic","name":"number"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"startObserving","variant":"declaration","kind":2048,"flags":{"isOptional":true,"isInherited":true},"signatures":[{"name":"startObserving","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Function that is automatically invoked when the first listener for an event with the given name is added.\nOverride it in a subclass to perform some additional setup once the event started being observed."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"AudioPlaylistEvents","package":"expo-audio"}}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.startObserving","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.startObserving","package":"expo-modules-core"}},{"name":"stopObserving","variant":"declaration","kind":2048,"flags":{"isOptional":true,"isInherited":true},"signatures":[{"name":"stopObserving","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Function that is automatically invoked when the last listener for an event with the given name is removed.\nOverride it in a subclass to perform some additional cleanup once the event is no longer observed."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"typeOperator","operator":"keyof","target":{"type":"reference","name":"AudioPlaylistEvents","package":"expo-audio"}}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.stopObserving","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.stopObserving","package":"expo-modules-core"}}],"extendedTypes":[{"type":"reference","target":{"packageName":"expo-modules-core","packagePath":"src/SharedObject.ts","qualifiedName":"SharedObject"},"typeArguments":[{"type":"reference","name":"AudioPlaylistEvents","package":"expo-audio"}],"name":"SharedObject","package":"expo-modules-core"}]},{"name":"AudioRecorder","variant":"declaration","kind":128,"children":[{"name":"currentTime","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The current length of the recording, in seconds."}]},"type":{"type":"intrinsic","name":"number"}},{"name":"id","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Unique identifier for the recorder object."}]},"type":{"type":"intrinsic","name":"string"}},{"name":"isRecording","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Boolean value indicating whether the recording is in progress."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"uri","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"The uri of the recording."}]},"type":{"type":"union","types":[{"type":"intrinsic","name":"string"},{"type":"literal","value":null}]}},{"name":"addListener","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"addListener","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Adds a listener for the given event name."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"recordingStatusUpdate"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}},{"name":"listener","variant":"param","kind":32768,"type":{"type":"indexedAccess","indexType":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true},"objectType":{"type":"reference","name":"RecordingEvents","package":"expo-audio"}}}],"type":{"type":"reference","target":{"packageName":"expo-modules-core","packagePath":"src/ts-declarations/EventEmitter.ts","qualifiedName":"EventSubscription"},"name":"EventSubscription","package":"expo-modules-core"},"inheritedFrom":{"type":"reference","name":"SharedObject.addListener","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.addListener","package":"expo-modules-core"}},{"name":"emit","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"emit","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Synchronously calls all the listeners attached to that specific event.\nThe event can include any number of arguments that will be passed to the listeners."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"recordingStatusUpdate"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}},{"name":"args","variant":"param","kind":32768,"flags":{"isRest":true},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Parameters"},"typeArguments":[{"type":"indexedAccess","indexType":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true},"objectType":{"type":"reference","name":"RecordingEvents","package":"expo-audio"}}],"name":"Parameters","package":"typescript"}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.emit","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.emit","package":"expo-modules-core"}},{"name":"getAvailableInputs","variant":"declaration","kind":2048,"signatures":[{"name":"getAvailableInputs","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Returns a list of available recording inputs. This method can only be called if the "},{"kind":"code","text":"`Recording`"},{"kind":"text","text":" has been prepared."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"A "},{"kind":"code","text":"`Promise`"},{"kind":"text","text":" that is fulfilled with an array of "},{"kind":"code","text":"`RecordingInput`"},{"kind":"text","text":" objects."}]}]},"type":{"type":"array","elementType":{"type":"reference","name":"RecordingInput","package":"expo-audio"}}}]},{"name":"getCurrentInput","variant":"declaration","kind":2048,"signatures":[{"name":"getCurrentInput","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Returns the currently-selected recording input. This method can only be called if the "},{"kind":"code","text":"`Recording`"},{"kind":"text","text":" has been prepared."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"A "},{"kind":"code","text":"`Promise`"},{"kind":"text","text":" that is fulfilled with a "},{"kind":"code","text":"`RecordingInput`"},{"kind":"text","text":" object."}]}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"reference","name":"RecordingInput","package":"expo-audio"}],"name":"Promise","package":"typescript"}}]},{"name":"getStatus","variant":"declaration","kind":2048,"signatures":[{"name":"getStatus","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Status of the current recording."}]},"type":{"type":"reference","name":"RecorderState","package":"expo-audio"}}]},{"name":"listenerCount","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"listenerCount","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Returns a number of listeners added to the given event."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"recordingStatusUpdate"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}}],"type":{"type":"intrinsic","name":"number"},"inheritedFrom":{"type":"reference","name":"SharedObject.listenerCount","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.listenerCount","package":"expo-modules-core"}},{"name":"pause","variant":"declaration","kind":2048,"signatures":[{"name":"pause","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Pause the recording."}]},"type":{"type":"intrinsic","name":"void"}}]},{"name":"prepareToRecordAsync","variant":"declaration","kind":2048,"signatures":[{"name":"prepareToRecordAsync","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Prepares the recording for recording."}]},"parameters":[{"name":"options","variant":"param","kind":32768,"flags":{"isOptional":true},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Partial"},"typeArguments":[{"type":"reference","name":"RecordingOptions","package":"expo-audio"}],"name":"Partial","package":"typescript"}}],"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]},{"name":"record","variant":"declaration","kind":2048,"signatures":[{"name":"record","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Starts the recording."}]},"parameters":[{"name":"options","variant":"param","kind":32768,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Optional recording configuration options."}]},"type":{"type":"reference","name":"RecordingStartOptions","package":"expo-audio"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"recordForDuration","variant":"declaration","kind":2048,"signatures":[{"name":"recordForDuration","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Stops the recording once the specified time has elapsed."}],"blockTags":[{"tag":"@deprecated","content":[{"kind":"text","text":"Use "},{"kind":"code","text":"`record({ forDuration: seconds })`"},{"kind":"text","text":" instead."}]}]},"parameters":[{"name":"seconds","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The time in seconds to stop recording at."}]},"type":{"type":"intrinsic","name":"number"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"release","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"release","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"A function that detaches the JS and native objects to let the native object deallocate\nbefore the JS object gets deallocated by the JS garbage collector. Any subsequent calls to native\nfunctions of the object will throw an error as it is no longer associated with its native counterpart.\n\nIn most cases, you should never need to use this function, except some specific performance-critical cases when\nmanual memory management makes sense and the native object is known to exclusively retain some native memory\n(such as binary data or image bitmap). Before calling this function, you should ensure that nothing else will use\nthis object later on. Shared objects created by React hooks are usually automatically released in the effect's cleanup phase,\nfor example: "},{"kind":"code","text":"`useVideoPlayer()`"},{"kind":"text","text":" from "},{"kind":"code","text":"`expo-video`"},{"kind":"text","text":" and "},{"kind":"code","text":"`useImage()`"},{"kind":"text","text":" from "},{"kind":"code","text":"`expo-image`"},{"kind":"text","text":"."}]},"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.release","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.release","package":"expo-modules-core"}},{"name":"removeAllListeners","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"removeAllListeners","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Removes all listeners for the given event name."}]},"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"literal","value":"recordingStatusUpdate"}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.removeAllListeners","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.removeAllListeners","package":"expo-modules-core"}},{"name":"removeListener","variant":"declaration","kind":2048,"flags":{"isInherited":true},"signatures":[{"name":"removeListener","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Removes a listener for the given event name."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"recordingStatusUpdate"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}},{"name":"listener","variant":"param","kind":32768,"type":{"type":"indexedAccess","indexType":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true},"objectType":{"type":"reference","name":"RecordingEvents","package":"expo-audio"}}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.removeListener","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.removeListener","package":"expo-modules-core"}},{"name":"setInput","variant":"declaration","kind":2048,"signatures":[{"name":"setInput","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Sets the current recording input."}],"blockTags":[{"tag":"@returns","content":[{"kind":"text","text":"A "},{"kind":"code","text":"`Promise`"},{"kind":"text","text":" that is resolved if successful or rejected if not."}]}]},"parameters":[{"name":"inputUid","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The uid of a "},{"kind":"code","text":"`RecordingInput`"},{"kind":"text","text":"."}]},"type":{"type":"intrinsic","name":"string"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"startObserving","variant":"declaration","kind":2048,"flags":{"isOptional":true,"isInherited":true},"signatures":[{"name":"startObserving","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Function that is automatically invoked when the first listener for an event with the given name is added.\nOverride it in a subclass to perform some additional setup once the event started being observed."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"recordingStatusUpdate"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.startObserving","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.startObserving","package":"expo-modules-core"}},{"name":"startRecordingAtTime","variant":"declaration","kind":2048,"signatures":[{"name":"startRecordingAtTime","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Starts the recording at the given time."}],"blockTags":[{"tag":"@deprecated","content":[{"kind":"text","text":"Use "},{"kind":"code","text":"`record({ atTime: seconds })`"},{"kind":"text","text":" instead."}]}]},"parameters":[{"name":"seconds","variant":"param","kind":32768,"comment":{"summary":[{"kind":"text","text":"The time in seconds to start recording at."}]},"type":{"type":"intrinsic","name":"number"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"stop","variant":"declaration","kind":2048,"signatures":[{"name":"stop","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Stop the recording."}]},"type":{"type":"reference","target":{"packageName":"typescript","packagePath":"lib/lib.es5.d.ts","qualifiedName":"Promise"},"typeArguments":[{"type":"intrinsic","name":"void"}],"name":"Promise","package":"typescript"}}]},{"name":"stopObserving","variant":"declaration","kind":2048,"flags":{"isOptional":true,"isInherited":true},"signatures":[{"name":"stopObserving","variant":"signature","kind":4096,"flags":{"isInherited":true},"comment":{"summary":[{"kind":"text","text":"Function that is automatically invoked when the last listener for an event with the given name is removed.\nOverride it in a subclass to perform some additional cleanup once the event is no longer observed."}]},"typeParameters":[{"name":"EventName","variant":"typeParam","kind":131072,"type":{"type":"literal","value":"recordingStatusUpdate"}}],"parameters":[{"name":"eventName","variant":"param","kind":32768,"type":{"type":"reference","name":"EventName","package":"expo-modules-core","refersToTypeParameter":true}}],"type":{"type":"intrinsic","name":"void"},"inheritedFrom":{"type":"reference","name":"SharedObject.stopObserving","package":"expo-modules-core"}}],"inheritedFrom":{"type":"reference","name":"SharedObject.stopObserving","package":"expo-modules-core"}}],"extendedTypes":[{"type":"reference","target":{"packageName":"expo-modules-core","packagePath":"src/SharedObject.ts","qualifiedName":"SharedObject"},"typeArguments":[{"type":"reference","name":"RecordingEvents","package":"expo-audio"}],"name":"SharedObject","package":"expo-modules-core"}]},{"name":"AndroidAudioEncoder","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Audio encoder options for Android recording.\n\nSpecifies the audio codec used to encode recorded audio on Android.\nDifferent encoders offer different quality, compression, and compatibility trade-offs."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]}]},"type":{"type":"union","types":[{"type":"literal","value":"default"},{"type":"literal","value":"amr_nb"},{"type":"literal","value":"amr_wb"},{"type":"literal","value":"aac"},{"type":"literal","value":"he_aac"},{"type":"literal","value":"aac_eld"}]}},{"name":"AndroidOutputFormat","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Audio output format options for Android recording.\n\nSpecifies the container format for recorded audio files on Android.\nDifferent formats have different compatibility and compression characteristics."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]}]},"type":{"type":"union","types":[{"type":"literal","value":"default"},{"type":"literal","value":"3gp"},{"type":"literal","value":"mpeg4"},{"type":"literal","value":"amrnb"},{"type":"literal","value":"amrwb"},{"type":"literal","value":"aac_adts"},{"type":"literal","value":"mpeg2ts"},{"type":"literal","value":"webm"}]}},{"name":"AudioEvents","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Event types that an "},{"kind":"code","text":"`AudioPlayer`"},{"kind":"text","text":" can emit.\n\nThese events allow you to listen for changes in playback state and receive real-time audio data.\nUse "},{"kind":"code","text":"`player.addListener()`"},{"kind":"text","text":" to subscribe to these events."}]},"children":[{"name":"audioSampleUpdate","variant":"declaration","kind":2048,"signatures":[{"name":"audioSampleUpdate","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Fired when audio sampling is enabled and new sample data is available."}]},"parameters":[{"name":"data","variant":"param","kind":32768,"type":{"type":"reference","name":"AudioSample","package":"expo-audio"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"playbackStatusUpdate","variant":"declaration","kind":2048,"signatures":[{"name":"playbackStatusUpdate","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Fired when the player's status changes (play/pause/seek/load and so on.)."}]},"parameters":[{"name":"status","variant":"param","kind":32768,"type":{"type":"reference","name":"AudioStatus","package":"expo-audio"}}],"type":{"type":"intrinsic","name":"void"}}]}]},{"name":"AudioLoadOptions","variant":"declaration","kind":2097152,"comment":{"summary":[],"blockTags":[{"tag":"@deprecated","content":[{"kind":"text","text":"Use "},{"kind":"code","text":"`AudioPlayerOptions`"},{"kind":"text","text":" instead.\nOptions for audio loading behavior."}]}]},"type":{"type":"reference","name":"AudioPlayerOptions","package":"expo-audio"}},{"name":"AudioLockScreenOptions","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Options for configuring which playback controls should be displayed on the lock screen."}]},"children":[{"name":"showSeekBackward","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether the seek backward button should be displayed on the lock screen."}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"showSeekForward","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether the seek forward button should be displayed on the lock screen."}]},"type":{"type":"intrinsic","name":"boolean"}}]},{"name":"AudioMetadata","variant":"declaration","kind":2097152,"children":[{"name":"albumTitle","variant":"declaration","kind":1024,"flags":{"isOptional":true},"type":{"type":"intrinsic","name":"string"}},{"name":"artist","variant":"declaration","kind":1024,"flags":{"isOptional":true},"type":{"type":"intrinsic","name":"string"}},{"name":"artworkUrl","variant":"declaration","kind":1024,"flags":{"isOptional":true},"type":{"type":"intrinsic","name":"string"}},{"name":"title","variant":"declaration","kind":1024,"flags":{"isOptional":true},"type":{"type":"intrinsic","name":"string"}}]},{"name":"AudioMode","variant":"declaration","kind":2097152,"children":[{"name":"allowsBackgroundRecording","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Whether audio recording should continue when the app moves to the background."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"android"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"allowsRecording","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Whether the audio session allows recording."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"interruptionMode","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Determines how the audio session interacts with other audio sessions.\n\n- "},{"kind":"code","text":"`'doNotMix'`"},{"kind":"text","text":": Requests exclusive audio focus. Other apps will pause their audio.\n- "},{"kind":"code","text":"`'duckOthers'`"},{"kind":"text","text":": Requests audio focus with ducking. Other apps lower their volume but continue playing.\n- "},{"kind":"code","text":"`'mixWithOthers'`"},{"kind":"text","text":": Audio plays alongside other apps without interrupting them.\n On Android, this means no audio focus is requested. Best suited for sound effects,\n UI feedback, or short audio clips."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"'mixWithOthers'"}]}]},"type":{"type":"reference","name":"InterruptionMode","package":"expo-audio"}},{"name":"interruptionModeAndroid","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Determines how the audio session interacts with other sessions on Android."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@deprecated","content":[{"kind":"text","text":"Use "},{"kind":"code","text":"`interruptionMode`"},{"kind":"text","text":" instead, which now works on both platforms."}]}]},"type":{"type":"reference","name":"InterruptionModeAndroid","package":"expo-audio"}},{"name":"playsInSilentMode","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Determines if audio playback is allowed when the device is in silent mode."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"ios"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"shouldPlayInBackground","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Whether the audio session stays active when the app moves to the background.\n\n> **Note**: On Android, you have to enable the lockscreen controls with ["},{"kind":"code","text":"`setActiveForLockScreen`"},{"kind":"text","text":"](#setactiveforlockscreenactive-metadata-options) for sustained background playback. Otherwise, the audio will stop after approximately 3 minutes of background playback (OS limitation). Make sure to also appropriately [configure the config-plugin](#configuration-in-app-config)."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"shouldRouteThroughEarpiece","variant":"declaration","kind":1024,"comment":{"summary":[{"kind":"text","text":"Whether the audio should route through the earpiece.\nOn iOS, this only has an effect when "},{"kind":"code","text":"`allowsRecording`"},{"kind":"text","text":" is "},{"kind":"code","text":"`true`"},{"kind":"text","text":" (i.e., the audio session\ncategory is "},{"kind":"code","text":"`.playAndRecord`"},{"kind":"text","text":"). When "},{"kind":"code","text":"`false`"},{"kind":"text","text":" (the default), audio is routed through the speaker."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"false"}]}]},"type":{"type":"intrinsic","name":"boolean"}}]},{"name":"AudioPlayerOptions","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Options for configuring audio player behavior."}]},"children":[{"name":"crossOrigin","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Determines the [cross origin policy](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/crossorigin) used by the underlying native view on web.\nIf "},{"kind":"code","text":"`undefined`"},{"kind":"text","text":" (default), does not use CORS at all. If set to "},{"kind":"code","text":"`'anonymous'`"},{"kind":"text","text":", the audio will be loaded with CORS enabled.\nNote that some audio may not play if CORS is enabled, depending on the CDN settings.\nIf you encounter issues, consider adjusting the "},{"kind":"code","text":"`crossOrigin`"},{"kind":"text","text":" property."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"web"}]},{"tag":"@default","content":[{"kind":"text","text":"undefined"}]}]},"type":{"type":"union","types":[{"type":"literal","value":"anonymous"},{"type":"literal","value":"use-credentials"}]}},{"name":"downloadFirst","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"If set to "},{"kind":"code","text":"`true`"},{"kind":"text","text":", the system will attempt to download the resource to the device before loading.\nThis value defaults to "},{"kind":"code","text":"`false`"},{"kind":"text","text":".\n\nWorks with:\n- Local assets from "},{"kind":"code","text":"`require('path/to/file')`"},{"kind":"text","text":"\n- Remote HTTP/HTTPS URLs\n- Asset objects\n\nWhen enabled, this ensures the audio file is fully downloaded before playback begins.\nThis can improve playback performance and reduce buffering, especially for users\nmanaging multiple audio players simultaneously.\n\nOn Android and iOS, this will download the audio file to the device's tmp directory before playback begins.\nThe system will purge the file at its discretion.\n\nOn web, this will download the audio file to the user's device memory and make it available for the user to play.\nThe system will usually purge the file from memory after a reload or on memory pressure.\nOn web, CORS restrictions apply to the blob url, so you need to make sure the server returns the "},{"kind":"code","text":"`Access-Control-Allow-Origin`"},{"kind":"text","text":" header."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]},{"tag":"@platform","content":[{"kind":"text","text":"android"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"keepAudioSessionActive","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"If set to "},{"kind":"code","text":"`true`"},{"kind":"text","text":", the audio session will not be deactivated when this player pauses or finishes playback.\nThis prevents interrupting other audio sources (like videos) when the audio ends.\n\nUseful for sound effects that should not interfere with ongoing video playback or other audio.\nThe audio session for this player will not be deactivated automatically when the player finishes playback.\n\n> **Note:** If needed, you can manually deactivate the audio session using "},{"kind":"code","text":"`setIsAudioActiveAsync(false)`"},{"kind":"text","text":"."}],"blockTags":[{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@default","content":[{"kind":"text","text":"false"}]}]},"type":{"type":"intrinsic","name":"boolean"}},{"name":"preferredForwardBufferDuration","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"The duration in seconds the player should buffer ahead of the current playback position.\nA higher value improves playback stability at the cost of more memory/network usage.\n\n- **iOS**: Maps to "},{"kind":"code","text":"`AVPlayerItem.preferredForwardBufferDuration`"},{"kind":"text","text":". A value of "},{"kind":"code","text":"`0`"},{"kind":"text","text":" lets the system decide.\n- **Android**: Configures ExoPlayer's "},{"kind":"code","text":"`DefaultLoadControl`"},{"kind":"text","text":" max buffer duration.\n- **Web**: Not applicable (browser manages buffering)."}],"blockTags":[{"tag":"@default","content":[{"kind":"text","text":"0 (system default)"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"android"}]}]},"type":{"type":"intrinsic","name":"number"}},{"name":"updateInterval","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"How often (in milliseconds) to emit playback status updates. Defaults to 500ms."}],"blockTags":[{"tag":"@example","content":[{"kind":"code","text":"```tsx\nimport { useAudioPlayer } from 'expo-audio';\n\nexport default function App() {\n const player = useAudioPlayer(source);\n\n // High-frequency updates for smooth progress bars\n const player = useAudioPlayer(source, { updateInterval: 100 });\n\n // Standard updates (default behavior)\n const player = useAudioPlayer(source, { updateInterval: 500 });\n\n // Low-frequency updates for better performance\n const player = useAudioPlayer(source, { updateInterval: 1000 });\n}\n```"}]},{"tag":"@default","content":[{"kind":"text","text":"500ms"}]},{"tag":"@platform","content":[{"kind":"text","text":"ios"}]},{"tag":"@platform","content":[{"kind":"text","text":"android"}]},{"tag":"@platform","content":[{"kind":"text","text":"web"}]}]},"type":{"type":"intrinsic","name":"number"}}]},{"name":"AudioPlaylistEvents","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Event types that an "},{"kind":"code","text":"`AudioPlaylist`"},{"kind":"text","text":" can emit.\n\nThese events allow you to listen for changes in playlist playback state.\nUse "},{"kind":"code","text":"`playlist.addListener()`"},{"kind":"text","text":" to subscribe to these events."}]},"children":[{"name":"playlistStatusUpdate","variant":"declaration","kind":2048,"signatures":[{"name":"playlistStatusUpdate","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Fired when the playlist's status changes (play/pause/seek/load/track change)."}]},"parameters":[{"name":"status","variant":"param","kind":32768,"type":{"type":"reference","name":"AudioPlaylistStatus","package":"expo-audio"}}],"type":{"type":"intrinsic","name":"void"}}]},{"name":"trackChanged","variant":"declaration","kind":2048,"signatures":[{"name":"trackChanged","variant":"signature","kind":4096,"comment":{"summary":[{"kind":"text","text":"Fired when the current track changes (next/previous/skip)."}]},"parameters":[{"name":"data","variant":"param","kind":32768,"type":{"type":"reflection","declaration":{"name":"__type","variant":"declaration","kind":65536,"children":[{"name":"currentIndex","variant":"declaration","kind":1024,"type":{"type":"intrinsic","name":"number"}},{"name":"previousIndex","variant":"declaration","kind":1024,"type":{"type":"intrinsic","name":"number"}}]}}}],"type":{"type":"intrinsic","name":"void"}}]}]},{"name":"AudioPlaylistLoopMode","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Loop mode for audio playlist playback.\n\n- "},{"kind":"code","text":"`'none'`"},{"kind":"text","text":": No looping. Playback stops after the last track.\n- "},{"kind":"code","text":"`'single'`"},{"kind":"text","text":": Loops the current track indefinitely.\n- "},{"kind":"code","text":"`'all'`"},{"kind":"text","text":": Loops the entire playlist, returning to the first track after the last."}]},"type":{"type":"union","types":[{"type":"literal","value":"none"},{"type":"literal","value":"single"},{"type":"literal","value":"all"}]}},{"name":"AudioPlaylistOptions","variant":"declaration","kind":2097152,"comment":{"summary":[{"kind":"text","text":"Options for configuring an audio playlist."}]},"children":[{"name":"crossOrigin","variant":"declaration","kind":1024,"flags":{"isOptional":true},"comment":{"summary":[{"kind":"text","text":"Sets the "},{"kind":"code","text":"`crossOrigin`"},{"kind":"text","text":" attribute on the "},{"kind":"code","text":"`
); } - -// - **tsconfig.json** -// - Contains the rules that TypeScript will use to enforce type safety throughout the project. diff --git a/docs/scenes/get-started/start-developing/TemplateFeatures/features/animations.mdx b/docs/scenes/get-started/start-developing/TemplateFeatures/features/animations.mdx index e1e3b120856526..0e3038e14a4f21 100644 --- a/docs/scenes/get-started/start-developing/TemplateFeatures/features/animations.mdx +++ b/docs/scenes/get-started/start-developing/TemplateFeatures/features/animations.mdx @@ -2,4 +2,4 @@ import { RawH3 } from '~/ui/components/Text'; Animations -The **components/HelloWave.tsx** component uses the Animated API to create a waving hand animation. On iOS, the **components/ParallaxScrollView.tsx** creates a parallax effect for the header image. +The **src/components/animated-icon.native.tsx** component uses `react-native-reanimated` to create the animated splash overlay and rotating glow effect on native platforms. The web version in **src/components/animated-icon.tsx** uses CSS animations instead, demonstrating platform-specific code using file extensions. diff --git a/docs/scenes/get-started/start-developing/TemplateFeatures/features/navigation.mdx b/docs/scenes/get-started/start-developing/TemplateFeatures/features/navigation.mdx index 4cc1b05b74f183..61395d568c2444 100644 --- a/docs/scenes/get-started/start-developing/TemplateFeatures/features/navigation.mdx +++ b/docs/scenes/get-started/start-developing/TemplateFeatures/features/navigation.mdx @@ -2,4 +2,4 @@ import { RawH3 } from '~/ui/components/Text'; File-based routing -The app has two screens: **app/(tabs)/index.tsx** and **app/(tabs)/explore.tsx**. The layout file in **app/(tabs)/\_layout.tsx** sets up the tab navigator. +The app has two screens: **src/app/index.tsx** and **src/app/explore.tsx**. The layout file in **src/app/\_layout.tsx** sets up navigation using a platform-specific **AppTabs** component that uses native tabs on Android and iOS, and Expo Router UI tabs on web. diff --git a/docs/ui/components/EASCLIReference/data/eas-cli-commands.json b/docs/ui/components/EASCLIReference/data/eas-cli-commands.json index e77103359a96b1..f3a9f72fedafb8 100644 --- a/docs/ui/components/EASCLIReference/data/eas-cli-commands.json +++ b/docs/ui/components/EASCLIReference/data/eas-cli-commands.json @@ -1,8 +1,8 @@ { "source": { "url": "https://raw.githubusercontent.com/expo/eas-cli/main/packages/eas-cli/README.md", - "fetchedAt": "2026-02-20T08:06:30.449Z", - "cliVersion": "18.0.3" + "fetchedAt": "2026-02-25T17:06:26.787Z", + "cliVersion": "18.0.5" }, "totalCommands": 102, "commands": [ @@ -379,12 +379,12 @@ { "command": "eas update", "description": "publish an update group", - "usage": "USAGE\n $ eas update [--branch ] [--channel ] [-m ] [--input-dir ] [--skip-bundler]\n [--clear-cache] [--emit-metadata] [--rollout-percentage ] [-p android|ios|all] [--auto] [--private-key-path\n ] [--environment ] [--json --non-interactive]\n\nFLAGS\n -m, --message= A short message describing the update\n -p, --platform=(android|ios|all) [default: all]\n --auto Use the current git branch and commit message for the EAS branch and update message\n --branch= Branch to publish the update group on\n --channel= Channel that the published update should affect\n --clear-cache Clear the bundler cache before publishing\n --emit-metadata Emit \"eas-update-metadata.json\" in the bundle folder with detailed information about\n the generated updates\n --environment= Environment to use for the server-side defined EAS environment variables during\n command execution, e.g. \"production\", \"preview\", \"development\"\n --input-dir= [default: dist] Location of the bundle\n --json Enable JSON output, non-JSON messages will be printed to stderr.\n --non-interactive Run the command in non-interactive mode.\n --private-key-path= File containing the PEM-encoded private key corresponding to the certificate in\n expo-updates' configuration. Defaults to a file named \"private-key.pem\" in the\n certificate's directory. Only relevant if you are using code signing:\n https://docs.expo.dev/eas-update/code-signing/\n --rollout-percentage= Percentage of users this update should be immediately available to. Users not in the\n rollout will be served the previous latest update on the branch, even if that update\n is itself being rolled out. The specified number must be an integer between 1 and\n 100. When not specified, this defaults to 100.\n --skip-bundler Skip running Expo CLI to bundle the app before publishing\n\nDESCRIPTION\n publish an update group" + "usage": "USAGE\n $ eas update [--branch ] [--channel ] [-m ] [--input-dir ] [--skip-bundler]\n [--clear-cache] [--emit-metadata] [--rollout-percentage ] [-p android|ios|all] [--auto] [--private-key-path\n ] [--environment ] [--json --non-interactive]\n\nFLAGS\n -m, --message= A short message describing the update\n -p, --platform=(android|ios|all) [default: all]\n --auto Use the current git branch and commit message for the EAS branch and update message\n --branch= Branch to publish the update group on\n --channel= Channel that the published update should affect\n --clear-cache Clear the bundler cache before publishing\n --emit-metadata Emit \"eas-update-metadata.json\" in the bundle folder with detailed information about\n the generated updates\n --environment= Environment to use for the server-side defined EAS environment variables during\n command execution, e.g. \"production\", \"preview\", \"development\". Required for\n projects using Expo SDK 55 or greater.\n --input-dir= [default: dist] Location of the bundle\n --json Enable JSON output, non-JSON messages will be printed to stderr.\n --non-interactive Run the command in non-interactive mode.\n --private-key-path= File containing the PEM-encoded private key corresponding to the certificate in\n expo-updates' configuration. Defaults to a file named \"private-key.pem\" in the\n certificate's directory. Only relevant if you are using code signing:\n https://docs.expo.dev/eas-update/code-signing/\n --rollout-percentage= Percentage of users this update should be immediately available to. Users not in the\n rollout will be served the previous latest update on the branch, even if that update\n is itself being rolled out. The specified number must be an integer between 1 and\n 100. When not specified, this defaults to 100.\n --skip-bundler Skip running Expo CLI to bundle the app before publishing\n\nDESCRIPTION\n publish an update group" }, { "command": "eas update:configure", "description": "configure the project to support EAS Update", - "usage": "USAGE\n $ eas update:configure [-p android|ios|all] [--environment ] [--non-interactive]\n\nFLAGS\n -p, --platform=(android|ios|all) [default: all] Platform to configure\n --environment= Environment to use for the server-side defined EAS environment variables during\n command execution, e.g. \"production\", \"preview\", \"development\"\n --non-interactive Run the command in non-interactive mode.\n\nDESCRIPTION\n configure the project to support EAS Update" + "usage": "USAGE\n $ eas update:configure [-p android|ios|all] [--environment ] [--non-interactive]\n\nFLAGS\n -p, --platform=(android|ios|all) [default: all] Platform to configure\n --environment= Environment to use for the server-side defined EAS environment variables during\n command execution, e.g. \"production\", \"preview\", \"development\". Required for\n projects using Expo SDK 55 or greater.\n --non-interactive Run the command in non-interactive mode.\n\nDESCRIPTION\n configure the project to support EAS Update" }, { "command": "eas update:delete GROUPID", diff --git a/docs/ui/components/Footer/Footer.tsx b/docs/ui/components/Footer/Footer.tsx index b221fc4bdb59c2..44b23da6343ab8 100644 --- a/docs/ui/components/Footer/Footer.tsx +++ b/docs/ui/components/Footer/Footer.tsx @@ -23,7 +23,7 @@ type Props = { }; const isDev = process.env.NODE_ENV === 'development'; -const LLMS_SDK_VERSIONS = ['v53.0.0', 'v52.0.0']; +const LLMS_SDK_VERSIONS = ['v53.0.0']; const LLMS_SDK_LATEST_VERSION = LLMS_SDK_VERSIONS[0]; export const Footer = ({ diff --git a/docs/ui/components/SDKTables/sdk-versions.json b/docs/ui/components/SDKTables/sdk-versions.json index 8463fcc04129bc..61cf46452b7604 100644 --- a/docs/ui/components/SDKTables/sdk-versions.json +++ b/docs/ui/components/SDKTables/sdk-versions.json @@ -1,12 +1,12 @@ { "sdkVersions": [ - { + { "sdk": "55.0.0", "android": "7+", "compileSdkVersion": "36", "targetSdkVersion": "36", "ios": "15.1+", - "xcode": "16.1+", + "xcode": "26.2+", "react-native": "0.83", "react-native-web": "0.21.0", "react-native-tvos": "0.83-stable", @@ -64,32 +64,6 @@ "react-native-tvos": "0.74-stable", "react": "18.2.0", "node": "18.20.x" - }, - { - "sdk": "50.0.0", - "android": "6+", - "compileSdkVersion": "34", - "targetSdkVersion": "34", - "ios": "13.4+", - "xcode": "15.4", - "react-native": "0.73", - "react-native-web": "0.19.6", - "react-native-tvos": "0.74-stable", - "react": "18.2.0", - "node": "18.19.x" - }, - { - "sdk": "49.0.0", - "android": "5+", - "compileSdkVersion": "33", - "targetSdkVersion": "33", - "ios": "13+", - "xcode": "15.4", - "react-native": "0.72", - "react-native-web": "0.19.6", - "react-native-tvos": "Not supported", - "react": "18.2.0", - "node": "16.20.x" } ] } diff --git a/packages/@expo/cli/CHANGELOG.md b/packages/@expo/cli/CHANGELOG.md index 2d5d66973a2573..9b97fc2669365e 100644 --- a/packages/@expo/cli/CHANGELOG.md +++ b/packages/@expo/cli/CHANGELOG.md @@ -10,6 +10,10 @@ ### šŸ’” Others +## 55.0.12 — 2026-02-25 + +_This version does not introduce any user-facing changes._ + ## 55.0.11 — 2026-02-25 ### šŸ› Bug fixes diff --git a/packages/@expo/cli/package.json b/packages/@expo/cli/package.json index aab733f22d482b..eadeb84274f285 100644 --- a/packages/@expo/cli/package.json +++ b/packages/@expo/cli/package.json @@ -1,6 +1,6 @@ { "name": "@expo/cli", - "version": "55.0.11", + "version": "55.0.12", "description": "The Expo CLI", "main": "build/bin/cli", "bin": { @@ -49,7 +49,7 @@ "@expo/json-file": "^10.0.12", "@expo/log-box": "55.0.7", "@expo/metro": "~54.2.0", - "@expo/metro-config": "~55.0.8", + "@expo/metro-config": "~55.0.9", "@expo/osascript": "^2.4.2", "@expo/package-manager": "^1.10.3", "@expo/plist": "^0.5.2", diff --git a/packages/@expo/metro-config/CHANGELOG.md b/packages/@expo/metro-config/CHANGELOG.md index e5a1192ee6e39c..ee3be556e12449 100644 --- a/packages/@expo/metro-config/CHANGELOG.md +++ b/packages/@expo/metro-config/CHANGELOG.md @@ -10,6 +10,12 @@ ### šŸ’” Others +## 55.0.9 — 2026-02-25 + +### šŸ’” Others + +- Bump to `babel-plugin-syntax-hermes-parser@^0.32.0` ([#43429](https://github.com/expo/expo/pull/43429) by [@kitten](https://github.com/kitten)) + ## 55.0.8 — 2026-02-25 ### šŸ› Bug fixes diff --git a/packages/@expo/metro-config/package.json b/packages/@expo/metro-config/package.json index 21a79abdcac4ce..280392ea077e1a 100644 --- a/packages/@expo/metro-config/package.json +++ b/packages/@expo/metro-config/package.json @@ -1,6 +1,6 @@ { "name": "@expo/metro-config", - "version": "55.0.8", + "version": "55.0.9", "description": "A Metro config for running React Native projects with the Metro bundler", "main": "build/ExpoMetroConfig.js", "types": "build/ExpoMetroConfig.d.ts", @@ -47,7 +47,7 @@ "debug": "^4.3.2", "getenv": "^2.0.0", "glob": "^13.0.0", - "hermes-parser": "^0.29.1", + "hermes-parser": "^0.32.0", "jsc-safe-url": "^0.2.4", "lightningcss": "^1.30.1", "picomatch": "^4.0.3", diff --git a/packages/babel-preset-expo/CHANGELOG.md b/packages/babel-preset-expo/CHANGELOG.md index f3af55d25f3479..f652a3fe21251e 100644 --- a/packages/babel-preset-expo/CHANGELOG.md +++ b/packages/babel-preset-expo/CHANGELOG.md @@ -10,6 +10,12 @@ ### šŸ’” Others +## 55.0.8 — 2026-02-25 + +### šŸ’” Others + +- Bump to `babel-plugin-syntax-hermes-parser@^0.32.0` ([#43429](https://github.com/expo/expo/pull/43429) by [@kitten](https://github.com/kitten)) + ## 55.0.7 — 2026-02-25 _This version does not introduce any user-facing changes._ diff --git a/packages/babel-preset-expo/package.json b/packages/babel-preset-expo/package.json index 62d2efac3dc4d5..3f8fff909bb929 100644 --- a/packages/babel-preset-expo/package.json +++ b/packages/babel-preset-expo/package.json @@ -1,6 +1,6 @@ { "name": "babel-preset-expo", - "version": "55.0.7", + "version": "55.0.8", "description": "The Babel preset for Expo projects", "main": "build/index.js", "files": [ @@ -44,7 +44,7 @@ "@babel/runtime": "^7.20.0", "react-refresh": ">=0.14.0 <1.0.0", "expo": "*", - "expo-widgets": "^55.0.0" + "expo-widgets": "^55.0.1" }, "peerDependenciesMeta": { "@babel/runtime": { @@ -78,7 +78,7 @@ "babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-transform-flow-enums": "^0.0.2", - "babel-plugin-syntax-hermes-parser": "^0.29.1", + "babel-plugin-syntax-hermes-parser": "^0.32.0", "debug": "^4.3.4", "resolve-from": "^5.0.0" }, diff --git a/packages/create-expo-module/src/__tests__/appleFrameworks-test.ts b/packages/create-expo-module/src/__tests__/appleFrameworks-test.ts new file mode 100644 index 00000000000000..3bb640a57f0202 --- /dev/null +++ b/packages/create-expo-module/src/__tests__/appleFrameworks-test.ts @@ -0,0 +1,21 @@ +import { isAppleFramework, ensureSafeModuleName } from '../appleFrameworks'; + +describe('ensureSafeModuleName', () => { + it('prefixes Apple framework names with Expo', () => { + expect(ensureSafeModuleName('MultipeerConnectivity')).toEqual({ + name: 'ExpoMultipeerConnectivity', + wasRenamed: true, + }); + }); + + it('does not modify safe names', () => { + expect(ensureSafeModuleName('MyCustomModule')).toEqual({ + name: 'MyCustomModule', + wasRenamed: false, + }); + }); + + it('is case-sensitive', () => { + expect(isAppleFramework('multipeerconnectivity')).toBe(false); + }); +}); diff --git a/packages/create-expo-module/src/appleFrameworks.ts b/packages/create-expo-module/src/appleFrameworks.ts new file mode 100644 index 00000000000000..5a34abe04b5a4b --- /dev/null +++ b/packages/create-expo-module/src/appleFrameworks.ts @@ -0,0 +1,305 @@ +/** + * List of known public Apple framework names. + * Using one of these names for a module podspec will cause build conflicts on iOS. + * @see https://developer.apple.com/documentation/technologies + */ +export const APPLE_FRAMEWORKS = new Set([ + 'Accelerate', + 'Accessibility', + 'AccessorySetupKit', + 'Accounts', + 'ActivityKit', + 'AdAttributionKit', + 'AddressBook', + 'AddressBookUI', + 'AdServices', + 'AdSupport', + 'AlarmKit', + 'AppClips', + 'AppIntents', + 'AppTrackingTransparency', + 'AppKit', + 'AppleArchive', + 'ApplicationServices', + 'ARKit', + 'AssetsLibrary', + 'Assignables', + 'AudioToolbox', + 'AudioUnit', + 'AuthenticationServices', + 'AutomatedDeviceEnrollment', + 'AutomaticAssessmentConfiguration', + 'Automator', + 'AVFAudio', + 'AVFoundation', + 'AVKit', + 'AVRouting', + 'BackgroundAssets', + 'BackgroundTasks', + 'BrowserEngineCore', + 'BrowserEngineKit', + 'BrowserKit', + 'BundleResources', + 'CallKit', + 'CarKey', + 'CarPlay', + 'CFNetwork', + 'Cinematic', + 'ClassKit', + 'ClockKit', + 'CloudKit', + 'Collaboration', + 'ColorSync', + 'Combine', + 'CompositorServices', + 'Compression', + 'ContactProvider', + 'Contacts', + 'ContactsUI', + 'CoreAnimation', + 'CoreAudio', + 'CoreAudioKit', + 'CoreAudioTypes', + 'CoreBluetooth', + 'CoreData', + 'CoreFoundation', + 'CoreGraphics', + 'CoreHaptics', + 'CoreHID', + 'CoreImage', + 'CoreLocation', + 'CoreLocationUI', + 'CoreMedia', + 'CoreMediaIO', + 'CoreMIDI', + 'CoreML', + 'CoreMotion', + 'CoreNFC', + 'CoreServices', + 'CoreSpotlight', + 'CoreTelephony', + 'CoreText', + 'CoreTransferable', + 'CoreVideo', + 'CoreWLAN', + 'CreateML', + 'CreateMLComponents', + 'CryptoKit', + 'CryptoTokenKit', + 'DarwinNotify', + 'DataDetection', + 'DeclaredAgeRange', + 'DeveloperToolsSupport', + 'DeviceActivity', + 'DeviceCheck', + 'DeviceDiscoveryExtension', + 'DeviceDiscoveryUI', + 'DiskArbitration', + 'Dispatch', + 'Distributed', + 'dnssd', + 'DockKit', + 'DriverKit', + 'EndpointSecurity', + 'EnergyKit', + 'EventKit', + 'EventKitUI', + 'ExceptionHandling', + 'ExecutionPolicy', + 'ExposureNotification', + 'ExtensionFoundation', + 'ExtensionKit', + 'ExternalAccessory', + 'FamilyControls', + 'FileProvider', + 'FileProviderUI', + 'FinanceKit', + 'FinanceKitUI', + 'FinderSync', + 'ForceFeedback', + 'Foundation', + 'FoundationModels', + 'FSKit', + 'GameController', + 'GameKit', + 'GameplayKit', + 'GameSave', + 'GeoToolbox', + 'GLKit', + 'GroupActivities', + 'GSS', + 'HealthKit', + 'HIDDriverKit', + 'HomeKit', + 'hvf', + 'Hypervisor', + 'iAd', + 'IdentityDocumentServices', + 'IdentityDocumentServicesUI', + 'ImageCaptureCore', + 'ImageIO', + 'ImagePlayground', + 'ImmersiveMediaSupport', + 'InputMethodKit', + 'IOBluetooth', + 'IOBluetoothUI', + 'IOKit', + 'IOSurface', + 'IOUSBHost', + 'iTunesLibrary', + 'JavaScriptCore', + 'JournalingSuggestions', + 'Kernel', + 'KernelManagement', + 'LatentSemanticMapping', + 'LightweightCodeRequirements', + 'LinkPresentation', + 'LiveCommunicationKit', + 'LocalAuthentication', + 'LocalAuthenticationEmbeddedUI', + 'LockedCameraCapture', + 'MailKit', + 'ManagedApp', + 'ManagedAppDistribution', + 'ManagedSettings', + 'ManagedSettingsUI', + 'MapKit', + 'MarketplaceKit', + 'Matter', + 'MatterSupport', + 'MediaAccessibility', + 'MediaExtension', + 'MediaLibrary', + 'MediaPlayer', + 'MediaSetup', + 'MediaToolbox', + 'MessageUI', + 'Messages', + 'Metal', + 'MetalFX', + 'MetalKit', + 'MetalPerformanceShaders', + 'MetalPerformanceShadersGraph', + 'MetricKit', + 'MLCompute', + 'ModelIO', + 'MultipeerConnectivity', + 'MusicKit', + 'NaturalLanguage', + 'NearbyInteraction', + 'Network', + 'NetworkExtension', + 'NotificationCenter', + 'ObjectiveC', + 'Observation', + 'OpenDirectory', + 'OpenGLES', + 'os', + 'OSLog', + 'ParavirtualizedGraphics', + 'PassKit', + 'PCIDriverKit', + 'PDFKit', + 'PencilKit', + 'PermissionKit', + 'PHASE', + 'PhotoKit', + 'PlaygroundSupport', + 'PreferencePanes', + 'ProximityReader', + 'PushKit', + 'PushToTalk', + 'Quartz', + 'QuickLook', + 'QuickLookThumbnailing', + 'QuickLookUI', + 'RealityKit', + 'RegexBuilder', + 'RelevanceKit', + 'ReplayKit', + 'RoomPlan', + 'SafariServices', + 'SafetyKit', + 'SceneKit', + 'ScreenCaptureKit', + 'ScreenSaver', + 'ScreenTime', + 'ScriptingBridge', + 'Security', + 'SecurityFoundation', + 'SecurityInterface', + 'SensitiveContentAnalysis', + 'SensorKit', + 'ServiceManagement', + 'ShaderGraph', + 'ShazamKit', + 'SiriKit', + 'Social', + 'SoundAnalysis', + 'Spatial', + 'Speech', + 'SpriteKit', + 'StoreKit', + 'StoreKitTest', + 'Swift', + 'SwiftCharts', + 'SwiftData', + 'SwiftUI', + 'Symbols', + 'Synchronization', + 'System', + 'SystemConfiguration', + 'SystemExtensions', + 'TabletopKit', + 'TabularData', + 'TelephonyMessagingKit', + 'ThreadNetwork', + 'TipKit', + 'TouchController', + 'Translation', + 'TranslationUIProvider', + 'TVMLKit', + 'TVMLKitJS', + 'TVServices', + 'TVUIKit', + 'UIKit', + 'UniformTypeIdentifiers', + 'UserNotifications', + 'UserNotificationsUI', + 'VideoSubscriberAccount', + 'VideoToolbox', + 'Virtualization', + 'Vision', + 'VisionKit', + 'VisualIntelligence', + 'vmnet', + 'WatchConnectivity', + 'WatchKit', + 'WeatherKit', + 'WebKit', + 'WidgetKit', + 'WiFiAware', + 'WirelessInsights', + 'WorkoutKit', + 'XcodeKit', + 'xcselect', + 'XPC', +]); + +/** + * Checks if the given name conflicts with an Apple framework. + */ +export function isAppleFramework(name: string): boolean { + return APPLE_FRAMEWORKS.has(name); +} + +/** + * Returns a safe module name by prefixing with "Expo" if it conflicts with an Apple framework. + * Returns the original name and whether it was renamed. + */ +export function ensureSafeModuleName(name: string): { name: string; wasRenamed: boolean } { + if (isAppleFramework(name)) { + return { name: `Expo${name}`, wasRenamed: true }; + } + return { name, wasRenamed: false }; +} diff --git a/packages/create-expo-module/src/create-expo-module.ts b/packages/create-expo-module/src/create-expo-module.ts index b5a6d1a1ecc70c..131faf8cf8e665 100644 --- a/packages/create-expo-module/src/create-expo-module.ts +++ b/packages/create-expo-module/src/create-expo-module.ts @@ -8,6 +8,7 @@ import path from 'node:path'; import prompts from 'prompts'; import validateNpmPackage from 'validate-npm-package-name'; +import { ensureSafeModuleName } from './appleFrameworks'; import { createExampleApp } from './createExampleApp'; import { installDependencies, @@ -497,9 +498,19 @@ async function askForSubstitutionDataAsync( filteredPrompts.length > 0 ? await prompts(filteredPrompts, { onCancel }) : {}; // Merge CLI-provided values with prompted values - const name = options.name ?? promptedValues.name ?? slugToModuleName(slug); + const rawName = options.name ?? promptedValues.name ?? slugToModuleName(slug); + const { name, wasRenamed } = ensureSafeModuleName(rawName); const projectPackage = options.package ?? promptedValues.package ?? slugToAndroidPackage(slug); + if (wasRenamed) { + console.log(); + console.log( + chalk.yellow( + `āš ļø Module name "${rawName}" conflicts with an Apple framework. Renamed to "${name}" to avoid build errors.` + ) + ); + } + if (isLocal) { return { project: { @@ -572,9 +583,19 @@ async function getSubstitutionDataFromOptions( isLocal: boolean, options: CommandOptions ): Promise { - const name = options.name ?? slugToModuleName(slug); + const rawName = options.name ?? slugToModuleName(slug); + const { name, wasRenamed } = ensureSafeModuleName(rawName); const projectPackage = options.package ?? slugToAndroidPackage(slug); + if (wasRenamed) { + console.log(); + console.log( + chalk.yellow( + `āš ļø Module name "${rawName}" conflicts with an Apple framework. Renamed to "${name}" to avoid build errors.` + ) + ); + } + debug(`Non-interactive mode: name="${name}", package="${projectPackage}"`); if (isLocal) { diff --git a/packages/create-expo-module/src/prompts.ts b/packages/create-expo-module/src/prompts.ts index 8f46fa7ce05ad0..9956d8d484ddd4 100644 --- a/packages/create-expo-module/src/prompts.ts +++ b/packages/create-expo-module/src/prompts.ts @@ -2,9 +2,21 @@ import path from 'node:path'; import type { Answers, PromptObject } from 'prompts'; import validateNpmPackage from 'validate-npm-package-name'; +import { ensureSafeModuleName } from './appleFrameworks'; import { findGitHubEmail, findMyName } from './utils/git'; import { findGitHubUserFromEmail, guessRepoUrl } from './utils/github'; +/** + * Converts a slug to a native module name (PascalCase), ensuring it doesn't conflict with Apple frameworks. + */ +function slugToSafeModuleName(slug: string): string { + const rawName = slug + .replace(/^@/, '') + .replace(/^./, (match) => match.toUpperCase()) + .replace(/\W+(\w)/g, (_, p1) => p1.toUpperCase()); + return ensureSafeModuleName(rawName).name; +} + function getInitialName(customTargetPath?: string | null): string { const targetBasename = customTargetPath && path.basename(customTargetPath); return targetBasename && validateNpmPackage(targetBasename).validForNewPackages @@ -43,12 +55,7 @@ export async function getSubstitutionDataPrompts(slug: string): Promise { - return slug - .replace(/^@/, '') - .replace(/^./, (match) => match.toUpperCase()) - .replace(/\W+(\w)/g, (_, p1) => p1.toUpperCase()); - }, + initial: () => slugToSafeModuleName(slug), validate: (input) => !!input || 'The native module name cannot be empty', }, { @@ -109,12 +116,7 @@ export async function getLocalSubstitutionDataPrompts( type: 'text', name: 'name', message: 'What is the native module name?', - initial: () => { - return slug - .replace(/^@/, '') - .replace(/^./, (match) => match.toUpperCase()) - .replace(/\W+(\w)/g, (_, p1) => p1.toUpperCase()); - }, + initial: () => slugToSafeModuleName(slug), validate: (input) => !!input || 'The native module name cannot be empty', }, { diff --git a/packages/expo-brownfield/CHANGELOG.md b/packages/expo-brownfield/CHANGELOG.md index 52081bcc1e85ab..7d555a62c3b255 100644 --- a/packages/expo-brownfield/CHANGELOG.md +++ b/packages/expo-brownfield/CHANGELOG.md @@ -10,6 +10,10 @@ ### šŸ’” Others +## 55.0.11 — 2026-02-25 + +### šŸ’” Others + - [test] run brownfield e2e tests (cli + plugin) in sdk/check-packages workflow ([#43391](https://github.com/expo/expo/pull/43391) by [@pmleczek](https://github.com/pmleczek)) ## 55.0.10 — 2026-02-25 diff --git a/packages/expo-brownfield/android/build.gradle b/packages/expo-brownfield/android/build.gradle index f262be45fcc828..186468309c2bcf 100644 --- a/packages/expo-brownfield/android/build.gradle +++ b/packages/expo-brownfield/android/build.gradle @@ -4,7 +4,7 @@ plugins { } group = 'expo.modules.brownfield' -version = '55.0.10' +version = '55.0.11' expoModule { canBePublished false @@ -14,7 +14,7 @@ android { namespace "expo.modules.brownfield" defaultConfig { versionCode 1 - versionName '55.0.10' + versionName '55.0.11' } } diff --git a/packages/expo-brownfield/package.json b/packages/expo-brownfield/package.json index 33c1b512b1d964..048f5c5defaff9 100644 --- a/packages/expo-brownfield/package.json +++ b/packages/expo-brownfield/package.json @@ -1,6 +1,6 @@ { "name": "expo-brownfield", - "version": "55.0.10", + "version": "55.0.11", "description": "Toolkit and APIs for adding brownfield setup to Expo projects", "main": "./build/index.js", "types": "./build/index.d.ts", diff --git a/packages/expo-camera/CHANGELOG.md b/packages/expo-camera/CHANGELOG.md index 509ed9e0093f98..e5132a981050db 100644 --- a/packages/expo-camera/CHANGELOG.md +++ b/packages/expo-camera/CHANGELOG.md @@ -6,12 +6,16 @@ ### šŸŽ‰ New features -- [Web] Expands support for barcode types that can be scanned. ([#43403](https://github.com/expo/expo/pull/43403) by [@alanjhughes](https://github.com/alanjhughes)) - ### šŸ› Bug fixes ### šŸ’” Others +## 55.0.9 — 2026-02-25 + +### šŸŽ‰ New features + +- [Web] Expands support for barcode types that can be scanned. ([#43403](https://github.com/expo/expo/pull/43403) by [@alanjhughes](https://github.com/alanjhughes)) + ## 55.0.8 — 2026-02-25 _This version does not introduce any user-facing changes._ diff --git a/packages/expo-camera/android/build.gradle b/packages/expo-camera/android/build.gradle index 82cc59da9ba9fe..9b5c7d56dc529f 100644 --- a/packages/expo-camera/android/build.gradle +++ b/packages/expo-camera/android/build.gradle @@ -4,7 +4,7 @@ plugins { } group = 'host.exp.exponent' -version = '55.0.8' +version = '55.0.9' def barcodeScannerEnabled = findProperty('expo.camera.barcode-scanner-enabled') def isBarcodeScannerEnabled = (barcodeScannerEnabled ?: "true").toString() != "false" @@ -13,7 +13,7 @@ android { namespace "expo.modules.camera" defaultConfig { versionCode 32 - versionName "55.0.8" + versionName "55.0.9" } } diff --git a/packages/expo-camera/package.json b/packages/expo-camera/package.json index 34cf736c0529df..12ff20f2fa6f58 100644 --- a/packages/expo-camera/package.json +++ b/packages/expo-camera/package.json @@ -1,6 +1,6 @@ { "name": "expo-camera", - "version": "55.0.8", + "version": "55.0.9", "description": "A React component that renders a preview for the device's either front or back camera. Camera's parameters like zoom, auto focus, white balance and flash mode are adjustable. With expo-camera, one can also take photos and record videos that are saved to the app's cache. Morever, the component is also capable of detecting faces and bar codes appearing on the preview.", "main": "build/index.js", "types": "build/index.d.ts", diff --git a/packages/expo-dev-client/CHANGELOG.md b/packages/expo-dev-client/CHANGELOG.md index 8ec348e356eec5..646656e7b93217 100644 --- a/packages/expo-dev-client/CHANGELOG.md +++ b/packages/expo-dev-client/CHANGELOG.md @@ -10,6 +10,10 @@ ### šŸ’” Others +## 55.0.9 — 2026-02-25 + +_This version does not introduce any user-facing changes._ + ## 55.0.8 — 2026-02-25 _This version does not introduce any user-facing changes._ diff --git a/packages/expo-dev-client/android/build.gradle b/packages/expo-dev-client/android/build.gradle index 9fb9e8dc1c9f57..6bebd92678aa4b 100644 --- a/packages/expo-dev-client/android/build.gradle +++ b/packages/expo-dev-client/android/build.gradle @@ -8,13 +8,13 @@ expoModule { } group = "host.exp.exponent" -version = "55.0.8" +version = "55.0.9" android { namespace "expo.modules.devclient" defaultConfig { versionCode 1 - versionName "55.0.8" + versionName "55.0.9" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/packages/expo-dev-client/package.json b/packages/expo-dev-client/package.json index c01d62c4de03e5..bd4f12d4711bd1 100644 --- a/packages/expo-dev-client/package.json +++ b/packages/expo-dev-client/package.json @@ -1,6 +1,6 @@ { "name": "expo-dev-client", - "version": "55.0.8", + "version": "55.0.9", "description": "Expo Development Client", "main": "build/DevClient.js", "types": "build/DevClient.d.ts", @@ -32,8 +32,8 @@ "license": "MIT", "homepage": "https://docs.expo.dev/versions/latest/sdk/dev-client/", "dependencies": { - "expo-dev-launcher": "55.0.9", - "expo-dev-menu": "55.0.8", + "expo-dev-launcher": "55.0.10", + "expo-dev-menu": "55.0.9", "expo-dev-menu-interface": "55.0.1", "expo-manifests": "~55.0.9", "expo-updates-interface": "~55.1.3" diff --git a/packages/expo-dev-launcher/CHANGELOG.md b/packages/expo-dev-launcher/CHANGELOG.md index f279369d918ee3..a2145dcb9150fe 100644 --- a/packages/expo-dev-launcher/CHANGELOG.md +++ b/packages/expo-dev-launcher/CHANGELOG.md @@ -10,6 +10,10 @@ ### šŸ’” Others +## 55.0.10 — 2026-02-25 + +_This version does not introduce any user-facing changes._ + ## 55.0.9 — 2026-02-25 ### šŸ› Bug fixes diff --git a/packages/expo-dev-launcher/android/build.gradle b/packages/expo-dev-launcher/android/build.gradle index d89fca24319761..58fb9066b6c7fe 100644 --- a/packages/expo-dev-launcher/android/build.gradle +++ b/packages/expo-dev-launcher/android/build.gradle @@ -26,13 +26,13 @@ expoModule { } group = "host.exp.exponent" -version = "55.0.9" +version = "55.0.10" android { namespace "expo.modules.devlauncher" defaultConfig { versionCode 9 - versionName "55.0.9" + versionName "55.0.10" } buildTypes { diff --git a/packages/expo-dev-launcher/ios/SwiftUI/DevLauncherViewModel.swift b/packages/expo-dev-launcher/ios/SwiftUI/DevLauncherViewModel.swift index ccdd546fe22de8..c9a87a0362b44a 100644 --- a/packages/expo-dev-launcher/ios/SwiftUI/DevLauncherViewModel.swift +++ b/packages/expo-dev-launcher/ios/SwiftUI/DevLauncherViewModel.swift @@ -337,7 +337,7 @@ class DevLauncherViewModel: ObservableObject { } browser?.browseResultsChangedHandler = { [weak self] results, _ in - guard let self = self else { return } + guard let self else { return } Task { @MainActor [weak self, results] in guard let self else { return } self.markNetworkPermissionGranted() diff --git a/packages/expo-dev-launcher/ios/SwiftUI/LocalNetworkPermissionView.swift b/packages/expo-dev-launcher/ios/SwiftUI/LocalNetworkPermissionView.swift index 787eeda901fabb..1500a41bcd4a83 100644 --- a/packages/expo-dev-launcher/ios/SwiftUI/LocalNetworkPermissionView.swift +++ b/packages/expo-dev-launcher/ios/SwiftUI/LocalNetworkPermissionView.swift @@ -17,9 +17,11 @@ struct LocalNetworkPermissionView: View { } var body: some View { - VStack(spacing: 0) { - Spacer() - + ZStack { + Color + .expoSystemBackground + .ignoresSafeArea(.all) + VStack(spacing: 24) { Image("radar-icon", bundle: getDevLauncherBundle()) .resizable() @@ -46,18 +48,8 @@ struct LocalNetworkPermissionView: View { } } } - - Spacer() - - HStack(alignment: .firstTextBaseline) { - Image(systemName: "info.circle") - Text("Dev servers advertise themselves on your local network using Bonjour. This permission allows the development client to discover them automatically.") - .fontWeight(.semibold) - } - .foregroundColor(.secondary) + .padding() } - .padding() - .background(Color.expoSystemBackground) .alert("Permission Not Granted", isPresented: $showTryAgainFailedAlert) { Button("Open Settings") { #if os(iOS) diff --git a/packages/expo-dev-launcher/package.json b/packages/expo-dev-launcher/package.json index 341dc7927cb416..343eb951aae31b 100644 --- a/packages/expo-dev-launcher/package.json +++ b/packages/expo-dev-launcher/package.json @@ -1,7 +1,7 @@ { "name": "expo-dev-launcher", "title": "Expo Development Launcher", - "version": "55.0.9", + "version": "55.0.10", "description": "Pre-release version of the Expo development launcher package for testing.", "repository": { "type": "git", @@ -16,7 +16,7 @@ "homepage": "https://docs.expo.dev", "dependencies": { "@expo/schema-utils": "^55.0.2", - "expo-dev-menu": "55.0.8", + "expo-dev-menu": "55.0.9", "expo-manifests": "~55.0.9" }, "peerDependencies": { diff --git a/packages/expo-dev-menu/CHANGELOG.md b/packages/expo-dev-menu/CHANGELOG.md index b463a89e3f8ae4..3dc103a12de684 100644 --- a/packages/expo-dev-menu/CHANGELOG.md +++ b/packages/expo-dev-menu/CHANGELOG.md @@ -10,6 +10,10 @@ ### šŸ’” Others +## 55.0.9 — 2026-02-25 + +_This version does not introduce any user-facing changes._ + ## 55.0.8 — 2026-02-25 _This version does not introduce any user-facing changes._ diff --git a/packages/expo-dev-menu/android/build.gradle b/packages/expo-dev-menu/android/build.gradle index f618f9d315e66b..68a8371ff9bcf4 100644 --- a/packages/expo-dev-menu/android/build.gradle +++ b/packages/expo-dev-menu/android/build.gradle @@ -12,7 +12,7 @@ apply plugin: 'expo-module-gradle-plugin' apply plugin: 'org.jetbrains.kotlin.plugin.compose' group = 'host.exp.exponent' -version = '55.0.8' +version = '55.0.9' def hasDevLauncher = findProject(":expo-dev-launcher") != null def configureInRelease = findProperty("expo.devmenu.configureInRelease") == "true" @@ -29,7 +29,7 @@ android { defaultConfig { versionCode 10 - versionName '55.0.8' + versionName '55.0.9' } buildTypes { diff --git a/packages/expo-dev-menu/ios/DevMenuManager.swift b/packages/expo-dev-menu/ios/DevMenuManager.swift index 57180b206dd48b..0fbf236b242e66 100644 --- a/packages/expo-dev-menu/ios/DevMenuManager.swift +++ b/packages/expo-dev-menu/ios/DevMenuManager.swift @@ -547,9 +547,9 @@ open class DevMenuManager: NSObject { fabWindow = DevMenuFABWindow(manager: self, windowScene: windowScene) } - public func updateFABVisibility(menuDismissing: Bool = false) { + public func updateFABVisibility() { DispatchQueue.main.async { [weak self] in - guard let self = self else { return } + guard let self else { return } if self.fabWindow == nil { if let windowScene = UIApplication.shared.connectedScenes @@ -559,7 +559,7 @@ open class DevMenuManager: NSObject { } let shouldShow = DevMenuPreferences.showFloatingActionButton - && (menuDismissing || !self.isVisible) + && !self.isVisible && self.currentBridge != nil && !self.isNavigatingHome && DevMenuPreferences.isOnboardingFinished @@ -567,7 +567,7 @@ open class DevMenuManager: NSObject { } } #else - public func updateFABVisibility(menuDismissing: Bool = false) { + public func updateFABVisibility() { // FAB not available on macOS/tvOS } #endif diff --git a/packages/expo-dev-menu/ios/DevMenuWindow-default.swift b/packages/expo-dev-menu/ios/DevMenuWindow-default.swift index 7553afd970afdb..380f51c90cfe78 100644 --- a/packages/expo-dev-menu/ios/DevMenuWindow-default.swift +++ b/packages/expo-dev-menu/ios/DevMenuWindow-default.swift @@ -103,12 +103,11 @@ class DevMenuWindow: UIWindow, PresentationControllerDelegate { self.backgroundColor = .clear } - DevMenuManager.shared.updateFABVisibility(menuDismissing: true) - devMenuViewController.dismiss(animated: true) { self.isDismissing = false self.isHidden = true self.backgroundColor = UIColor(white: 0, alpha: 0.4) + DevMenuManager.shared.updateFABVisibility() completion?() } } diff --git a/packages/expo-dev-menu/ios/DevMenuWindow-macOS.swift b/packages/expo-dev-menu/ios/DevMenuWindow-macOS.swift index dda841959d2163..da6a939f1711f7 100644 --- a/packages/expo-dev-menu/ios/DevMenuWindow-macOS.swift +++ b/packages/expo-dev-menu/ios/DevMenuWindow-macOS.swift @@ -100,7 +100,7 @@ class DevMenuWindow: NSObject, AnyObject { ctx.duration = 0.3 overlay.animator().alphaValue = 0.0 }, completionHandler: { [weak self] in - guard let self = self else { return } + guard let self else { return } overlay.removeFromSuperview() self.overlayView = nil self.hostingView = nil diff --git a/packages/expo-dev-menu/ios/FAB/DevMenuFABView.swift b/packages/expo-dev-menu/ios/FAB/DevMenuFABView.swift index 007330816539dc..bdb2dadcb08770 100644 --- a/packages/expo-dev-menu/ios/FAB/DevMenuFABView.swift +++ b/packages/expo-dev-menu/ios/FAB/DevMenuFABView.swift @@ -6,8 +6,8 @@ import SwiftUI enum FABConstants { static let iconSize: CGFloat = 44 - static let margin: CGFloat = 16 - static let verticalPadding: CGFloat = 20 + static let margin: CGFloat = 10 + static let verticalPadding: CGFloat = 0 static let dragThreshold: CGFloat = 10 static let momentumFactor: CGFloat = 0.35 static let labelDismissDelay: TimeInterval = 10 @@ -52,7 +52,7 @@ struct FabPill: View { } if showLabel { - Text("Dev menu") + Text("Tools") .font(.system(size: 11, weight: .medium)) .foregroundStyle(.secondary) .fixedSize() @@ -77,15 +77,6 @@ struct FabPill: View { } } - @ViewBuilder - private var actionButton: some View { - if #available(iOS 26.0, *) { - liquidGlassButton - } else { - classicButton - } - } - private func startIdleTimer() { idleTask?.cancel() idleTask = Task { @@ -99,22 +90,7 @@ struct FabPill: View { } } - @available(iOS 26.0, *) - private var liquidGlassButton: some View { - Image(systemName: "gearshape.fill") - .resizable() - .frame(width: FABConstants.imageSize, height: FABConstants.imageSize) - .foregroundStyle(.white) - .frame(width: FABConstants.iconSize, height: FABConstants.iconSize) - .background( - Circle() - .frame(width: FABConstants.iconSize + 10, height: FABConstants.iconSize + 10) - .glassEffect(.clear, in: Circle()) - ) - .shadow(color: .black.opacity(0.4), radius: 8, x: 0, y: 4) - } - - private var classicButton: some View { + private var actionButton: some View { Image(systemName: "gearshape.fill") .resizable() .frame(width: FABConstants.imageSize, height: FABConstants.imageSize) @@ -136,15 +112,32 @@ struct DevMenuFABView: View { private let fabSize = CGSize(width: 72, height: FABConstants.iconSize + 50) + // UserDefaults keys for persisting position + private static let positionXKey = "DevMenuFAB.positionX" + private static let positionYKey = "DevMenuFAB.positionY" + private static let hasStoredPositionKey = "DevMenuFAB.hasStoredPosition" + + private static func loadStoredPosition() -> CGPoint? { + guard UserDefaults.standard.bool(forKey: hasStoredPositionKey) else { return nil } + let x = UserDefaults.standard.double(forKey: positionXKey) + let y = UserDefaults.standard.double(forKey: positionYKey) + return CGPoint(x: x, y: y) + } + + private static func savePosition(_ position: CGPoint) { + UserDefaults.standard.set(position.x, forKey: positionXKey) + UserDefaults.standard.set(position.y, forKey: positionYKey) + UserDefaults.standard.set(true, forKey: hasStoredPositionKey) + } + @State private var position: CGPoint = .zero - @State private var targetPosition: CGPoint = .zero @State private var isDragging = false @State private var isPressed = false @State private var dragStartPosition: CGPoint = .zero private let dragSpring: Animation = .spring( - response: 0.35, - dampingFraction: 0.35, + response: 0.25, + dampingFraction: 0.85, blendDuration: 0 ) @@ -161,7 +154,21 @@ struct DevMenuFABView: View { .position(x: currentFrame.midX, y: currentFrame.midY) .gesture(dragGesture(bounds: geometry.size, safeArea: safeArea)) .onAppear { - let initialPos = defaultPosition(bounds: geometry.size, safeArea: safeArea) + let initialPos: CGPoint + if let storedPos = Self.loadStoredPosition() { + let margin = FABConstants.margin + let minX = margin / 2 + let maxX = geometry.size.width - fabSize.width - margin / 2 + let minY = safeArea.top + FABConstants.verticalPadding + let maxY = geometry.size.height - fabSize.height - safeArea.bottom - FABConstants.verticalPadding + + initialPos = CGPoint( + x: storedPos.x.clamped(to: minX...maxX), + y: storedPos.y.clamped(to: minY...maxY) + ) + } else { + initialPos = defaultPosition(bounds: geometry.size, safeArea: safeArea) + } position = initialPos onFrameChange(CGRect(origin: initialPos, size: fabSize)) } @@ -192,19 +199,10 @@ struct DevMenuFABView: View { dragStartPosition = position } if isDragging { - let margin = FABConstants.margin - let minX = margin - let maxX = bounds.width - fabSize.width - margin - let minY = margin + safeArea.top + FABConstants.verticalPadding - let maxY = bounds.height - fabSize.height - margin - safeArea.bottom - FABConstants.verticalPadding - let rawX = dragStartPosition.x + value.translation.width let rawY = dragStartPosition.y + value.translation.height - position = CGPoint( - x: rawX.clamped(to: minX...maxX), - y: rawY.clamped(to: minY...maxY) - ) + position = CGPoint(x: rawX, y: rawY) onFrameChange(currentFrame) } } @@ -231,6 +229,7 @@ struct DevMenuFABView: View { DispatchQueue.main.async { isDragging = false position = newPos + Self.savePosition(newPos) onFrameChange(CGRect(origin: newPos, size: fabSize)) } } @@ -239,8 +238,8 @@ struct DevMenuFABView: View { private func defaultPosition(bounds: CGSize, safeArea: EdgeInsets) -> CGPoint { CGPoint( - x: bounds.width - fabSize.width - FABConstants.margin, - y: bounds.height * 0.25 + x: bounds.width - fabSize.width - FABConstants.margin / 2, + y: safeArea.top + FABConstants.verticalPadding ) } @@ -251,16 +250,17 @@ struct DevMenuFABView: View { safeArea: EdgeInsets ) -> CGPoint { let margin = FABConstants.margin + let edgeMargin = margin / 2 // Closer to screen edge when snapped let momentumX = velocity.x * FABConstants.momentumFactor let momentumY = velocity.y * FABConstants.momentumFactor let estimatedCenterX = point.x + self.fabSize.width / 2 + momentumX let targetX: CGFloat = estimatedCenterX < bounds.width / 2 - ? margin - : bounds.width - self.fabSize.width - margin + ? edgeMargin + : bounds.width - self.fabSize.width - edgeMargin - let minY = margin + safeArea.top + FABConstants.verticalPadding - let maxY = bounds.height - self.fabSize.height - margin - safeArea.bottom - FABConstants.verticalPadding + let minY = safeArea.top + FABConstants.verticalPadding + let maxY = bounds.height - self.fabSize.height - safeArea.bottom - FABConstants.verticalPadding let targetY = (point.y + momentumY).clamped(to: minY...maxY) return CGPoint(x: targetX, y: targetY) diff --git a/packages/expo-dev-menu/ios/FAB/DevMenuFABWindow.swift b/packages/expo-dev-menu/ios/FAB/DevMenuFABWindow.swift index d7246ae885696c..d86d3c3e847fc3 100644 --- a/packages/expo-dev-menu/ios/FAB/DevMenuFABWindow.swift +++ b/packages/expo-dev-menu/ios/FAB/DevMenuFABWindow.swift @@ -10,6 +10,8 @@ class DevMenuFABWindow: UIWindow { private weak var manager: DevMenuManager? private var hostingController: UIHostingController? var fabFrame: CGRect = .zero + private var currentAnimator: UIViewPropertyAnimator? + private var targetVisibility: Bool? init(manager: DevMenuManager, windowScene: UIWindowScene) { self.manager = manager @@ -51,44 +53,48 @@ class DevMenuFABWindow: UIWindow { } func setVisible(_ visible: Bool, animated: Bool = true) { + // Skip if already animating to the same state + if targetVisibility == visible { + return + } + + // Cancel any in-progress animation and reset to clean state + if currentAnimator != nil { + currentAnimator?.stopAnimation(true) + currentAnimator = nil + transform = .identity + } + + targetVisibility = visible + if visible { isHidden = false - if animated { - alpha = 0 - transform = edgeTranslation - UIView.animate( - withDuration: 0.5, - delay: 0, - usingSpringWithDamping: 0.6, - initialSpringVelocity: 0.8, - options: .curveEaseOut - ) { - self.alpha = 1 - self.transform = .identity - } - } else { - alpha = 1 - transform = .identity + alpha = 0 + transform = edgeTranslation + + let animator = UIViewPropertyAnimator(duration: animated ? 0.5 : 0, dampingRatio: 0.6) { + self.alpha = 1 + self.transform = .identity } + animator.addCompletion { [weak self] _ in + self?.targetVisibility = nil + } + currentAnimator = animator + animator.startAnimation() } else { - if animated { - UIView.animate( - withDuration: 0.4, - delay: 0, - usingSpringWithDamping: 0.6, - initialSpringVelocity: 0.8, - options: .curveEaseIn - ) { - self.alpha = 0 - self.transform = self.edgeTranslation - } completion: { _ in - self.isHidden = true - self.transform = .identity + let animator = UIViewPropertyAnimator(duration: animated ? 0.3 : 0, dampingRatio: 0.8) { + self.alpha = 0 + self.transform = self.edgeTranslation + } + animator.addCompletion { [weak self] position in + self?.targetVisibility = nil + if position == .end { + self?.isHidden = true + self?.transform = .identity } - } else { - alpha = 0 - isHidden = true } + currentAnimator = animator + animator.startAnimation() } } diff --git a/packages/expo-dev-menu/ios/Modules/DevMenuPreferences.swift b/packages/expo-dev-menu/ios/Modules/DevMenuPreferences.swift index 2f252bcfe6c647..393672c11252d8 100644 --- a/packages/expo-dev-menu/ios/Modules/DevMenuPreferences.swift +++ b/packages/expo-dev-menu/ios/Modules/DevMenuPreferences.swift @@ -42,7 +42,7 @@ public class DevMenuPreferences: Module { keyCommandsEnabledKey: true, showsAtLaunchKey: false, isOnboardingFinishedKey: false, - showFloatingActionButtonKey: false + showFloatingActionButtonKey: true ]) #endif diff --git a/packages/expo-dev-menu/ios/SwiftUI/DevMenuAppInfo.swift b/packages/expo-dev-menu/ios/SwiftUI/DevMenuAppInfo.swift index 6cdc900c390b62..cdbdc1b4a787d7 100644 --- a/packages/expo-dev-menu/ios/SwiftUI/DevMenuAppInfo.swift +++ b/packages/expo-dev-menu/ios/SwiftUI/DevMenuAppInfo.swift @@ -12,8 +12,6 @@ struct DevMenuAppInfo: View { .foregroundColor(.primary.opacity(0.6)) VStack(spacing: 0) { - Divider() - InfoRow(title: "Version", value: viewModel.appInfo?.appVersion ?? "Unknown") if let runtimeVersion = viewModel.appInfo?.runtimeVersion { @@ -44,8 +42,7 @@ struct DevMenuAppInfo: View { } } .padding(.horizontal) - .background(Color.expoSystemBackground) - .cornerRadius(18) + .background(Color.expoSecondarySystemBackground, in: RoundedRectangle(cornerRadius: 18)) } } } diff --git a/packages/expo-dev-menu/ios/SwiftUI/DevMenuButtons.swift b/packages/expo-dev-menu/ios/SwiftUI/DevMenuButtons.swift index 3850cb44012f99..6c626b7cb6b338 100644 --- a/packages/expo-dev-menu/ios/SwiftUI/DevMenuButtons.swift +++ b/packages/expo-dev-menu/ios/SwiftUI/DevMenuButtons.swift @@ -59,6 +59,8 @@ struct DevMenuToggleButton: View { Text(title) .foregroundColor(disabled ? .secondary : .primary) + .lineLimit(1) + .minimumScaleFactor(0.8) Spacer() diff --git a/packages/expo-dev-menu/ios/SwiftUI/DevMenuDeveloperTools.swift b/packages/expo-dev-menu/ios/SwiftUI/DevMenuDeveloperTools.swift index e223e4c0fdaf95..144e30eb7040d2 100644 --- a/packages/expo-dev-menu/ios/SwiftUI/DevMenuDeveloperTools.swift +++ b/packages/expo-dev-menu/ios/SwiftUI/DevMenuDeveloperTools.swift @@ -79,15 +79,14 @@ struct DevMenuDeveloperTools: View { Divider() DevMenuToggleButton( - title: "Show dev menu button", + title: "Tools button", icon: "hand.tap", isEnabled: viewModel.showFloatingActionButton, action: viewModel.toggleFloatingActionButton ) #endif } - .background(Color.expoSystemBackground) - .cornerRadius(18) + .background(Color.expoSecondarySystemBackground, in: RoundedRectangle(cornerRadius: 18)) } } } diff --git a/packages/expo-dev-menu/package.json b/packages/expo-dev-menu/package.json index 0a51c8d8959f2f..942296c26f3132 100644 --- a/packages/expo-dev-menu/package.json +++ b/packages/expo-dev-menu/package.json @@ -1,6 +1,6 @@ { "name": "expo-dev-menu", - "version": "55.0.8", + "version": "55.0.9", "description": "Expo/React Native module with the developer menu.", "main": "build/DevMenu.js", "types": "build/DevMenu.d.ts", @@ -39,7 +39,7 @@ "@babel/preset-typescript": "^7.7.4", "@testing-library/react-native": "^13.3.0", "babel-plugin-module-resolver": "^5.0.0", - "babel-preset-expo": "~55.0.7", + "babel-preset-expo": "~55.0.8", "expo-module-scripts": "^55.0.2", "react": "19.2.0", "react-native": "0.83.2" diff --git a/packages/expo-doctor/package.json b/packages/expo-doctor/package.json index 83592727ecfa7f..87fa3956896c88 100644 --- a/packages/expo-doctor/package.json +++ b/packages/expo-doctor/package.json @@ -33,7 +33,7 @@ "prepublishOnly": "expo-module prepublishOnly" }, "devDependencies": { - "@expo/cli": "55.0.11", + "@expo/cli": "55.0.12", "@expo/config": "~55.0.8", "@expo/env": "~2.1.1", "@expo/json-file": "~10.0.12", diff --git a/packages/expo-module-scripts/package.json b/packages/expo-module-scripts/package.json index 6d4e36fe714a5c..3ffd29cb0ce6d6 100644 --- a/packages/expo-module-scripts/package.json +++ b/packages/expo-module-scripts/package.json @@ -81,7 +81,7 @@ "@tsconfig/node18": "^18.2.2", "@types/jest": "^29.2.1", "babel-plugin-dynamic-import-node": "^2.3.3", - "babel-preset-expo": "~55.0.7", + "babel-preset-expo": "~55.0.8", "commander": "^12.1.0", "eslint-config-universe": "^15.0.3", "glob": "^13.0.0", diff --git a/packages/expo-module-template-local/android/build.gradle b/packages/expo-module-template-local/android/build.gradle index 0f46c3ac05429d..7705ebb73b6fc2 100644 --- a/packages/expo-module-template-local/android/build.gradle +++ b/packages/expo-module-template-local/android/build.gradle @@ -1,16 +1,41 @@ -plugins { - id 'com.android.library' - id 'expo-module-gradle-plugin' -} +apply plugin: 'com.android.library' group = '<%- project.package %>' -version = '0.7.7' +version = '0.7.8' + +def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") +apply from: expoModulesCorePlugin +applyKotlinExpoModulesCorePlugin() +useCoreDependencies() +useExpoPublishing() + +// If you want to use the managed Android SDK versions from expo-modules-core, set this to true. +// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code. +// Most of the time, you may like to manage the Android SDK versions yourself. +def useManagedAndroidSdkVersions = false +if (useManagedAndroidSdkVersions) { + useDefaultAndroidSdkVersions() +} else { + buildscript { + // Simple helper that allows the root project to override versions declared by this library. + ext.safeExtGet = { prop, fallback -> + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback + } + } + project.android { + compileSdkVersion safeExtGet("compileSdkVersion", 36) + defaultConfig { + minSdkVersion safeExtGet("minSdkVersion", 24) + targetSdkVersion safeExtGet("targetSdkVersion", 36) + } + } +} android { namespace "<%- project.package %>" defaultConfig { versionCode 1 - versionName "0.7.7" + versionName "0.7.8" } lintOptions { abortOnError false diff --git a/packages/expo-module-template-local/package.json b/packages/expo-module-template-local/package.json index 01fef2a7fabdb0..a8a09321215b55 100644 --- a/packages/expo-module-template-local/package.json +++ b/packages/expo-module-template-local/package.json @@ -1,6 +1,6 @@ { "name": "expo-module-template-local", - "version": "0.7.7", + "version": "0.7.8", "description": "ExpoModuleTemplate standalone module", "scripts": {}, "keywords": [ diff --git a/packages/expo-module-template/$package.json b/packages/expo-module-template/$package.json index 325c92db9dd07e..9cb7c14e2cd750 100644 --- a/packages/expo-module-template/$package.json +++ b/packages/expo-module-template/$package.json @@ -32,7 +32,7 @@ "devDependencies": { "@types/react": "~19.1.1", "expo-module-scripts": "^55.0.2", - "expo": "^55.0.0", + "expo": "^55.0.1", "react-native": "0.82.1" }, "peerDependencies": { diff --git a/packages/expo-module-template/android/build.gradle b/packages/expo-module-template/android/build.gradle index e2ff5dd182899a..5aafb1a67345e2 100644 --- a/packages/expo-module-template/android/build.gradle +++ b/packages/expo-module-template/android/build.gradle @@ -1,11 +1,36 @@ -plugins { - id 'com.android.library' - id 'expo-module-gradle-plugin' -} +apply plugin: 'com.android.library' group = '<%- project.package %>' version = '<%- project.version %>' +def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle") +apply from: expoModulesCorePlugin +applyKotlinExpoModulesCorePlugin() +useCoreDependencies() +useExpoPublishing() + +// If you want to use the managed Android SDK versions from expo-modules-core, set this to true. +// The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code. +// Most of the time, you may like to manage the Android SDK versions yourself. +def useManagedAndroidSdkVersions = false +if (useManagedAndroidSdkVersions) { + useDefaultAndroidSdkVersions() +} else { + buildscript { + // Simple helper that allows the root project to override versions declared by this library. + ext.safeExtGet = { prop, fallback -> + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback + } + } + project.android { + compileSdkVersion safeExtGet("compileSdkVersion", 36) + defaultConfig { + minSdkVersion safeExtGet("minSdkVersion", 24) + targetSdkVersion safeExtGet("targetSdkVersion", 36) + } + } +} + android { namespace "<%- project.package %>" defaultConfig { diff --git a/packages/expo-module-template/package.json b/packages/expo-module-template/package.json index a7c53bcb677ac8..b2d72340e3ecce 100644 --- a/packages/expo-module-template/package.json +++ b/packages/expo-module-template/package.json @@ -1,6 +1,6 @@ { "name": "expo-module-template", - "version": "55.0.7", + "version": "55.0.8", "description": "ExpoModuleTemplate standalone module", "scripts": {}, "keywords": [ diff --git a/packages/expo-modules-core/CHANGELOG.md b/packages/expo-modules-core/CHANGELOG.md index ad8fa272d3746a..93119e44decd6e 100644 --- a/packages/expo-modules-core/CHANGELOG.md +++ b/packages/expo-modules-core/CHANGELOG.md @@ -10,6 +10,10 @@ ### šŸ’” Others +## 55.0.12 — 2026-02-25 + +_This version does not introduce any user-facing changes._ + ## 55.0.11 — 2026-02-25 ### šŸ› Bug fixes @@ -27,7 +31,6 @@ - Fixed view updates for Jetpack Compose integration. ([#42732](https://github.com/expo/expo/pull/42732) by [@kudo](https://github.com/kudo)) - [Android] Promoted `Either` type stable. ([#43267](https://github.com/expo/expo/pull/43267) by [@lukmccall](https://github.com/lukmccall)) -- [Android] Remove legacy `ExpoModulesCorePlugin`. ([#43312](https://github.com/expo/expo/pull/43312) by [@lukmccall](https://github.com/lukmccall)) ## 55.0.9 — 2026-02-16 diff --git a/packages/expo-modules-core/android/ExpoModulesCorePlugin.gradle b/packages/expo-modules-core/android/ExpoModulesCorePlugin.gradle new file mode 100644 index 00000000000000..7910c7721652da --- /dev/null +++ b/packages/expo-modules-core/android/ExpoModulesCorePlugin.gradle @@ -0,0 +1,123 @@ +class KotlinExpoModulesCorePlugin implements Plugin { + void apply(Project project) { + // For compatibility reasons the plugin needs to declare that it provides common build.gradle + // options for the modules + project.rootProject.ext.expoProvidesDefaultConfig = { + true + } + + project.ext.safeExtGet = { prop, fallback -> + project.rootProject.ext.has(prop) ? project.rootProject.ext.get(prop) : fallback + } + + project.buildscript { + project.ext.kotlinVersion = { + project.rootProject.ext.has("kotlinVersion") + ? project.rootProject.ext.get("kotlinVersion") + : "2.0.21" + } + + project.ext.kspVersion = { + def kspVersionsMap = [ + "1.6.10": "1.6.10-1.0.4", + "1.6.21": "1.6.21-1.0.6", + "1.7.22": "1.7.22-1.0.8", + "1.8.0": "1.8.0-1.0.9", + "1.8.10": "1.8.10-1.0.9", + "1.8.20": "1.8.20-1.0.11", + "1.8.22": "1.8.22-1.0.11", + "1.9.23": "1.9.23-1.0.20", + "1.9.24": "1.9.24-1.0.20", + "2.0.21": "2.0.21-1.0.28" + ] + + project.rootProject.ext.has("kspVersion") + ? project.rootProject.ext.get("kspVersion") + : kspVersionsMap.containsKey(project.ext.kotlinVersion()) + ? kspVersionsMap.get(project.ext.kotlinVersion()) + : "2.0.21-1.0.28" + } + } + } +} + +ext.applyKotlinExpoModulesCorePlugin = { + try { + // Tries to apply the kotlin-android plugin if the client project does not apply yet. + // On previous `applyKotlinExpoModulesCorePlugin`, it is inside the `project.buildscript` block. + // We cannot use `project.plugins.hasPlugin()` yet but only to try-catch instead. + apply plugin: 'kotlin-android' + } catch (e) {} + + apply plugin: KotlinExpoModulesCorePlugin +} + +// Apply JVM Toolchain version for KSP +ext.applyKspJvmToolchain = { + project.ksp { + kotlin.jvmToolchain(17) + } +} + +// Setup build options that are common for all modules +ext.useDefaultAndroidSdkVersions = { + project.android { + compileSdkVersion project.ext.safeExtGet("compileSdkVersion", 36) + + defaultConfig { + minSdkVersion project.ext.safeExtGet("minSdkVersion", 24) + targetSdkVersion project.ext.safeExtGet("targetSdkVersion", 36) + } + + lintOptions { + abortOnError false + } + } +} + +ext.useExpoPublishing = { + if (!project.plugins.hasPlugin('maven-publish')) { + apply plugin: 'maven-publish' + } + + if (components.findByName("release") == null) { + return + } + + project.android { + publishing { + singleVariant("release") { + withSourcesJar() + } + } + } + + project.afterEvaluate { + publishing { + publications { + release(MavenPublication) { + from components.release + } + } + repositories { + maven { + url = mavenLocal().url + } + } + } + } +} + +ext.useCoreDependencies = { + dependencies { + // Avoids cyclic dependencies + if (!project.project.name.startsWith("expo-modules-core")) { + implementation project.project(':expo-modules-core') + } + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${project.ext.kotlinVersion()}" + } +} + +ext.boolish = { value -> + return value.toString().toBoolean() +} diff --git a/packages/expo-modules-core/android/build.gradle b/packages/expo-modules-core/android/build.gradle index ffc14148186a31..f115278d751342 100644 --- a/packages/expo-modules-core/android/build.gradle +++ b/packages/expo-modules-core/android/build.gradle @@ -29,7 +29,7 @@ if (shouldIncludeCompose) { } group = 'host.exp.exponent' -version = '55.0.11' +version = '55.0.12' def isExpoModulesCoreTests = { Gradle gradle = getGradle() @@ -96,7 +96,7 @@ android { defaultConfig { consumerProguardFiles 'proguard-rules.pro' versionCode 1 - versionName "55.0.11" + versionName "55.0.12" buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\"" buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "true" diff --git a/packages/expo-modules-core/package.json b/packages/expo-modules-core/package.json index 46de346de4ff63..770b0286c89212 100644 --- a/packages/expo-modules-core/package.json +++ b/packages/expo-modules-core/package.json @@ -1,6 +1,6 @@ { "name": "expo-modules-core", - "version": "55.0.11", + "version": "55.0.12", "description": "The core of Expo Modules architecture", "main": "src/index.ts", "types": "build/index.d.ts", diff --git a/packages/expo-router/CHANGELOG.md b/packages/expo-router/CHANGELOG.md index 6532f3319d1ae2..ea77e08e78aa33 100644 --- a/packages/expo-router/CHANGELOG.md +++ b/packages/expo-router/CHANGELOG.md @@ -6,14 +6,21 @@ ### šŸŽ‰ New features +### šŸ› Bug fixes + +### šŸ’” Others + +## 55.0.1 — 2026-02-25 + +### šŸŽ‰ New features + - [ios] support custom images in submenus ([#43414](https://github.com/expo/expo/pull/43414) by [@Ubax](https://github.com/Ubax)) ### šŸ› Bug fixes +- [android] Fix `Color.android.dynamic.background` always returning null by adding missing `background` mapping to `attrMap`. ([#43336](https://github.com/expo/expo/pull/43336) by [@just1and0](https://github.com/just1and0)) - [ios] add better modal and preview handling when using zoom transition ([#43370](https://github.com/expo/expo/pull/43370) by [@Ubax](https://github.com/Ubax)) -### šŸ’” Others - ## 55.0.0 — 2026-02-25 ### šŸ› Bug fixes diff --git a/packages/expo-router/android/build.gradle b/packages/expo-router/android/build.gradle index 92701ccafb0927..9b5044a933f2be 100644 --- a/packages/expo-router/android/build.gradle +++ b/packages/expo-router/android/build.gradle @@ -4,13 +4,13 @@ plugins { } group = 'expo.modules.router' -version = '55.0.0' +version = '55.0.1' android { namespace "expo.modules.router" defaultConfig { versionCode 1 - versionName "55.0.0" + versionName "55.0.1" } lintOptions { abortOnError false diff --git a/packages/expo-router/android/src/main/java/expo/modules/router/ExpoRouterModule.kt b/packages/expo-router/android/src/main/java/expo/modules/router/ExpoRouterModule.kt index 5d3074b1c415a8..369cd65886415c 100644 --- a/packages/expo-router/android/src/main/java/expo/modules/router/ExpoRouterModule.kt +++ b/packages/expo-router/android/src/main/java/expo/modules/router/ExpoRouterModule.kt @@ -114,6 +114,7 @@ class ExpoRouterModule : Module() { "onerrorcontainer" to com.google.android.material.R.attr.colorOnErrorContainer, "outline" to com.google.android.material.R.attr.colorOutline, "outlinevariant" to com.google.android.material.R.attr.colorOutlineVariant, + "background" to android.R.attr.colorBackground, "onbackground" to com.google.android.material.R.attr.colorOnBackground, "surface" to com.google.android.material.R.attr.colorSurface, "onsurface" to com.google.android.material.R.attr.colorOnSurface, diff --git a/packages/expo-router/package.json b/packages/expo-router/package.json index 1d4ee95ae55d49..0344799270dda6 100644 --- a/packages/expo-router/package.json +++ b/packages/expo-router/package.json @@ -1,6 +1,6 @@ { "name": "expo-router", - "version": "55.0.0", + "version": "55.0.1", "description": "Expo Router is a file-based router for React Native and web applications.", "author": "650 Industries, Inc.", "license": "MIT", diff --git a/packages/expo-sqlite/CHANGELOG.md b/packages/expo-sqlite/CHANGELOG.md index 5cb9e6dee15371..dc2167ca0be914 100644 --- a/packages/expo-sqlite/CHANGELOG.md +++ b/packages/expo-sqlite/CHANGELOG.md @@ -10,6 +10,10 @@ ### šŸ’” Others +## 55.0.10 — 2026-02-25 + +### šŸ’” Others + - Bumped dev-plugin-webui to SDK 55. ([#43412](https://github.com/expo/expo/pull/43412) by [@kudo](https://github.com/kudo)) ## 55.0.9 — 2026-02-25 diff --git a/packages/expo-sqlite/android/build.gradle b/packages/expo-sqlite/android/build.gradle index baf447bd69c082..b97911a503364c 100644 --- a/packages/expo-sqlite/android/build.gradle +++ b/packages/expo-sqlite/android/build.gradle @@ -47,13 +47,13 @@ def reactNativeArchitectures() { } group = 'host.exp.exponent' -version = '55.0.9' +version = '55.0.10' android { namespace "expo.modules.sqlite" defaultConfig { versionCode 18 - versionName "55.0.9" + versionName "55.0.10" buildConfigField "boolean", "USE_LIBSQL", project.ext.USE_LIBSQL.toString() buildConfigField "boolean", "WITH_SQLITE_VEC", project.ext.WITH_SQLITE_VEC.toString() diff --git a/packages/expo-sqlite/package.json b/packages/expo-sqlite/package.json index b406a0d6fd98b1..1e9623a70101b3 100644 --- a/packages/expo-sqlite/package.json +++ b/packages/expo-sqlite/package.json @@ -1,6 +1,6 @@ { "name": "expo-sqlite", - "version": "55.0.9", + "version": "55.0.10", "description": "Provides access to a database using SQLite (https://www.sqlite.org/). The database is persisted across restarts of your app.", "main": "build/index.js", "types": "build/index.d.ts", diff --git a/packages/expo-ui/CHANGELOG.md b/packages/expo-ui/CHANGELOG.md index bdbbb43ce9e887..590998279fd473 100644 --- a/packages/expo-ui/CHANGELOG.md +++ b/packages/expo-ui/CHANGELOG.md @@ -6,12 +6,16 @@ ### šŸŽ‰ New features -- [iOS] Added `luminanceToAlpha` modifier. ([#43417](https://github.com/expo/expo/pull/43417) by [@jakex7](https://github.com/jakex7)) - ### šŸ› Bug fixes ### šŸ’” Others +## 55.0.1 — 2026-02-25 + +### šŸŽ‰ New features + +- [iOS] Added `luminanceToAlpha` modifier. ([#43417](https://github.com/expo/expo/pull/43417) by [@jakex7](https://github.com/jakex7)) + ## 55.0.0 — 2026-02-25 ### šŸŽ‰ New features diff --git a/packages/expo-ui/android/build.gradle b/packages/expo-ui/android/build.gradle index 6a13eb1cb7af80..ca222e51a6b832 100644 --- a/packages/expo-ui/android/build.gradle +++ b/packages/expo-ui/android/build.gradle @@ -12,13 +12,13 @@ apply plugin: 'expo-module-gradle-plugin' apply plugin: 'org.jetbrains.kotlin.plugin.compose' group = 'expo.modules.ui' -version = '55.0.0' +version = '55.0.1' android { namespace "expo.modules.ui" defaultConfig { versionCode 1 - versionName "55.0.0" + versionName "55.0.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildFeatures { diff --git a/packages/expo-ui/package.json b/packages/expo-ui/package.json index 47908f67d1bb34..c7988df3beff3c 100644 --- a/packages/expo-ui/package.json +++ b/packages/expo-ui/package.json @@ -1,6 +1,6 @@ { "name": "@expo/ui", - "version": "55.0.0", + "version": "55.0.1", "description": "A collection of UI components", "sideEffects": [ "*.fx.js" diff --git a/packages/expo-widgets/CHANGELOG.md b/packages/expo-widgets/CHANGELOG.md index 841eed06c6af22..987a7d6cb720ef 100644 --- a/packages/expo-widgets/CHANGELOG.md +++ b/packages/expo-widgets/CHANGELOG.md @@ -8,11 +8,15 @@ ### šŸ› Bug fixes +### šŸ’” Others + +## 55.0.1 — 2026-02-25 + +### šŸ› Bug fixes + - Fix "blinking" on interactions. ([#43416](https://github.com/expo/expo/pull/43416) by [@jakex7](https://github.com/jakex7)) - Generate WidgetBundle for live activity even when there are no widgets. ([#43425](https://github.com/expo/expo/pull/43425) by [@jakex7](https://github.com/jakex7)) -### šŸ’” Others - ## 55.0.0 — 2026-02-25 ### šŸŽ‰ New features diff --git a/packages/expo-widgets/package.json b/packages/expo-widgets/package.json index 7d6605fba63dab..6fe797783ec1e8 100644 --- a/packages/expo-widgets/package.json +++ b/packages/expo-widgets/package.json @@ -1,6 +1,6 @@ { "name": "expo-widgets", - "version": "55.0.0", + "version": "55.0.1", "description": "Widgets.", "main": "build/index.js", "types": "build/index.d.ts", @@ -35,7 +35,7 @@ "@expo/config-plugins": "~55.0.6", "@expo/config-types": "^55.0.5", "@expo/plist": "^0.5.2", - "@expo/ui": "55.0.0" + "@expo/ui": "55.0.1" }, "devDependencies": { "expo-module-scripts": "^55.0.2" diff --git a/packages/expo/CHANGELOG.md b/packages/expo/CHANGELOG.md index 88aa1fd8cbbf5b..f33c1ede5835f9 100644 --- a/packages/expo/CHANGELOG.md +++ b/packages/expo/CHANGELOG.md @@ -10,6 +10,10 @@ ### šŸ’” Others +## 55.0.1 — 2026-02-25 + +_This version does not introduce any user-facing changes._ + ## 55.0.0 — 2026-02-25 ### šŸ’” Others diff --git a/packages/expo/android/build.gradle b/packages/expo/android/build.gradle index c94538a7d5dd8d..a952e7a16dfe82 100644 --- a/packages/expo/android/build.gradle +++ b/packages/expo/android/build.gradle @@ -10,7 +10,7 @@ buildscript { } group = 'host.exp.exponent' -version = '55.0.0' +version = '55.0.1' expoModule { // We can't prebuild the module because it depends on the generated files. @@ -21,7 +21,7 @@ android { namespace "expo.core" defaultConfig { versionCode 1 - versionName "55.0.0" + versionName "55.0.1" consumerProguardFiles("proguard-rules.pro") } testOptions { diff --git a/packages/expo/bundledNativeModules.json b/packages/expo/bundledNativeModules.json index 20963efab42c59..0f29d8de49c8da 100644 --- a/packages/expo/bundledNativeModules.json +++ b/packages/expo/bundledNativeModules.json @@ -2,7 +2,7 @@ "@expo/fingerprint": "~0.16.5", "@expo/metro-runtime": "~55.0.6", "@expo/vector-icons": "^15.0.2", - "@expo/ui": "~55.0.0", + "@expo/ui": "55.0.1", "@react-native-async-storage/async-storage": "2.2.0", "@react-native-community/datetimepicker": "8.6.0", "@react-native-masked-view/masked-view": "0.3.2", @@ -26,17 +26,17 @@ "expo-battery": "~55.0.8", "expo-blur": "~55.0.8", "expo-brightness": "~55.0.8", - "expo-brownfield": "~55.0.10", + "expo-brownfield": "~55.0.11", "expo-build-properties": "~55.0.9", "expo-calendar": "~55.0.8", - "expo-camera": "~55.0.8", + "expo-camera": "~55.0.9", "expo-cellular": "~55.0.8", "expo-checkbox": "~55.0.3", "expo-clipboard": "~55.0.8", "expo-constants": "~55.0.7", "expo-contacts": "~55.0.8", "expo-crypto": "~55.0.8", - "expo-dev-client": "~55.0.8", + "expo-dev-client": "~55.0.9", "expo-device": "~55.0.9", "expo-document-picker": "~55.0.8", "expo-file-system": "~55.0.9", @@ -63,14 +63,14 @@ "expo-mcp": "~0.2.1", "expo-media-library": "~55.0.9", "expo-mesh-gradient": "~55.0.8", - "expo-module-template": "~55.0.7", - "expo-modules-core": "~55.0.11", + "expo-module-template": "~55.0.8", + "expo-modules-core": "~55.0.12", "expo-navigation-bar": "~55.0.8", "expo-network": "~55.0.8", "expo-notifications": "~55.0.10", "expo-print": "~55.0.8", "expo-live-photo": "~55.0.8", - "expo-router": "~55.0.0", + "expo-router": "55.0.1", "expo-screen-capture": "~55.0.8", "expo-screen-orientation": "~55.0.8", "expo-secure-store": "~55.0.8", @@ -80,7 +80,7 @@ "expo-sms": "~55.0.8", "expo-speech": "~55.0.8", "expo-splash-screen": "~55.0.9", - "expo-sqlite": "~55.0.9", + "expo-sqlite": "~55.0.10", "expo-status-bar": "~55.0.4", "expo-store-review": "~55.0.8", "expo-symbols": "~55.0.4", diff --git a/packages/expo/package.json b/packages/expo/package.json index 6c8f6e8a2422d1..340ce2ff957d1f 100644 --- a/packages/expo/package.json +++ b/packages/expo/package.json @@ -1,6 +1,6 @@ { "name": "expo", - "version": "55.0.0", + "version": "55.0.1", "description": "The Expo SDK", "main": "src/Expo.ts", "module": "src/Expo.ts", @@ -77,7 +77,7 @@ "homepage": "https://github.com/expo/expo/tree/main/packages/expo", "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "55.0.11", + "@expo/cli": "55.0.12", "@expo/config": "~55.0.8", "@expo/config-plugins": "~55.0.6", "@expo/devtools": "55.0.2", @@ -85,17 +85,17 @@ "@expo/local-build-cache-provider": "55.0.6", "@expo/log-box": "55.0.7", "@expo/metro": "~54.2.0", - "@expo/metro-config": "55.0.8", + "@expo/metro-config": "55.0.9", "@expo/vector-icons": "^15.0.2", "@ungap/structured-clone": "^1.3.0", - "babel-preset-expo": "~55.0.7", + "babel-preset-expo": "~55.0.8", "expo-asset": "~55.0.7", "expo-constants": "~55.0.7", "expo-file-system": "~55.0.9", "expo-font": "~55.0.4", "expo-keep-awake": "~55.0.4", "expo-modules-autolinking": "55.0.8", - "expo-modules-core": "55.0.11", + "expo-modules-core": "55.0.12", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-minimum": "^0.1.1" diff --git a/packages/patch-project/package.json b/packages/patch-project/package.json index f8d3d060bf5ec1..e09781f8e6bdac 100644 --- a/packages/patch-project/package.json +++ b/packages/patch-project/package.json @@ -42,7 +42,7 @@ }, "homepage": "https://github.com/expo/expo/tree/main/packages/patch-project#readme", "devDependencies": { - "@expo/cli": "55.0.11", + "@expo/cli": "55.0.12", "expo-module-scripts": "^55.0.2", "memfs": "^3.2.0" }, diff --git a/templates/expo-template-bare-minimum/package.json b/templates/expo-template-bare-minimum/package.json index 1fcaddbceb2cb7..45b73448e7df9c 100644 --- a/templates/expo-template-bare-minimum/package.json +++ b/templates/expo-template-bare-minimum/package.json @@ -2,7 +2,7 @@ "name": "expo-template-bare-minimum", "description": "This bare project template includes a minimal setup for using unimodules with React Native.", "license": "0BSD", - "version": "55.0.12", + "version": "55.0.13", "main": "index.js", "scripts": { "start": "expo start --dev-client", @@ -11,7 +11,7 @@ "web": "expo start --web" }, "dependencies": { - "expo": "~55.0.0", + "expo": "~55.0.1", "expo-status-bar": "~55.0.4", "react": "19.2.0", "react-native": "0.83.2" diff --git a/templates/expo-template-blank-typescript/package.json b/templates/expo-template-blank-typescript/package.json index 030b71b414dbc9..ce0616079e7254 100644 --- a/templates/expo-template-blank-typescript/package.json +++ b/templates/expo-template-blank-typescript/package.json @@ -2,7 +2,7 @@ "name": "expo-template-blank-typescript", "description": "The Blank project template includes the minimum dependencies to run and an empty root component.", "license": "0BSD", - "version": "55.1.7", + "version": "55.1.8", "main": "index.ts", "scripts": { "start": "expo start", @@ -11,7 +11,7 @@ "web": "expo start --web" }, "dependencies": { - "expo": "~55.0.0", + "expo": "~55.0.1", "expo-status-bar": "~55.0.4", "react": "19.2.0", "react-native": "0.83.2" diff --git a/templates/expo-template-blank/package.json b/templates/expo-template-blank/package.json index d4b823fe9e7153..97099b6c5197d5 100644 --- a/templates/expo-template-blank/package.json +++ b/templates/expo-template-blank/package.json @@ -2,7 +2,7 @@ "name": "expo-template-blank", "description": "The Blank project template includes the minimum dependencies to run and an empty root component.", "license": "0BSD", - "version": "55.1.7", + "version": "55.1.8", "main": "index.js", "scripts": { "start": "expo start", @@ -11,7 +11,7 @@ "web": "expo start --web" }, "dependencies": { - "expo": "~55.0.0", + "expo": "~55.0.1", "expo-status-bar": "~55.0.4", "react": "19.2.0", "react-native": "0.83.2" diff --git a/templates/expo-template-default/package.json b/templates/expo-template-default/package.json index 70220e89dc2ac7..3dfa1917628d22 100644 --- a/templates/expo-template-default/package.json +++ b/templates/expo-template-default/package.json @@ -2,7 +2,7 @@ "name": "expo-template-default", "license": "0BSD", "main": "expo-router/entry", - "version": "55.1.7", + "version": "55.1.8", "scripts": { "start": "expo start", "reset-project": "node ./scripts/reset-project.js", @@ -16,14 +16,14 @@ "@react-navigation/bottom-tabs": "^7.7.3", "@react-navigation/elements": "^2.8.1", "@react-navigation/native": "^7.1.28", - "expo": "~55.0.0", + "expo": "~55.0.1", "expo-constants": "~55.0.7", "expo-device": "~55.0.9", "expo-font": "~55.0.4", "expo-glass-effect": "~55.0.7", "expo-image": "~55.0.5", "expo-linking": "~55.0.7", - "expo-router": "~55.0.0", + "expo-router": "~55.0.1", "expo-splash-screen": "~55.0.9", "expo-status-bar": "~55.0.4", "expo-symbols": "~55.0.4", diff --git a/templates/expo-template-tabs/package.json b/templates/expo-template-tabs/package.json index d1bdf6bc98b20b..8a9da739b2ad8c 100644 --- a/templates/expo-template-tabs/package.json +++ b/templates/expo-template-tabs/package.json @@ -3,7 +3,7 @@ "main": "expo-router/entry", "description": "The Tab Navigation project template includes several example screens.", "license": "0BSD", - "version": "55.1.7", + "version": "55.1.8", "scripts": { "start": "expo start", "android": "expo start --android", @@ -13,11 +13,11 @@ "dependencies": { "@expo/vector-icons": "^15.0.2", "@react-navigation/native": "^7.1.28", - "expo": "~55.0.0", + "expo": "~55.0.1", "expo-constants": "~55.0.7", "expo-font": "~55.0.4", "expo-linking": "~55.0.7", - "expo-router": "~55.0.0", + "expo-router": "~55.0.1", "expo-splash-screen": "~55.0.9", "expo-status-bar": "~55.0.4", "expo-web-browser": "~55.0.9", diff --git a/tools/src/commands/PromotePackages.ts b/tools/src/commands/PromotePackages.ts index 099126775e7b31..63a77b99111495 100644 --- a/tools/src/commands/PromotePackages.ts +++ b/tools/src/commands/PromotePackages.ts @@ -40,6 +40,11 @@ export default (program: Command) => { 'Lists packages with unpublished changes since the previous version.', false ) + .option( + '-r, --reverse', + 'Promote packages in reverse alphabetical order (Z to A).', + false + ) /* debug */ .option('-D, --dry', 'Whether to skip `npm dist-tag add` command.', false) diff --git a/tools/src/promote-packages/helpers.ts b/tools/src/promote-packages/helpers.ts index bfd80c191bda90..d8929879fe6c63 100644 --- a/tools/src/promote-packages/helpers.ts +++ b/tools/src/promote-packages/helpers.ts @@ -54,7 +54,11 @@ function printPackagesToPromoteInternal(parcels: Parcel[], headerText: string): if (parcels.length > 0) { logger.log(' ', magenta(headerText)); - for (const { pkg, state } of parcels) { + const sorted = [...parcels].sort((a, b) => + a.pkg.packageName.localeCompare(b.pkg.packageName) + ); + + for (const { pkg, state } of sorted) { logger.log( ' ', green(pkg.packageName), diff --git a/tools/src/promote-packages/tasks/promotePackages.ts b/tools/src/promote-packages/tasks/promotePackages.ts index db59c94324f221..75981d9fc5a1ae 100644 --- a/tools/src/promote-packages/tasks/promotePackages.ts +++ b/tools/src/promote-packages/tasks/promotePackages.ts @@ -22,53 +22,57 @@ export const promotePackages = new Task( async (parcels: Parcel[], options: CommandOptions): Promise => { logger.info(`\nšŸš€ Promoting packages to ${yellow.bold(options.tag)} tag...`); - await Promise.all( - parcels.map(async ({ pkg, state }) => { - const currentVersion = pkg.packageVersion; - const { versionToReplace } = state; + // Sort alphabetically, optionally reversed. + const sorted = [...parcels].sort((a, b) => + a.pkg.packageName.localeCompare(b.pkg.packageName) + ); + if (options.reverse) { + sorted.reverse(); + } - const batch = logger.batch(); - const action = state.isDemoting ? red('Demoting') : green('Promoting'); - batch.log(' ', green.bold(pkg.packageName)); - batch.log( - ' ', - action, - yellow(options.tag), - formatVersionChange(versionToReplace, currentVersion) - ); + // check if two factor auth is required for publishing + const npmProfile = await Npm.getProfileAsync(); + const requiresOTP = npmProfile?.tfa?.mode === 'auth-and-writes'; - // check if two factor auth is required for publishing - const npmProfile = await Npm.getProfileAsync(); - const requiresOTP = npmProfile?.tfa?.mode === 'auth-and-writes'; + for (const { pkg, state } of sorted) { + const currentVersion = pkg.packageVersion; + const { versionToReplace } = state; - // Tag the local version of the package. - if (!options.dry) { - await Npm.addTagAsync(pkg.packageName, pkg.packageVersion, options.tag, { - stdio: requiresOTP ? 'inherit' : undefined, - }); - } + const action = state.isDemoting ? red('Demoting') : green('Promoting'); + logger.log(' ', green.bold(pkg.packageName)); + logger.log( + ' ', + action, + yellow(options.tag), + formatVersionChange(versionToReplace, currentVersion) + ); - // If the local version had any tags assigned, we can drop the old ones. - // If assigning `sdk-` tag, don't drop any other tags. This one is additive. - if ( - options.drop && - state.distTags && - !state.distTags.includes(options.tag) && - !options.tag.startsWith('sdk-') - ) { - for (const distTag of state.distTags) { - batch.log(' ', `Dropping ${yellow(distTag)} tag (${cyan(currentVersion)})...`); + // Tag the local version of the package. + if (!options.dry) { + await Npm.addTagAsync(pkg.packageName, pkg.packageVersion, options.tag, { + stdio: requiresOTP ? 'inherit' : undefined, + }); + } - if (!options.dry) { - await Npm.removeTagAsync(pkg.packageName, distTag, { - stdio: requiresOTP ? 'inherit' : undefined, - }); - } + // If the local version had any tags assigned, we can drop the old ones. + // If assigning `sdk-` tag, don't drop any other tags. This one is additive. + if ( + options.drop && + state.distTags && + !state.distTags.includes(options.tag) && + !options.tag.startsWith('sdk-') + ) { + for (const distTag of state.distTags) { + logger.log(' ', `Dropping ${yellow(distTag)} tag (${cyan(currentVersion)})...`); + + if (!options.dry) { + await Npm.removeTagAsync(pkg.packageName, distTag, { + stdio: requiresOTP ? 'inherit' : undefined, + }); } } - batch.flush(); - }) - ); + } + } logger.success(`\nāœ… Successfully promoted ${cyan(parcels.length + '')} packages.`); } diff --git a/tools/src/promote-packages/tasks/selectPackagesToPromote.ts b/tools/src/promote-packages/tasks/selectPackagesToPromote.ts index 408758ee952f37..27b327facc487f 100644 --- a/tools/src/promote-packages/tasks/selectPackagesToPromote.ts +++ b/tools/src/promote-packages/tasks/selectPackagesToPromote.ts @@ -38,8 +38,12 @@ export const selectPackagesToPromote = new Task( * Prompts the user to select packages to promote or demote. */ async function promptForPackagesToPromoteAsync(parcels: Parcel[]): Promise { - const maxLength = parcels.reduce((acc, { pkg }) => Math.max(acc, pkg.packageName.length), 0); - const choices = parcels.map(({ pkg, state }) => { + // Sort parcels alphabetically by package name. + const sorted = [...parcels].sort((a, b) => a.pkg.packageName.localeCompare(b.pkg.packageName)); + + const maxLength = sorted.reduce((acc, { pkg }) => Math.max(acc, pkg.packageName.length), 0); + + const choices = sorted.map(({ pkg, state }) => { const action = state.isDemoting ? red.bold('demote') : green.bold('promote'); return { @@ -57,8 +61,7 @@ async function promptForPackagesToPromoteAsync(parcels: Parcel[]): Promise !choice.checked), ...choices.filter((choice) => choice.checked), ], diff --git a/tools/src/promote-packages/types.ts b/tools/src/promote-packages/types.ts index ad286baa9a98e9..42fc2dca03bdd8 100644 --- a/tools/src/promote-packages/types.ts +++ b/tools/src/promote-packages/types.ts @@ -12,6 +12,7 @@ export type CommandOptions = { demote: boolean; dry: boolean; list: boolean; + reverse?: boolean; }; /** diff --git a/tools/src/publish-packages/tasks/loadRequestedParcels.ts b/tools/src/publish-packages/tasks/loadRequestedParcels.ts index 2c84e02d434ee7..d0a3f6c809009a 100644 --- a/tools/src/publish-packages/tasks/loadRequestedParcels.ts +++ b/tools/src/publish-packages/tasks/loadRequestedParcels.ts @@ -29,7 +29,7 @@ export const loadRequestedParcels = new Task( 'Loaded requested workspace packages' ); - const graph = new PackagesGraph(allPackages); + const graph = new PackagesGraph(allPackages.filter((pkg) => !pkg.packageJson.private)); const allPackagesObj = allPackages.reduce((acc, pkg) => { acc[pkg.packageName] = pkg; return acc; diff --git a/yarn.lock b/yarn.lock index 5537ce50d880e9..b2770f2d1e8afa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5704,12 +5704,12 @@ babel-plugin-syntax-hermes-parser@0.32.0: dependencies: hermes-parser "0.32.0" -babel-plugin-syntax-hermes-parser@^0.29.1: - version "0.29.1" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.29.1.tgz#09ca9ecb0330eba1ef939b6d3f1f55bb06a9dc33" - integrity sha512-2WFYnoWGdmih1I1J5eIqxATOeycOqRwYxAQBu3cUu/rhwInwHUg7k60AFNbuGjSDL8tje5GDrAnxzRLcu2pYcA== +babel-plugin-syntax-hermes-parser@^0.32.0: + version "0.32.1" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.1.tgz#ececc38408d408744bff3091bb1c1379a96c3f02" + integrity sha512-HgErPZTghW76Rkq9uqn5ESeiD97FbqpZ1V170T1RG2RDp+7pJVQV2pQJs7y5YzN0/gcT6GM5ci9apRnIwuyPdQ== dependencies: - hermes-parser "0.29.1" + hermes-parser "0.32.1" babel-plugin-syntax-trailing-function-commas@^7.0.0-beta.0: version "7.0.0-beta.0" @@ -9284,22 +9284,15 @@ hermes-compiler@0.14.1: resolved "https://registry.yarnpkg.com/hermes-compiler/-/hermes-compiler-0.14.1.tgz#5381d2bb88454027d16736b8cb7fddaaf1556538" integrity sha512-+RPPQlayoZ9n6/KXKt5SFILWXCGJ/LV5d24L5smXrvTDrPS4L6dSctPczXauuvzFP3QEJbD1YO7Z3Ra4a+4IhA== -hermes-estree@0.29.1: - version "0.29.1" - resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.29.1.tgz#043c7db076e0e8ef8c5f6ed23828d1ba463ebcc5" - integrity sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ== - hermes-estree@0.32.0: version "0.32.0" resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.32.0.tgz#bb7da6613ab8e67e334a1854ea1e209f487d307b" integrity sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ== -hermes-parser@0.29.1, hermes-parser@^0.29.1: - version "0.29.1" - resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.29.1.tgz#436b24bcd7bb1e71f92a04c396ccc0716c288d56" - integrity sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA== - dependencies: - hermes-estree "0.29.1" +hermes-estree@0.32.1: + version "0.32.1" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.32.1.tgz#04075556a559da284d8a3eba01c07595d5e05ce3" + integrity sha512-ne5hkuDxheNBAikDjqvCZCwihnz0vVu9YsBzAEO1puiyFR4F1+PAz/SiPHSsNTuOveCYGRMX8Xbx4LOubeC0Qg== hermes-parser@0.32.0: version "0.32.0" @@ -9308,6 +9301,13 @@ hermes-parser@0.32.0: dependencies: hermes-estree "0.32.0" +hermes-parser@0.32.1, hermes-parser@^0.32.0: + version "0.32.1" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.32.1.tgz#c1bce9405055609233710ad26de6955396a706ba" + integrity sha512-175dz634X/W5AiwrpLdoMl/MOb17poLHyIqgyExlE8D9zQ1OPnoORnGMB5ltRKnpvQzBjMYvT2rN/sHeIfZW5Q== + dependencies: + hermes-estree "0.32.1" + hogan.js@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/hogan.js/-/hogan.js-3.0.2.tgz#4cd9e1abd4294146e7679e41d7898732b02c7bfd"