A few days ago I released, what turned out to be, a very popular article around developing a Ripple XRP wallet. It was titled, Create a Cross-Platform Desktop Ripple XRP Wallet with Vue.js and Electron, and it focused on cross-platform desktop application development. A popular request on Twitter was around developing a mobile Android and iOS wallet for Ripple XRP coins.
We’re going to see how to use Ionic Framework and Angular to create a Ripple XRP wallet for Android and iOS. Because our desktop application focused on the JavaScript stack, our mobile application will be able to recycle a lot of our code.
There are a lot of mobile frameworks that make use of JavaScript. For example, React Native, NativeScript, and Ionic Framework are all options for mobile development with JavaScript. However, the Ripple library is only Node.js or browser compatible. You might be wondering, well if it is Node.js compatible, what is the problem? The problem is that it uses libraries that aren’t yet available for native frameworks like NativeScript and React Native. Because Ionic Framework operates within a packaged web browser, we’ll be able to use the browser compatible version of the library.
So what do we plan on building in this example? Take a look at the screenshot below.
We’re trying to reproduce what we saw in the previous example, but this time mobile friendly. We’re going to take an address, look up the balance, and compare it to the current market value using libraries and public APIs.
At this point we’re going to assume your computer is properly configured for Android and iOS development and that you have the Ionic Framework CLI installed. Using the CLI, create a new project with the following command:
ionic start ionic-ripple-wallet blank
This will leave us with a clean Ionic Framework project, which as of now, is version 3.9.2. We won’t be including any Node Package Module (NPM) dependencies for this particular project.
At this point we can get our JavaScript library for Ripple and start implementing some logic.
Before we can start developing, we need to build the Ripple library for use in web browsers. Remember, our previous example had full Node.js support, so we could use NPM. We don’t have that luxury for this example as we’ll get errors if we tried.
Outside your Ionic Framework project, execute the following commands:
git clone https://github.com/ripple/ripple-lib
cd ripple-lib
npm install
npm run build
The above commands will clone the latest ripple-lib project code and build for browser usage. You’ll end up with a build directory with several different JavaScript files.
For best results, we’re going to work with the versioned file. In my scenario, I’ll be using ripple-0.17.9.min.js, but feel free to use the latest or whatever version you’ve built.
Take the ripple-0.17.9.min.js file and copy it to your Ionic Framework project’s src/assets/js/ directory.
The Ripple library has a dependency on lodash so we’ll need to download it as well. Download lodash.js and place it in your Ionic Framework project’s src/assets/js/ directory.
With the Ripple and lodash libraries in place, let’s start development.
We’re going to start with the easy stuff such as project preparation before we jump into some cool business logic. Open the project’s src/index.html file and include the following two lines:
<script src="assets/js/lodash.js"></script>
<script src="assets/js/ripple-0.17.9-min.js"></script>
For clarity, your src/index.html file should look something like the following in the end:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8">
<title>Ionic App</title>
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<link rel="icon" type="image/x-icon" href="assets/icon/favicon.ico">
<link rel="manifest" href="manifest.json">
<meta name="theme-color" content="#4e8ef7">
<!-- add to homescreen for ios -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<script src="assets/js/lodash.js"></script>
<script src="assets/js/ripple-0.17.9-min.js"></script>
<!-- cordova.js required for cordova apps (remove if not needed) -->
<script src="cordova.js"></script>
<!-- un-comment this code to enable service worker
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js')
.then(() => console.log('service worker installed'))
.catch(err => console.error('Error', err));
}
</script>-->
<link href="build/main.css" rel="stylesheet">
</head>
<body>
<!-- Ionic's root component and where the app will load -->
<ion-app></ion-app>
<!-- The polyfills js is generated during the build process -->
<script src="build/polyfills.js"></script>
<!-- The vendor js is generated during the build process
It contains all of the dependencies in node_modules -->
<script src="build/vendor.js"></script>
<!-- The main bundle js is generated during the build process -->
<script src="build/main.js"></script>
</body>
</html>
We’ve added the scripts to the <head>
because we want them to be loaded and available before Angular starts doing its magic. Since we’ll be issuing HTTP requests, we’ll need to add the Angular HTTP module.
Open the project’s src/app/app.module.ts file and make it look like the following:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { HttpModule } from "@angular/http";
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
@NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp),
HttpModule
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
You’ll notice that we’ve just imported the HttpModule
and added it to the imports
array of the @NgModule
block. Now we’ll be able to use it in our components.
Alright, this is where the actual development begins.
Open the project’s src/pages/home/home.ts file and include the following TypeScript. Don’t worry, we’ll break it down after.
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { Http } from "@angular/http";
import { Observable } from "rxjs";
declare var ripple: any;
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
private api: any;
public address: any;
public wallet: any;
public market: any;
public constructor(public navCtrl: NavController, private http: Http) {
this.api = new ripple.RippleAPI({ server: "wss://s1.ripple.com/" });
this.address = "rn7HJDaYaEcDJvcELQFbnGXAEo2YA1RLNc";
this.wallet = {
balance: 0,
usd: 0
};
this.market = {};
}
public ngOnInit() {
Observable.forkJoin([this.getWalletBalance(), this.getMarketValue()])
.subscribe(result => {
this.wallet.balance = result[0].value;
this.wallet.usd = (result[0].value * result[1].price_usd).toFixed(2)
this.market = result[1];
});
}
public getWalletBalance() {
return Observable
.fromPromise(this.api.connect())
.switchMap(() => Observable.fromPromise(this.api.getBalances(this.address)))
.map(balance => balance[0])
.do(() => this.api.disconnect());
}
public getMarketValue() {
return this.http.get("https://api.coinmarketcap.com/v1/ticker/ripple/")
.map(result => result.json())
.map(result => result[0]);
}
}
So what is happening in the above code? Notice the following line:
declare var ripple: any;
Remember, we’ve included our libraries in the src/index.html file rather than NPM. For this reason we’ll have to declare the library variable to prevent TypeScript compilation errors.
In the constructor
method we are initializing our variables:
public constructor(public navCtrl: NavController, private http: Http) {
this.api = new ripple.RippleAPI({ server: "wss://s1.ripple.com/" });
this.address = "rn7HJDaYaEcDJvcELQFbnGXAEo2YA1RLNc";
this.wallet = {
balance: 0,
usd: 0
};
this.market = {};
}
We are defining the Ripple server and an address. I’ve provided my own public address, rn7HJDaYaEcDJvcELQFbnGXAEo2YA1RLNc, but you could easily use the ripple-keypairs library to get your address from your private key.
To get your wallet balance, you’d call the getWalletBalance
function:
public getWalletBalance() {
return Observable
.fromPromise(this.api.connect())
.switchMap(() => Observable.fromPromise(this.api.getBalances(this.address)))
.map(balance => balance[0])
.do(() => this.api.disconnect());
}
To keep things Angular friendly, we’re going to work with observables rather than promises. We create a new observable, convert our connection promise into an observable, switch to another observable stream after connecting, the new observable stream will get our balance, and then we’ll disconnect from the API. When we subscribe to this observable, everything in the chain happens. Welcome to RxJS and reactive extensions!
The getMarketValue
function is a little simpler:
public getMarketValue() {
return this.http.get("https://api.coinmarketcap.com/v1/ticker/ripple/")
.map(result => result.json())
.map(result => result[0]);
}
For to get the market value of Ripple, we just call the CoinMarketCap API and transform the result. Nothing fancy is happening here.
Using the two functions is where things get interesting. When the Angular ngOnInit
lifecycle hook triggers, we have the following:
public ngOnInit() {
Observable.forkJoin([this.getWalletBalance(), this.getMarketValue()])
.subscribe(result => {
this.wallet.balance = result[0].value;
this.wallet.usd = (result[0].value * result[1].price_usd).toFixed(2)
this.market = result[1];
});
}
We want to subscribe to both observables in parallel similar to Promise.all
and this can be done with a forkJoin
operator. When the observables have finished, we can do some basic math to figure out the dollar value for our XRP coins.
Now we can jump over to the HTML markup. Open the project’s src/pages/home/home.html and include the following:
<ion-header>
<ion-navbar>
<ion-title>
XRP Wallet
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-card>
<ion-card-content>
<h1>{{ wallet.balance }} XRP</h1>
<p>{{ wallet.usd }} USD</p>
</ion-card-content>
</ion-card>
<ion-card>
<ion-list>
<ion-item>Price: ${{ market.price_usd }}</ion-item>
<ion-item>Market Cap: ${{ market.market_cap_usd }}</ion-item>
<ion-item>1H Change: {{ market.percent_change_1h }}%</ion-item>
<ion-item>24H Change: {{ market.percent_change_24h }}%</ion-item>
<ion-item>7D Change: {{ market.percent_change_7d }}%</ion-item>
</ion-list>
</ion-card>
</ion-content>
<ion-footer>
<ion-toolbar>
<p style="padding: 0 12px">2017 © thepolyglotdeveloper.com</p>
</ion-toolbar>
</ion-footer>
Nothing fancy is happening in the above markup. We’re using some Ionic Framework tags and rendering our public variables to the screen.
At this point, you should be able to build for either Android or iOS.
You just saw how to create a mobile Ripple XRP coin wallet for Android and iOS using web technologies with Ionic Framework. While not a native mobile application, we chose Ionic Framework and the WebView because ripple-lib was browser friendly and not compatible with the native frameworks.
If you wanted to, you could take the previous Vue.js with Electron tutorial, swap out Vue.js, and include Angular. You could even rely strictly on the renderer process with the browser version of the library.
My public Ripple XRP address is rn7HJDaYaEcDJvcELQFbnGXAEo2YA1RLNc if you’re interested in donating some coin.