Build a React Native DApp using Celo Composer

Build a React Native DApp using Celo Composer https://celo.academy/uploads/default/optimized/2X/b/b1e9684256a2ae65d96095559815517b5834369b_2_1024x576.jpeg
none 0.0 0

Introduction

In this tutorial, we’ll walk through the process of building a decentralized application (DApp) using React Native and Celo Composer. We’ll create a simple DApp that allows users to send and receive tokens on the Celo blockchain. We will also perform contract interactions writing and reading from the blockchain.

We will be using web3Modal to connect our wallet and sign transactions.

By the end of this tutorial, you’ll have a working React Native DApp that you can use to interact with the Celo blockchain and transfer tokens. To easily follow along this tutorial the full code can be found here.

This a preview of what we will be building in this tutorial

Prerequisites​​

To successfully follow along in this tutorial you need basic knowledge of:

  • HTML, CSS, React Native and Expo
  • Blockchain, solidity and hardhat
  • Celo Valora / Alfajores account

Requirements​

To build this DApp we will need the following tools:

  • Vscode - But you can use any code editor of your choice
  • Hardhat - used to deploy the smart contract
  • Valora App - required to connect to the dApp and sign transactions
  • Node- an open-source, cross-platform JavaScript runtime environment
  • Celo Composer- starter project with all code needed to build, deploy, and upgrade a dapps on Celo.
  • React Native Web3Modal- used for connecting our react native dapp to a web3 wallet on mobile
  • React Native TailwindCSS - for styling our component

Let’s Get Started​

To get started we will be scaffolding our project with Celo Composer. First let’s have some understanding of what Celo composer is about.

What is Celo Composer​

Celo-composer is a starter project with all code needed to build, deploy, and upgrade dapps on Celo.

Step 1: Setup the Project​

First, let’s set up our project. Create a new directory and run the following commands and follow the steps

Select React Native with expo option and then enter your project name. For details on the steps checkout the Celo Composer github readme page.

Fig 1-0: Celo Composer Setup

Once you have successfully completed the steps do npm install or yarn to install all required dependencies. Once that is done you are ready to start building.

Now open your newly created project. You will see a packages folder inside the package folder you will see hardhat and react-app folder.

For security reasons in order not to expose your private keys to the public create a new file named .env in the root of the hardhat folder add this line of code:

PRIVATE KEY = <YOUR PRIVATE KEY>

Set up a Celo Account​

To get started building a simple Greeters Dapp you will need to fund your account, We need to set up a Celo Account. You can use the Celo Mobile Valora Wallet app, which is available on both iOS and Android devices.

Once you have set up your account go to the Celo faucet to get some testnet tokens. You will need to fund your account to deploy the contract and perform other smart contract interactions.

Step 2: Write the Smart Contract​

We will be making use of the Greeter.sol smart contract generated for us after creating our project with Celo Composer. Inside the packages directory navigate to the hardhat directory and open the contract directory, you will see some sample contracts. For the purpose of this tutorial we will be making use of the Greeter.sol contract. The contract should look like this :point_down:

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

// remember to remove unnecessary imports and its use when deploying your smart contract
import "hardhat/console.sol";

contract Greeter {
    event newGreeting(string greeting, address sender);

    string private greeting;

    constructor(string memory _greet) {
        console.log("Deploying a Greeter with greeting:", _greet);
        greeting = _greet;
    }

    function greet() external view returns (string memory) {
        return greeting;
    }

    function setGreeting(string calldata _greeting) external {
        console.log("Changing greeting from '%s' to '%s'", greeting, _greeting);
        greeting = _greeting;
        emit newGreeting(_greeting, msg.sender);
    }
}

