Skip to main content
Glama
screenshot.md8.31 kB
--- 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" />

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jaksm/expo-docs-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server