Create Publication

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

Solution Thumbnail

Building Mobile Apps Using React-Native-Algo Library

Overview

react-native-algo

This is a React Native wrapper around the java-algorand-sdk, it enables mobile developers to continue writing their code in javascript while also being able to call some methods in the java-algorand-sdk. If you are building a React Native app and you want to leverage some Algorand functions, you definitely need to consider using this library in your next project.

Current Features

  • Create Algorand Account
  • Recover Algorand Account
  • Get Account Balance
  • Create and Sign Transaction
  • Send Funds
  • Create Multisignature Address,
  • Create Multisignature Transaction
  • Approve Multisignature Transaction
  • Submit Transaction to the network
  • Create ASA(Algorand Standard Asset)
  • Change ASA Manager
  • Opt-in To receive ASA
  • Transfer ASA
  • Freeze ASA
  • Revoke ASA
  • Destroy ASA
  • Atomic Transactions
  • Connect to a network (Local Node, Pure Stake)

Prerequisites

  • JDK installation
  • Android SDK installation
  • Mobile device emulator
  • React Native framework

Notice

Currently the library only supports Android. iOS support depends on a Swift SDK.

User Interface - Example App

Quick Start - Running the Example App

Setup Development Environment

Ensure your development environment is setup properly with react-native-cli, Android SDK and JDK version 8 (or above).

Install Library and Other Dependencies

Install the react-native-algo library, react-native-paper (learn more) and @react-native-community/clipboard for capturing data to the clipboard so we can search for it in the explorer if we want to.

$ npm install react-native-algo react-native-paper @react-native-community/clipboard

Run the Example App

First, clone the project:

$ git clone https://github.com/Jesulonimi21/react-native-algo
$ cd react-native-algo

Next, install the packages:

$ yarn install

Now, make sure you have your android emulator running or an android device setup for debugging. Run the example app:

$ yarn example android

Finally, you should see the example app running in the emulator or a connected device set up for debugging. See the video below for a walkthrough.

Video Walkthrough - Example App

Building A React-Native App With the Library

Prerequisites

Ensure your development environment, libraries and other dependencies are configured as outlined above in the Quick Start section.

Create New Project

Run the following from terminal in your preferred directory to start a new React Native project:

$ react-native init <Project-Name> 

Wait for the React Native CLI to finish creating the project for you, then navigate into the created project directory from terminal:

$ cd <Project-Name>

Open the project in your preferred editor or IDE. If you are using VS Code, you can simply run the code below from the project directory in terminal:

code .

Configure Project

Next, we need to add the multidex library to our app. Multidex library is needed for apps that contain over 64k methods in their app to compile properly, and our app already drags in the Java SDK behind the scenes so we need this.

While in your editor, navigate to /android/app/build.gradle and add the following to the defaultConfig section and the dependencies section

 defaultConfig {
     //....
    multiDexEnabled true
    }

dependencies {
    //....
     implementation 'com.android.support:multidex:1.0.3'
}

With the above, we have successfully configured our app to use the react-native-algo library.

Start Building

Now, time to start using the functions and methods in our library, so from the project directory, navigate to /App.js and replace the code there with the code below:

import * as React from 'react';
import { 
  StyleSheet,
  ScrollView,
  View,
} from 'react-native';
//Import the react-native-algo library
import Algo from 'react-native-algo';
import { Appbar } from 'react-native-paper';
import { RadioButton, Text,Button,Card,ActivityIndicator, Colors,TextInput,Divider,
  Banner} from 'react-native-paper';
  import Clipboard from '@react-native-community/clipboard';

state={
  accountInfo:"AccountInfo",
  signedTrans:"",
  node:"purestake",
  network:"testnet",
  seedphraseInput:"",
  recoverAcccountBanner:false,
  recoverAccountData:"",
  createAccountBanner:false,
  createAccountData:"",
  accountBalanceAddress:"",
  accountBalanceBanner:false,
  accountBalanceLoader:false,
  accountBalanceData:"",
  connectToNode:false,
  connectToNodeData:"",
  connectToNodeBanner:false,
  transferFundsBanner:false,
  transferFundsLoading:false,
  transferFundsData:"",
  transferFundsAddress:"",
  amount:"",
  note:"",
  customNodeAddress:"10.0.2.2",
  customNodePort:4001,
  customNodeToken:"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
}


