Setup
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.
1yarn global add expo-cli
Run the following commands to create a new React Native project called "ActivityFeedExample":
123expo init -t blank --name activity-feed-example cd activity-feed-example 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
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:
12345678910111213import React from 'react'; import { SafeAreaView } from 'react-native'; import { StreamApp } from 'expo-activity-feed'; const App = () => { return ( <SafeAreaView style={{ flex: 1 }}> <StreamApp apiKey='YOUR_API_KEY' appId='APP_ID' token='USER_TOKEN' /> </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):
123456// npm install getstream --save let stream = require('getstream'); let client = stream.connect('YOUR_API_KEY', 'YOUR_API_SECRET'); let userToken = client.createUserToken('USER_ID');
# pip install stream-python import stream client = stream.connect('YOUR_API_KEY', 'YOUR_API_SECRET', location='us-east') user_token = client.create_user_token('USER_ID')
# gem install "stream-ruby" # 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', 'YOUR_API_SECRET', :location => 'us-east') user_token = client.create_user_session_token('USER_ID')
// composer require get-stream/stream $client = new GetStreamStreamClient('YOUR_API_KEY', 'YOUR_API_SECRET'); $userToken = client->createUserSessionToken('USER_ID');
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.
1yarn 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.
Timeline Feed
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.
123456789101112131415import React from 'react'; import { SafeAreaView } from 'react-native'; import { StreamApp, FlatFeed } from 'expo-activity-feed'; const App = () => { return ( <SafeAreaView style={{ flex: 1 }}> <StreamApp apiKey='YOUR_API_KEY' appId='APP_ID' token='USER_TOKEN'> <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.
1<FlatFeed feedGroup='timeline' userId='USER_ID' />
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:
1<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.
1234567891011121314151617181920import 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='YOUR_API_KEY' appId='APP_ID' token='USER_TOKEN'> <FlatFeed Activity={CustomActivity} /> </StreamApp> </SafeAreaView> ); }; export default App;
Status Update
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.
1234567891011121314151617181920212223import React from 'react'; import SafeAreaView from 'react-native-safe-area-view'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { StreamApp, FlatFeed, Activity, LikeButton, StatusUpdateForm } from 'expo-activity-feed'; const CustomActivity = (props) => { return <Activity {...props} Footer={<LikeButton {...props} />} />; }; const App = () => { return ( <SafeAreaProvider> <SafeAreaView style={{ flex: 1 }}> <StreamApp apiKey='YOUR_API_KEY' appId='APP_ID' token='USER_TOKEN'> <FlatFeed Activity={CustomActivity} /> <StatusUpdateForm feedGroup='timeline' /> </StreamApp> </SafeAreaView> </SafeAreaProvider> ); }; export default App;
The built-in StatusUpdateForm component does several things out-of-the-box for you:
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 Uploads Upload images directly from your Camera Roll.
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.
1234567891011121314151617181920212223import React from 'react'; import SafeAreaView from 'react-native-safe-area-view'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { StreamApp, FlatFeed, Activity, LikeButton, StatusUpdateForm } from 'expo-activity-feed'; const CustomActivity = (props) => { return <Activity {...props} Footer={<LikeButton {...props} />} />; }; const App = () => { return ( <SafeAreaProvider> <SafeAreaView style={{ flex: 1 }}> <StreamApp apiKey='YOUR_API_KEY' appId='APP_ID' token='USER_TOKEN'> <FlatFeed Activity={CustomActivity} notify /> <StatusUpdateForm feedGroup='timeline' /> </StreamApp> </SafeAreaView> </SafeAreaProvider> ); }; 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:
1234567891011121314151617181920212223// npm install getstream --save // let stream = require('getstream'); let client = stream.connect('YOUR_API_KEY', 'YOUR_API_SECRET'); let feed = client.feed('timeline', 'USER_ID'); feed.addActivity({ actor: client.user('USER_ID').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', 'YOUR_API_SECRET') feed = client.feed('timeline', 'USER_ID') feed.add_activity({ "actor": client.users.create_reference('USER_ID'), "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', 'YOUR_API_SECRET') feed = client.feed('user', 'USER_ID') feed.add_activity({ actor: client.collections.create_user_reference('USER_ID'), 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', 'YOUR_API_SECRET'); $feed = $client->feed('user', 'USER_ID'); $feed->addActivity([ 'actor' => $client->collections()->createUserReference('USER_ID'), '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", "YOUR_API_SECRET").build(); FlatFeed feed = client.flatFeed("timeline", "USER_ID"); feed.addActivity(Activity.builder() .actor(Enrichment.createUserReference("USER_ID")) .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", "YOUR_API_SECRET") if err != nil { // ... } feed := client.FlatFeed("user", "USER_ID") _, err = feed.AddActivity(stream.Activity{ Actor: client.Collections().CreateUserReference("USER_ID"), 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 { // ... }
1234567891011121314151617181920212223242526272829// dotnet add package stream-net var client = new Stream.StreamClient("YOUR_API_KEY", "YOUR_API_SECRET"); var feed = client.Feed("timeline", "USER_ID"); var user = Stream.Users.Ref("USER_ID"); var activity = new Stream.Activity(Stream.Users.Ref("USER_ID"), "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);
Final Thoughts
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 an application.
Adding feeds to an 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!