HowTo: Modify theta-client React Native Demo using the React Native Paper library and other additional styles

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:

  1. Cloning the theta-client Repository: Step-by-step instructions to clone the theta-client SDK to your local machine.

  2. Setting up the Development Environment: Setup process for preparing your development environment using Android Studio.

  3. 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

Step 2: 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

theta-git-bash

Step 4: Open the Project in Your Text Editor

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:

  1. 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.
  1. 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:

  1. 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
    

  1. Install the Required Packages:

    • Run the following command to install all the dependencies needed for the demo to work perfectly:
    $ yarn install
    
  2. Install React Native Paper:

    • Add React Native Paper to your project by running:
    $ yarn add react-native-paper
    
  3. 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:

  1. Import PaperProvider:

    • First, import PaperProvider at the top of your App.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
    
  2. Wrap Your App with PaperProvider:

    • 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

  1. Navigate to MainMenu.tsx inside the src folder:

    • Open the MainMenu.tsx file in your code editor.
  2. 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';
    
  3. 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
    

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 the title prop to specify the tooltip text and enterTouchDelay and leaveTouchDelay to control the tooltip display timing.
  • Button Wrapping: Each TouchableOpacity button is wrapped inside a Tooltip 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

  1. Open your browser and go to undraw.co.
  2. 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

  1. 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:

  1. Install Required Packages:

    • Run the following command to install react-native-svg and react-native-svg-transformer:
    $ yarn add react-native-svg react-native-svg-transformer
    
  2. 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"],
        },
      };
    })();
    
  3. Add TypeScript Declarations for SVG:

    • Create a folder named types inside the demo-react-native directory.
    • Inside the types folder, create a file named declarations.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;
    }
    
  4. 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 to cam.svg.
    • Inside the svgImages folder, create an index.ts file with the following code:
    import Cam from './cam.svg';
    export { Cam };
    
    • In the assets folder, create an index.ts file and add this line of code:
    export * from './svgImages';
    

Step 3: Integrating the SVG into MainMenu.tsx

  1. Import the SVG:

    • In MainMenu.tsx, import the Cam SVG:
    import { Cam } from '../assets';
    
  2. 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
    

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

  1. Update the Container Style:

    • Open the Styles.tsx file inside the src folder.
    • Modify the container style to the following:
    container: {
        flex: 1,
        justifyContent: 'flex-start',
        paddingTop: 100,
        alignItems: 'center',
        backgroundColor: 'white',
    },
    
  2. 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.

  1. 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 from container to the newly created listContainer.
<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 as Tex.

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 it SkeletonCardLoader.tsx.
  • Inside SkeletonCardLoader.tsx, import the necessary modules and define the skeleton UI. Here, we will make use of the View component and try as much to build up a Card-like structure just like the one in the ListPhotos 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.

  1. Import the SkeletonCardLoader component into your ListPhotos component.
import SkeletonCardLoader from './SkeletonCardLoader';
  1. Use the refreshing state to determine if the photos are loaded. If refreshing is true, display the SkeletonCardLoader; 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

  1. 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';
  1. 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;
  1. Set Up the Animation Sequence
    Use the useEffect 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]);
  1. 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],
});
  1. 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%',
  },
});
  1. Integrate the Gradient with Animation
    Combine the Animated.View and LinearGradient 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>
  1. 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!

2 Likes

As I normally use Flutter, I found this article on theta-client React Native demo interesting. Thank you for sharing it.

Initially, I had problems running the React Native demo itself.

yarn android
yarn run v1.22.22
$ react-native run-android
info Starting JS server...
info Installing the app...

FAILURE: Build failed with an exception.

* What went wrong:
Could not open settings generic class cache for settings file '/home/craig/Development/ricoh/2024/theta-client/demos/demo-react-native/android/settings.gradle' (/home/craig/.gradle/caches/7.5.1/scripts/72hrb2xr734f4zpavzjlq0bu3).
> BUG! exception in phase 'semantic analysis' in source unit '_BuildScript_' Unsupported class file major version 63

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 525ms

