Getting Started with the RICOH Theta-Client-SDK using React Native and React Native Paper
This guide aims to provide a comprehensive introduction to using the RICOH theta-client-SDK in a React Native application, with the help of React Native Paper for UI development. Whether you’re a seasoned developer or new to mobile development, this guide will walk you through the essential steps to get started.
Before we get into the nitty-gritty of this guide, let’s quickly review the three major components of this topic: React Native, React Native Paper, and Theta-Client SDK.
What is React Native?
React Native is an open-source framework developed by Facebook for building mobile applications using JavaScript and React. It allows developers to create natively rendered mobile apps for iOS and Android from a single codebase, making development more efficient and streamlined. Leveraging the power of React’s component-based architecture and state management, React Native offers features such as fast refresh, access to native functionalities through third-party libraries, and a vast ecosystem of pre-built components and libraries.
What is React Native Paper?
Based on Material Design principles, React Native Paper is a top-notch, standard-compliant toolkit that offers a range of modifiable, themable UI components for React Native applications. Its extensive component library, integrated theming support, and adherence to Material Design principles make it simple for developers to create aesthetically pleasing and consistent user experiences.
What is RICOH Theta-Client SDK
The theta-client SDK is a library designed to facilitate the control and interaction with RICOH THETA cameras using the RICOH THETA API v2.1. This SDK enables developers to build applications that can perform a variety of operations with RICOH THETA cameras, such as taking photos and videos, acquiring media files, and managing camera settings and statuses.
Prerequisites
- Fundamental knowledge of JavaScript/TypeScript
- Basic understanding of React Native
- Using Android Studio for Development Environment Setup
This guide is structured into three main sections:
-
Cloning the theta-client Repository: Step-by-step instructions to clone the theta-client SDK to your local machine.
-
Setting up the Development Environment: Setup process for preparing your development environment using Android Studio.
-
Modifying and Enhancing the React Native Demo with React Native Paper: Instructions on integrating React Native Paper to enhance the UI of your React Native demo application.
Cloning the theta-client Repository to Your Local Machine
Step 1: Navigate to the theta-client Repository
- Open your web browser and go to the theta-client repository on GitHub.
Step 2: Copy the Clone URL
- Click on the “Code” button and copy the clone URL.
Step 3: Clone the Repository
- Open Git Bash or your terminal on your desktop.
- Navigate to the directory where you want the project to be stored.
- Use the following command to clone the repository:
$ git clone https://github.com/ricohapi/theta-client.git
Step 4: Open the Project in Your Text Editor
- Open the cloned project in your text editor, preferably Visual Studio Code.
The way the build directory structure looks:
For the sake of this build, our real business is in the “demo-react-native” directory, which is in the “demos” directory.
Note: By default, we need a RICOH camera to test the build, but the team that built this SDK already created a solution for cases where the camera is absent.
Step 5: Configure the build to be tested even without the RICOH camera.
- Since the essence of this build is to control the THETA camera and we do not have access to the actual THETA camera, we’ll use the fake-theta API.
- Replace the current API endpoint in the
MainMenu.tsx
with the fake API endpoint: https://fake-theta.vercel.app.
From this:
// ... preceding code
const initTheta = async () => {
const endpoint = 'http://192.168.1.1';
const config = {
// clientMode: { // Client mode authentication settings
// username: 'THETAXX12345678',
// password: '12345678',
// }
};
await initialize(endpoint, config);
console.log('initialized.');
};
// ... following code
To this:
// ... preceding code
const initTheta = async () => {
const endpoint = 'https://fake-theta.vercel.app';
const config = {
// clientMode: { // Client mode authentication settings
// username: 'THETAXX12345678',
// password: '12345678',
// }
};
await initialize(endpoint, config);
console.log('initialized.');
};
// ... following code
Following the steps above, you will successfully clone the project and set up the build using the fake API endpoint.
Setting up the Development Environment
In order to speed things up, I won’t dwell much on the details of this since a section of React Native Documentation explicitly covers it.
First, follow the official React Native documentation to set up your development environment: React Native Setup Guide.
While setting up my Virtual Device using Android Studio, I encountered some setbacks, specifically in installing HASM, which I will explain in this section.
As an additional note, I chose Android Studio for my setup since I’m using a Windows Operating System. In the last section of the “setting up environment” from the official React Native Docs, you are required to create a virtual device which you need to install HASM by following the instructions here. But HASM won’t install on your local device unless you’ve enabled some permissions.
Note: To successfully get HASM to install without any fail prompt, you need to enable Virtualization Technology (VT-x). Here are the required steps:
- Enable Virtualization in BIOS:
- For HP Systems (might also apply to other system make, else look it up on Google):
- Restart your computer.
- Press F10 during boot to enter the BIOS setup.
- Navigate to the Advanced Settings.
- Enable Virtualization Technology (VT-x).
- Save and exit the BIOS setup.
- Your system will restart.
- Enable Hyper-V and Windows Virtualization:
- Open the Control Panel.
- Navigate to Programs > Turn Windows features on or off.
- Check the boxes for Hyper-V and Windows Hypervisor Platform.
- Click OK and restart your computer if prompted.
If you follow the steps for setting up the environment and also got HASM successfully installed then you can go ahead to create your Virtual device manager - you can follow the steps here for that.
Launch your Android Studio and then open up the virtual device you installed.
When you click on the play button, it launches the Virtual Device.
Modifying the build using React Native Paper
When you open certain files in the project, you might notice red lines indicating errors. These errors occur because the required modules have not yet been installed. To resolve this, you need to install all the necessary packages. Follow these steps to ensure a smooth setup:
- Ensure you are in the Correct Directory:
- Navigate to the
demos-react-native
directory where the demo project is located.
$ cd demos/demo-react-native
- Navigate to the
-
Install the Required Packages:
- Run the following command to install all the dependencies needed for the demo to work perfectly:
$ yarn install
-
Install React Native Paper:
- Add React Native Paper to your project by running:
$ yarn add react-native-paper
-
Install Required Peer Dependencies:
- React Native Paper requires certain peer dependencies. Install them using:
$ yarn add react-native-vector-icons react-native-safe-area-context
Now that we’ve installed the necessary React Native Paper package, let’s modify the build using it.
Integrating React Native Paper
Since App.tsx
is the root file in this build, we can wrap our application with the PaperProvider from React Native Paper. The PaperProvider component supplies the theme to all the components within the framework and functions as a portal for components that need to be rendered at the top level.
Here’s how you can integrate PaperProvider
into your App.tsx
:
-
Import
PaperProvider
:- First, import
PaperProvider
at the top of yourApp.tsx
file.
import React from 'react'; import { Provider as PaperProvider } from 'react-native-paper'; import { NavigationContainer } from '@react-navigation/native'; import { createStackNavigator } from '@react-navigation/stack'; import MainMenu from './MainMenu'; import TakePhoto from './TakePhoto'; import ListPhotos from './ListPhotos'; import PhotoSphere from './PhotoSphere' // ... following code
- First, import
-
Wrap Your
App
withPaperProvider
:- Use PaperProvider to wrap the entire application inside the App function. This ensures that all components have access to the theme provided by React Native Paper.
// ... preceding code const App = () => { return ( <PaperProvider> <NavigationContainer> <Stack.Navigator screenOptions={screenOptions}> <Stack.Screen options={{title: 'Theta SDK Sample App'}} name="main" component={MainMenu} /> <Stack.Screen options={{title: 'Take Photo'}} name="take" component={TakePhoto} /> <Stack.Screen options={{title: 'List Photos'}} name="list" component={ListPhotos} /> <Stack.Screen options={{title: 'Sphere'}} name="sphere" component={PhotoSphere} /> </Stack.Navigator> </NavigationContainer> </PaperProvider> ); } // ... following code
This is what your App
function should look like after incorporating PaperProvider
. By doing this, you ensure that the React Native Paper components have the necessary theme context, enhancing your application’s overall styling and functionality.
Now, let’s run the build using the command below:
$ yarn android
Note: Make sure your Virtual Device is running!
When the build runs successfully, you get this:
And this will be what you get on your virtual device:
Using React Native Paper to Enhance the Demo Build
After installing the necessary packages and wrapping our components to utilize styles from React Native Paper, the next step is deciding how to enhance the current demo build.
Objective 1: Adding a PopOver to the Buttons
We aim to add a tooltip to the buttons on long press. We’ll use the Tooltip
component from React Native Paper. This enhancement will provide users with additional information when they long-press the buttons.
Here are the steps to implement Tooltips
-
Navigate to
MainMenu.tsx
inside thesrc
folder:- Open the
MainMenu.tsx
file in your code editor.
- Open the
-
Import the Tooltip Component:
- First, import the Tooltip component from React Native Paper at the top of the file.
$ import { Tooltip } from 'react-native-paper';
-
Wrap the Buttons (“Take a Photo” and “List Photos”) with Tooltip:
- Next, wrap the existing buttons with the
Tooltip
component and provide appropriate titles and delays.
// ... preceding code <Tooltip title="This button triggers the take photo component" enterTouchDelay={200} leaveTouchDelay={200}> <TouchableOpacity style={styles.buttonBack} onPress={goTake}> <Text style={styles.button}>Take a Photo</Text> </TouchableOpacity> </Tooltip> <View style={styles.spacer} /> <Tooltip title="This button lists the current photos" enterTouchDelay={200} leaveTouchDelay={200}> <TouchableOpacity style={styles.buttonBack} onPress={goList}> <Text style={styles.button}>List Photos</Text> </TouchableOpacity> </Tooltip> // ... following code
- Next, wrap the existing buttons with the
In the code snippet above, we imported the tooltip component and also used it to wrap the buttons to add a popover feature that gives a simple description of the buttons’ functions.
- Tooltip Component: The
Tooltip
component from React Native Paper provides a way to display additional information when a user long-presses a button. We have used thetitle
prop to specify the tooltip text andenterTouchDelay
andleaveTouchDelay
to control thetooltip
display timing. - Button Wrapping: Each
TouchableOpacity
button is wrapped inside aTooltip
component to enhance user interaction by providing context about the button’s function.
I believe your build is still running actively. If so, save the new changes and test the newly added popover feature.
Objective 2: Enhancing the First Page with an Illustration
To make the first page of our app more attractive, we will add an illustration from undraw.co. Follow these steps to integrate the SVG illustration into your React Native project.
Step 1: Downloading an Illustration
- Open your browser and go to undraw.co.
- Search for a camera illustration, edit the color (#6200ee) to tally with that of the buttons, and download the SVG version of the first illustration you see.
Edit color to tally with buttons snapshot
Download svg snapshot
- Save the SVG file in a temporary location on your computer; we’ll integrate it into the build later.
Step 2: Setting Up React Native to Use SVG Files
To use SVG files in React Native, follow these steps:
-
Install Required Packages:
- Run the following command to install
react-native-svg
andreact-native-svg-transformer
:
$ yarn add react-native-svg react-native-svg-transformer
- Run the following command to install
-
Configure Metro to Support SVG Files:
- Navigate to the
metro.config.js
file in your project and update it as follows:
/** * Metro configuration for React Native * https://github.com/facebook/react-native * * @format */ const { getDefaultConfig } = require("metro-config"); module.exports = (async () => { const { resolver: { sourceExts, assetExts }, } = await getDefaultConfig(); return { transformer: { getTransformOptions: async () => ({ transform: { experimentalImportSupport: false, inlineRequires: true, }, }), babelTransformerPath: require.resolve('react-native-svg-transformer'), }, resolver: { assetExts: assetExts.filter(ext => ext !== "svg"), sourceExts: [...sourceExts, "jsx", "js", "svg", "ts", "tsx"], }, }; })();
- Navigate to the
-
Add TypeScript Declarations for SVG:
- Create a folder named
types
inside thedemo-react-native
directory. - Inside the
types
folder, create a file nameddeclarations.d.ts
and add the following code:
declare module "*.svg" { import React from "react"; import { SvgProps } from "react-native-svg"; const content: React.FC<SvgProps>; export default content; }
- Create a folder named
-
Organize SVG Assets:
- Create an assets folder inside the
demo-react-native
directory. - Create another folder named
svgImages
inside the assets folder. - Move your downloaded SVG file into the
svgImages
folder and rename it tocam.svg
. - Inside the
svgImages
folder, create anindex.ts
file with the following code:
import Cam from './cam.svg'; export { Cam };
- In the
assets
folder, create anindex.ts
file and add this line of code:
export * from './svgImages';
- Create an assets folder inside the
Step 3: Integrating the SVG into MainMenu.tsx
-
Import the SVG:
- In
MainMenu.tsx
, import theCam
SVG:
import { Cam } from '../assets';
- In
-
Render the SVG:
- Modify the return statement of your
MainMenu
component to include the SVG:
// ... preceding code return ( <SafeAreaView style={styles.container}> <StatusBar barStyle="light-content" /> <Cam width={350} height={350} /> <Tooltip title="This button triggers the take photo component" enterTouchDelay={200} leaveTouchDelay={200}> <TouchableOpacity style={styles.buttonBack} onPress={goTake}> <Text style={styles.button}>Take a Photo</Text> </TouchableOpacity> </Tooltip> <View style={styles.spacer} /> <Tooltip title="This button lists the current photos" enterTouchDelay={200} leaveTouchDelay={200}> <TouchableOpacity style={styles.buttonBack} onPress={goList}> <Text style={styles.button}>List Photos</Text> </TouchableOpacity> </Tooltip> </SafeAreaView> ); // ... following code
- Modify the return statement of your
Save the new changes and check out the current view of the build on the virtual device.
Objective 3: Improving Button Layout
In this section, we will adjust the layout of the buttons (Take a Photo and List Photos) to be in a row. Follow the steps below to implement these changes.
Step 1: Modifying Styles
-
Update the Container Style:
- Open the
Styles.tsx
file inside thesrc
folder. - Modify the container style to the following:
container: { flex: 1, justifyContent: 'flex-start', paddingTop: 100, alignItems: 'center', backgroundColor: 'white', },
- Open the
-
Add a New Style for Button Wrapper:
- Add a new style to handle the button layout:
buttonWrapper: { marginTop: 20, flexDirection: 'row', gap: 15, },
Step 2: Implementing the New Button Wrapper Styles in MainMenu.tsx
.
- Wrap the Buttons with a View Component:
- Navigate to MainMenu.tsx.
- Wrap the existing buttons with a View component and apply the buttonWrapper style to it.
// ... preceding code <View style={styles.buttonWrapper}> <Tooltip title="This button triggers the take photo component" enterTouchDelay={200} leaveTouchDelay={200}> <TouchableOpacity style={styles.buttonBack} onPress={goTake}> <Text style={styles.button}>Take a Photo</Text> </TouchableOpacity> </Tooltip> <View style={styles.spacer} /> <Tooltip title="This button lists the current photos" enterTouchDelay={200} leaveTouchDelay={200}> <TouchableOpacity style={styles.buttonBack} onPress={goList}> <Text style={styles.button}>List Photos</Text> </TouchableOpacity> </Tooltip> </View> // ... following code
An overview of what the steps in this section did:
- Container Style: The
container
style is updated to position the content appropriately, adding padding at the top and aligning items to the center. - Button Wrapper Style: A new
buttonWrapper
style is added to arrange the buttons in a row with a gap between them.
In addition, the buttons are now wrapped inside a View
component with the buttonWrapper
style applied, aligning them horizontally.
Save the newly added changes and preview the build.
Next, we want to make the photos in the ListPhotos
component into an individual card-like structure. But before we do this, click on the “List Photos” button, which will take you to the “List Photos” page, and you will notice that the list didn’t start from the top. This is due to modifying the container
styles, which the ListPhotos
component is also using.
To fix this, navigate to the Styles.tsx
and create a new style named listContainer
:
listContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
},
Next, apply this newly created style to the ListPhotos
component.
- Open the
ListPhotos.tsx
file. - Change the style applied to the
SafeAreaView
container fromcontainer
to the newly createdlistContainer
.
<SafeAreaView style={styles.listContainer} edges={['left', 'right', 'bottom']}>
<StatusBar barStyle="light-content" />
<ScrollView
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}>
{items}
</ScrollView>
</SafeAreaView>
Objective 4: Enhance the Photo List with a Card-Like Structure and Shimmer Effect.
In this segment, we will enhance the ListPhotos
component by displaying each photo in a card-like structure using the Card
component from React Native Paper. Additionally, we’ll add a shimmer effect to improve the visual appeal.
Step 1: Import the needed Components
First, import the necessary components from React Native Paper:
import { Avatar, Button, Card, Text as Tex } from 'react-native-paper';
Note: To avoid conflicts between the
Text
component from React Native and the one from React Native Paper, we alias the latter asTex
.
Step 2: Update the Items Rendering
Next, update the items expression to use the card structure. Replace the current implementation with the following:
const LeftContent = props => <Avatar.Text size={40} label="IM" />;
const items = files.map(item => (
<TouchableOpacity
style={styles.fileItemBase}
key={item.name}
onPress={() => onSelect(item)}>
>
<Card>
<Card.Title
title="Photo"
subtitle={item.dateTimeZone}
left={LeftContent}
/>
<Card.Content>
<Tex variant="titleLarge">{item.name}</Tex>
<Tex variant="bodyMedium">{item?.imageDescription || 'Simple Image'}</Tex>
</Card.Content>
<Card.Cover source={{ uri: item.thumbnailUrl }} />
<Card.Actions>
<Button onPress={() => onSelect(item)}>View</Button>
</Card.Actions>
</Card>
</TouchableOpacity>
));
Step 3: Update the Button onPress
Event
Move the onPress
event from the TouchableOpacity
to the Button
(View) inside the card:
<Card.Actions>
<Button onPress={() => onSelect(item)}>View</Button>
</Card.Actions>
Step 4: Create Styles for the Card Component
Add the following styles to your Styles.tsx
file to enhance the card’s appearance:
scrollViewContent: {
alignItems: 'center',
},
cardWrapper: {
width: '95%',
marginVertical: 10,
},
card: {
padding: 10,
},
Step 5: Integrate the new Styles into the ListPhotos
Component.
We are adding two of the newly created styles—card
and cardWrapper
—to the Items function.
The TouchableOpacity style:
// replace style={styles.fileItemBase} with style={styles.cardWrapper}
// ... preceding code
<TouchableOpacity
style={styles.cardWrapper}
key={item.name}
>
// the code
</TouchableOpacity>
// ... following code
The Card component itself:
// ... preceding code
<Card style={styles.card}>
// code
</Card>
// ... following code
Set the contentContainerStyle
for the ScrollView
inside the SafeAreaView
component:
<ScrollView
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
} contentContainerStyle={styles.scrollViewContent}>
{ items }
</ScrollView>
Save all the changes and preview the build.
Adding a Shimmer Effect to Display a Skeleton UI While Loading Photos
When you click the “List Photos” button, it takes you to the Photos list page. However, there can be a delay before the photos load. To improve the user experience, we want to display a skeleton UI with a linear gradient animation to indicate that data is being loaded. This is better than leaving the screen blank while waiting for the images to load.
Step 1: Install the Required Package
First, we need to install the react-native-linear-gradient package
. Run the following command:
$ yarn add react-native-linear-gradient
Step 2: Create the Skeleton UI
We will create a skeleton UI that mimics the structure of the card component displayed in the Photos list page.
- Create a new file inside the
src
folder and name itSkeletonCardLoader.tsx
. - Inside
SkeletonCardLoader.tsx
, import the necessary modules and define the skeleton UI. Here, we will make use of theView
component and try as much to build up aCard-like
structure just like the one in theListPhotos
component.
Here is the finished code of the Skeleton UI, look through it, just to add, it is just a basic structure.
import React from 'react';
import { StyleSheet, View } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
const SkeletonCardLoader = () => {
return (
<View style={styles.cardWrapper}>
<View style={styles.card}>
{/* Header section */}
<View style={styles.header}>
<View style={styles.avatar} />
<View>
<View style={styles.title} />
<View style={styles.subtitle} />
</View>
</View>
<View style={{ marginTop: 10 }}>
<View style={styles.name} />
<View style={styles.description} />
</View>
<View style={styles.image} />
<View style={styles.actions} />
</View>
</View>
);
};
const styles = StyleSheet.create({
cardWrapper: {
marginVertical: 10,
},
card: {
height: 380,
backgroundColor: '#e0e0e0',
borderRadius: 10,
overflow: 'hidden',
position: 'relative',
padding: 10,
marginBottom: 10,
width: '95%',
},
header: {
flexDirection: 'row',
alignItems: 'center',
},
avatar: {
height: 60,
width: 60,
backgroundColor: '#c0c0c0',
margin: 10,
borderRadius: 30,
},
title: {
height: 20,
width: 160,
backgroundColor: '#c0c0c0',
borderRadius: 5,
marginBottom: 5,
},
subtitle: {
height: 15,
width: 200,
backgroundColor: '#c0c0c0',
borderRadius: 5,
marginBottom: 5,
},
name: {
height: 25,
width: 100,
backgroundColor: '#c0c0c0',
margin: 5,
borderRadius: 5,
},
description: {
height: 20,
width: 160,
backgroundColor: '#c0c0c0',
margin: 5,
borderRadius: 5,
},
image: {
height: 150,
width: '100%',
backgroundColor: '#c0c0c0',
margin: 5,
borderRadius: 5,
},
actions: {
height: 30,
width: 60,
backgroundColor: '#c0c0c0',
borderRadius: 15,
alignSelf: 'flex-end',
margin: 5,
},
});
export default SkeletonCardLoader;
Step 3: Integrate the Skeleton UI into the Photos List Page
We want to display the skeleton UI when the photos are still loading. This gives the user a visual indication that data is being fetched, preventing them from thinking the app is unresponsive.
- Import the
SkeletonCardLoader
component into yourListPhotos
component.
import SkeletonCardLoader from './SkeletonCardLoader';
- Use the
refreshing
state to determine if the photos are loaded. Ifrefreshing
istrue
, display theSkeletonCardLoader
; otherwise, display the actual photos.
Here’s how you can update your ListPhotos component to implement the logic above:
// ... preceding code
return (
<SafeAreaView style={styles.listContainer} edges={['left', 'right', 'bottom']}>
<StatusBar barStyle="light-content" />
<ScrollView
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
} contentContainerStyle={styles.scrollViewContent}>
{/* the logic */}
{
refreshing ? (<SkeletonCardLoader/>) : (items)
}
</ScrollView>
</SafeAreaView>
);
// ... following code
Conditional Rendering: In the ListPhotos
component, the refreshing
state determines whether to show the SkeletonCardLoader
or the actual list of items. When refreshing is true, the skeleton UI is displayed, indicating that data is being loaded.
Save up and preview the build.
Now that we’ve built the basic skeleton UI, the next step is to add a shimmer effect using an animated linear gradient. This effect gives users a loading indication by making it look like a light is sweeping across the elements.
Here is a Step-by-Step approach
- Import Necessary Libraries
First, import the necessary libraries for animation and gradient effects:
import React, { useEffect, useRef } from 'react';
import { StyleSheet, View, Animated } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
- Create an Animated Value
Create a reference to store the animated value which will drive the shimmer effect:
const animatedValue = useRef(new Animated.Value(0)).current;
- Set Up the Animation Sequence
Use theuseEffect
hook to set up the animation sequence. This sequence will continuously loop to create the shimmer effect:
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(animatedValue, {
toValue: 1,
duration: 1500,
useNativeDriver: true,
}),
Animated.timing(animatedValue, {
toValue: 0,
duration: 1500,
useNativeDriver: true,
}),
])
).start();
}, [animatedValue]);
- Interpolate the Animated Value
Interpolate the animated value to create a translation effect for the gradient:
const translateX = animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [-100, 100],
});
- Define Styles for the Gradient
Add styles for the gradient wrapper and the gradient itself:
const styles = StyleSheet.create({
// Other styles...
gradientWrapper: {
...StyleSheet.absoluteFillObject,
},
gradient: {
flex: 1,
width: '200%',
},
});
- Integrate the Gradient with Animation
Combine theAnimated.View
andLinearGradient
to create the shimmer effect:
<Animated.View
style={[
styles.gradientWrapper,
{
transform: [{ translateX }],
},
]}
>
<LinearGradient
colors={[
'rgba(255,255,255,0)',
'rgba(255,255,255,0.5)',
'rgba(255,255,255,0.6)',
'rgba(255,255,255,0)',
'rgba(255,255,255,0.5)',
]}
start={{ x: 0, y: 1 }}
end={{ x: 1, y: 1 }}
style={styles.gradient}
/>
</Animated.View>
- Duplicate the Skeleton UI
To fill the vertical height of the device, duplicate the skeleton UI elements. This example duplicates it four times, but you can adjust the number as needed:
return (
<View style={styles.cardWrapper}>
{[...Array(4)].map((_, index) => (
<View key={index} style={styles.card}>
<View style={styles.header}>
<View style={styles.avatar}/>
<View>
<View style={styles.title} />
<View style={styles.subtitle} />
</View>
</View>
<View style={{ marginTop: 10}}>
<View style={styles.name}/>
<View style={styles.description}/>
</View>
<View style={styles.image} />
<View style={styles.actions} />
<Animated.View
style={[
styles.gradientWrapper,
{
transform: [{ translateX }],
},
]}
>
<LinearGradient
colors={[
'rgba(255,255,255,0)',
'rgba(255,255,255,0.5)',
'rgba(255,255,255,0.6)',
'rgba(255,255,255,0)',
'rgba(255,255,255,0.5)',
]}
start={{ x: 0, y: 1 }}
end={{ x: 1, y: 1 }}
style={styles.gradient}
/>
</Animated.View>
</View>
))}
</View>
);
The overall code in the SkeletonCardLoader
component:
import React, { useEffect, useRef } from 'react';
import { StyleSheet, View, Animated } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
const SkeletonCardLoader = () => {
const animatedValue = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(animatedValue, {
toValue: 1,
duration: 1500,
useNativeDriver: true,
}),
Animated.timing(animatedValue, {
toValue: 0,
duration: 1500,
useNativeDriver: true,
}),
])
).start();
}, [animatedValue]);
const translateX = animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [-100, 100],
});
return (
<View style={styles.cardWrapper}>
{[...Array(4)].map((_, index) => (
<View key={index} style={styles.card}>
<View style={styles.header}>
<View style={styles.avatar}/>
<View>
<View style={styles.title} />
<View style={styles.subtitle} />
</View>
</View>
<View style={{ marginTop: 10}}>
<View style={styles.name}/>
<View style={styles.description}/>
</View>
<View style={styles.image} />
<View style={styles.actions} />
<Animated.View
style={[
styles.gradientWrapper,
{
transform: [{ translateX }],
},
]}
>
<LinearGradient
colors={[
'rgba(255,255,255,0)',
'rgba(255,255,255,0.5)',
'rgba(255,255,255,0.6)',
'rgba(255,255,255,0)',
'rgba(255,255,255,0.5)',
]}
start={{ x: 0, y: 1 }}
end={{ x: 1, y: 1 }}
style={styles.gradient}
/>
</Animated.View>
</View>
))}
</View>
);
};
const styles = StyleSheet.create({
cardWrapper: {
marginVertical: 10,
// width: '95%',
// alignItems: 'center',
},
card: {
height: 380,
backgroundColor: '#e0e0e0',
borderRadius: 10,
overflow: 'hidden',
position: 'relative',
padding: 10,
marginBottom: 10,
width: '95%'
},
header: {
flexDirection: 'row',
alignItems: 'center'
},
avatar: {
height: 60,
backgroundColor: '#c0c0c0',
width: 60,
margin: 10,
borderRadius: 50,
},
title: {
height: 20,
backgroundColor: '#c0c0c0',
width: 160,
borderRadius: 5,
marginBottom: 5,
},
subtitle: {
height: 15,
width: 200,
backgroundColor: '#c0c0c0',
marginBottom: 5,
borderRadius: 5,
},
name: {
height: 25,
backgroundColor: '#c0c0c0',
width: 100,
margin: 5,
borderRadius: 5,
},
description: {
height: 20,
backgroundColor: '#c0c0c0',
width: 160,
margin: 5,
borderRadius: 5,
},
image: {
height: 150,
backgroundColor: '#c0c0c0',
margin: 5,
borderRadius: 5,
},
actions: {
height: 30,
width: 60,
marginLeft: 285,
backgroundColor: '#c0c0c0',
margin: 5,
borderRadius: 15,
},
gradientWrapper: {
...StyleSheet.absoluteFillObject,
},
gradient: {
flex: 1,
width: '200%',
},
});
export default SkeletonCardLoader;
While this approach manually creates the shimmer effect, there is a Skeleton Placeholder
package that can simplify this process. However, it requires macOS for setup, which is why we opted for this manual implementation.
Conclusion
In this guide, we examined how to use the React Native Paper to add new styles and modifications to the React Native Demo build. Just in case you want to compare your overall code with mine, you can check out the repo here.
If you enjoyed this, please like, share and comment!