This tutorial is an introduction to how you can use the Stream Feed API and React Native to create a timeline news feed on mobile applications. In the end, you will have a fully functioning activity feed with built-in support for real-time updates, URL enrichment, likes, and image uploads.
At the end of the guide you can find links to resources and docs to learn about other functionality such as: comments, notification feeds and aggregation.
💬 Pssst: Make sure to check out our React Native Chat and React Chat tutorials! Build Feeds and Chat in less time with React and Stream!
To use the code from this tutorial, you need a working Node development environment with React Native and Expo. We suggest creating a fresh project using expo-cli
.
Make sure that you have a recent version of Node (>=10) and Yarn installed.
yarn global add expo-cli
Run the following commands to create a new React Native project called "activity-feed-example":
expo init -t blank --name activity-feed-example
cd activity-feed-example
yarn add expo-activity-feed
expo install expo-activity-feed expo-permissions expo-image-picker
This will create the skeleton of the project and install the React Native Activity feed library
Now that we have a project ready, let's add the initial configuration to use Stream's APIs
Open App.js in your text editor of choice and make the following changes:
import React from 'react';
import { SafeAreaView } from 'react-native';
import { StreamApp } from 'expo-activity-feed';
const App = () => {
return (
<SafeAreaView style={{flex: 1}}>
<StreamApp
apiKey="5rqsbgqvqphs"
appId="40273"
token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiN2JiYzE3MGYtMjQzZC00MzQzLTlhODctZmY4NDhhYmUzZGRhIn0.XJIoZ7kAxjCiEtfsCu9Ttr8svypM-t09ZW7bQ_LgnE8"
/>
</SafeAreaView>
);
};
export default App;
Your application is now configured with the API credentials for the demo application.
StreamApp
is going to be the parent element for all other components and manages current user's session (token
) and
your Stream application API credentials (apiKey
and appId
).
Because this is a tutorial, we do not have a real signup flow and we will use a pre-generated user session token, on a real-world application, your authentication backend would generate such token at log-in / signup and hand it over to the mobile app.
This is how you would generate the token server side (no need to do this for this tutorial):
// npm install getstream --save
let stream = require('getstream');
let client = stream.connect('YOUR_API_KEY', 'API_KEY_SECRET');
let userToken = client.createUserSessionToken(userId);
# pip install stream-python
import stream
client = stream.connect('YOUR_API_KEY', 'API_KEY_SECRET', location='us-east')
user_token = client.create_user_token(user_id)
# gem install "stream-ruby"
# or add this to your Gemfile and then run bundler
# gem "stream-ruby"
require 'stream'
client = Stream::Client.new('YOUR_API_KEY', 'API_KEY_SECRET', :location => 'us-east')
user_token = client.create_user_session_token(userId)
# composer require get-stream/stream
$client = new GetStream\Stream\Client('YOUR_API_KEY', 'API_KEY_SECRET');
$userToken = client->createUserSessionToken($userId);
To preview the application, you will need to install the Expo app on your phone and connect to WiFi so that you are on the same network as your computer. If you have Android Studio or Xcode you can also preview the application using the built-in emulator.
yarn start
This will start the React Native development server, you can leave it running, it will live reload your news feed application when you make code changes.
The example news feed is loaded with some demo data, this way you can see how different kinds of activities are rendered out of the box.
Adding a timeline feed to your app is very simple, the library comes with a built-in FlatFeed
component which loads the current user's timeline using the APIs and renders its content.
import React from 'react';
import { SafeAreaView } from 'react-native';
import { StreamApp, FlatFeed } from 'expo-activity-feed';
const App = () => {
return (
<SafeAreaView style={{flex: 1}}>
<StreamApp
apiKey="5rqsbgqvqphs"
appId="40273"
token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiN2JiYzE3MGYtMjQzZC00MzQzLTlhODctZmY4NDhhYmUzZGRhIn0.XJIoZ7kAxjCiEtfsCu9Ttr8svypM-t09ZW7bQ_LgnE8"
>
<FlatFeed />
</StreamApp>
</SafeAreaView>
);
};
export default App;
Let's make our first customization to the timeline feed and show a like button under each activity. The library has a LikeButton
component that integrates with the feed and we are going to add it by changing the layout of feeds' activities.
The Activity
component rendering is divided in three parts: Header, Content and Footer. Each of these sections can be changed via a prop. In this case we are replacing the footer (which is empty by default) with LikeButton
:
<Activity
{...props}
Footer={
<LikeButton {...props} />
}
/>
The FlatFeed
component has a prop called Activity
that allows you to change how activities are rendered, in our case we will pass the function returning our custom activity element defined above.
import React from 'react';
import { SafeAreaView } from 'react-native';
import {
StreamApp,
FlatFeed,
Activity,
LikeButton,
} from 'expo-activity-feed';
const CustomActivity = (props) => {
return (
<Activity
{...props}
Footer={
<LikeButton {...props} />
}
/>
);
};
const App = () => {
return (
<SafeAreaView style={{flex: 1}}>
<StreamApp
apiKey="5rqsbgqvqphs"
appId="40273"
token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiN2JiYzE3MGYtMjQzZC00MzQzLTlhODctZmY4NDhhYmUzZGRhIn0.XJIoZ7kAxjCiEtfsCu9Ttr8svypM-t09ZW7bQ_LgnE8"
>
<FlatFeed Activity={CustomActivity} />
</StreamApp>
</SafeAreaView>
);
};
export default App;
Now that we have managed to read your timeline and add a likes to it, let's add a status update screen so that we can add new activities ourselves.
For this we can use the StatusUpdateForm
which comes with image upload and open-graph support.
We can add this at the bottom of the render method and it will automatically fit with the rest of the UI.
import React from 'react';
import { SafeAreaView } from 'react-native';
import {
StreamApp,
FlatFeed,
Activity,
LikeButton,
StatusUpdateForm,
} from 'expo-activity-feed';
const CustomActivity = (props) => {
return (
<Activity
{...props}
Footer={
<LikeButton {...props} />
}
/>
);
};
const App = () => {
return (
<SafeAreaView style={{flex: 1}}>
<StreamApp
apiKey="5rqsbgqvqphs"
appId="40273"
token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiN2JiYzE3MGYtMjQzZC00MzQzLTlhODctZmY4NDhhYmUzZGRhIn0.XJIoZ7kAxjCiEtfsCu9Ttr8svypM-t09ZW7bQ_LgnE8"
>
<FlatFeed Activity={CustomActivity} />
<StatusUpdateForm feedGroup="timeline" />
</StreamApp>
</SafeAreaView>
);
};
export default App;
The built-in StatusUpdateForm
component does several things out-of-the-box for you:
Whenever you paste or insert a URL, a preview of the page will be fetched from Stream's APIs and presented in a preview box. Try copy/paste https://goo.gl/Hok8hp in the form to see it in action.
Upload images directly from your Camera Roll.
Once the form is submitted, an activity is created and added to the same feed. You can scroll down the feed to load the new activity.
As a final touch, we are connecting the timeline to Stream's realtime APIs so that we can show a message each time a new activity is added to the feed. In this case we don't have to add a new component but only enable FlatFeed's built-in pager using the notify
prop.
import React from 'react';
import { SafeAreaView } from 'react-native';
import {
StreamApp,
FlatFeed,
Activity,
LikeButton,
StatusUpdateForm,
} from 'expo-activity-feed';
const CustomActivity = (props) => {
return (
<Activity
{...props}
Footer={
<LikeButton {...props} />
}
/>
);
};
const App = () => {
return (
<SafeAreaView style={{flex: 1}}>
<StreamApp
apiKey="5rqsbgqvqphs"
appId="40273"
token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiN2JiYzE3MGYtMjQzZC00MzQzLTlhODctZmY4NDhhYmUzZGRhIn0.XJIoZ7kAxjCiEtfsCu9Ttr8svypM-t09ZW7bQ_LgnE8"
>
<FlatFeed Activity={CustomActivity} notify />
<StatusUpdateForm feedGroup="timeline" />
</StreamApp>
</SafeAreaView>
);
};
export default App;
So far we looked at how you can read and post activities using React Native. In most cases you will also need to perform server-side interactions such as creating follow relationships, adding activities or change user-data. All the functionality we looked at in this tutorial is exposed via Stream's REST API and can be used server-side.
This is how we can add an activity from server-side:
// npm install getstream --save
// let stream = require('getstream');
let client = stream.connect('YOUR_API_KEY', 'API_KEY_SECRET');
let feed = client.feed('timeline', 'user-one');
feed.addActivity({
'actor': client.user('user-one').ref(),
'verb': 'post',
'object': 'I love this picture',
'attachments': {
'og': {
'title': 'Crozzon di Brenta photo by Lorenzo Spoleti',
'description': 'Download this photo in Italy by Lorenzo Spoleti',
'url': 'https://unsplash.com/photos/yxKHOTkAins',
'images': [
{
'image': 'https://goo.gl/7dePYs'
}
]
}
}
})
# pip install stream-python
import stream
client = stream.connect('YOUR_API_KEY', 'API_KEY_SECRET')
feed = client.feed('timeline', 'user-one')
feed.add_activity({
"actor": client.users.create_reference('user-one'),
"verb": "post",
"object": "I love this picture",
"attachments": {
"og": {
"title": "Crozzon di Brenta photo by Lorenzo Spoleti",
"description": "Download this photo in Italy by Lorenzo Spoleti",
"url": "https://unsplash.com/photos/yxKHOTkAins",
"images": [
{
"image": "https://goo.gl/7dePYs"
}
]
}
}
})
# gem install "stream-ruby"
require 'stream'
client = Stream::Client.new('YOUR_API_KEY', 'API_KEY_SECRET')
feed = client.feed('user', 'user-one')
feed.add_activity({
actor: client.collections.create_user_reference('user-one'),
verb: 'post',
object: 'I love this picture',
attachments: {
og: {
title: 'Crozzon di Brenta photo by Lorenzo Spoleti',
description: 'Download this photo in Italy by Lorenzo Spoleti',
url: 'https://unsplash.com/photos/yxKHOTkAins',
images: [
{
image: 'https://goo.gl/7dePYs'
}
]
}
}
})
// composer require get-stream/stream
$client = new GetStream\Stream\Client('YOUR_API_KEY', 'API_KEY_SECRET');
$feed = $client->feed('user', 'user-one');
$feed->addActivity([
'actor' => $client->collections()->createUserReference('user-one'),
'verb' => 'post',
'object' => 'I love this picture',
'attachments' => [
'og' => [
'title' => 'Crozzon di Brenta photo by Lorenzo Spoleti',
'description' => 'Download this photo in Italy by Lorenzo Spoleti',
'url' => 'https://unsplash.com/photos/yxKHOTkAins',
'images' => (
[
'image' => 'https://goo.gl/7dePYs'
]
)
]
]
]);
// See installation details at https://github.com/GetStream/stream-java
Client client = Client.builder("YOUR_API_KEY", "API_KEY_SECRET").build();
FlatFeed feed = client.flatFeed("timeline", "user-one");
feed.addActivity(Activity.builder()
.actor(Enrichment.createUserReference("user-one"))
.verb("post")
.object("I love this picture")
.extraField("attachments", ImmutableMap.of("og", new ImmutableMap.Builder<String, Object>()
.put("title", "Crozzon di Brenta photo by Lorenzo Spoleti")
.put("description", "Download this photo in Italy by Lorenzo Spoleti")
.put("url", "https://unsplash.com/photos/yxKHOTkAins")
.put("images", Lists.newArrayList(ImmutableMap.of("image","https://goo.gl/7dePYs")))
.build()))
.build()).join();
import (
stream "gopkg.in/GetStream/stream-go2.v1"
)
client, err := stream.NewClient("YOUR_API_KEY", "API_KEY_SECRET")
if err != nil {
// ...
}
feed := client.FlatFeed("user", "user-one")
_, err = feed.AddActivity(stream.Activity{
Actor: client.Collections().CreateUserReference("user-one"),
Verb: "post",
Object: "I love this picture",
Extra: map[string]interface{}{
"attachments": map[string]interface{}{
"og": map[string]interface{}{
"title": "Crozzon di Brenta photo by Lorenzo Spoleti",
"description": "Download this photo in Italy by Lorenzo Spoleti",
"url": "https://unsplash.com/photos/yxKHOTkAins",
"images": []interface{}{
map[string]string{}{
"image": "https://goo.gl/7dePYs",
},
},
},
},
},
})
if err != nil {
// ...
}
//dotnet add package stream-net
var client = new Stream.StreamClient("YOUR_API_KEY", "API_KEY_SECRET");
var feed = client.Feed("timeline", "user-one");
var user = Stream.Users.Ref("user-one");
var activity = new Stream.Activity(Stream.Users.Ref("user-one"), "post", "i love this picture");
var ogData = new Dictionary<string, object>()
{
{"title", "Crozzon di Brenta photo by Lorenzo Spoleti"},
{"description", "Download this photo in Italy by Lorenzo Spoleti"},
{"url", "https://unsplash.com/photos/yxKHOTkAins"},
{"images", new Dictionary<string,string>[]
{
new Dictionary<string, string>()
{
{"image", "https://goo.gl/7dePYs"}
}
}
}
};
var attachment = new Dictionary<string, object>()
{
{"og", ogData}
};
activity.SetData("attachments", attachment);
await feed.AddActivity(activity);
In this tutorial we saw how easy it is to use Stream API and the React Native library to add a fully featured timeline to a mobile application. You might also be interested in our similar tutorial focused on feeds with React.
Adding feeds to a mobile app can take weeks or months, even if you're a seasoned React Native developer. Stream makes it easy and gives you the tools and the resources to improve user engagement within your app. Time to add a feed!
Stream is an API for building scalable news feeds and activity streams. Try it out in this 5 minute interactive tutorial. To run the examples you'll need an API key. Register to continue...
const client = stream.connect('GET YOUR KEY', null, '11893');
const ericFeed = client.feed('user', 'eric', 'MNZtwnaASNqVjnvyEG3AORTZQhk');
// Add the activity to the feed
await ericFeed.addActivity({
actor: 'eric',
tweet: 'Hello world',
verb: 'tweet',
object: 1
});
Flexible Implementation