error Failed to install the app. Make sure you have the Android development environment set up: https://reactnative.dev/docs/environment-setup.
Error: Command failed: ./gradlew app:installDebug -PreactNativeDevServerPort=8081

FAILURE: Build failed with an exception.

* What went wrong:
Could not open settings generic class cache for settings file '/home/craig/Development/ricoh/2024/theta-client/demos/demo-react-native/android/settings.gradle' (/home/craig/.gradle/caches/7.5.1/scripts/72hrb2xr734f4zpavzjlq0bu3).
> BUG! exception in phase 'semantic analysis' in source unit '_BuildScript_' Unsupported class file major version 63

Could not open settings generic class cache for settings file

I believe I found a workaround on Stackoverflow.

There was a mismatch between the version of Java I specified in JAVA_HOME and the version bundled with Android Studio.

Java 19.0.2 From Ubuntu 22.04

java -version
openjdk version "19.0.2" 2023-01-17
OpenJDK Runtime Environment (build 19.0.2+7-Ubuntu-0ubuntu322.04)
OpenJDK 64-Bit Server VM (build 19.0.2+7-Ubuntu-0ubuntu322.04, mixed mode, sharing)

Setting to Java 17.0.7 from Android Studio

On Ubuntu, Android Studio is often installed in a location that is specific to each person’s workstation. On my Linux computer, I have Android Studio installed

export JAVA_HOME="/opt/android-studio/jbr/"
export PATH=$JAVA_HOME/bin:$PATH
java -version
openjdk version "17.0.7" 2023-04-18
OpenJDK Runtime Environment (build 17.0.7+0-17.0.7b1000.6-10550314)
OpenJDK 64-Bit Server VM (build 17.0.7+0-17.0.7b1000.6-10550314, mixed mode)

yarn android

Although the application installed, it did not run.

image

npx react-native start

image

select a

now it runs

linter error

image

The application works, so I’m just gong forward.

running on physical device

Live preview working.

Take picture works

List Photos work

Tip

I had more reliability with the 5GHz connection.
Turn off auto-sleep during testing and power your THETA X from a USB cable.

Dev Environment

Using physical THETA X and physical Google Pixel 4a.

image

Paper Install

Went Smoothly

Tooltip

SVG Image

image

Styling


I’m going to continue this fine tutorial by @shegz101 after a short break.

1 Like

Converting ThetaClient ListPhotos Into Cards

First with custom style

Currently not taking up entire width


@shegz101 I’ve run into the styling problem mentioned above. The card width does not go across the whole screen as shown in your fine tutorial.

If you have time, can you look at my code and see where I made a mistake?

This is the component that is narrow.


If I change the styling as follows, then the width of the card is forced to the width of the phone screen minus 38 pixels:

      <Card style={{width: Dimensions.get('window').width - 38,}}>
        <Card.Title 
          title="Photo" 

I’m curious as to why the original cardWrapper style doesn’t work in my app.

  cardWrapper: {
    width: '100%',
    marginVertical: 10,
  },

1 Like

Let me check that out on my end, then get back to you. Thank you!

Hello, try to add the style directly to the “TouchableOpacity” since that is what is standing as the wrapper for all individual cards.
Instead of this:

<Card style={{width: Dimensions.get('window').width - 38,}}>
        <Card.Title 
          title="Photo" 

Do this:

<TouchableOpacity
      style={{width: Dimensions.get('window').width - 38,}}
    >
      <Card style={{ padding: 10}}>
        <Card.Title title="Testing" subtitle={item.dateTimeZone} left={LeftContent} />
2 Likes

@shegz101
Thanks everything is working now. With a physical camera, I am also able to pull the latitude and longitude.

You produced a great tutorial. Thank you.