render(){
  let{node,network,seedphraseInput,recoverAcccountBanner,   createAccountBanner,accountBalanceAddress,accountBalanceBanner,
    connectToNode,connectToNodeData,connectToNodeBanner,
    recoverAccountData,createAccountData,accountBalanceLoader,
    accountBalanceData,transferFundsBanner,
    transferFundsData,transferFundsLoading,transferFundsAddress,  amount,note,customNodeAddress,customNodePort,customNodeToken}=this.state;


  return( <View style={{marginTop:0,alignItems:'stretch',flex:1,justifyContent:"flex-start"}}>
<ScrollView>
<Appbar.Header >
       <Appbar.Content title="React-Native-Algo" subtitle={'Showcase'} />
    </Appbar.Header>

   <Card  style={{marginLeft:10,marginRight:10,paddingBottom:10}} >
   <Card.Title
    titleStyle={{textAlign:'center',}}
    title="Connect to a Node"
   />
   <ActivityIndicator style={{position:"absolute",top:0,right:0,bottom:0,left:0,elevation:10}} animating={connectToNode} color={Colors.red800} />
      <View style={{flexDirection:'row',alignItems:"center",
      justifyContent:"space-around",marginTop:15}}>
      <View style={{flexDirection:'row',alignItems:"center"}}>
        <Text>PureStake</Text>
        <RadioButton value= "purestake"
        status={node=="purestake"?"checked":"unchecked"}
        onPress={()=>{
          console.log("purree")

          this.setState({node:"purestake"})}}

          />

      </View>

      <View style={{flexDirection:'row',alignItems:"center"}}>
        <Text>Custom Node</Text>
        <RadioButton value="customnode" 
        status={node=="customnode"?"checked":"unchecked"}
        onPress={()=>{
          console.log("purree")

          this.setState({node:"customnode"})}}


        />

      </View>
      </View>
      {node=="customnode"?<View>
      <TextInput
            style={{backgroundColor:"#ffffff"}}
            label="Enter Api Address"
            mode="outlined"
            onChangeText={(text)=>this.setState({customNodeAddress:text})}
            value={customNodeAddress}
          />

    <TextInput
            style={{backgroundColor:"#ffffff"}}
            label="Enter Port Number"
            mode="outlined"
            onChangeText={(text)=>this.setState({customNodePort:text})}
            value={`${customNodePort}`}
            keyboardType="numeric"
          />
           <TextInput
            style={{backgroundColor:"#ffffff"}}
            label="Enter Api Token"
            mode="outlined"
            onChangeText={(text)=>this.setState({note:text})}
            value={customNodeToken}
          />

      </View>:      <View style={{flexDirection:'row',alignItems:"center",
      justifyContent:"space-around",marginTop:15}}>
      <View style={{flexDirection:'row',alignItems:"center"}}>
        <Text>Test net</Text>
        <RadioButton value= "testnet"
        status={network=="testnet"?"checked":"unchecked"}
        onPress={()=>{
          console.log("purree")

          this.setState({network:"testnet"})}}

          />

      </View>
      <View style={{flexDirection:'row',alignItems:"center"}}>
        <Text>Main Net</Text>
        <RadioButton value="mainnet" 
        status={network=="mainnet"?"checked":"unchecked"}
        onPress={()=>{
          console.log("purree")

          this.setState({network:"mainnet"})}}


        />
      </View> 
      </View>
}


  <View style={{alignItems:'center',marginTop:15}}>
      <Button  style={{width:"70%",}} mode="contained" onPress={this.handleConnectToNodelicked}>
          Connect
       </Button>

       </View>   
       <Banner
              visible={connectToNodeBanner}
              actions={[
                {
                  label: 'Ok',
                  onPress: () => this.setState({connectToNodeBanner:false}),
                },

              ]}
     >
       {connectToNodeData}
     </Banner>
       </Card>
       <Divider />
       <Card style={{alignItems:'stretch',marginTop:15,marginLeft:10,marginRight:10,paddingBottom:10}}>
          <Card.Title
           titleStyle={{textAlign:'center'}}
           title="Account Creation And Recovery"
           subtitle="Recover Account "
          />


          <TextInput
           style={{backgroundColor:"#ffffff"}}
            label="Enter Seed Phrase"
            mode="outlined"
            onChangeText={(text)=>this.setState({seedphraseInput:text})}
            value={seedphraseInput}

          />
          <View style={{alignItems:'center',marginTop:15}}>
           <Button  style={{width:"70%",}} mode="contained" onPress={() => {
             this.handleRecoverAccountClicked()
             console.log('Pressed')}}>
             Recover Account
            </Button>
            </View>
            <Banner
              visible={recoverAcccountBanner}
              actions={[
                {
                  label: 'Copy Address',
                  onPress: () => {
                    Clipboard.setString(recoverAccountData)
                    this.setState({recoverAcccountBanner:false})},
                },
                {
                  label: 'Hide',
                  onPress: () => this.setState({recoverAcccountBanner:false}),
                },
              ]}
     >
      {recoverAccountData}
    </Banner>

    <Card.Title
           subtitle="Create Account "
          />
    <View style={{alignItems:'center',marginTop:0}}>
              <Button  style={{width:"70%",}} mode="contained" onPress={() => {
                this.handleCreateAccountClicked()
                this.setState({createAccountBanner:true})
                console.log('Pressed')}}>
                Create Account
                </Button>
                </View>
                <Banner
                  visible={createAccountBanner}
                  actions={[
                    {
                      label: 'Copy Address',
                      onPress: () => {
                        Clipboard.setString(createAccountData)
                        this.setState({createAccountBanner:false})},
                    },
                    {
                      label: 'Hide',
                      onPress: () => this.setState({createAccountBanner:false}),
                    },
                  ]}
        >
  {createAccountData}
        </Banner>

       </Card>


       <Card style={{marginTop:15,marginLeft:10,marginRight:10,paddingBottom:10,position:'relative'}}>
         <Card.Title
           title="Get Account Balance"
           titleStyle={{textAlign:'center'}}
         />
         <ActivityIndicator style={{position:'absolute',top:0,left:0,right:0,}} animating={accountBalanceLoader} color={Colors.black} />
          <TextInput
            style={{backgroundColor:"#ffffff"}}
            label="Enter Address"
            mode="outlined"
            onChangeText={(text)=>this.setState({accountBalanceAddress:text})}
            value={accountBalanceAddress}
          />

<View style={{alignItems:'center',marginTop:15}}>
           <Button  style={{width:"70%",}} mode="contained" onPress={() => {
             this.handleGetAccountBalanceClicked();
             console.log('Pressed')}}>
             Get Account Balance
            </Button>
            </View>
       <Banner
              visible={accountBalanceBanner}
              actions={[
                {
                  label: 'Copy Amount',
                  onPress: () => {
                    Clipboard.setString(accountBalanceData)
                    this.setState({accountBalanceBanner:false})},
                },
                {
                  label: 'Hide',
                  onPress: () => this.setState({accountBalanceBanner:false}),
                },
              ]}
     >
      {accountBalanceData}
    </Banner>    
       </Card>

       <Card style={{marginTop:15,marginLeft:10,marginRight:10,paddingBottom:10,position:'relative'}}>
         <Card.Title
           title="Transfer Funds"
           titleStyle={{textAlign:'center'}}
         />
         <ActivityIndicator style={{position:'absolute',top:0,left:0,right:0,}} animating={transferFundsLoading} color={Colors.black} />
          <TextInput
            style={{backgroundColor:"#ffffff"}}
            label="Enter Address"
            mode="outlined"
            onChangeText={(text)=>this.setState({transferFundsAddress:text})}
            value={transferFundsAddress}
          />

    <TextInput
            style={{backgroundColor:"#ffffff"}}
            label="Enter Amount"
            mode="outlined"
            onChangeText={(text)=>this.setState({amount:text})}
            value={amount}
            keyboardType="numeric"
          />
           <TextInput
            style={{backgroundColor:"#ffffff"}}
            label="Enter Note"
            mode="outlined"
            onChangeText={(text)=>this.setState({note:text})}
            value={note}
          />

<View style={{alignItems:'center',marginTop:15}}>
           <Button  style={{width:"70%",}} mode="contained" onPress={() => {
             this.handleTransferFundsClicked();
             console.log('Pressed')}}>
            Transfer Funds
            </Button>
            </View>
       <Banner
              visible={transferFundsBanner}
              actions={[
                {
                  label: 'Copy Id',
                  onPress: () => {
                    this.setState({transferFundsBanner:false})
                    Clipboard.setString(transferFundsData)
                  },
                },
                {
                  label: 'Hide',
                  onPress: () => this.setState({transferFundsBanner:false}),
                },
              ]}
     >
      {transferFundsData}
    </Banner>    
       </Card>
      </ScrollView>
    </View>    

  )
}
}

