---
title: Take a screenshot
description: In this tutorial, learn how to capture a screenshot using a third-party library and Expo Media Library.
hasVideoLink: true
---
In this chapter, we'll learn how to take a screenshot using a third-party library and save it on the device's media library. We'll use [`react-native-view-shot`](https://github.com/gre/react-native-view-shot) to take a screenshot and [`expo-media-library`](/versions/latest/sdk/media-library/) to save an image on device's media library.
> **info** So far, we have used third-party libraries, such as `react-native-gesture-handler`, `react-native-reanimated`. We can find hundreds of other third-party libraries on [React Native Directory](https://reactnative.directory/) depending on a use case.
---
## Install libraries
To install `react-native-view-shot` and `expo-media-library`, run the following commands:
## Prompt for permissions
An app that requires sensitive information, such as accessing a device's media library, has to prompt permission to allow or deny access. Using `usePermissions()` hook from `expo-media-library`, we can use the permission `status` and `requestPermission()` method to ask for access.
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. After getting the access, the value of the `status` changes to `granted`.
Add the following code snippet inside the **app/(tabs)/index.tsx**:
{/* prettier-ignore */}
```tsx app/(tabs)/index.tsx
/* @tutinfo Import <CODE>expo-media-library</CODE>. */import * as MediaLibrary from 'expo-media-library';/* @end */
// ...rest of the code remains same
/* @tutinfo Add this statement to import the permissions status and <CODE>requestPermission()</CODE> method from the hook. */const [status, requestPermission] = MediaLibrary.usePermissions();/* @end */
// ...rest of the code remains same
/* @tutinfo 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. */
if (status === null) {
requestPermission();
}
/* @end */
// ...rest of the code remains same
}
```
## Create a ref to save the current view
We'll use `react-native-view-shot` to allow the user to take a screenshot within the app. This library captures the screenshot of a `<View>` as an image using the `captureRef()` method. It returns the URI of the captured screenshot image file.
1. Import `captureRef` from `react-native-view-shot` and `useRef` from React.
2. Create an `imageRef` reference variable to store the reference of the screenshot image captured.
3. Wrap the `<ImageViewer>` and `<EmojiSticker>` components inside a `<View>` and then pass the reference variable to it.
{/* prettier-ignore */}
```tsx app/(tabs)/index.tsx|collapseHeight=440
/* @tutinfo Import <CODE>useRef</CODE> hook from <CODE>react</CODE>. */import { useState, useRef } from 'react';/* @end */
/* @tutinfo Import <CODE>captureRef</CODE> from <CODE>react-native-view-shot</CODE>. */import { captureRef } from 'react-native-view-shot';/* @end */
/* @tutinfo Create an imageRef variable. */ const imageRef = useRef<View>(null);/* @end */
// ...rest of the code remains same
return (
<GestureHandlerRootView style={styles.container}>
<View style={styles.imageContainer}>
/* @tutinfo Add a View component to wrap the ImageViewer and EmojiSticker inside it. */<View ref={imageRef} collapsable={false}>/* @end */
{pickedEmoji && }
/* @tutinfo */</View>/* @end */
</View>
{/* ...rest of the code remains same */}
</GestureHandlerRootView>
);
}
```
In the above snippet, the `collapsable` prop is set to `false`. This allows the `<View>` component to screenshot only of the background image and emoji sticker.
## Capture a screenshot and save it
We can capture a screenshot of the view by calling the `captureRef()` method from `react-native-view-shot` inside the `onSaveImageAsync()` function. It accepts an optional argument where we can pass the `width` and `height` of the screenshot capturing area. We 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).
The `captureRef()` method also returns a promise that fulfills with the screenshot's URI. We will pass this URI as a parameter to [`MediaLibrary.saveToLibraryAsync()`](/versions/latest/sdk/media-library/#medialibrarysavetolibraryasynclocaluri) and save the screenshot to the device's media library.
Inside **app/(tabs)/index.tsx**, update the `onSaveImageAsync()` function with the following code:
{/* prettier-ignore */}
```tsx app/(tabs)/index.tsx|collapseHeight=440
const PlaceholderImage = require('@/assets/images/background-image.png');
const [selectedImage, setSelectedImage] = useState<string | undefined>(undefined);
const [showAppOptions, setShowAppOptions] = useState<boolean>(false);
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
const [pickedEmoji, setPickedEmoji] = useState<ImageSourcePropType | undefined>(undefined);
const [status, requestPermission] = MediaLibrary.usePermissions();
const imageRef = useRef<View>(null);
if (status === null) {
requestPermission();
}
const pickImageAsync = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
allowsEditing: true,
quality: 1,
});
if (!result.canceled) {
setSelectedImage(result.assets[0].uri);
setShowAppOptions(true);
} else {
alert('You did not select any image.');
}
};
const onReset = () => {
setShowAppOptions(false);
};
const onAddSticker = () => {
setIsModalVisible(true);
};
const onModalClose = () => {
setIsModalVisible(false);
};
/* @tutinfo Replace the comment with the code to capture the screenshot and save the image. */
const onSaveImageAsync = async () => {
try {
const localUri = await captureRef(imageRef, {
height: 440,
quality: 1,
});
await MediaLibrary.saveToLibraryAsync(localUri);
if (localUri) {
alert('Saved!');
}
} catch (e) {
console.log(e);
}
};
/* @end */
return (
<GestureHandlerRootView style={styles.container}>
<View style={styles.imageContainer}>
<View ref={imageRef} collapsable={false}>
{pickedEmoji && }
</View>
</View>
{showAppOptions ? (
<View style={styles.optionsContainer}>
<View style={styles.optionsRow}>
</View>
</View>
) : (
<View style={styles.footerContainer}>
<Button label="Use this photo" onPress={() => setShowAppOptions(true)} />
</View>
)}
<EmojiPicker isVisible={isModalVisible} onClose={onModalClose}>
</EmojiPicker>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#25292e',
alignItems: 'center',
},
imageContainer: {
flex: 1,
},
footerContainer: {
flex: 1 / 3,
alignItems: 'center',
},
optionsContainer: {
position: 'absolute',
bottom: 80,
},
optionsRow: {
alignItems: 'center',
flexDirection: 'row',
},
});
```
Now, choose a photo and add a sticker in the app. Then tap the "Save" button. We should see the following result on Android and iOS:
## Summary
<ProgressTracker
currentChapterIndex={6}
name="GET_STARTED"
summary={
<>
We've successfully used react-native-view-shot and expo-media-library to capture a
screenshot and save it on the device's library.
</>
}
nextChapterDescription="In the next chapter, let's learn how to handle the differences between mobile and web
platforms to implement the same functionality on web."
nextChapterTitle="Handle platform differences"
nextChapterLink="/tutorial/platform-differences"
/>