Let’s take a closer look at the above code;

  • The contract is named Greeter and is defined with the contract keyword.
  • The event newGreeting is declared to emit an event whenever the greeting is changed. It includes the new greeting string and the address of the sender.
  • The string private greeting is a private state variable that holds the current greeting message.
  • The constructor function is called when the contract is deployed. It takes a string parameter _greet to initialize the greeting message. The console.log statement is used to log a message with the provided greeting.
  • The greet function is an external view function that allows users to retrieve the current greeting. It returns a string.
  • The setGreeting function is an external function that allows users to change the greeting. It takes a string parameter _greeting and updates the greeting state variable. It also emits the newGreeting event with the new greeting and the sender’s address.

Remember to remove the import "hardhat/console.sol"; statement and the console.log statements if you’re deploying the contract to a production environment. These statements are for debugging purposes and should not be included in the final deployment.

:information_source: NOTE
This code is a basic example and does not include error handling or access control mechanisms. In a real-world scenario, you would need to consider implementing additional security measures and validations.

Step 3: Deploy the Contract​

Inside the hardhat directory navigate to the scripts directory and open the sample-scripts.js file. You will see the deployment code. The code should look like this :point_down:

// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// When running the script with `npx hardhat run <script>` you'll find the Hardhat
// Runtime Environment's members available in the global scope.
const hre = require("hardhat");

