Skip to content

Asset

Turn require('./logo.png') (or a plain URI) into a file you can hand to anything that wants a local path: download it once, read localUri, reuse it. @symbiote-native/asset wraps expo-asset so every SymbioteNative adapter can do it, not just React. It exists mainly so @symbiote-native/font can accept the same number and Asset font sources as upstream expo-font, and works standalone too.

OS platform Support
iOS live
Android live
Framework adapter Support
React live
Vue live
Angular live
Svelte live
Solid live
Terminal window
npm install @symbiote-native/asset

Scaffolding or extending a SymbioteNative app? npx @symbiote-native/cli new --asset (or add --asset in an existing app) installs and wires this for you - see @symbiote-native/cli.

expo-asset and expo-modules-core come along as regular dependencies, pinned to exact versions. Never install either yourself, and never add the expo meta-package to your project (it bundles its own Metro/Babel pipeline, which conflicts with this project’s own).

Each adapter wraps Asset.loadAsync and loads once on mount, matching upstream’s useAssets contract. assets stays empty until the download finishes.

import { useAssets } from '@symbiote-native/asset/react';
export function Logo() {
const [assets, error] = useAssets(require('./assets/logo.png'));
return assets ? <image source={{ uri: assets[0].localUri ?? undefined }} /> : null;
}

Outside a component, use the Asset class directly:

import { Asset } from '@symbiote-native/asset';
const [logo] = await Asset.loadAsync(require('./assets/logo.png'));
console.log(logo.localUri); // file:// path in the cache
Signature Description
Asset.fromModule(moduleIdOrUriOrSource): Asset Builds an Asset from a require() module id, a URI, or an object with uri, width, height
Asset.fromMetadata(meta): Asset Builds an Asset from bundler metadata (AssetMetadata)
Asset.fromURI(uri): Asset Builds an Asset for a plain URI, without downloading it
Asset.loadAsync(moduleId): Promise<Asset[]> Resolves one or many module ids (or URIs) and downloads each. Resolves the assets, in order
asset.downloadAsync(): Promise<Asset> Downloads this asset to a local cache file and sets localUri. Resolves the same asset
Adapter Entry point Description
React useAssets(moduleIds) Returns [assets: Asset[] | undefined, error: Error | undefined]
Vue useAssets(moduleIds) Returns { assets, error } as refs
Svelte useAssets(moduleIds) Returns an object with reactive assets and error
Solid createAssets(moduleIds) Returns { assets(), error() } as accessors
Angular inject(AssetsService).connect(moduleIds) Returns { assets, error } as signals

The hook entry points accept number | number[] only, matching upstream’s own hook (narrower than Asset.loadAsync, which also takes strings).

  • localUri is null until the asset is downloaded. Read it after loadAsync or downloadAsync resolves, and pass ?? undefined where an image source expects string | undefined.
  • useAssets does not reload. Like upstream, it loads once on mount; passing a different module id later has no effect.
  • Expo Go and expo-updates code paths are ported but inert. getLocalAssetUri, the manifest2 dev-server resolution and the Image source transformer are real, tested code. This repo ships neither Expo Go nor expo-updates, so IS_ENV_WITH_LOCAL_ASSETS is false. If your app installs expo-updates itself, this package picks it up, same as upstream.
  • The expo-asset config plugin is not ported. It links files into the native project at build time via app.json; this repo runs no Expo prebuild step. Bundle files through Metro with require(), and add unusual extensions (such as .lottie) to assetExts in your Metro config.
  • The web platform is not ported. Only the native path exists, as with every package here.

asset.localUri is null. It stays null until the asset is downloaded. Read it after Asset.loadAsync(...) or asset.downloadAsync() resolves, or let useAssets do the wait for you.

How do I preload several assets before showing a screen? Pass them all to one call: await Asset.loadAsync([require('./a.png'), require('./b.png')]). It resolves the assets in order.

Where does the downloaded file go? A file in the app’s cache directory, so the OS may remove it later. Do not store anything there you cannot re-create.

Does the hook reload when I change the module id? No. It loads once on mount, like upstream. Mount a new component (or use a key) for a different asset.

Sources: Expo docs: Asset, expo/expo#8222 downloadAsync should not download if file exists.

expo-asset’s JS is hand-ported into this package’s core/, resolving ExpoAsset through expo-modules-core rather than the expo meta-package:

packages/asset/src/
|-- core/ # asset.ts, asset-sources.ts, asset-uris.ts, local-assets.ts, platform-utils.ts
|-- react/ # hooks/ useAssets
|-- vue/ # composables/ useAssets
|-- svelte/ # runes/ useAssets
|-- solid/ # primitives/ createAssets
`-- angular/ # services/ AssetsService

Each adapter file is lifecycle only. The native code is never vendored: expo-modules-autolinking resolves it from node_modules (see the native setup guide).