Build a Custom Timeline Feed with Kotlin on Android

In this post, we'll create a simple social network, called "The Stream", that allows a user to post messages to followers.

The app will allow a user to post a message to their followers. Stream's Activity Feed API, combined with Android, makes it straightforward to build this sort of complex interaction. All source code for this application is available on GitHub. This application is fully functional on Android.

Often there is context around those code snippets, which are essential, such as layout or navigation. Please refer to the full source if you're confused about how something works, what libraries and versions are used, or how we got to a screen. Each snippet will be accompanied by a comment explaining which file and line it came from.

Building "The Stream"

To build our social network, we'll need both a backend and a mobile application. Most of the work is done in the mobile app, but we need the backend to create frontend tokens for interacting with the Stream API securely.

For the backend, we'll rely on Express (Node.js) leveraging Stream Feed's JavaScript library.

For the frontend, we'll build it with Kotlin wrapping Stream Feed's Java library.

There are two main actions a user takes, posting, and viewing messages. To post a message, the mobile application goes through his flow:

  • User types their name into our mobile application to log in.
  • The Android app registers the user with our backend and receives a Stream Activity Feed frontend token.
  • User types in their message and hits "Post". The mobile app uses the Stream token to create a Stream activity on their user feed via Stream's REST API using the Java library.
  • User views their posts. The mobile app does this by retrieving its user feed via Stream.

Here's what happens if another user wants to follow a user and view their messages:

  • User logs in.
  • User navigates to the user list and selects a user to follow. The mobile app communicates with Stream API to create a follower relationship on their timeline feed.
  • User views their timeline. The mobile app uses Stream API to retrieve their timeline feed, which is composed of all the messages from who they follow.

The code is split between the Android application contained in the android directory, and the Express backend is in the backend directory. See the README.md in each folder to see installing and running instructions. If you'd like to follow along with running code, make sure you get both the backend and mobile app running before continuing.

Prerequisites

Basic knowledge of Node.js (JavaScript) and Android (Kotlin) is required to follow this tutorial. This code is intended to run locally on your machine.

If you'd like to follow along, you'll need an account with Stream. Please make sure you can build a simple Android app before embarking on this tutorial. If you haven't done so, make sure you have Android Studio installed.

Once you have an account with Stream, you need to set up a development app. This is done from your dashboard:

Create App

You'll need to add the credentials from the Stream app to the backend .env file and start the server for the mobile application to work. See the backend README.md for more information.

Let's get to building!

User Posts a Message

We'll start with a user posting a message.

Step 1: Log In

To communicate with the Stream API, we need a secure frontend token that allows our mobile application to authenticate with Stream directly. This avoids having to proxy all calls through the backend (which is another way of building your application). To do this, we'll need a backend endpoint that uses our Stream account secrets to generate this token. Once we have this token, we don't need the backend to do anything else, since the mobile app has access to the Stream API limited by that user's permissions.

First, we'll be building the login screen which looks like:

Login

To start, let's lay our form out in Android. In our activity_main.xml layout, we have a simple ConstraintLayout with an EditText and Button:

https://gist.github.com/nparsons08/4c315229700a403f88f3a4a84414188f

Let's bind to this layout and respond in MainActivity:

https://gist.github.com/nparsons08/ddb97df95244ca9ba6df89c1015fdbef

Note: The asynchronous approach in this tutorial is not necessarily the best or most robust approach. It's merely a straightforward way to show async interactions without cluttering the code too much. Please research and pick the best asynchronous solution for your application.

Here we bind to our button and user input. We listen to the submit button and sign in to our backend. Since this work is making network calls, we need to do this asynchronously. We use Kotlin's coroutines to accomplish this by binding to the MainScope. We dispatch our sign-in code which tells our BackendService to perform two tasks, sign in to the backend and get the feed frontend credentials. We'll look at how the BackendService accomplishes this in a second.

Once we have our tokens, we initialize our FeedService so we can talk to Stream's API (we'll see this in a second as well). When the user is fully authed, and we have our credentials, we start a new activity called AuthedMainActivity, which is the rest of the application.

Before seeing how we post a message, let's see how we auth and initialize the Stream Feed. First, we sign in to the backend via BackendService.signIn:

https://gist.github.com/nparsons08/2c0ed1f45f8a406e07a1ffbe98ad766d

We make a simple POST HTTP request to our backend endpoint /v1/users, which returns a backend authToken that allows the mobile application to make further requests against the backend. Since this not a real implementation of auth, we'll skip the backend code. Please refer to the source if you're curious. Also, keep in mind, this token is not the Stream token. We need to make another call for that.

Once the user is signed in with our backend we can get our feed credentials via BackendService.getFeedCredentials():

https://gist.github.com/nparsons08/8aeb11f185f17509305bab89ae4e9f0a

Similar to before, we POST to our backend to get our feed credentials. The one difference being we use our authToken to authenticate against our backend. Since this backend endpoint creates a Stream user for us, let's take a look:

https://gist.github.com/nparsons08/cff53ce8ed377b2d23a9ed81107f2dc2

