Building a social app with online presence using Ionic
You will need Node and Ionic installed on your machine. A basic knowledge of modern JavaScript development will be helpful.
In this tutorial, we are going to explore how to build a simple social application using Ionic and Pusher. Ionic will be used to create the mobile demo and Pusher Channels to add online functionality. At the end of this tutorial, you will have built an application that looks like this:
A while back on Twitter, the official Twitter handle teased their followers with a sample of an interesting idea they were exploring. They were exploring the possibility of letting you know who on your Twitter feed is online. This is something very different and it got a lot of mixed reactions because we know Twitter for for likes and retweets but not “online five minutes ago”. In other messaging applications we use, it is important to know if who you’re interacting with is online. As a developer, you are likely curious about how this all works.
Requirements
To be follow through this article properly, you’ll need the following:
- Basic knowledge of JavaScript
- Ionic installed on your machine, you can read up on how to do that here
- Node.js installed on your machine
- NPM installed on your machine
Getting started
To get started, create a new ionic application using the command:
ionic start twi-clone blank
This creates a starter ionic project in a folder titled twi-clone
. To see the demo application at work, go to your terminal and run the command:
cd twi-clone
ionic serve
This serves the Ionic application. Navigate your browser to http://locahost:8100
and you should get a view that looks like this:
Building the backend server
The backend server of our application will be responsible for doing the following:
- Sharing users’ posts
- Authenticating new users on the presence channel
Create a /server
folder in your project:
mkdir server
cd server
Install the Node modules that we will need to power the backend server:
npm install express body-parser pusher
express
will power the web serverbody-parser
to handle incoming requestspusher
to add realtime functionality and online presence
Afterwards, create a server.js
file in the server/
directory:
touch server.js
Edit the server.js
file to look as follows:
// server/server.js
const express = require('express')
const bodyParser = require('body-parser')
const Pusher = require('pusher');
// create a express application
const app = express();
// initialize pusher
let pusher = new Pusher({
appId: 'PUSHER_APP_ID',
key: 'PUSHER_APP_KEY',
secret: 'PUSHER_APP_SECRET',
cluster: 'PUSHER_APP_CLUSTER',
encrypted: true
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
// to Allow CORS
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
next();
});
[...]
We include the necessary JavaScript libraries and then create a new Pusher object using your Pusher application credentials.
To obtain your Pusher credentials, create a new account here. Afterwards, you’ll be redirected to your Pusher dashboard. Go ahead and create a new project, obtain your
PUSHER_APP_ID
,PUSHER_APP_KEY
,PUSHER_APP_SECRET
,PUSHER_APP_CLUSTER
and add them to yourserver.js
file.
Afterwards, we specify some application middleware to handle incoming requests. The backend server will have two routes:
/pusher/auth
- will be used to authenticate users joining the presence channel/create-post
- will be used when a new post is created.
With Pusher, when a new client tries to join a presence channel, a POST
request is first made to authenticate the new client. In this case, a random string is created to identify the client and this makes up the presenceData
object. The presenceData
, channel
and socketId
are then passed to Pusher to authenticate the client.
The /create-post
route accepts the incoming data and then triggers a new-post
event to the presence-channel
.
In later parts of the article, we will see how the channel is created in our Ionic application
Add the code below to your server/server.js
file:
// server/server.js
[...]
app.post('/pusher/auth', (req, res) => {
let socketId = req.body.socket_id;
let channel = req.body.channel_name;
random_string = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);
let presenceData = {
user_id: random_string,
user_info: {
username: '@' + random_string,
}
};
let auth = pusher.authenticate(socketId, channel, presenceData);
res.send(auth);
});
app.post('/create-post', (req, res) => {
// trigger a new post event via pusher
pusher.trigger('presence-channel', 'new-post', {
'username': req.body.username,
'content': req.body.content
})
res.json({ 'status': 200 });
});
let port = 3128;
app.listen(port);
console.log('listening');
Now that the backend server is created, you can run it by entering the command:
node server.js
Now, let’s look at how to build the rest of the application.
Creating the interface
The starter application created a default homepage which we will then update to fit our particular use case. Update your home.html
file to look like this:
<!-- src/pages/home/home.html -->
<ion-header>
<ion-navbar>
<ion-title style="text-align: center">
Let's Go Social
</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<form (ngSubmit)="submitPost()">
<div class="compose-post-area">
<ion-textarea class="post-compose" placeholder="Post something...." [(ngModel)]="post.content" name="content"></ion-textarea>
<button ion-button round class="tweet-post" type="submit">POST</button>
</div>
</form>
<div>
<ion-card>
<ion-item>
<h3>Hi <i>@{{ current_user }}</i> </h3>
<h3>Friends Online: {{ get_users_online() }}</h3>
</ion-item>
</ion-card>
<div *ngFor="let post of post_list">
<ion-card>
<ion-item>
<ion-avatar item-start>
<img src="https://api.adorable.io/avatars/100/avatar.png" alt="">
</ion-avatar>
<p>@{{ post.username }} <i>{{ isOnline(post.username) }} </i></p>
</ion-item>
<ion-card-content>
<p>{{ post.content }}</p>
</ion-card-content>
<ion-row>
<ion-col>
<button ion-button ion-start clear small>
<ion-icon name="repeat"></ion-icon>
<div> 932 reposts</div>
</button>
</ion-col>
<ion-col>
<button ion-button ion-start clear small color="danger">
<ion-icon name="heart"></ion-icon>
<div> 12k likes</div>
</button>
</ion-col>
</ion-row>
</ion-card>
</div>
</div>
</ion-content>
The page also has the following styling:
# src/page/home/home.scss
.compose-post-area{
display: flex;
align-items: center;
}
ion-content{
background-color: #FAFAFA;
}
We have seen the interface of the application. To add any functionality to our application, we need to edit the home.ts
file to look like this:
// src/app/pages/home/home.ts
import { Component } from '@angular/core';
import { NavController, AlertController } from 'ionic-angular';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
// this will represent a new post by a user
post: any = {};
// this will represent your default pusher presence channel
presence_channel: any;
// this will represent the username of the current user
current_user;
// this will online a list of users online
users_online = {};
// list of default posts
post_list = [
{
'username': 'og',
'content': 'Making money was the plan oooo'
},
{
'username': 'daddywon',
'content': 'You can catch me on the express'
}
];
constructor(public navCtrl: NavController) {
[...]
}
[...]
}
We can see that the home page component contains the variables that were referenced in the homepage template.
Creating the Pusher service
To know the number of friends online and when someone who shares a post is online, let’s make use of Presence Channels by Pusher. To use Pusher in our Ionic application, we need to install the library using the command:
npm install pusher-js
Now, let’s create a simple Pusher provider. To do this, head over to your terminal and run the command:
ionic generate provider pusher-service
Now, a pusher-service
provider has been created. In the pusher-service.ts
create a new Pusher object in the constructor by specifying the PUSHER_APP_KEY
, PUSHER_APP_CLUSTER
and the authEndpoint
created on our backend server earlier in the article.
Afterwards, subscribe to the presence-channel
. The init()
function is then responsible for returning the created presence channel which is to be used in our home.ts
file. Update your pusher-service.ts
to look like this:
// src/providers/pusher-service/pusher-service.ts
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import Pusher from 'pusher-js';
@Injectable()
export class PusherServiceProvider {
presenceChannel;
constructor(public http: HttpClient) {
let pusher = new Pusher('PUSHER_APP_KEY', {
authEndpoint: 'http://localhost:3128/pusher/auth',
cluster: 'PUSHER_APP_CLUSTER'
});
this.presenceChannel = pusher.subscribe('presence-channel');
}
public init() {
return this.presenceChannel;
}
}
Update your app.module.ts
to ensure that the Pusher provider and the HttpClientModule are loaded:
// src/app/app.module.ts
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 { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { PusherServiceProvider } from '../providers/pusher-service/pusher-service';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
BrowserModule,
HttpClientModule,
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
StatusBar,
SplashScreen,
{ provide: ErrorHandler, useClass: IonicErrorHandler },
PusherServiceProvider
]
})
export class AppModule { }
Now, let’s go back to the home page component.
Adding realtime functionality and online presence with Pusher
We are going to update the home.ts
file to allow users know when other users are online. We first get the Pusher Channel using the Pusher provider we created earlier and then listen for the pusher:subscription_succeeded
event. Once the client has successfully subscribed to the presence-channel
, a members
object is returned that contains information about the people subscribed to the presence-channel
.
We also listen for a new-post
event on the presence-channel
. When a new-post
event is triggered, the post_list
is then updated to contain the new post. At this point, your home.ts
should look like this:
// src/pages/home/home.ts
import { Component } from '@angular/core';
import { NavController, AlertController } from 'ionic-angular';
import { PusherServiceProvider } from '../../providers/pusher-service/pusher-service';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
// this will represent a new post by a user
post: any = {};
// this will represent your default pusher presence channel
presence_channel: any;
// this will represent the username of the current user
current_user;
// this will online a list of users online
users_online = {
};
// list of default posts
post_list = [
{
'username': 'og',
'content': 'Making money was the plan oooo'
},
{
'username': 'daddywon',
'content': 'You can catch me on the express'
}
];
constructor(public navCtrl: NavController, private pusher: PusherServiceProvider, private http: HttpClient, public alertCtrl: AlertController) {
let self = this
this.presence_channel = this.pusher.init();
// update the list of users online
this.presence_channel.bind('pusher:subscription_succeeded', function (members) {
console.log(members);
self.users_online = members.members;
self.current_user = members.myID;
})
this.presence_channel.bind('new-post', function (body) {
self.post_list.unshift(body);
})
}
[...]
}
Finally, we have three other methods in the component:
get_users_online()
which returns the number of users that are currently onlineisOnline()
that checks if a particular user is currently onlinesubmitPost()
that submits a post by a user
Add the methods to your home.ts
file:
// src/pages/home/home.ts
[...]
get_users_online() {
return Object.keys(this.users_online).length - 1;
}
isOnline(username: string) {
if (username in this.users_online) {
return 'online'
} else {
return 'offline'
}
}
submitPost() {
let self = this;
// make a post request to the server
let body = {
'username': this.current_user,
'content': this.post.content
}
const alert = this.alertCtrl.create({
title: 'Post Shared!',
subTitle: `Users online to see your post: ${self.get_users_online()}`,
buttons: ['OK']
});
// submit post to the backend server to trigger pusher event
this.http.post('http://localhost:3128/create-post', body).subscribe(() => {
alert.present();
});
}
}
Recall in the home.html
interface, we had the following form:
<form (ngSubmit)="submitPost()">
<div class="compose-post-area">
<ion-textarea class="post-compose" placeholder="Post something...." [(ngModel)]="post.content" name="content"></ion-textarea>
<button ion-button round class="tweet-post" type="submit">POST</button>
</div>
</form>
When the POST
button is clicked, the submitPost()
function is called and the post content and username of the current user are sent to the /create-post
of the backend server which then triggers the new-post
event on the presence-channel
and the post_list
is updated accordingly.
To see it all at work, serve your Ionic application using the command:
npm start # or ionic serve
Ensure your backend server is on.
Navigate on your browser to http://localhost:8100
and you should get an experience that looks like this:
Testing your Ionic application on a mobile device
If you’re building with Ionic, you generally don’t intend to have your application on your browser. Rather, you’d want to have it running on mobile devices. Before you do that, you should serve the backend of your application on a tunnel using ngrok. Head over here and follow the download instructions for your platform.
After you’ve had it installed, tunnel your backend server using the command:
ngrok http 3128
You should get a view that looks like this:
ngrok by @inconshreveable (Ctrl+C to quit)
Session Status online
Session Expires 7 hours, 59 minutes
Version 2.2.8
Region United States (us)
Web Interface http://127.0.0.1:4040
Forwarding http://b3b88c11.ngrok.io -> localhost:3128
Forwarding https://b3b88c11.ngrok.io -> localhost:3128
Connections ttl opn rt1 rt5 p50 p90
0 0 0.00 0.00 0.00 0.00
This means that you can now access your backend server using the public URL → https://b3b88c11.ngrok.io
Update the authEndpoint
of your pusher-service.ts
to use the new public URL:
// src/providers/pusher-service/pusher-service.ts
[...]
let pusher = new Pusher('9effdb6e1245bda33b17', {
authEndpoint: 'https://b3b88c11.ngrok.io/pusher/auth',
cluster: 'mt1'
});
[...]
Also update the POST request in the submitPost
of your home.ts
file:
// src/pages/home/home.ts
[...]
// submit post to the backend server to trigger pusher event
this.http.post('http://localhost:3128/create-post', body).subscribe(() => {
alert.present();
});
[...]
To test the application on your mobile device, download the IonicDevApp on your mobile device. Ensure that your device and computer are on the same network and you should see this demo application listed there:
Now, testing with the device, we get the following:
Conclusion
In this tutorial, we saw how to use Pusher Channels and Ionic to build a social media application that lets you know who’s online and how many online friends your post reach when they’re shared. There are many more use cases for Pusher Channels, so feel free to use concepts shared here in your own application. Here’s a link to the GitHub repository.
4 December 2018
by Oreoluwa Ogundipe