1---
2title: Handling platform differences
3---
4
5import { SnackInline} from '~/ui/components/Snippet';
6import Video from '~/components/plugins/Video';
7import { Terminal } from '~/ui/components/Snippet';
8
9In the perfect world we want to write code to perform our task just once and have it run the same on every platform. Even on the web, where this is an explicit design goal, it's often necessary to consider differences between web browsers.
10
11Expo tools try to handle smoothing over these differences between iOS, Android, web (and different web browsers) for you, but it isn't always possible. There are two important differences between how the iOS and Android share APIs work and how the Web Share API works.
12
131. It isn't always available on web.
142. We can't share local file URIs on web.
15
16> It's actually technically possible in the latest versions of Chrome for Android to share files, but it's very new and not yet supported by `expo-sharing`, so we will ignore that here for now.
17
18## A workaround for sharing on web
19
20We are going to work around spotty support for the Web Share API and the lack of support for sharing files from our device by uploading the file to some web service and then letting the users copy the URL.
21
22To install a library to handle uploading the file for us run:
23
24<Terminal cmd={['$ npx expo install anonymous-files']} />
25
26Then make these changes to your app:
27
28<SnackInline label="Sharing web workaround" templateId="tutorial/sharing-web-workaround" dependencies={['expo-image-picker', 'expo-sharing', 'anonymous-files']} defaultPlatform="web">
29
30{/* prettier-ignore */}
31```js
32import React from 'react';
33import { Image, /* @info Import Platform, you can order imports however you like, here we did it alphabetically. */Platform,/* @end */ StyleSheet, Text, TouchableOpacity, View } from 'react-native';
34import * as ImagePicker from 'expo-image-picker';
35import * as Sharing from 'expo-sharing';
36/* @info Import our newly installed package, it exports a function called uploadToAnonymousFilesAsync */
37import uploadToAnonymousFilesAsync from 'anonymous-files'; /* @end */
38
39
40export default function App() {
41  const [selectedImage, setSelectedImage] = React.useState(null);
42
43  let openImagePickerAsync = async () => {
44    let permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
45
46    if (permissionResult.granted === false) {
47      alert('Permission to access camera roll is required!');
48      return;
49    }
50
51    let pickerResult = await ImagePicker.launchImageLibraryAsync();
52    if (pickerResult.cancelled === true) {
53      return;
54    }
55
56    /* @info When the platform OS (operating system) is web, upload the file and set the remoteUri */
57    if (Platform.OS === 'web') {
58      let remoteUri = await uploadToAnonymousFilesAsync(pickerResult.uri);
59      setSelectedImage({ localUri: pickerResult.uri, remoteUri });
60    } else {
61      setSelectedImage({ localUri: pickerResult.uri, remoteUri: null });
62    } /* @end */
63
64  };
65
66  let openShareDialogAsync = async () => {
67    if (!(await Sharing.isAvailableAsync())) {
68      /* @info Give the user a link to the website that the file is uploaded to */
69      alert(`The image is available for sharing at: ${selectedImage.remoteUri}`);/* @end */
70
71      return;
72    }
73
74    Sharing.shareAsync(/* @info Use the remoteUri if set, otherwise the localUri */selectedImage.remoteUri || selectedImage.localUri/* @end */);
75  };
76
77  /* the rest of the app is unchanged */
78}
79```
80
81</SnackInline>
82
83## Up next
84
85Our app does everything we set out for it to do, so it's time to shift our focus towards the purely aesthetic. In the next step of this tutorial we will [customize our app icon and splash screen](../tutorial/configuration.mdx).
86