Real-time media library change notifications with incremental update support for efficient UI updates, allowing applications to respond to external changes to the media library.
Subscribes to media library change events to receive real-time notifications when assets are added, modified, or deleted.
/**
* Subscribes to media library changes for real-time notifications
* @param listener - Callback function to handle media library change events
* @returns EventSubscription object with remove() method for cleanup
*/
function addListener(
listener: (event: MediaLibraryAssetsChangeEvent) => void
): EventSubscription;
interface MediaLibraryAssetsChangeEvent {
/** Whether the changes are incremental and detailed */
hasIncrementalChanges: boolean;
/** Assets that were inserted (if hasIncrementalChanges is true) */
insertedAssets?: Asset[];
/** Assets that were deleted (if hasIncrementalChanges is true) */
deletedAssets?: Asset[];
/** Assets that were updated (if hasIncrementalChanges is true) */
updatedAssets?: Asset[];
}
interface EventSubscription {
/** Remove this specific event subscription */
remove(): void;
}Usage Examples:
import * as MediaLibrary from "expo-media-library";
// Basic event listener
const subscription = MediaLibrary.addListener((event) => {
if (event.hasIncrementalChanges) {
console.log(`Inserted: ${event.insertedAssets?.length || 0}`);
console.log(`Deleted: ${event.deletedAssets?.length || 0}`);
console.log(`Updated: ${event.updatedAssets?.length || 0}`);
} else {
console.log('Media library changed, full refresh needed');
}
});
// React component with event listener
import React, { useState, useEffect } from 'react';
function MediaGallery() {
const [assets, setAssets] = useState([]);
useEffect(() => {
// Initial load
const loadAssets = async () => {
const result = await MediaLibrary.getAssetsAsync({ first: 20 });
setAssets(result.assets);
};
loadAssets();
// Listen for changes
const subscription = MediaLibrary.addListener((event) => {
if (event.hasIncrementalChanges) {
setAssets(currentAssets => {
let updatedAssets = [...currentAssets];
// Remove deleted assets
if (event.deletedAssets) {
const deletedIds = event.deletedAssets.map(asset => asset.id);
updatedAssets = updatedAssets.filter(asset =>
!deletedIds.includes(asset.id)
);
}
// Add new assets
if (event.insertedAssets) {
updatedAssets = [...event.insertedAssets, ...updatedAssets];
}
// Update modified assets
if (event.updatedAssets) {
event.updatedAssets.forEach(updatedAsset => {
const index = updatedAssets.findIndex(asset =>
asset.id === updatedAsset.id
);
if (index !== -1) {
updatedAssets[index] = updatedAsset;
}
});
}
return updatedAssets;
});
} else {
// Full refresh needed
loadAssets();
}
});
return () => subscription.remove();
}, []);
return (
<div>
{assets.map(asset => (
<img key={asset.id} src={asset.uri} alt={asset.filename} />
))}
</div>
);
}
// Advanced listener with error handling
const advancedSubscription = MediaLibrary.addListener((event) => {
try {
if (event.hasIncrementalChanges) {
// Handle incremental changes efficiently
handleIncrementalChanges(event);
} else {
// Fallback to full refresh
handleFullRefresh();
}
} catch (error) {
console.error('Error handling media library change:', error);
}
});
function handleIncrementalChanges(event) {
// Process insertions
if (event.insertedAssets && event.insertedAssets.length > 0) {
console.log('New assets added:', event.insertedAssets.map(a => a.filename));
// Update UI to show new assets
}
// Process deletions
if (event.deletedAssets && event.deletedAssets.length > 0) {
console.log('Assets deleted:', event.deletedAssets.map(a => a.filename));
// Remove assets from UI
}
// Process updates
if (event.updatedAssets && event.updatedAssets.length > 0) {
console.log('Assets updated:', event.updatedAssets.map(a => a.filename));
// Update asset metadata in UI
}
}
function handleFullRefresh() {
console.log('Full media library refresh needed');
// Reload all assets from scratch
}Removes a specific event subscription while leaving other listeners active.
/**
* Removes specific event subscription
* @param subscription - EventSubscription object to remove
* @returns void
*/
function removeSubscription(subscription: EventSubscription): void;Usage Examples:
import * as MediaLibrary from "expo-media-library";
// Create and later remove specific subscription
const subscription = MediaLibrary.addListener((event) => {
console.log('Media library changed');
});
// Remove using the subscription object
subscription.remove(); // Preferred method
// Or remove using the removeSubscription function
MediaLibrary.removeSubscription(subscription);
// Multiple subscriptions management
const subscriptions = [];
// Add multiple listeners
subscriptions.push(MediaLibrary.addListener(handleAssetChanges));
subscriptions.push(MediaLibrary.addListener(handleAlbumChanges));
subscriptions.push(MediaLibrary.addListener(logChanges));
// Remove specific subscription
MediaLibrary.removeSubscription(subscriptions[1]);
// Clean up all subscriptions
subscriptions.forEach(sub => sub.remove());Removes all active media library event listeners at once.
/**
* Removes all media library event listeners
* @returns void
*/
function removeAllListeners(): void;Usage Examples:
import * as MediaLibrary from "expo-media-library";
// Add several listeners
const listener1 = MediaLibrary.addListener(handleChange1);
const listener2 = MediaLibrary.addListener(handleChange2);
const listener3 = MediaLibrary.addListener(handleChange3);
// Remove all at once
MediaLibrary.removeAllListeners();
// Useful in app cleanup or navigation
class MediaScreen extends Component {
componentDidMount() {
this.subscription1 = MediaLibrary.addListener(this.handleChanges);
this.subscription2 = MediaLibrary.addListener(this.handleUploads);
}
componentWillUnmount() {
// Clean up all listeners when leaving screen
MediaLibrary.removeAllListeners();
}
}
// React hook for automatic cleanup
function useMediaLibraryListener(callback) {
useEffect(() => {
const subscription = MediaLibrary.addListener(callback);
return () => {
// Clean up this specific listener
subscription.remove();
// Or clean up all listeners if this is the main component
// MediaLibrary.removeAllListeners();
};
}, [callback]);
}Handle rapid changes efficiently by debouncing events.
import * as MediaLibrary from "expo-media-library";
let debounceTimeout;
const debouncedListener = MediaLibrary.addListener((event) => {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(() => {
processMediaLibraryChange(event);
}, 300); // Wait 300ms before processing
});
function processMediaLibraryChange(event) {
if (event.hasIncrementalChanges) {
// Process incremental changes
updateUIIncrementally(event);
} else {
// Full refresh
refreshAllAssets();
}
}Batch multiple changes for efficient UI updates.
import * as MediaLibrary from "expo-media-library";
let pendingChanges = {
inserted: [],
deleted: [],
updated: []
};
let batchTimeout;
const batchedListener = MediaLibrary.addListener((event) => {
if (event.hasIncrementalChanges) {
// Accumulate changes
if (event.insertedAssets) {
pendingChanges.inserted.push(...event.insertedAssets);
}
if (event.deletedAssets) {
pendingChanges.deleted.push(...event.deletedAssets);
}
if (event.updatedAssets) {
pendingChanges.updated.push(...event.updatedAssets);
}
// Batch process after brief delay
clearTimeout(batchTimeout);
batchTimeout = setTimeout(processBatchedChanges, 100);
} else {
// Immediate full refresh for non-incremental changes
refreshAllAssets();
}
});
function processBatchedChanges() {
const changes = { ...pendingChanges };
// Reset pending changes
pendingChanges = { inserted: [], deleted: [], updated: [] };
// Apply all batched changes at once
applyUIChanges(changes);
}interface MediaLibraryAssetsChangeEvent {
/**
* Whether changes provide detailed incremental information.
* If false, a full refresh of the media library is recommended.
*/
hasIncrementalChanges: boolean;
/**
* Assets that were added to the media library.
* Only available when hasIncrementalChanges is true.
*/
insertedAssets?: Asset[];
/**
* Assets that were removed from the media library.
* Only available when hasIncrementalChanges is true.
*/
deletedAssets?: Asset[];
/**
* Assets that were modified in the media library.
* Only available when hasIncrementalChanges is true.
*/
updatedAssets?: Asset[];
}
interface EventSubscription {
/** Remove this specific event listener subscription */
remove(): void;
}
interface Asset {
id: string;
filename: string;
uri: string;
mediaType: MediaTypeValue;
mediaSubtypes?: MediaSubtype[];
width: number;
height: number;
creationTime: number;
modificationTime: number;
duration: number;
albumId?: string;
}hasIncrementalChanges before processing