How do I create an activity feed?

We’d like to track and visualize our applications in a central place. Feeds are great for this! Let’s build an Android app with an activity feed showing the temperature of your home.

In this tutorial, using Pusher Channels, we are going to build a feed as an Android app to monitor the activity of a Node.js REST API. Every time an endpoint of the API is hit, it will publish an event with some information (let’s say temperatures) to a channel. This event will be received in realtime, on all the connected Android devices.

This is how our final Android app will look like:

How do I create an activity feed?

For the back-end, we will be using Node.js with Express to create a simple REST API. A basic knowledge of Node/Express is required to understand the code, but we won’t be using a database or anything special so you can replace this stack with the one you’re most comfortable with. The source code of this part is also available on Github.

So let’s get started!

Setting up Pusher

Create a free account with Pusher.

When you first log in, you’ll be asked to enter some configuration options:

How do I create an activity feed?

Enter a name, choose Android as your front-end tech, and Node.js as your back-end tech. This will give you some sample code to get you started:

How do I create an activity feed?

But don’t worry, this won’t lock you into this specific set of technologies, you can always change them. With Pusher, you can use any combination of libraries.

Then go to the App Keys tab to copy your App ID, Key, and Secret credentials, we’ll need them later.

The Node server

First, let’s create a default

{
  ...
    "dependencies": {
    "body-parser": "1.16.0",
    "express": "4.14.1",
    "pusher": "1.5.1"
  }
}
9 configuration file with:

npm init -y

We’ll need Express, Pusher, and other dependencies, let’s add them with:

npm install --save express body-parser pusher

In case a future version of a dependency breaks the code, here’s the dependencies section on the

{
  ...
    "dependencies": {
    "body-parser": "1.16.0",
    "express": "4.14.1",
    "pusher": "1.5.1"
  }
}
9 file:

{
  ...
    "dependencies": {
    "body-parser": "1.16.0",
    "express": "4.14.1",
    "pusher": "1.5.1"
  }
}

Next, create a server.js file. First, let’s require the modules we’re going to need:

var express = require('express');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var Pusher = require('pusher');

Then, configure the Express object:

var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

Next, the Pusher object is created by passing the configuration object with the ID, key, and the secret for the app created in the Pusher Dashboard:

var pusher = new Pusher({
  appId      : process.env.PUSHER_APP_ID,
  key        : process.env.PUSHER_APP_KEY,
  secret     : process.env.PUSHER_APP_SECRET,
  encrypted  : true,
});

Pusher will be used to publish any events that happen in our application. These events have a channel, which allows events to relate to a particular topic, an event-name used to identify the type of the event, and a payload, which you can attach any additional information to the message.

We are going to publish an event to a Pusher channel when an endpoint of our API is called to create/update/delete a record, and send the information as an attachment so we can show it in an activity feed.

Here’s the definition of our API’s REST endpoints. Notice how an ID for the record is created using the first four characters of the

var express = require('express');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var Pusher = require('pusher');
1 string generated by
var express = require('express');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var Pusher = require('pusher');
2 (to avoid using an external library):

app.post('/api', function (req, res) {
  var event = {
    data: req.body.data,
    id: crypto.randomBytes(16).toString('hex').substring(0, 4),
  };

  // Do something with the data...

  // Publish event to the Pusher channel
  pusher.trigger(channel, 'created', event);

  res.status(200).json(event);
});

app.route('/api/:id')
  // PUT  endpoint to update a record
  .put(function (req, res) {
    var event = {
     data: req.body.data,
     id: req.params.id,
    };

    // Do something with the data...

    // Publish event to the Pusher channel
    pusher.trigger(channel, 'updated', event);

    res.status(200).json(event);
  })

  // DELETE  endpoint to delete a record
  .delete(function (req, res) {
    var event = {
      id: req.params.id,
    };

    // Do something with the data...

    // Publish event to the Pusher channel
    pusher.trigger(channel, 'deleted', event);

    res.status(200).json(event);
  });

This way, a POST request like this:

{
  "data": "Temperature: 75°F"
}