We use the Stream JavaScript library to create a user (if they don't exist) and generate a Stream frontend token. We return this token, alongside some API information, back to the Android app.

In the mobile app, we use the returned credentials to initialize our FeedService by calling FeedService.init in MainActivity. Here's the init:

https://gist.github.com/nparsons08/0debaf7f469085e833b9d32289a4b675

The FeedService is a singleton (by using Kotlin's object) which stores a CloudClient instance. CloudClient is a class provided by Stream's Java library. This class is specifically used to provide the functionality to client applications via frontend tokens. Stream's Java library contains a normal client for backend applications, so don't get confused about which client to use. The normal client requires private credentials that you don't want to embed in your mobile application!

Now that we're authenticated with Stream, we're ready to post our first message!

Step 2: Posting a Message

Now we'll build the form to post a status message to our Stream activity feed. We won't dive into navigation and layout in this tutorial. Please refer to the source if you're curious about how we get to this screen or skip ahead to Step 3. We'll need to build a form that takes what the user wants to say to their followers and submit that to Stream. We use an activity called CreatePostActivity to handle new posts:

https://gist.github.com/nparsons08/ccd50ec23c9b6df8743feafaf62f4117

With the layout:

https://gist.github.com/nparsons08/d6988b73650bf5700753087cb1073f55

This simple layout is identical to our log in widget. When bind to our submit and take the EditText text value, send it to FeedService.post. When completed, we set a success result and finish the activity. Let's look at FeedService.post:

https://gist.github.com/nparsons08/7550ce9c78b9b9541351fb68cb898d7a

Here we use Stream Java's CloudClient from the Cloud package. This set of classes takes our frontend token, which allows the mobile app to communicate directly with Stream. We are authenticated only to post activities for the actor SU:john (SU means Stream User). Since we aren't storing a corresponding object in a database, we generate an id to keep each post unique. We also pass along a message payload, which is what our followers will see.

You may be wondering what the client.flatFeed("user") is referring to. For this to work, we need to set up a flat feed called "user" in Stream. This is where every user's feed, which only contains their messages, will be stored. Later we'll see how one user can follow another user's feed.

Inside of your Stream development app create a flat feed called "user":

Create User Feed

That's all we need for Stream to store our messages. Once all of the functions return, we return to the user's profile screen to display our posted messages. We'll build this next.

Step 3: Displaying Messages on our Profile

Let's first build a fragment that will contain the user's messages. Here is what the screen will look like:

Profile

And here is the code for the ProfileFragment:

https://gist.github.com/nparsons08/e520112f2166893ff4f9ba6c227245ca

And the layout:

https://gist.github.com/nparsons08/7bcaf9714bb9c722f43274b74280cd4b

The ProfileFragment does two things. First, it has a floating action button that starts our CreatePostActivity, which we saw in Step 2, and handles the result. Second, it loads their feed and displays those messages.

Let's see how we load our messages via FeedService.profileFeed() invoked inside of loadProfileFeed:

https://gist.github.com/nparsons08/21f61e855b814fe869c017e4e65f8eba

Using this result, we pass the activities into a FeedAdapter which backs a simple ListAdapter. Here you can see how to use the raw results from Stream however you want. In this case, we'll simply display the activity's message and author:

https://gist.github.com/nparsons08/7a4dffde668339ca9ff6aa2afb3484d9

To keep things simple, we use a simple list layout, with a custom view (feed_item, please see source) that shows the message and author. However, you can build any view you want! Here, the message is in extras to show you how to include arbitrary data in your activities. The extras container allows you to attach any data you want to an Activity.

Next, we'll see how to follow multiple users via a timeline feed.

User Timeline

Now that users can post messages, we'd like to follow a few and see a combined feed of all the messages for users we follow.

Step 1: Follow a User

The first thing we need to do is view a list of users and pick a few to follow. We'll start by creating a view that shows all the users and lets a user follow a few. Here is the screen that shows all the users:

Users

This is backed by a PeopleFragment:

https://gist.github.com/nparsons08/8427f6452783abb8009f6a12c83c4413

And the layout:

https://gist.github.com/nparsons08/929bac4823789cbd1e116160f8dea9fb

The PeopleFragment is a simple list view that displays our people. To populate the users on load, we use BackendService.getUsers() which is a simple GET request against our backend:

https://gist.github.com/nparsons08/c5001b717495bb06e673bf61edbc5e03

The backend is a mock implementation that simply stores the users in an object. We won't go into this here, but refer to source if exciting and be sure to back this with a real implementation in your production application.

Once we have our users, we build our list and bind a click listener. This listener will pop open an alert dialog that allows us to follow a user. If a user chooses to follow a user, we call FeedService.follow:

https://gist.github.com/nparsons08/3dc1fd6acea2dab343666b2c8083d4fc

Here we're adding a follow relationship to another user's user feed to this user's timeline feed. All this means is anytime a user posts to their user feed (implemented in the first part), we'll see it on our timeline feed. The cool part is, we can add any number of users feeds to our timeline feed, and Stream will return a well-ordered list of activities.

Since we have a new feed type, we need to set this up in Stream. Just like the user feed, navigate to the Stream app you set up and create a flat feed group called timeline:

Create Timeline Feed

Step 2: View Timeline

Now that we have a way to follow users, we can view our timeline. When we're done, assuming we've followed "bob" and "sara" we'll see a screen that looks like this:

Timeline

Let's look at the code to display our timeline. We have a TimelineFragment:

https://gist.github.com/nparsons08/84efc4e44af6e7dc15a7e2baabac5831

And our layout:

https://gist.github.com/nparsons08/7f35aa07b3237656053c41a23a02773a

Since we already built our FeedAdapter, we simply need to get the activities for timeline via FeedService.timelineFeeed:

https://gist.github.com/nparsons08/9c22d440c1ea5b9946adeb2d23932006

This code is the same as getting our profile, except we ask for our timeline feed instead. And that's it! We now have a fully functioning mini social network powered by Stream.

TutorialsFeeds