I’m going to try and build a video with this content to point more people to your work.

Wow! I’m happy I could help. I’m also looking forward to your content. Thank you!

2 Likes

I’ve been trying to change the Avatar from text to icon. I’m getting a random icon, not the specified folder icon. Has anyone seen this before?

const LeftContent = props => <Avatar.Icon icon="folder" size={40} />;

I read this Stackoverflow answer, but it didn’t solve the problem for me.

https://www.reddit.com/r/reactnative/comments/wobqry/why_are_my_reactnativepaper_icons_are_jumbled_up/

First, I believe the icon was imported well. Also can you share your code snippet so I can try it on my end and get back to you. Thank you!

I am new to react native and I don’t think I imported the icon properly. Do you know how to import icons for the Avatar.icon?

Alright, I think you need to import the specific icon. My system is presently down, when I charge it, I’d get try to resolve things from my own end and get back to you. Thank you!

2 Likes

From what I can tell on Stackoverflow, I either did not install the icons properly or I did not import it properly.

Thanks again for your great tutorial. It’s really well-written and nicely organized. I’m using your work as a base to learn more about React Native Paper Avatar.

1 Like

@shegz101 in your opinion, is react-native-paper much more popular than material-ui?

Is there a version of react-native-paper for the web? We are using MUI for some web-based (not mobile) projects and I’m wondering if MUI is not the way to go.

image

Do people ever use things like this to build a web site with React native Paper components?

React Native for Web


Brainstorming for future articles

the most common workflow for businesses with mobile app:

  1. place camera in scene
  2. preview scene (livePreview, motionJPEG) - not really possible with fake-theta
  3. take picture
  4. download picture from camera
  5. store picture on local device
  6. create a data file with local device and match the image with the data file
  7. store data and image in cloud-based database, likely the image itself is stored in something like S3
  8. make data and image web accessible for customers

With this workflow, ideas for 3 separate tutorials are:

  1. how to download image from the URL in the camera and save to local mobile app device storage, then create a simple form to save data matched with the image.
  2. how to upload image text data to a database and match the the image on a storage site. Any database is likely fine, but I think that supabase and firebase have free tiers, which is good for tutorials. I believe that supabase also has a storage free tier.
  3. how to build a basic React web site to view the image in the cloud, possibly reusing a few components from the mobile app

Example data for the image

real estate, construction, building insurance

  • Address
  • description of individual image - like a bedroom or bathroom
  • description of entire building - like a house
  • notes

used car

  • vehicle ID number
  • make of car (like Toyota)
  • model of car (like Camry)
  • local of car (like Austin, Texas, USA)
  • notes

In a way I feel React Native Paper is Material UI that is compatible with React Native. React Native Paper is still Material UI just that MUI is for web and React Native Paper is for Mobile

2 Likes

I found this video interesting about what React Native Paper is.

The person interviewed in Dawid Ubaniak, the lead engineering for React Native Paper.

To understand the philosophy of React Native Paper, do you also recommend learning about Material Design, not just Material UI?

Here are 3 ideas for articles, please discuss with @jcasman

Thanks for the resources. I will discuss with Jesse about these new topics, thanks a lot!

2 Likes

Here’s some ideas for articles 4 and 5

I want the demo with fake-theta to look like the demo below. The demo below no longer works smoothly

thumbnails

Here’s the first video in this series. Thanks for all your work.

In tsconfig.json, I configured it as follows:

// prettier-ignore
{
  "extends": "@tsconfig/react-native/tsconfig.json",     /* Recommended React Native TSConfig base */
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig.json to read more about this file */

    /* Completeness */
    "skipLibCheck": true,
    "noImplicitAny": false,   
  }
}

The main section for me with VSCode is the "noImplicityAny": false

before

With red error from errorLens

after

now with no error.

image

reference

https://medium.com/front-end-weekly/typescript-error-ts7031-makes-me-go-huh-c81cf76c829b