Will return something like the following:

{
  "data": "Temperature: 75°F",
  "id": "d2t6"
}

We start the server with:

app.listen(3000, function () {
  console.log('Node server running on port 3000');
});

And that’s all. To run the server, execute the following command passing your Pusher credentials:

npm install --save express body-parser pusher
0

The android app

Open Android Studio and create a new project:

How do I create an activity feed?

We’re not going to use anything special, so we can safely support a low API level:

How do I create an activity feed?

Next, create an initial empty activity:

How do I create an activity feed?

And use the default name of

var express = require('express');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var Pusher = require('pusher');
3 with backward compatibility:

How do I create an activity feed?

Once everything is set up, let’s install the project dependencies. In the

var express = require('express');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var Pusher = require('pusher');
4 section of the
var express = require('express');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var Pusher = require('pusher');
5 file of your application module add:

npm install --save express body-parser pusher
1

At the time of this writing, the latest SDK version is 25, so that’s my target SDK version.

We’re going to use the

var express = require('express');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var Pusher = require('pusher');
6 and
var express = require('express');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var Pusher = require('pusher');
7 components from the Support Library, so make sure you have it installed (in Tools -> Android -> SDK Manager -> SDK Tools tab the Android Support Repository must be installed).

Sync the Gradle project so the modules can be installed and the project built.

Before we forget (I always do), let’s add the

var express = require('express');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var Pusher = require('pusher');
8 permission to the
var express = require('express');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var Pusher = require('pusher');
9 file. This is required so we can connect to Pusher and get the events in realtime:

npm install --save express body-parser pusher
2

If you want to modify the style of the app, in the

var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
0 folder, modify the colors.xml file so it looks like this:

npm install --save express body-parser pusher
3

As well as the styles.xml file to match these color definitions:

npm install --save express body-parser pusher
4

Now, modify the layout file activity_main.xml so it looks like this:

npm install --save express body-parser pusher
5

We’re going to use a RecyclerView to display the events, which we’ll store in a list. Each item in this list is displayed in an identical manner, so let’s define another layout file to inflate each item.

Create the file event_row.xml with the following content:

npm install --save express body-parser pusher
6

Here we’re using a CardView to show the information inside a card, with shadows and rounded corners. For each item, we’re going to present:

  • A
    var app = express();
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));
    
    1 for the name of the event (created, updated, or deleted).
  • A
    var app = express();
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));
    
    1 for the ID of the record (for example, c2d6).
  • A RelativeTimeTextView, a custom
    var app = express();
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));
    
    1 that displays the relative time with respect to the reference point (the moment the event is received), automatically refreshing the text as needed.
  • A
    var app = express();
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));
    
    1 for the data contained in the record (anything the user sends, for example, Temperature: 80°F).

Now, to store the information of each event, let’s create a class, com.pusher.feed.Event:

npm install --save express body-parser pusher
7

var express = require('express');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var Pusher = require('pusher');
6 works with an Adapter to manage the items of its data source (in this case a list of
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
6 instances), and a ViewHolder to hold a view representing a single list item, so first create the class com.pusher.feed.EventAdapter with the following code:

npm install --save express body-parser pusher
8

We initialize the class with a list of

var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
6, provide a method to add
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
6 instances at the beginning of the list (
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
9) and then notify the insertion so the view can be refreshed, and implement
var pusher = new Pusher({
  appId      : process.env.PUSHER_APP_ID,
  key        : process.env.PUSHER_APP_KEY,
  secret     : process.env.PUSHER_APP_SECRET,
  encrypted  : true,
});
0 so it returns the size of the list.

Then, let’s add the

var pusher = new Pusher({
  appId      : process.env.PUSHER_APP_ID,
  key        : process.env.PUSHER_APP_KEY,
  secret     : process.env.PUSHER_APP_SECRET,
  encrypted  : true,
});
1 as an inner class, it references the
var pusher = new Pusher({
  appId      : process.env.PUSHER_APP_ID,
  key        : process.env.PUSHER_APP_KEY,
  secret     : process.env.PUSHER_APP_SECRET,
  encrypted  : true,
});
2 components for each item in the list:

