xref: /expo/docs/pages/tutorial/screenshot.mdx (revision 9d7b0c19)
1---
2title: Take a screenshot
3sidebar_title: Take screenshot
4---
5
6import { SnackInline, Terminal } from '~/ui/components/Snippet';
7import Video from '~/components/plugins/Video';
8import { A } from '~/ui/components/Text';
9import { Step } from '~/ui/components/Step';
10import { BoxLink } from '~/ui/components/BoxLink';
11import { BookOpen02Icon } from '@expo/styleguide-icons';
12
13In this chapter, we will learn how to take a screenshot using a third-party library and save it on the device's media library.
14We'll use the following libraries [`react-native-view-shot`](https://github.com/gre/react-native-view-shot) that allows taking a screenshot,
15and <A href="/versions/latest/sdk/media-library/" openInNewTab>`expo-media-library`</A> that allows accessing a device's media library to save an image.
16
17> So far, we have been using some third-party libraries such as `react-native-gesture-handler`, `react-native-reanimated`, and now `react-native-view-shot`.
18> We can find hundreds of other third-party libraries on [React Native Directory](https://reactnative.directory/).
19
20<Step label="1">
21
22## Install libraries
23
24To install both libraries, run the following commands:
25
26<Terminal cmd={['$ npx expo install react-native-view-shot expo-media-library']} />
27
28</Step>
29
30<Step label="2">
31
32## Prompt for permissions
33
34When creating an app that requires access to potentially sensitive information, such as access to the media library, we must first request the user's permission.
35
36`expo-media-library` provides a `usePermissions()` hook that gives the permission `status`, and a `requestPermission()` method to ask for access to the media library when permission is not granted.
37
38Initially, when the app loads for the first time and the permission status is neither granted nor denied, the value of the `status` is `null`. When asked for permission, a user can either grant the permission or deny it. We can add a condition to check if it is `null`, and if it is, trigger the `requestPermission()` method.
39
40Add the following code snippet inside the `<App>` component:
41
42{/* prettier-ignore */}
43```jsx App.js
44/* @info Import expo-media-library. */import * as MediaLibrary from 'expo-media-library';/* @end */
45
46// ...rest of the code remains same
47
48export default function App() {
49  /* @info Add this statement to import the permissions status and requestPermission() method from the hook. */const [status, requestPermission] = MediaLibrary.usePermissions();/* @end */
50  // ...rest of the code remains same
51
52  /* @info Add an if statement to check the status of permission. The requestPermission() method will trigger a dialog box for the user to grant or deny the permission. */
53  if (status === null) {
54    requestPermission();/* @end */
55  }
56
57  // ...rest of the code remains same
58}
59```
60
61Once permission is given, the value of the `status` changes to `granted`.
62
63</Step>
64
65<Step label="3">
66
67## Picking a library to take screenshots
68
69To allow the user to take a screenshot within the app, we'll use [`react-native-view-shot`](https://github.com/gre/react-native-view-shot). It allows capturing a `<View>` as an image.
70
71Let's import it into **App.js** file:
72
73```jsx App.js
74import { captureRef } from 'react-native-view-shot';
75```
76
77</Step>
78
79<Step label="4">
80
81## Create a ref to save the current view
82
83The `react-native-view-shot` library provides a method called `captureRef()` that captures a screenshot of a `<View>` in the app and returns the URI of the screenshot image file.
84
85To capture a `<View>`, wrap the `<ImageViewer>` and `<EmojiSticker>` components inside a `<View>` and then pass a reference to it. Using the `useRef()` hook from React, let's create an `imageRef` variable inside `<App>`.
86
87{/* prettier-ignore */}
88```jsx App.js
89import { useState, /* @info Import the useRef hook from React. */useRef/* @end */ } from 'react';
90
91export default function App() {
92  /* @info Create an imageRef variable. */ const imageRef = useRef();/* @end */
93
94  // ...rest of the code remains same
95
96  return (
97    <GestureHandlerRootView style={styles.container}>
98      <View style={styles.imageContainer}>
99        /* @info Add a View component to wrap the ImageViewer and EmojiSticker inside it. */<View ref={imageRef} collapsable={false}>/* @end */
100          <ImageViewer placeholderImageSource={PlaceholderImage} selectedImage={selectedImage} />
101          {pickedEmoji !== null ? (
102            <EmojiSticker imageSize={40} stickerSource={pickedEmoji} />
103          ) : null}
104        /* @info */</View>/* @end */
105      </View>
106      /* ...rest of the code remains same */
107    </GestureHandlerRootView>
108  );
109}
110```
111
112The `collapsable` prop is set to `false` in the above snippet because this `<View>` component is used to take a screenshot of the background image and the emoji sticker.
113The rest of the contents of the app screen (such as buttons) are not part of the screenshot.
114
115</Step>
116
117<Step label="5">
118
119## Capture a screenshot and save it
120
121Now we can capture a screenshot of the view by calling the `captureRef()` method from `react-native-view-shot` inside the `onSaveImageAsync()` function.
122`captureRef()` accepts an optional argument where we can pass the `width` and `height` of the area we'd like to capture a screenshot for.
123We can read more about available options in [the library's documentation](https://github.com/gre/react-native-view-shot#capturerefview-options-lower-level-imperative-api).
124
125The `captureRef()` method returns a promise that fulfills with the URI of the captured screenshot.
126We will pass this URI as a parameter to <A href="/versions/latest/sdk/media-library/#medialibrarysavetolibraryasynclocaluri" openInNewTab>`MediaLibrary.saveToLibraryAsync()`</A>,
127which will save the screenshot to the device's media library.
128
129Update the `onSaveImageAsync()` function with the following code:
130
131<SnackInline
132label="Take a screenshot"
133templateId="tutorial/07-screenshot/App"
134dependencies={['expo-image-picker', '@expo/vector-icons/FontAwesome', '@expo/vector-icons', 'expo-status-bar', '@expo/vector-icons/MaterialIcons', 'react-native-gesture-handler', 'react-native-reanimated', 'react-native-view-shot', 'expo-media-library']}
135files={{
136  'assets/images/background-image.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/503001f14bb7b8fe48a4e318ad07e910',
137  'assets/images/emoji1.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/be9751678c0b3f9c6bf55f60de815d30',
138  'assets/images/emoji2.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/7c0d14b79e134d528c5e0801699d6ccf',
139  'assets/images/emoji3.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/d713e2de164764c2ab3db0ab4e40c577',
140  'assets/images/emoji4.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/ac2163b98a973cb50bfb716cc4438f9a',
141  'assets/images/emoji5.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/9cc0e2ff664bae3af766b9750331c3ad',
142  'assets/images/emoji6.png': 'https://snack-code-uploads.s3.us-west-1.amazonaws.com/~asset/ce614cf0928157b3f7daa3cb8e7bd486',
143  'components/ImageViewer.js': 'tutorial/02-image-picker/ImageViewer.js',
144  'components/Button.js': 'tutorial/03-button-options/Button.js',
145  'components/CircleButton.js': 'tutorial/03-button-options/CircleButton.js',
146  'components/IconButton.js': 'tutorial/03-button-options/IconButton.js',
147  'components/EmojiPicker.js': 'tutorial/04-modal/EmojiPicker.js',
148  'components/EmojiList.js': 'tutorial/05-emoji-list/EmojiList.js',
149  'components/EmojiSticker.js': 'tutorial/06-gestures/CompleteEmojiSticker.js',
150}}>
151
152{/* prettier-ignore */}
153```jsx
154export default function App() {
155  /* @info Replace the comment with the code to capture the screenshot and save the image. */
156  const onSaveImageAsync = async () => {
157    try {
158      const localUri = await captureRef(imageRef, {
159        height: 440,
160        quality: 1,
161      });
162
163      await MediaLibrary.saveToLibraryAsync(localUri);
164      if (localUri) {
165        alert("Saved!");
166      }
167    } catch (e) {
168      console.log(e);
169    }
170  };
171  /* @end */
172  // ...rest of the code remains same
173}
174```
175
176</SnackInline>
177
178Now, choose a photo and add a sticker. Then tap the “Save” button. We should see the following result:
179
180<Video file="tutorial/saving-screenshot.mp4" />
181
182</Step>
183
184## Next step
185
186The `react-native-view-shot` and `expo-media-library` work only on Android and iOS, however, we'd like our app to work on the web as well.
187
188<BoxLink
189  title="Handle platform differences"
190  Icon={BookOpen02Icon}
191  description="In the next chapter, let's learn how to handle the differences between mobile and web platforms."
192  href="/tutorial/platform-differences"
193/>
194