• react-logo Created with Sketch. React Components  

Activity Feeds with React Native tutorial

This tutorial is a getting started on how you can use the Stream API and React Native to create a newsfeed on mobile applications. In the end, you will have a fully functioning 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!

Setup

To use the code from this tutorial, you need a working Node development environment with React Native and Expo. We highly suggest creating a fresh project using the awesome create-react-native-app script from Facebook.

Make sure that you have a recent version of Node (>6) and Yarn installed.

copy-paste Created with Sketch. copied!
yarn global add create-react-native-app

Run the following commands to create a new React Native project called "ActivityFeedExample":

copy-paste Created with Sketch. copied!
create-react-native-app ActivityFeedExample
cd ActivityFeedExample
yarn add expo-activity-feed

This will create the skeleton of the project and install the React Native Activity feed library

Add Stream to your application

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:

copy-paste Created with Sketch. copied!
import React from 'react';
import SafeAreaView from 'react-native-safe-area-view';
import { StreamApp } from 'expo-activity-feed';

const App = () => {
  return (
    <SafeAreaView style={{flex: 1}} forceInset={{ top: 'always' }}>
      <StreamApp
          apiKey="5rqsbgqvqphs"
          appId="40273"
          token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmI0ZDhkNDctZGE3Yy00NzA2LWI4MDgtZmZmMDQ3OWMxMmUwIn0.f4CbQn8Pr2A9jQqctVllysGt8iBWR3Nwypd6f3wRqKk"
      />
    </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.

copy-paste Created with Sketch. copied!
yarn start

This will start the React Native development server, you can leave it running, it will live reload your newsfeed application when you make code changes.

react native tutorial

Timeline Feed

The example newsfeed is loaded with some demo data, this way you can see how different kind of activities are rendered out of the box.

Adding a timeline feed 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.

copy-paste Created with Sketch. copied!
import React from 'react';
import SafeAreaView from 'react-native-safe-area-view';
import { StreamApp, FlatFeed } from 'expo-activity-feed';

const App = () => {
  return (
    <SafeAreaView style={{flex: 1}} forceInset={{ top: 'always' }}>
      <StreamApp
          apiKey="5rqsbgqvqphs"
          appId="40273"
          token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmI0ZDhkNDctZGE3Yy00NzA2LWI4MDgtZmZmMDQ3OWMxMmUwIn0.f4CbQn8Pr2A9jQqctVllysGt8iBWR3Nwypd6f3wRqKk"
      >
        <FlatFeed />
      </StreamApp>
    </SafeAreaView>
  );
};

export default App;

Your application will now reload and show the newsfeed. Pagination included as well as Open-Graph, hashtags, mentions and image rendering.

All it took here was adding the FlatFeed component which renders the timeline feed for the user configured in StreamApp parent element.

copy-paste Created with Sketch. copied!
<FlatFeed
  feedGroup="timeline"
  userId="fb4d8d47-da7c-4706-b808-fff0479c12e0"
/>

This is how the FlatFeed element would look like if we were not using the defaults provided by the parent StreamApp element.

Like Button

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:

copy-paste Created with Sketch. copied!
<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.

copy-paste Created with Sketch. copied!
import React from 'react';
import SafeAreaView from 'react-native-safe-area-view';
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}} forceInset={{ top: 'always' }}>
      <StreamApp
          apiKey="5rqsbgqvqphs"
          appId="40273"
          token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmI0ZDhkNDctZGE3Yy00NzA2LWI4MDgtZmZmMDQ3OWMxMmUwIn0.f4CbQn8Pr2A9jQqctVllysGt8iBWR3Nwypd6f3wRqKk"
      >
        <FlatFeed Activity={CustomActivity} />
      </StreamApp>
    </SafeAreaView>
  );
};

export default App;

Status Updates

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.

copy-paste Created with Sketch. copied!
import React from 'react';
import SafeAreaView from 'react-native-safe-area-view';
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}} forceInset={{ top: 'always' }}>
      <StreamApp
        apiKey="5rqsbgqvqphs"
        appId="40273"
        token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmI0ZDhkNDctZGE3Yy00NzA2LWI4MDgtZmZmMDQ3OWMxMmUwIn0.f4CbQn8Pr2A9jQqctVllysGt8iBWR3Nwypd6f3wRqKk"
      >
        <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:

URL preview component

URL previews

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.

Image upload module

Image uploads

Upload images directly from your Camera Roll.

Multifunctional status update form

Post to user feed

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.

Realtime updates

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.

support for realtime updates
copy-paste Created with Sketch. copied!
import React from 'react';
import SafeAreaView from 'react-native-safe-area-view';
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}} forceInset={{ top: 'always' }}>
      <StreamApp
          apiKey="5rqsbgqvqphs"
          appId="40273"
          token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiZmI0ZDhkNDctZGE3Yy00NzA2LWI4MDgtZmZmMDQ3OWMxMmUwIn0.f4CbQn8Pr2A9jQqctVllysGt8iBWR3Nwypd6f3wRqKk"
      >
        <FlatFeed Activity={CustomActivity} notify />
        <StatusUpdateForm feedGroup="timeline" />
      </StreamApp>
    </SafeAreaView>
  );
};

export default App;

Server-Side Integration

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);

Conclusion and Next Steps

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.

Adding feeds to a mobile app can take weeks or months, even if you're a 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!

THE API FOR SCALABLE FEEDS

Try Stream for yourself

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...

Try the API Only takes 5 mins!
var client = stream.connect('GET YOUR KEY', null, '11893');
var ericFeed = client.feed('user', 'eric', 'MNZtwnaASNqVjnvyEG3AORTZQhk');
// Add the activity to the feed
ericFeed.addActivity({
  actor: 'eric',
  tweet: 'Hello world',
  verb: 'tweet',
  object: 1
});

Flexible Implementation

Stream supported platforms