npm install --save express body-parser pusher
9

And implement the methods

var pusher = new Pusher({
  appId      : process.env.PUSHER_APP_ID,
  key        : process.env.PUSHER_APP_KEY,
  secret     : process.env.PUSHER_APP_SECRET,
  encrypted  : true,
});
3 and
var pusher = new Pusher({
  appId      : process.env.PUSHER_APP_ID,
  key        : process.env.PUSHER_APP_KEY,
  secret     : process.env.PUSHER_APP_SECRET,
  encrypted  : true,
});
4:

{
  ...
    "dependencies": {
    "body-parser": "1.16.0",
    "express": "4.14.1",
    "pusher": "1.5.1"
  }
}
0

In the

var pusher = new Pusher({
  appId      : process.env.PUSHER_APP_ID,
  key        : process.env.PUSHER_APP_KEY,
  secret     : process.env.PUSHER_APP_SECRET,
  encrypted  : true,
});
3 method, we inflate the layout with the content of the
var pusher = new Pusher({
  appId      : process.env.PUSHER_APP_ID,
  key        : process.env.PUSHER_APP_KEY,
  secret     : process.env.PUSHER_APP_SECRET,
  encrypted  : true,
});
6 file we created earlier, and in
var pusher = new Pusher({
  appId      : process.env.PUSHER_APP_ID,
  key        : process.env.PUSHER_APP_KEY,
  secret     : process.env.PUSHER_APP_SECRET,
  encrypted  : true,
});
4, we set the values of the views with the event in turn. Notice how we set the reference time on
var pusher = new Pusher({
  appId      : process.env.PUSHER_APP_ID,
  key        : process.env.PUSHER_APP_KEY,
  secret     : process.env.PUSHER_APP_SECRET,
  encrypted  : true,
});
8 so it can display a text like Just now or 10 minutes ago.

In the class com.pusher.feed.MainActivity, let’s start by defining the private fields we’re going to need:

{
  ...
    "dependencies": {
    "body-parser": "1.16.0",
    "express": "4.14.1",
    "pusher": "1.5.1"
  }
}
1

var express = require('express');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var Pusher = require('pusher');
6 works with a LayoutManager to handle the layout and scroll direction of the list. We declare the
app.post('/api', function (req, res) {
  var event = {
    data: req.body.data,
    id: crypto.randomBytes(16).toString('hex').substring(0, 4),
  };

  // Do something with the data...

  // Publish event to the Pusher channel
  pusher.trigger(channel, 'created', event);

  res.status(200).json(event);
});

app.route('/api/:id')
  // PUT  endpoint to update a record
  .put(function (req, res) {
    var event = {
     data: req.body.data,
     id: req.params.id,
    };

    // Do something with the data...

    // Publish event to the Pusher channel
    pusher.trigger(channel, 'updated', event);

    res.status(200).json(event);
  })

  // DELETE  endpoint to delete a record
  .delete(function (req, res) {
    var event = {
      id: req.params.id,
    };

    // Do something with the data...

    // Publish event to the Pusher channel
    pusher.trigger(channel, 'deleted', event);

    res.status(200).json(event);
  });
0, the
app.post('/api', function (req, res) {
  var event = {
    data: req.body.data,
    id: crypto.randomBytes(16).toString('hex').substring(0, 4),
  };

  // Do something with the data...

  // Publish event to the Pusher channel
  pusher.trigger(channel, 'created', event);

  res.status(200).json(event);
});

app.route('/api/:id')
  // PUT  endpoint to update a record
  .put(function (req, res) {
    var event = {
     data: req.body.data,
     id: req.params.id,
    };

    // Do something with the data...

    // Publish event to the Pusher channel
    pusher.trigger(channel, 'updated', event);

    res.status(200).json(event);
  })

  // DELETE  endpoint to delete a record
  .delete(function (req, res) {
    var event = {
      id: req.params.id,
    };

    // Do something with the data...

    // Publish event to the Pusher channel
    pusher.trigger(channel, 'deleted', event);

    res.status(200).json(event);
  });
