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 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
userfeed 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
- User views their timeline. The mobile app uses Stream API to retrieve their
timelinefeed, 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.
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:
You'll need to add the credentials from the Stream app to the
.env file and start the server for the mobile application to work. See the
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:
To start, let's lay our form out in Android. In our
activity_main.xml layout, we have a simple
ConstraintLayout with an
Let's bind to this layout and respond in
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
We make a simple
POST HTTP request to our backend endpoint
/v1/users, which returns a
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
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:
In the mobile app, we use the returned credentials to initialize our
FeedService by calling
MainActivity. Here's the
FeedService is a singleton (by using Kotlin's object) which stores a
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:
With the layout:
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
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":
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:
And here is the code for the
And the layout:
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
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:
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.
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:
This is backed by a
And the layout:
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:
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
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:
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:
Let's look at the code to display our timeline. We have a
And our layout:
Since we already built our
FeedAdapter, we simply need to get the activities for
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.
- React Native: How To Build Bidirectional Infinite Scroll
- Swift WebSockets: Starscream or URLSession in 2021?
- Crunchbase Adds Real-Time Updates with Stream’s Activity Feed API
- Clean Chat Example App with Jetpack Compose
- Jetpack Compose: First Impressions and Learning Resources
- Flutter vs React Native: The Ultimate Comparison