Skip to content

Images and cache

Image and ImageBackground use the native image pipeline. Requests, deduplication, disk I/O and cache-hit decoding run below the PHP component layer; recycled views reject late results from an older source generation. Since PAM Native 0.5.69, Android also retries bounded transient failures while the image remains attached. A completed same-source request is reused only when it still owns a drawable, so a first-visible VirtualizedList image cannot remain blank until its cell is recycled.

asset:// paths are relative to the PAM project root on Android and iOS:

<Image source="asset://assets/logos/brand.png" />

This loads the checked-in assets/logos/brand.png. The runtime owns the internal pam/ bundle prefix, so do not include it in application code. The same rule applies to Image, ImageBackground, defaultSource, loadingIndicatorSource, and packaged @font-face files. Empty paths, directory traversal, queries, fragments, and malformed path segments are rejected before a platform file API is called.

use Pam\Native\ImageFit;
use Pam\Native\ImageResizeMethod;
use Pam\Native\MediaCachePolicy;
use Pam\Native\MediaPriority;
use Pam\Native\UI\Image;
$avatar = Image::make($user->avatarUrl)
->fit(ImageFit::Cover)
->defaultSource('asset://avatar-placeholder.png')
->loadingIndicatorSource('asset://avatar-loading.png')
->fadeDuration(180)
->resizeMethod(ImageResizeMethod::Resize)
->resize(512, 512)
->resizeMultiplier(2)
->progressiveRendering()
->sourceSet("{$user->avatarUrl} 1x, {$user->avatar2xUrl} 2x")
->headers(['Authorization' => "Bearer {$token}"])
->cache(MediaCachePolicy::StaleWhileRevalidate)
->cacheKey("avatar:{$user->id}:v3")
->maxAge(30 * 24 * 60 * 60 * 1000)
->cacheTags(['avatar', "user:{$user->id}"])
->priority(MediaPriority::Visible)
->maxCacheSize(64 * 1024 * 1024)
->pinOffline()
->onLoad(fn ($event) => $this->recordSize($event->width, $event->height))
->onError(fn ($event) => $this->reportImageError($event->error));
<Image
:source="$user->avatarUrl"
fit="cover"
defaultSource="asset://avatar-placeholder.png"
cache="stale-while-revalidate"
cache-key="avatar:user-42:v3"
cache-max-age="30d"
cache-tags="avatar,user-42"
cache-max-bytes="64mb"
priority="visible"
resize-width="512"
resize-height="512"
pin-offline
on:cacheHit="cacheHit"
on:cacheMiss="cacheMiss"
on:cacheReady="cacheReady"
/>
$hero = ImageBackground::make(
'asset://hero.webp',
Column::make(
Text::make('Build without accepting small boundaries'),
Button::make('Start')->onPress($start),
),
)->fit(ImageFit::Cover);

Available policies are none, memory, disk, memory-and-disk, cache-first, network-first, cache-only, and stale-while-revalidate. Cache events expose the stable key, loaded/total bytes and disk origin. Image lifecycle events include load start, progress, decoded size, error and load end.

The compact cachePolicy compatibility property also accepts default, reload, force-cache, and only-if-cached. Since PAM Native 0.5.80, memory-disk and memoryDisk are aliases for force-cache, so an Expo Image port can keep its familiar spelling while still using PAM’s native memory-plus-disk pipeline:

<Image
:source="$avatarUrl"
cachePolicy="memory-disk"
fadeDuration="0"
/>

Animated GIF and animated WebP sources use the same request coalescing and cache. On supported Android versions, PAM decodes them as a looping native drawable; no WebView, JavaScript animation loop, or Lottie dependency is required. Prefer animated WebP when the provider exposes both formats because it normally transfers fewer bytes. In long pickers and feeds, place animated images in a VirtualizedList so only visible rows are mounted.

Cache files use atomic activation, bounded downloads, checksum validation, least-recently-used eviction and offline pin protection. Adaptive-stream offline downloads remain a separate platform manifest/license workflow.

PAM Native 0.5.34 exposes typed, asynchronous cache metrics and cleanup for Android and iOS:

use Pam\Native\CacheUsage;
use Pam\Native\System\Caches;
Caches::usage(static function (CacheUsage $usage): void {
printf(
"Images: %d, media: %d, temporary: %d bytes",
$usage->imageBytes,
$usage->mediaBytes,
$usage->temporaryBytes,
);
});
Caches::clear(
static function (CacheUsage $usage): void {
printf("Freed %d bytes", $usage->freedBytes);
},
);

Cleanup preserves media marked with pinOffline() by default. A destructive “remove offline downloads” action can pass preserveOffline: false, preferably after explicit user confirmation:

Caches::clear($onCleared, preserveOffline: false);

Counting and disk cleanup run away from the UI thread.

DrawingCanvas reuses the same source and cache pipeline, then places a platform-native drawing surface over the aspect-fit image. It supports brush and eraser modes, bounded coalesced points, tokenized undo/clear, and one change event per completed stroke:

<DrawingCanvas
:source="$photo"
:value="$drawing"
brushColor="#FFFFCC00"
brushWidth="8"
drawingMode="brush"
:undoRequest="$undoRequest"
:clearRequest="$clearRequest"
on:change="updateDrawing"
/>

See Files and media for export through the off-UI-thread image editor.