If you run the app as it presently is, you’ll only see the user interface with nothing happening when you click a button because we haven’t yet added the code to enable several functionalities,
so lets do that now. Add the code below in the App Class, just before the render method:

handleConnectToNodelicked=()=>{
// gets the selected node from the state
  let{node,network}=this.state;
  if(node=="hackathon"){
    this.setState({connectToNode:true})
    //Creates a Client from the hackathon instance
    Algo.createClientFromHackathonInstance((error,result)=>{
      if(error){
        console.error(error);
        console.log("error")
        this.setState({connectToNodeData:error,  connectToNodeBanner:true,connectToNode:false})
        return;
      }
      console.log("Success")
      console.log(error);
      console.log(result);
      this.setState({connectToNodeData:result,  connectToNodeBanner:true,connectToNode:false})
    });
  }
   //Creates a Client from the Purestake node
  else if(node=="purestake"){
    if(network=="testnet"){
      this.setState({connectToNode:true})

      Algo.createClientFromPurestake("TESTNET",443,"Your-Purestake-Api-Key",(error,result)=>{
        if(error){
          this.setState({connectToNode:false})
          console.error(error);
          this.setState({connectToNodeData:error,  connectToNodeBanner:true,connectToNode:false})
          return;
        }
        console.log(result);
        this.setState({connectToNodeData:result,  connectToNodeBanner:true,connectToNode:false})
      });
    }else{
      this.setState({connectToNode:true})
      Algo.createClientFromPurestake("MAINNET",443,PURESTAKE_API_KEY,(error,result)=>{
        this.setState({connectToNode:false})
        if(error){
          console.error(error);
          this.setState({connectToNodeData:error,  connectToNodeBanner:true,connectToNode:false})
          return;
        }
        this.setState({connectToNodeData:result,  connectToNodeBanner:true,connectToNode:false})
        console.log(result);
      });

    }
  }else if(node=="customnode"){
    this.setState({connectToNode:true});
    let{customNodeAddress,customNodePort,customNodeToken}=this.state;
    Algo.createClientFromSandbox(customNodeAddress,parseInt(customNodePort),customNodeToken,
    (error,result)=>{
      if(error){
        console.error(error);
        this.setState({connectToNodeData:error,  connectToNodeBanner:true,connectToNode:false})
        return;
      }
      this.setState({connectToNodeData:result,  connectToNodeBanner:true,connectToNode:false})
      console.log(result);
    });
  }
}

