Module 8: Native Features and Device APIs
Sharing and Linking
Share content with other apps and open URLs, deep links, and external applications
π― Learning Objectives
- Use expo-sharing to share files and content
- Open URLs and external apps with expo-linking
- Handle incoming deep links
- Work with the clipboard using expo-clipboard
- Implement common sharing patterns
Sharing Files with expo-sharing
The expo-sharing module opens the native share dialog to share files with other apps.
Basic Sharing
import * as Sharing from 'expo-sharing';
import * as FileSystem from 'expo-file-system';
// Check if sharing is available
async function checkSharingAvailable() {
const isAvailable = await Sharing.isAvailableAsync();
if (!isAvailable) {
alert('Sharing is not available on this device');
return false;
}
return true;
}
// Share a file
async function shareFile(fileUri: string) {
if (!(await checkSharingAvailable())) return;
try {
await Sharing.shareAsync(fileUri);
console.log('Shared successfully');
} catch (error) {
console.error('Error sharing:', error);
}
}
// Usage
shareFile(FileSystem.documentDirectory + 'myDocument.pdf');
Sharing Options
import * as Sharing from 'expo-sharing';
async function shareWithOptions(fileUri: string) {
await Sharing.shareAsync(fileUri, {
// MIME type of the file
mimeType: 'application/pdf',
// Dialog title (Android only)
dialogTitle: 'Share this document',
// UTI for iOS (Uniform Type Identifier)
UTI: 'com.adobe.pdf',
});
}
// Common MIME types
const mimeTypes = {
pdf: 'application/pdf',
png: 'image/png',
jpg: 'image/jpeg',
gif: 'image/gif',
mp4: 'video/mp4',
mp3: 'audio/mpeg',
txt: 'text/plain',
json: 'application/json',
zip: 'application/zip',
};
Sharing Images
import * as Sharing from 'expo-sharing';
import * as FileSystem from 'expo-file-system';
import * as MediaLibrary from 'expo-media-library';
// Share an image from the camera roll
async function shareImage(assetUri: string) {
// For local assets, we can share directly
await Sharing.shareAsync(assetUri, {
mimeType: 'image/jpeg',
});
}
// Share a remote image (must download first)
async function shareRemoteImage(imageUrl: string) {
// Download to local file system
const filename = imageUrl.split('/').pop() || 'image.jpg';
const localUri = FileSystem.cacheDirectory + filename;
const downloadResult = await FileSystem.downloadAsync(
imageUrl,
localUri
);
if (downloadResult.status !== 200) {
throw new Error('Failed to download image');
}
// Share the downloaded file
await Sharing.shareAsync(downloadResult.uri, {
mimeType: 'image/jpeg',
});
// Optionally clean up
await FileSystem.deleteAsync(localUri, { idempotent: true });
}
// Share a base64 image
async function shareBase64Image(base64Data: string) {
const filename = FileSystem.cacheDirectory + 'shared-image.png';
// Write base64 to file
await FileSystem.writeAsStringAsync(filename, base64Data, {
encoding: FileSystem.EncodingType.Base64,
});
await Sharing.shareAsync(filename, {
mimeType: 'image/png',
});
}
Share Component Example
import React, { useState } from 'react';
import { View, Text, Pressable, Image, StyleSheet, Alert } from 'react-native';
import * as Sharing from 'expo-sharing';
import * as FileSystem from 'expo-file-system';
interface ShareButtonProps {
imageUri: string;
title?: string;
}
export function ShareImageButton({ imageUri, title = 'Share' }: ShareButtonProps) {
const [isSharing, setIsSharing] = useState(false);
const handleShare = async () => {
// Check availability
const isAvailable = await Sharing.isAvailableAsync();
if (!isAvailable) {
Alert.alert('Error', 'Sharing is not available on this device');
return;
}
setIsSharing(true);
try {
// If it's a remote URL, download first
if (imageUri.startsWith('http')) {
const filename = 'shared-image.jpg';
const localUri = FileSystem.cacheDirectory + filename;
const { uri } = await FileSystem.downloadAsync(imageUri, localUri);
await Sharing.shareAsync(uri);
// Clean up
await FileSystem.deleteAsync(uri, { idempotent: true });
} else {
// Local file, share directly
await Sharing.shareAsync(imageUri);
}
} catch (error) {
Alert.alert('Error', 'Failed to share image');
console.error(error);
} finally {
setIsSharing(false);
}
};
return (
<Pressable
style={({ pressed }) => [
styles.shareButton,
pressed && styles.shareButtonPressed,
isSharing && styles.shareButtonDisabled,
]}
onPress={handleShare}
disabled={isSharing}
>
<Text style={styles.shareButtonText}>
{isSharing ? 'Sharing...' : `π€ ${title}`}
</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
shareButton: {
backgroundColor: '#007AFF',
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 8,
alignItems: 'center',
},
shareButtonPressed: {
opacity: 0.8,
},
shareButtonDisabled: {
backgroundColor: '#ccc',
},
shareButtonText: {
color: 'white',
fontSize: 16,
fontWeight: '600',
},
});
β οΈ Sharing Limitations
expo-sharingshares files, not plain text or URLs- For sharing text/URLs, use the React Native
ShareAPI instead - The file must be accessible (local file system or downloaded)
- Some apps may not appear in the share sheet for certain file types
Sharing Text and URLs (React Native Share API)
import { Share, Alert } from 'react-native';
// Share text content
async function shareText(message: string) {
try {
const result = await Share.share({
message: message,
});
if (result.action === Share.sharedAction) {
if (result.activityType) {
// Shared with specific activity type (iOS)
console.log('Shared via:', result.activityType);
} else {
// Shared successfully
console.log('Shared!');
}
} else if (result.action === Share.dismissedAction) {
// Dismissed (iOS only)
console.log('Share dismissed');
}
} catch (error) {
Alert.alert('Error', 'Failed to share');
}
}
// Share a URL
async function shareUrl(url: string, title?: string) {
try {
await Share.share({
message: url,
title: title, // Android only
url: url, // iOS only (will be appended to message)
});
} catch (error) {
console.error(error);
}
}
// Share with both message and URL
async function shareContent(title: string, message: string, url: string) {
try {
await Share.share(
{
title,
message: `${message}\n\n${url}`,
url, // iOS will use this instead of appending to message
},
{
dialogTitle: title, // Android dialog title
subject: title, // Email subject
}
);
} catch (error) {
console.error(error);
}
}
// Usage
shareContent(
'Check out this app!',
'I found this amazing app that you should try.',
'https://example.com/app'
);
Opening URLs with expo-linking
The expo-linking module provides utilities for opening URLs, handling deep links, and interacting with other apps.
Opening URLs
import * as Linking from 'expo-linking';
// Open a website in the default browser
async function openWebsite(url: string) {
const supported = await Linking.canOpenURL(url);
if (supported) {
await Linking.openURL(url);
} else {
console.log(`Cannot open URL: ${url}`);
}
}
// Open URL without checking (simpler but less safe)
async function openUrl(url: string) {
try {
await Linking.openURL(url);
} catch (error) {
console.error('Failed to open URL:', error);
}
}
// Usage
openWebsite('https://reactnative.dev');
openWebsite('https://docs.expo.dev');
Opening Settings
import * as Linking from 'expo-linking';
import { Platform } from 'react-native';
// Open app settings (useful after permission denial)
async function openAppSettings() {
if (Platform.OS === 'ios') {
await Linking.openURL('app-settings:');
} else {
await Linking.openSettings();
}
}
// Convenience function
async function openSettings() {
await Linking.openSettings();
}
Common URL Schemes
import * as Linking from 'expo-linking';
// Phone call
async function makePhoneCall(phoneNumber: string) {
const url = `tel:${phoneNumber}`;
await Linking.openURL(url);
}
// Send SMS
async function sendSMS(phoneNumber: string, message?: string) {
const url = message
? `sms:${phoneNumber}?body=${encodeURIComponent(message)}`
: `sms:${phoneNumber}`;
await Linking.openURL(url);
}
// Send email
async function sendEmail(
to: string,
subject?: string,
body?: string
) {
let url = `mailto:${to}`;
const params: string[] = [];
if (subject) params.push(`subject=${encodeURIComponent(subject)}`);
if (body) params.push(`body=${encodeURIComponent(body)}`);
if (params.length > 0) {
url += `?${params.join('&')}`;
}
await Linking.openURL(url);
}
// FaceTime (iOS only)
async function startFaceTime(contact: string) {
await Linking.openURL(`facetime:${contact}`);
}
// Usage examples
makePhoneCall('+1234567890');
sendSMS('+1234567890', 'Hello from the app!');
sendEmail('support@example.com', 'Help Request', 'I need assistance with...');
URL Scheme Reference
| Action | URL Scheme | Example |
|---|---|---|
| Website | https:// |
https://example.com |
| Phone call | tel: |
tel:+1234567890 |
| SMS | sms: |
sms:+1234567890?body=Hello |
mailto: |
mailto:test@example.com |
|
| App settings | app-settings: |
app-settings: (iOS) |
Deep Linking into Other Apps
Many popular apps have their own URL schemes that you can use to open specific content or actions within those apps.
Opening Maps
import * as Linking from 'expo-linking';
import { Platform } from 'react-native';
// Open location in maps app
async function openMaps(
latitude: number,
longitude: number,
label?: string
) {
const encodedLabel = label ? encodeURIComponent(label) : '';
const url = Platform.select({
ios: `maps:0,0?q=${latitude},${longitude}${label ? `(${encodedLabel})` : ''}`,
android: `geo:${latitude},${longitude}?q=${latitude},${longitude}${label ? `(${encodedLabel})` : ''}`,
});
if (url) {
await Linking.openURL(url);
}
}
// Open address in maps
async function openAddress(address: string) {
const encodedAddress = encodeURIComponent(address);
const url = Platform.select({
ios: `maps:0,0?q=${encodedAddress}`,
android: `geo:0,0?q=${encodedAddress}`,
});
if (url) {
await Linking.openURL(url);
}
}
// Open directions
async function openDirections(
destLat: number,
destLng: number,
destLabel?: string
) {
const destination = `${destLat},${destLng}`;
const url = Platform.select({
ios: `maps:0,0?daddr=${destination}`,
android: `google.navigation:q=${destination}`,
});
if (url) {
const canOpen = await Linking.canOpenURL(url);
if (canOpen) {
await Linking.openURL(url);
} else {
// Fallback to web Google Maps
await Linking.openURL(
`https://www.google.com/maps/dir/?api=1&destination=${destination}`
);
}
}
}
// Usage
openMaps(37.7749, -122.4194, 'San Francisco');
openAddress('1 Infinite Loop, Cupertino, CA');
openDirections(37.7749, -122.4194);
Opening Social Media Apps
import * as Linking from 'expo-linking';
// Twitter/X
async function openTwitterProfile(username: string) {
const appUrl = `twitter://user?screen_name=${username}`;
const webUrl = `https://twitter.com/${username}`;
const canOpenApp = await Linking.canOpenURL(appUrl);
await Linking.openURL(canOpenApp ? appUrl : webUrl);
}
async function openTweet(tweetId: string) {
const appUrl = `twitter://status?id=${tweetId}`;
const webUrl = `https://twitter.com/i/status/${tweetId}`;
const canOpenApp = await Linking.canOpenURL(appUrl);
await Linking.openURL(canOpenApp ? appUrl : webUrl);
}
// Instagram
async function openInstagramProfile(username: string) {
const appUrl = `instagram://user?username=${username}`;
const webUrl = `https://instagram.com/${username}`;
const canOpenApp = await Linking.canOpenURL(appUrl);
await Linking.openURL(canOpenApp ? appUrl : webUrl);
}
// Facebook
async function openFacebookProfile(profileId: string) {
const appUrl = `fb://profile/${profileId}`;
const webUrl = `https://facebook.com/${profileId}`;
const canOpenApp = await Linking.canOpenURL(appUrl);
await Linking.openURL(canOpenApp ? appUrl : webUrl);
}
// YouTube
async function openYouTubeVideo(videoId: string) {
const appUrl = `youtube://watch?v=${videoId}`;
const webUrl = `https://youtube.com/watch?v=${videoId}`;
const canOpenApp = await Linking.canOpenURL(appUrl);
await Linking.openURL(canOpenApp ? appUrl : webUrl);
}
// LinkedIn
async function openLinkedInProfile(profileId: string) {
const appUrl = `linkedin://profile/${profileId}`;
const webUrl = `https://linkedin.com/in/${profileId}`;
const canOpenApp = await Linking.canOpenURL(appUrl);
await Linking.openURL(canOpenApp ? appUrl : webUrl);
}
// WhatsApp
async function openWhatsAppChat(phoneNumber: string, message?: string) {
// Phone number should include country code without + or spaces
const cleanNumber = phoneNumber.replace(/\D/g, '');
let url = `whatsapp://send?phone=${cleanNumber}`;
if (message) {
url += `&text=${encodeURIComponent(message)}`;
}
const canOpen = await Linking.canOpenURL(url);
if (canOpen) {
await Linking.openURL(url);
} else {
// Web fallback
await Linking.openURL(
`https://wa.me/${cleanNumber}${message ? `?text=${encodeURIComponent(message)}` : ''}`
);
}
}
Creating a Universal Link Helper
import * as Linking from 'expo-linking';
interface AppLink {
appUrl: string;
webUrl: string;
}
async function openWithFallback({ appUrl, webUrl }: AppLink) {
try {
const canOpenApp = await Linking.canOpenURL(appUrl);
if (canOpenApp) {
await Linking.openURL(appUrl);
} else {
await Linking.openURL(webUrl);
}
} catch (error) {
// Last resort: try web URL
await Linking.openURL(webUrl);
}
}
// Usage
openWithFallback({
appUrl: 'spotify://album/1234567890',
webUrl: 'https://open.spotify.com/album/1234567890',
});
π‘ iOS URL Scheme Queries
On iOS, you must declare URL schemes you want to query in app.json:
{
"expo": {
"ios": {
"infoPlist": {
"LSApplicationQueriesSchemes": [
"twitter",
"instagram",
"fb",
"whatsapp",
"youtube"
]
}
}
}
}
Without this, canOpenURL will return false even if the app is installed.
Working with the Clipboard
The expo-clipboard module provides access to the system clipboard for copying and pasting text and images.
Basic Clipboard Operations
import * as Clipboard from 'expo-clipboard';
// Copy text to clipboard
async function copyToClipboard(text: string) {
await Clipboard.setStringAsync(text);
console.log('Copied to clipboard!');
}
// Get text from clipboard
async function getFromClipboard(): Promise<string> {
const text = await Clipboard.getStringAsync();
return text;
}
// Check if clipboard has content
async function hasClipboardContent(): Promise<boolean> {
const hasString = await Clipboard.hasStringAsync();
return hasString;
}
// Usage
await copyToClipboard('Hello, World!');
const pastedText = await getFromClipboard();
console.log('Pasted:', pastedText);
Copy Button Component
import React, { useState, useCallback } from 'react';
import { Pressable, Text, StyleSheet } from 'react-native';
import * as Clipboard from 'expo-clipboard';
interface CopyButtonProps {
text: string;
label?: string;
onCopy?: () => void;
}
export function CopyButton({ text, label = 'Copy', onCopy }: CopyButtonProps) {
const [copied, setCopied] = useState(false);
const handleCopy = useCallback(async () => {
await Clipboard.setStringAsync(text);
setCopied(true);
onCopy?.();
// Reset after 2 seconds
setTimeout(() => setCopied(false), 2000);
}, [text, onCopy]);
return (
<Pressable
style={({ pressed }) => [
styles.button,
copied && styles.buttonCopied,
pressed && styles.buttonPressed,
]}
onPress={handleCopy}
>
<Text style={styles.buttonText}>
{copied ? 'β Copied!' : `π ${label}`}
</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
button: {
backgroundColor: '#f0f0f0',
paddingVertical: 8,
paddingHorizontal: 16,
borderRadius: 6,
flexDirection: 'row',
alignItems: 'center',
},
buttonCopied: {
backgroundColor: '#d4edda',
},
buttonPressed: {
opacity: 0.7,
},
buttonText: {
fontSize: 14,
color: '#333',
},
});
Copiable Text Component
import React, { useState } from 'react';
import { Text, Pressable, StyleSheet, View } from 'react-native';
import * as Clipboard from 'expo-clipboard';
import * as Haptics from 'expo-haptics';
interface CopiableTextProps {
children: string;
style?: object;
showHint?: boolean;
}
export function CopiableText({
children,
style,
showHint = true
}: CopiableTextProps) {
const [copied, setCopied] = useState(false);
const handleLongPress = async () => {
await Clipboard.setStringAsync(children);
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
};
return (
<View>
<Pressable onLongPress={handleLongPress}>
<Text style={[styles.text, style]}>{children}</Text>
</Pressable>
{showHint && !copied && (
<Text style={styles.hint}>Long press to copy</Text>
)}
{copied && (
<Text style={styles.copiedHint}>Copied!</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
text: {
fontSize: 16,
padding: 8,
backgroundColor: '#f5f5f5',
borderRadius: 4,
},
hint: {
fontSize: 12,
color: '#888',
marginTop: 4,
},
copiedHint: {
fontSize: 12,
color: '#28a745',
marginTop: 4,
},
});
Clipboard with Images (iOS 14+)
import * as Clipboard from 'expo-clipboard';
import * as FileSystem from 'expo-file-system';
// Copy image to clipboard (iOS 14+ only)
async function copyImageToClipboard(imageUri: string) {
// Read image as base64
const base64 = await FileSystem.readAsStringAsync(imageUri, {
encoding: FileSystem.EncodingType.Base64,
});
await Clipboard.setImageAsync(base64);
}
// Get image from clipboard
async function getImageFromClipboard(): Promise<string | null> {
const hasImage = await Clipboard.hasImageAsync();
if (!hasImage) {
return null;
}
const image = await Clipboard.getImageAsync({ format: 'png' });
return image?.data ?? null; // Returns base64 string
}
// Check clipboard content type
async function getClipboardContentType() {
const hasString = await Clipboard.hasStringAsync();
const hasImage = await Clipboard.hasImageAsync();
const hasUrl = await Clipboard.hasUrlAsync();
return { hasString, hasImage, hasUrl };
}
Hands-On Exercises
Exercise 1: Build a Share Sheet
Create a component that presents multiple sharing options for a piece of content.
Requirements:
- Share via native share sheet
- Copy link to clipboard
- Share to specific apps (WhatsApp, Twitter)
- Show feedback when copied
Show Solution
import React, { useState } from 'react';
import { View, Text, Pressable, Share, StyleSheet, Modal } from 'react-native';
import * as Clipboard from 'expo-clipboard';
import * as Linking from 'expo-linking';
import * as Haptics from 'expo-haptics';
interface ShareSheetProps {
visible: boolean;
onClose: () => void;
content: {
title: string;
message: string;
url: string;
};
}
export function ShareSheet({ visible, onClose, content }: ShareSheetProps) {
const [copied, setCopied] = useState(false);
const fullMessage = `${content.message}\n\n${content.url}`;
const handleNativeShare = async () => {
await Share.share({
title: content.title,
message: fullMessage,
});
onClose();
};
const handleCopyLink = async () => {
await Clipboard.setStringAsync(content.url);
await Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
setCopied(true);
setTimeout(() => {
setCopied(false);
onClose();
}, 1500);
};
const handleWhatsApp = async () => {
const url = `whatsapp://send?text=${encodeURIComponent(fullMessage)}`;
const canOpen = await Linking.canOpenURL(url);
if (canOpen) {
await Linking.openURL(url);
} else {
await Linking.openURL(
`https://wa.me/?text=${encodeURIComponent(fullMessage)}`
);
}
onClose();
};
const handleTwitter = async () => {
const url = `twitter://post?message=${encodeURIComponent(fullMessage)}`;
const webUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(fullMessage)}`;
const canOpen = await Linking.canOpenURL(url);
await Linking.openURL(canOpen ? url : webUrl);
onClose();
};
return (
<Modal
visible={visible}
transparent
animationType="slide"
onRequestClose={onClose}
>
<Pressable style={styles.overlay} onPress={onClose}>
<View style={styles.sheet}>
<View style={styles.handle} />
<Text style={styles.title}>Share</Text>
<View style={styles.options}>
<ShareOption
icon="π€"
label="Share"
onPress={handleNativeShare}
/>
<ShareOption
icon={copied ? "β" : "π"}
label={copied ? "Copied!" : "Copy Link"}
onPress={handleCopyLink}
/>
<ShareOption
icon="π¬"
label="WhatsApp"
onPress={handleWhatsApp}
/>
<ShareOption
icon="π¦"
label="Twitter"
onPress={handleTwitter}
/>
</View>
<Pressable style={styles.cancelButton} onPress={onClose}>
<Text style={styles.cancelText}>Cancel</Text>
</Pressable>
</View>
</Pressable>
</Modal>
);
}
function ShareOption({ icon, label, onPress }) {
return (
<Pressable
style={({ pressed }) => [
styles.option,
pressed && styles.optionPressed,
]}
onPress={onPress}
>
<Text style={styles.optionIcon}>{icon}</Text>
<Text style={styles.optionLabel}>{label}</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
overlay: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.4)',
justifyContent: 'flex-end',
},
sheet: {
backgroundColor: 'white',
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
padding: 20,
paddingBottom: 40,
},
handle: {
width: 40,
height: 4,
backgroundColor: '#ddd',
borderRadius: 2,
alignSelf: 'center',
marginBottom: 20,
},
title: {
fontSize: 18,
fontWeight: '600',
textAlign: 'center',
marginBottom: 20,
},
options: {
flexDirection: 'row',
justifyContent: 'space-around',
marginBottom: 20,
},
option: {
alignItems: 'center',
padding: 12,
},
optionPressed: {
opacity: 0.6,
},
optionIcon: {
fontSize: 32,
marginBottom: 8,
},
optionLabel: {
fontSize: 12,
color: '#666',
},
cancelButton: {
padding: 16,
alignItems: 'center',
},
cancelText: {
fontSize: 16,
color: '#007AFF',
},
});
Exercise 2: Open in Maps with Options
Create a location card that offers multiple map options when tapped.
Requirements:
- Display location name and address
- Offer Apple Maps, Google Maps options
- Get directions option
- Handle apps not being installed
Show Solution
import React, { useState } from 'react';
import { View, Text, Pressable, StyleSheet, ActionSheetIOS, Platform, Alert } from 'react-native';
import * as Linking from 'expo-linking';
interface Location {
name: string;
address: string;
latitude: number;
longitude: number;
}
export function LocationCard({ location }: { location: Location }) {
const { name, address, latitude, longitude } = location;
const openAppleMaps = async (directions = false) => {
const url = directions
? `maps://app?daddr=${latitude},${longitude}`
: `maps://app?ll=${latitude},${longitude}&q=${encodeURIComponent(name)}`;
await Linking.openURL(url);
};
const openGoogleMaps = async (directions = false) => {
const appUrl = directions
? `comgooglemaps://?daddr=${latitude},${longitude}&directionsmode=driving`
: `comgooglemaps://?center=${latitude},${longitude}&q=${encodeURIComponent(name)}`;
const webUrl = directions
? `https://www.google.com/maps/dir/?api=1&destination=${latitude},${longitude}`
: `https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}`;
const canOpen = await Linking.canOpenURL(appUrl);
await Linking.openURL(canOpen ? appUrl : webUrl);
};
const showOptions = () => {
if (Platform.OS === 'ios') {
ActionSheetIOS.showActionSheetWithOptions(
{
options: [
'Cancel',
'Open in Apple Maps',
'Open in Google Maps',
'Get Directions (Apple Maps)',
'Get Directions (Google Maps)',
],
cancelButtonIndex: 0,
},
async (buttonIndex) => {
switch (buttonIndex) {
case 1:
await openAppleMaps(false);
break;
case 2:
await openGoogleMaps(false);
break;
case 3:
await openAppleMaps(true);
break;
case 4:
await openGoogleMaps(true);
break;
}
}
);
} else {
// Android - use Alert with buttons
Alert.alert(
'Open Location',
'Choose an option',
[
{ text: 'Cancel', style: 'cancel' },
{ text: 'Google Maps', onPress: () => openGoogleMaps(false) },
{ text: 'Get Directions', onPress: () => openGoogleMaps(true) },
]
);
}
};
return (
<Pressable
style={({ pressed }) => [
styles.card,
pressed && styles.cardPressed,
]}
onPress={showOptions}
>
<Text style={styles.icon}>π</Text>
<View style={styles.content}>
<Text style={styles.name}>{name}</Text>
<Text style={styles.address}>{address}</Text>
<Text style={styles.hint}>Tap for directions</Text>
</View>
<Text style={styles.arrow}>βΊ</Text>
</Pressable>
);
}
const styles = StyleSheet.create({
card: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
backgroundColor: '#fff',
borderRadius: 12,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
cardPressed: {
opacity: 0.8,
},
icon: {
fontSize: 28,
marginRight: 12,
},
content: {
flex: 1,
},
name: {
fontSize: 16,
fontWeight: '600',
marginBottom: 4,
},
address: {
fontSize: 14,
color: '#666',
marginBottom: 4,
},
hint: {
fontSize: 12,
color: '#007AFF',
},
arrow: {
fontSize: 24,
color: '#ccc',
},
});
Summary
Sharing and linking are essential features that connect your app to the broader mobile ecosystem, enabling users to share content and interact with other apps seamlessly.
π― Key Takeaways
- expo-sharing: Share files through the native share sheet
- React Native Share: Share text and URLs without files
- expo-linking: Open URLs, apps, and handle deep links
- URL schemes: tel:, mailto:, sms:, and app-specific schemes
- expo-clipboard: Copy and paste text and images
- Always check availability: Use canOpenURL before opening app URLs
- Provide fallbacks: Open web URLs when apps aren't installed
- iOS requires LSApplicationQueriesSchemes: Declare schemes in app.json
In the next lesson, we'll explore the file systemβreading, writing, and managing files on the device.