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