1 object and the identifier for the Pusher channel. Remember to replace your Pusher app key, if you still don’t have one, this would be a good time to sign up for a free account and create you app.

Inside the

app.post('/api', function (req, res) {
  var event = {
    data: req.body.data,
    id: crypto.randomBytes(16).toString('hex').substring(0, 4),
  };

  // Do something with the data...

  // Publish event to the Pusher channel
  pusher.trigger(channel, 'created', event);

  res.status(200).json(event);
});

app.route('/api/:id')
  // PUT  endpoint to update a record
  .put(function (req, res) {
    var event = {
     data: req.body.data,
     id: req.params.id,
    };

    // Do something with the data...

    // Publish event to the Pusher channel
    pusher.trigger(channel, 'updated', event);

    res.status(200).json(event);
  })

  // DELETE  endpoint to delete a record
  .delete(function (req, res) {
    var event = {
      id: req.params.id,
    };

    // Do something with the data...

    // Publish event to the Pusher channel
    pusher.trigger(channel, 'deleted', event);

    res.status(200).json(event);
  });
2 method, let’s assign a LinearLayoutManager to the
var express = require('express');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var Pusher = require('pusher');
6 and create the
app.post('/api', function (req, res) {
  var event = {
    data: req.body.data,
    id: crypto.randomBytes(16).toString('hex').substring(0, 4),
  };

  // Do something with the data...

  // Publish event to the Pusher channel
  pusher.trigger(channel, 'created', event);

  res.status(200).json(event);
});

app.route('/api/:id')
  // PUT  endpoint to update a record
  .put(function (req, res) {
    var event = {
     data: req.body.data,
     id: req.params.id,
    };

    // Do something with the data...

    // Publish event to the Pusher channel
    pusher.trigger(channel, 'updated', event);

    res.status(200).json(event);
  })

  // DELETE  endpoint to delete a record
  .delete(function (req, res) {
    var event = {
      id: req.params.id,
    };

    // Do something with the data...

    // Publish event to the Pusher channel
    pusher.trigger(channel, 'deleted', event);

    res.status(200).json(event);
  });
0 with an empty list:

{
  ...
    "dependencies": {
    "body-parser": "1.16.0",
    "express": "4.14.1",
    "pusher": "1.5.1"
  }
}
2

For the Pusher part, we first subscribe to the channel:

{
  ...
    "dependencies": {
    "body-parser": "1.16.0",
    "express": "4.14.1",
    "pusher": "1.5.1"
  }
}
3

Then, we create the listener that will be executed when an event arrives:

{
  ...
    "dependencies": {
    "body-parser": "1.16.0",
    "express": "4.14.1",
    "pusher": "1.5.1"
  }
}
4

Here, the JSON string that we receive is converted to an

var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
6 object, the name of the event is set to the name of the event received, and the object is added to the adapter. Finally, we move to the top of the list.

Next, bind the events to this listener and call the

app.post('/api', function (req, res) {
  var event = {
    data: req.body.data,
    id: crypto.randomBytes(16).toString('hex').substring(0, 4),
  };

  // Do something with the data...

  // Publish event to the Pusher channel
  pusher.trigger(channel, 'created', event);

  res.status(200).json(event);
});

app.route('/api/:id')
  // PUT  endpoint to update a record
  .put(function (req, res) {
    var event = {
     data: req.body.data,
     id: req.params.id,
    };

    // Do something with the data...

    // Publish event to the Pusher channel
    pusher.trigger(channel, 'updated', event);

    res.status(200).json(event);
  })

  // DELETE  endpoint to delete a record
  .delete(function (req, res) {
    var event = {
      id: req.params.id,
    };

    // Do something with the data...

    // Publish event to the Pusher channel
    pusher.trigger(channel, 'deleted', event);

    res.status(200).json(event);
  });
6 method on the Pusher object:

{
  ...
    "dependencies": {
    "body-parser": "1.16.0",
    "express": "4.14.1",
    "pusher": "1.5.1"
  }
}
5

The