async function main() {
  // Hardhat always runs the compile task when running scripts with its command
  // line interface.
  //
  // If this script is run directly using `node` you may want to call compile
  // manually to make sure everything is compiled
  // await hre.run('compile');

  // We get the contract to deploy
  const Greeter = await hre.ethers.getContractFactory("Greeter");
  const greeter = await Greeter.deploy("Hello, Hardhat!");

  await greeter.deployed();

  console.log("Greeter deployed to:", greeter.address);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

To deploy the above code from your terminal or command prompt run npx hardhat run scripts/sample-script.js. This will give you the deployment address and the generated ABI. The contract address and ABI will be used later in our react native app during contract interactions. You can find the generated ABI for the Greeter contract in the artifact directory. artifacts/contracts/Greeter.sol/greeter.json. Copy the json file and create a new file inside the react-native-app directory name the file greeter.json and paste the abi code there. Also take note of the deployed contract address.

Step 4: React Native Frontend Interactions

To interact with the above deployed contract and to perform all other frontend related activities, navigate to the react-native-app directory. There are a bunch of files already created for us. but for this tutorial we will be creating one additional screens and making some few changes to the existing code to suite our purpose.

:information_source: NOTE
Before diving deeper into the frontend code there are some things to note and some challenges you might likely encounter and how to fix that.

As at the time of this writing, if you are using the Celo composer to create your react app you might encounter issues connecting your dapp with wallet connect as there is currently an upgrade to V2.

To avoid connection issues there are some couple of changes that needs to be made. First you will need to install web3Modal and replace wherever you see useWalletConnect with useWeb3Modal.

To install web3modal from your terminal if using npm run npm install @web3modal/react-native or yarn run yarn add @web3modal/react-native

Additionally add these extra packages to help with async storage, polyfills, modals and SVG’s.
yarn add @react-native-async-storage/async-storage react-native-get-random-values react-native-modal react-native-svg

For more detail setup for web3modal you can follow the guide here

Incase you encounter bigInt issue. you will need to install it. npm i big-integer

Also navigate to the global.ts file inside the react-native-app directory and add this
if (typeof BigInt === "undefined") global.BigInt = require("big-integer");

For our react native app styling we will be making use of react native tailwind for that you will install this package
npm install twrnc Check this for detail guide on how to use the library.

Enough talk now let’s get down to the code. For this section ensure you are in the react-native-app directory as we will be visiting some of the files and making some changes.

Greeter.tsx

Navigate to Screens/Greeter.tsx file. After the changes we made your code should look like this;

import { useState, useEffect, useContext } from "react";
import { Text, View,   } from "../components/Themed";
import { TextInput, Button, TouchableOpacity, ScrollView } from "react-native";
import * as WebBrowser from "expo-web-browser";
import Web3 from "web3";
import { ThemeContext } from "../context/ThemeProvider";
import { useWeb3Modal } from "@web3modal/react-native";
import { ethers } from 'ethers';
import { useMemo } from 'react';
import greeterABI from "../greeter.json"
import tw from "twrnc"
import AlertMessage from "../components/Alert";
import { RefreshControl } from "react-native";
const web3 = new Web3("https://alfajores-forno.celo-testnet.org");

export default function Greeter() {
	const { styles } = useContext(ThemeContext);
	const { provider, address } = useWeb3Modal()

	const web3Provider = useMemo(
	() => (provider ? new ethers.providers.Web3Provider(provider) : undefined),
	[provider]
	);
	
    const contractAddress = "0x421d8e7f1717A6B3B104865Da28bC3620fa3A829";
	
    // const { contractData } = props;
    const [greeterValue, setGreeterValue] = useState("");
    const [greetingInput, setGreetingInput] = useState("");
    const [contractLink, setContractLink] = useState("");
    const [settingGreeting, setSettingGreeting] = useState(false);
    const [loadGreeting, setLoadGreeting] = useState(false);
    const [signer, setSigner] = useState<ethers.providers.JsonRpcSigner | undefined>(undefined);
    const [refreshing, setFreshing] = useState(false)
    const contract = new web3.eth.Contract(greeterABI.abi, contractAddress)

	useEffect(() => {
	setContractLink(
        `https://alfajores-blockscout.celo-testnet.org/address/${contractAddress}`
    );
    }, []);
    

    const sendGreeting = async () => {
        setSettingGreeting(true);
        const sign = web3Provider?.getSigner();
        setSigner(sign);
        if (!greetingInput) {
            return AlertMessage("Greeting", "Input field required")
        } else {
            try {
            const txResponse =  signer && await signer.sendTransaction({
            to: contractAddress, // Replace with the contract address
            data: contract.methods.setGreeting(greetingInput).encodeABI(),
            });
            console.log('Transaction sent:', txResponse);
            setGreetingInput("")
            getGreeting()
        } catch (e) {
            console.error('Failed to send transaction:', e);
        } finally {
            setSettingGreeting(false);
        }
        }
    };

    const getGreeting = async () => {
      setFreshing(true)
		setLoadGreeting(true);
		try {
            const result = (await contract?.methods.greet().call()) as string;
            setGreeterValue(result);
            setLoadGreeting(false);
            setFreshing(false)
		} catch (e) {
			console.log(e);
		} finally {
            setLoadGreeting(false);
            setFreshing(false)
		}
  };
    

	function handlePress() {
		WebBrowser.openBrowserAsync(contractLink);
	}

    useEffect(() => {     
        getGreeting()
    },[greeterValue])

    
	return (
        <View style={tw `flex-1 justify-center pt-36 px-8`}>
            <View style={styles.innerContainer}>
                <Text style={styles.title}>Greeter Contract</Text>         
                <TouchableOpacity
                    style={styles.externalLink}
                    onPress={() => handlePress()}
                >
                    <Text style={styles.externalLink}>
                        {`${contractAddress.substr(
                            0,
                            5
                        )}...${contractAddress.substr(-5)}`}
                    </Text>
                </TouchableOpacity>
                
            </View>
            <View style={styles.separator}></View>
            <View style={styles.innerContainer}>
                <Text>Write Contract</Text>
                <TextInput
                    value={greetingInput}
                    onChangeText={(newValue) => setGreetingInput(newValue)}
                    style={styles.textInput}
                />
                <View style={tw `border rounded` }>
                    <Button title="Send Greeting" onPress={() =>  sendGreeting()}/>
                </View>
            </View>
            <View style={styles.separator}></View>
            <ScrollView
                refreshControl={
                    <RefreshControl refreshing={refreshing} onRefresh={getGreeting} />
                }>  
                <View style={styles.innerContainer}>
                <Text style={tw `text-lg font-bold`}>Read Contract</Text> 
                {loadGreeting ? <Text style={ tw `text-lg`}>Fetching Greeting...</Text> :
                 greeterValue && 
                    <View>
                        <Text style={{ marginVertical: 10 }}>
                        Greeter Contract Value: {greeterValue}
                        </Text>         
                    </View>
                
                }
            </View>
            </ScrollView>
            
        </View>
    );
}

The above code contains a TextInput field to enter greetings and also a button that will call the greetings function, and a useEffect to call the getGreeting function on initial render. Also a text field to display the updated state of greeting value.

The sendGreeting function will write to the blockchain. After contract initialization.
getGreeting function will get the greeting value from the blockchain.
handlepress function will open the web browser to view the transactions detail on the celo alfajores block explorer.

TransferScreen.tsx

Create a new file inside the Screens directory called TransferScreen Screens/TransferScreen.tsx file. Copy and paste this code inside. Your code should look like this;

import React, { useState, useMemo, useEffect } from 'react'
import { TextInput, Text, Button, View } from 'react-native'
import { useWeb3Modal } from '@web3modal/react-native';
import { ethers, BigNumber } from 'ethers';
import tw from "twrnc"
import AlertMessage from '../components/Alert';

export default function TransferScreen() {    
  const { address, provider } = useWeb3Modal()
  const [signer, setSigner] = useState<ethers.providers.JsonRpcSigner | undefined>(undefined)
  const [receiver, setReceiver] = useState<string>("")
  const [amount, setAmount] = useState<string>("")
  const [balance, setBalance] = useState<string>("0")
  const [loading, setLoading] = useState<boolean>(false)

  	const web3Provider = useMemo(
    () => (provider ? new ethers.providers.Web3Provider(provider) : undefined),
    [provider]
	);


  const sendTransaction = async () => {
        const sign = web3Provider?.getSigner()
        setSigner(sign)
      try {
        if (!receiver || !amount) {
          return AlertMessage("Send", "Input field required")
        } else {
            const txResponse = signer && await signer.sendTransaction({
              from: address,
              to: receiver,
              value: ethers.utils.parseEther(amount)
          });
          console.log('Transaction sent:', txResponse);
          }
        } catch (error) {
            console.error('Failed to send transaction:', error);
        }
     };

  const accountBalance = async () => {
    setLoading(true)
    const response = await web3Provider?.getBalance(address as string)
    const convertHexValue = parseInt(response, 16)
    setBalance(ethers.utils.formatEther(convertHexValue))
    console.log(balance);
    setLoading(false)
  }

  useEffect(() => {
    accountBalance()
  }, [])

  return (
    <View style={tw`flex-1 align-item-center justify-center mx-8`}>
      <Text style={tw`text-center text-lg my-4`}>Transfer Fund</Text>
      {loading ? <Text>Fetching balance...</Text> : <Text>{ `Account Balance ${balance}`}</Text>  }
     
      <TextInput style={tw`border p-2 rounded`} placeholder='enter receiver addresss' value={receiver}  onChangeText={(newValue) => setReceiver(newValue)}/>
      <TextInput style={tw`border p-2 rounded my-2`} placeholder='enter amount' value={amount} onChangeText={(newValue) => setAmount(newValue)} />
      <View style={tw`border-0 rounded`}>
        <Button  title='Transfer Fund' onPress={sendTransaction}/>
      </View>
    </View>
  )
}

The provided code is a React Native component for a transfer screen in a mobile app. It allows users to send funds (Ether) to another address using the Web3Modal library. Here’s a breakdown of the code:

  • The component imports necessary dependencies, including React Native components, the Web3Modal library, ethers.js, and custom components.
  • The component defines state variables using the useState hook to manage the receiver address, amount, balance, signer, and loading state.
  • The useWeb3Modal hook is used to access the connected address and provider from the Web3Modal context.
  • The web3Provider variable is memoized using the useMemo hook to create an instance of Web3Provider.
  • The sendTransaction function is called when the user clicks the “Transfer Fund” button. It retrieves the signer and sends a transaction using the provided receiver address and amount.
  • The accountBalance function fetches the account balance using the getBalance method from the Web3Provider.

Navigation Tab

We will be making some changes to the bottom navigation tab. To do that cd out of the Screens directory and navigate to the navigation directory. Open index.tsx and add this two Nvaigation at the bottom

<BottomTab.Screen name="Greeter" component={Greeter} />
 <BottomTab.Screen name="Send" component={Transfer} />

After the above changes, the full code inside navigation/index.tsx should look like this :point_down:

/**
 * If you are not familiar with React Navigation, refer to the "Fundamentals" guide:
 * https://reactnavigation.org/docs/getting-started
 *
 */
import { SafeAreaProvider } from "react-native-safe-area-context";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import {
    NavigationContainer,
    DefaultTheme,
    DarkTheme,
} from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import * as React from "react";
import { ColorSchemeName, Button } from "react-native";
import Colors from "../constants/Colors";
import useColorScheme from "../hooks/useColorScheme";
import ModalScreen from "../screens/ModalScreen";
import NotFoundScreen from "../screens/NotFoundScreen";
import { RootStackParamList, RootTabParamList } from "../types";
import LinkingConfiguration from "./LinkingConfiguration";
import LoginScreen from "../screens/LoginScreen";
// import deployedContracts from "@celo-composer/hardhat/deployments/hardhat_contracts.json";
import Account from "../screens/Account";
import { useWeb3Modal } from "@web3modal/react-native";
import Docs from "../screens/Docs";
import Greeter from "../screens/Greeter"
import Transfer from "../screens/TransferScreen"
export default function Navigation({
    colorScheme,
}: {
    colorScheme: ColorSchemeName;
}) {
    return (
        <NavigationContainer
            linking={LinkingConfiguration}
            theme={colorScheme === "dark" ? DarkTheme : DefaultTheme}
        >
            <RootNavigator />
        </NavigationContainer>
    );
}

/**
 * A root stack navigator is often used for displaying modals on top of all other content.
 * https://reactnavigation.org/docs/modal
 */
const Stack = createNativeStackNavigator<RootStackParamList>();

function RootNavigator() {
    const { isConnected } = useWeb3Modal();
    return (
        <Stack.Navigator>
            {isConnected ? (
                <Stack.Screen
                    name="Root"
                    // the Root path renders the component mentioned below.
                    component={BottomTabNavigator}
                    options={{ headerShown: false }}
                />
            ) : (
                <Stack.Screen
                    name="Root"
                    component={LoginScreen}
                    options={{ headerShown: false }}
                />
            )}
            <Stack.Screen
                name="NotFound"
                component={NotFoundScreen}
                options={{ title: "Oops!" }}
            />
            <Stack.Group screenOptions={{ presentation: "modal" }}>
                <Stack.Screen name="Modal" component={ModalScreen} />
            </Stack.Group>
        </Stack.Navigator>
    );
}

/**
 * A bottom tab navigator displays tab buttons on the bottom of the display to switch screens.
 * https://reactnavigation.org/docs/bottom-tab-navigator
 */
const BottomTab = createBottomTabNavigator<RootTabParamList>();

function BottomTabNavigator() {
    const theme = useColorScheme();

    // const contracts = deployedContracts["44787"]?.["alfajores"]?.contracts;

    return (
        <SafeAreaProvider>
            <BottomTab.Navigator
                // first screen visible after login
                initialRouteName="Docs"
                screenOptions={{
                    headerShown: false,
                    tabBarActiveTintColor: Colors["brand"].light.text,
                    tabBarActiveBackgroundColor:
                        Colors["brand"][theme].background,
                    tabBarLabelPosition: "beside-icon",
                    tabBarIconStyle: { display: "none" },
                    tabBarLabelStyle: { textAlign: "center" },
                }}
            >
                {/* <BottomTab.Screen
                    name="Greeter"
                    children={(props) => (
                        <Greeter contractData={contracts.Greeter} {...props} />
                    )}
                    options={() => ({
                        title: "Greeter Contract",
                        headerShown: false,
                        // render icons if any
                        tabBarIcon: ({
                            focused: boolean,
                            color: string,
                            size: number,
                        }) => {
                            return <></>;
                        },
                        tabBarLabelPosition: "beside-icon",
                    })}
                />
                <BottomTab.Screen
                    name="Storage"
                    children={(props) => (
                        <Storage contractData={contracts.Storage} {...props} />
                    )}
                    options={{
                        title: "Storage Contract",
                        headerShown: false,
                        tabBarIcon: ({
                            focused: boolean,
                            color: string,
                            size: number,
                        }) => {
                            return <></>;
                        },
                        tabBarLabelPosition: "beside-icon",
                    }}
                /> */}
                <BottomTab.Screen name="Docs" component={Docs} />
                <BottomTab.Screen
                    name="Account"
                    component={Account}
                    options={() => ({
                        title: "Account",
                        headerShown: false,
                        tabBarIcon: ({
                            focused: boolean,
                            color: string,
                            size: number,
                        }) => {
                            return <></>;
                        },
                        tabBarLabelPosition: "beside-icon",
                    })}
                />
                <BottomTab.Screen name="Greeter" component={Greeter} />
                <BottomTab.Screen name="Send" component={Transfer} />
            </BottomTab.Navigator>
        </SafeAreaProvider>
    );
}

The provided code is a navigation configuration using the React Navigation library. It sets up a stack navigator and a bottom tab navigator to handle navigation between screens.

Here’s a breakdown of the code:

  • The Navigation component is the entry point and wraps the entire app with the NavigationContainer component from React Navigation. It provides the navigation context and sets the theme based on the color scheme.
  • The RootNavigator component is a stack navigator responsible for handling navigation between screens. It conditionally renders the BottomTabNavigator if the user is connected (based on the isConnected flag from the useWeb3Modal hook), otherwise it renders the LoginScreen. It also includes a NotFoundScreen for handling unknown routes and a modal screen.
  • The BottomTabNavigator component is a bottom tab navigator that displays tab buttons at the bottom of the screen for switching between screens. It includes several screens: Docs, Account, Greeter, and Send.
  • Each screen in the BottomTabNavigator is defined using the BottomTab.Screen component. Screens like Greeter and Send have corresponding components (Greeter and Transfer) that are rendered when the respective tab is active. These components can be customized with additional props and options.

:information_source: NOTE
At the root of the react-native-app directory create a file called expo-crypto-shim.js and add this file.

// https://github.com/expo/expo/issues/17270#issuecomment-1445149952
// Polyfill for expo-crypto until issue with react-native-get-random-values is solved
// Apply only with Expo SDK >= 48

import { getRandomValues as expoCryptoGetRandomValues } from 'expo-crypto';

class Crypto {
  getRandomValues = expoCryptoGetRandomValues;
}

// eslint-disable-next-line no-undef
const webCrypto = typeof crypto !== 'undefined' ? crypto : new Crypto();

(() => {
  if (typeof crypto === 'undefined') {
    Object.defineProperty(window, 'crypto', {
      configurable: true,
      enumerable: true,
      get: () => webCrypto,
    });
  }
})();

Still at the root of the react-native-app directory your global.ts file should look like this

// https://github.com/expo/expo/issues/17270#issuecomment-1445149952
// Polyfill for expo-crypto until issue with react-native-get-random-values is solved
// Apply only with Expo SDK >= 48

import { getRandomValues as expoCryptoGetRandomValues } from 'expo-crypto';

class Crypto {
  getRandomValues = expoCryptoGetRandomValues;
}

// eslint-disable-next-line no-undef
const webCrypto = typeof crypto !== 'undefined' ? crypto : new Crypto();

(() => {
  if (typeof crypto === 'undefined') {
    Object.defineProperty(window, 'crypto', {
      configurable: true,
      enumerable: true,
      get: () => webCrypto,
    });
  }
})();

App.tsx

After adding the Web3Modal component and imports. The full code of your app.tsx file should look like this :point_down:

import React from "react";
// // Only for Expo SDK 48+
import './expo-crypto-shim.js'
import { StatusBar } from "expo-status-bar";
import { SafeAreaProvider } from "react-native-safe-area-context";
import { useEffect } from "react";
import { LogBox } from "react-native";
import useCachedResources from "./hooks/useCachedResources";
import useColorScheme from "./hooks/useColorScheme";
import Navigation from "./navigation";
import { Web3Modal } from "@web3modal/react-native";
import { providerMetadata, sessionParams } from "./constants/Config.js";
// import { REACT_APP_ENV_PROJECT_ID } from "@env";
import { ThemeProvider } from "./context/ThemeProvider";

export default function App() {
    const isLoadingComplete = useCachedResources();
    const colorScheme = useColorScheme();
    
    // avoid warnings showing up in app. comment below code if you want to see warnings.
    useEffect(() => {
        LogBox.ignoreAllLogs();
    }, []);

    if (!isLoadingComplete) {
        return null;
    } else {
        return (
            <ThemeProvider>
                <SafeAreaProvider>
                    <Navigation colorScheme={colorScheme} />
                    <StatusBar />
                    <Web3Modal
                    projectId={process.env.REACT_APP_ENV_PROJECT_ID!!}
                    providerMetadata={providerMetadata}
                    sessionParams={sessionParams}
                    />
                </SafeAreaProvider>
            </ThemeProvider>
        );
    }
}

The projectId should be in your environment variables. At the root of your project directory create .env file and place this code there

REACT_APP_ENV_PROJECT_ID = <YOUR PROJECT ID>

You can get the projectId here.

After doing the above you can test your application by simply doing npm start this will start the development server.

If you are testing with expo ensure you have expo installed on your mobile device and then scan the QR code. This should open the app. And you can start testing the functionalities of what we have done so far.

Generate an APK to Test on Android Device

For testing on other devices we will need to generate a downloadable link, you will need to run the following command;

Follow this setup guide to generate an APK.
:information_source: NOTE
Ensure you have eas installed globally. If it’s not already installed run this command npm install -g eas-cli
Once it’s installed. If you don’t already have eas.json file add it to the root of your react-native-app directory and the code should look like this :point_down:

{
  "build": {
    "preview": {
      "android": {
        "buildType": "apk"
      }
    },
    "preview2": {
      "android": {
        "gradleCommand": ":app:assembleRelease"
      }
    },
    "preview3": {
      "developmentClient": true
    },
    "production": {}
  }
}

Run this command to generate a sharable android APK link eas build -p android --profile preview


To test the android version of the app download the apk here

:information_source: NOTE
To test on IOS device follow the guide here.

Conclusion​

Congratulations :tada: on finishing this tutorial! Thank you for taking the time to complete it. In this tutorial, you have learnt how to create a React Native DApp that you can use to interact with the Celo blockchain and transfer tokens.

If you have any issue while following this tutorial feel free to drop it in the comment. And if this tutorial helped you or you find it interesting please hit the love button.

Here is the link to the project repo on github.

Next Steps

As a next step, apply what you have learnt in this tutorial and explore the other powerful features of celo composer react native with and without expo. Also extend the functionalities of this dapp to something more advance.

About the Author​

Glory Agatevure is a blockchain engineer, technical writer, and co-founder of Africinnovate. You can connect with me on Linkedin, Twitter and Github.

References

3 Likes

Approved for you to get started. You can manage the tutorial here by changing the category to Proposals > In Progress then Proposals > Review as you complete the tutorial. Thanks!

1 Like

@joenyzio and @ishan.pathak2711 How do I move it to in-progress

I will be reviewing this

1 Like

Can I know why it was moved in publish section

1 Like