handleRecoverAccountClicked=()=>{
//Gets the seedphrase from the state
  let{seedphraseInput}=this.state;
  console.log(seedphraseInput);
  //Uses the seedpgraqse to create an account
  Algo.recoverAccount(seedphraseInput.trim(),(error,result)=>{
    if(error){
      console.error(error);
      this.setState({recoverAccountData:error,recoverAcccountBanner:true})
      return;
    }
    console.log(result);
    this.setState({recoverAccountData:`Address : ${result.publicAddress}\n Mnemonic : ${result.mnemonic}`,recoverAcccountBanner:true});
  })
}

handleCreateAccountClicked=()=>{
//Creates a new Account by calling  createNewAccount function from the react-native-algo library and passing it a callback
  Algo.createNewAccount((result)=>{
    this.setState({createAccountData:`Address : ${result.publicAddress}\n Mnemonic : ${result.mnemonic}`});
    console.log(result);
  });

}

handleGetAccountBalanceClicked=()=>{
// Gets the account address from the state
  let{accountBalanceAddress}=this.state;
  this.setState({accountBalanceLoader:true})
  //Passes it  and a callback to the getAccountBalanceMethod of the react-native-algo library
  Algo.getAccountBalance(accountBalanceAddress,(error,result)=>{
    if(error){
      console.error(error);
      this.setState({accountBalanceData:error,accountBalanceLoader:false,accountBalanceBanner:true})
      return;
    }
    console.log(result);
    this.setState({accountBalanceData:`${result/1000000} Algos`,accountBalanceLoader:false,accountBalanceBanner:true})

  });

}

handleTransferFundsClicked=()=>{
// Gets the address, note and the amount from the state
  let{transferFundsAddress,note,amount}=this.state;
  this.setState({transferFundsLoading:true})
  //Passes it and a callback to the sendFunds method of the react-native-algo library
  Algo.sendFunds(transferFundsAddress, note,parseInt(amount)*1000000,
  (error,result)=>{
    if(error){
      console.error(error);
      this.setState({transferFundsData:error,transferFundsLoading:false,transferFundsBanner:true})
      return;
    }
    this.setState({transferFundsData:result,transferFundsLoading:false,transferFundsBanner:true})
    console.log(result);
  });
}

With the code we’ve written so far, all you need to do is to go to your project directory in terminal and run:

react-native run-android 

Wait for it to run the app and you should see your app running fine on your emulator.

Future Development

  • Indexer Capabilities
  • Application Call transaction support
  • iOS Support

See the contributing guide to learn how to contribute to the repository and the development workflow.

License

MIT