app.post('/api', function (req, res) {
  var event = {
    data: req.body.data,
    id: crypto.randomBytes(16).toString('hex').substring(0, 4),
  };

  // Do something with the data...

  // Publish event to the Pusher channel
  pusher.trigger(channel, 'created', event);

  res.status(200).json(event);
});

app.route('/api/:id')
  // PUT  endpoint to update a record
  .put(function (req, res) {
    var event = {
     data: req.body.data,
     id: req.params.id,
    };

    // Do something with the data...

    // Publish event to the Pusher channel
    pusher.trigger(channel, 'updated', event);

    res.status(200).json(event);
  })

  // DELETE  endpoint to delete a record
  .delete(function (req, res) {
    var event = {
      id: req.params.id,
    };

    // Do something with the data...

    // Publish event to the Pusher channel
    pusher.trigger(channel, 'deleted', event);

    res.status(200).json(event);
  });
6 method can take a listener that can be helpful to debug problems you might have:

{
  ...
    "dependencies": {
    "body-parser": "1.16.0",
    "express": "4.14.1",
    "pusher": "1.5.1"
  }
}
6

Finally,

var express = require('express');
var bodyParser = require('body-parser');
var crypto = require('crypto');
var Pusher = require('pusher');
3 also needs to implement the
app.post('/api', function (req, res) {
  var event = {
    data: req.body.data,
    id: crypto.randomBytes(16).toString('hex').substring(0, 4),
  };

  // Do something with the data...

  // Publish event to the Pusher channel
  pusher.trigger(channel, 'created', event);

  res.status(200).json(event);
});

app.route('/api/:id')
  // PUT  endpoint to update a record
  .put(function (req, res) {
    var event = {
     data: req.body.data,
     id: req.params.id,
    };

    // Do something with the data...

    // Publish event to the Pusher channel
    pusher.trigger(channel, 'updated', event);

    res.status(200).json(event);
  })

  // DELETE  endpoint to delete a record
  .delete(function (req, res) {
    var event = {
      id: req.params.id,
    };

    // Do something with the data...

    // Publish event to the Pusher channel
    pusher.trigger(channel, 'deleted', event);

    res.status(200).json(event);
  });
9 method so we can have the opportunity to unsubscribe from Pusher when the activity is destroyed:

{
  ...
    "dependencies": {
    "body-parser": "1.16.0",
    "express": "4.14.1",
    "pusher": "1.5.1"
  }
}
7

And that’s all the code on the Android part. Let’s test it.

Testing the app

Execute the app, either on a real device or a virtual one:

How do I create an activity feed?

You’ll be presented with an almost blank screen:

How do I create an activity feed?

For the back-end, you can use something to call the API endpoints with a JSON payload, like cURL:

{
  ...
    "dependencies": {
    "body-parser": "1.16.0",
    "express": "4.14.1",
    "pusher": "1.5.1"
  }
}
8

Or use a tool like Postman:

How do I create an activity feed?

When a request is received on the API side, the event will show up in the app:

How do I create an activity feed?

Or if you only want to test the app, you can use the Pusher Debug Console on your dashboard:

How do I create an activity feed?

Conclusion

Hopefully, this tutorial has shown you in an easy way how to build an activity feed for Android apps with Pusher. You can improve the app by changing the design, showing more information, or saving it to a database.

What are activity feeds?

Sometimes called a newsfeed or activity stream, an activity feed is a real-time list of actions performed by users on an app or website. Activity feeds display information from a user's online community such as likes, follows, comments, posts, and content shares.

How do I add an activity feed to Brightspace?

To add Activity Feed to a course homepage From a course's navbar, click Course Admin, and then Homepages. Edit an available homepage, copy an existing homepage to edit, or create a new homepage. In the Widgets section, from a large panel, click Add Widgets. Locate the Activity Feed widget, and click Add.

Where is the activity feed on Brightspace?

From the list of courses, tap the course you want to view. Tap the Activity Feed tab. Tap the post you want to comment on, and then tap Add Comment. A browser opens, displaying the Activity Feed for the course in Brightspace Learning Environment.