Streamdata.io is a service acting as a reverse proxy that translates REST APIs into a stream of data. In this tutorial, we will show you step-by-step how to use streamdata.io in an Android App to display a real-time feed of Stocktwits $EURUSD symbol.
1. Install Android Studio & SDK
If you haven’t already, follow this guide to install Android Studio.
2. Dependencies
In order to use streamdata.io in your Android app, you need the following dependencies:
For the purposes of this tutorial, you will also need to download these dependencies:
- To display images from an URL: ION & Picasso
- To translate HTML: Jakarta Commons Lang
3. Setting up
Setting up streamdata.io
In order to use the streamdata.io proxy, you need an App Token. Register on our Portal to create an account and get a valid token.
The streamdata.io request URL looks like this:
https://streamdata.motwin.net/https://my.api.com/service?X-Sd-Token=[YOURTOKEN]
As you can see, it is composed of three parts:
- https://streamdata.motwin.net/: the streamdata.io proxy url,
- https://my.api.com/service/: the api you are targetting (your API),
- ?X-Sd-Token=[YOURTOKEN]: your streamdata.io App Token.
You can also append additional arguments that will be passed to the targetted URL with ¶m1=value1¶m2=value2
Setting up the Android App
In order to connect your App to the internet, you will need to allow networking operations. Just add the following lines to your AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Also, in order to use Eventsource, you should create and connect the event source from a separate thread. If you wish to handle your event source in the main thread, you’ll need to add the following instructions at the beginning of the onCreate() android callback:
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
4. Using streamdata.io in an Android App
You will need to declare in the main Activity class a variable for each part of the streamdata.io request url:
private String sdProxyPrefix = "https://streamdata.motwin.net/";
private String sdToken =[YOUR TOKEN];
private String api ="https://api.stocktwits.com/api/2/streams/symbol/EURUSD.json";
Note that the Stocktwits API url calls the EURUSD feed. You can get a feed for any other symbol by changing “$EURUSD” in the url by any other security symbol available on Stocktwits (e.g.: AAPL for a the Apple Stocktwits feed will give https://api.stocktwits.com/api/2/streams/symbol/AAPL.json).
Also, keep in mind that this API call is unauthenticated, so only 200 calls per hour are allowed. You can manage the polling frequency for your API in your streamdata.io account, in “My APIs”.
Next, let’s create an eventsource to start listening to incoming messages. Declare the eventSource variable along with the three previous strings, in the main Activity class.
Here’s the connect() method:
protected void connect() {
Map<String, String> header = new HashMap<String, String>();
header.put("X-Sd-Token", sdToken);
try {
eventSource = new EventSource(new URI(sdProxyPrefix), new URI(api), this, header);
// Start receiving data
eventSource.connect();
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
Now, to stop listening to messages:
protected void disconnect(){
if(eventSource != null && eventSource.isConnected())
eventSource.close();
}
For performance reasons, we strongly recommend calling connect() in the onResume() callback and disconnect() in onPause(). This way, the user only receives messages in the resumed state.
If you copied both these methods in an Android project, you should have an error on the this parameter. That’s because we haven’t implemented the SSEHandler interface yet.
Let’s do it on the main activity of the android project :
public class MainActivity extends AppCompatActivity implements EventSourceHandler {
...
/**
* SSE handler for connection starting
*/
@Override
public void onConnect() {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "SSE Connected");
}
/* Do whatever you like in when the stream gets open */}
/* SSE incoming message handler */
@Override
public void onMessage(String event, MessageEvent message) throws IOException {
if ("data".equals(event)) {
// SSE message is the first snapshot
data = mapper.readTree(message.data);
// FIX ME
} else if ("patch".equals(event)) {
// SSE message is a patch
try {
JsonNode patchNode = mapper.readTree(message.data);
JsonPatch patch = JsonPatch.fromJson(patchNode);
data = patch.apply(data);
// ADD YOUR UI CODE HERE
updateList();
} catch (JsonPatchException e) {
e.printStackTrace();
}
} else {
throw new RuntimeException("Unexpected SSE message: " + event);
}
}
/* SSE error Handler */
@Override
public void onError(Throwable t) {
/* Do whatever you like in case of error */}
/* SSE Handler for connection interruption */@Override
public void onClosed(boolean willReconnect) {
/* Do whatever you like when the stream gets stopped */}
}
Once the patch is applied to the dataset, you need to apply those updates to the UI. In this app, we call an updateList() function.
First, let’s create the necessary objects to easily manipulate the data and represent Stocktwit tweet:
package com.streamdataio.stocktwits;
import java.io.Serializable;
public class Tweet implements Serializable {
private String body;
private String dateTime;
private String imgURL;
private User user;
public Tweet(String aBody, String aDateTime, String anImgUrl, User aUser){
this.body = aBody;
this.dateTime = aDateTime;
this.imgURL = anImgUrl;
this.user = aUser;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getDateTime() {
return dateTime;
}
public void setDateTime(String dateTime) {
this.dateTime = dateTime;
}
public String getImgURL() {
return imgURL;
}
public void setImgURL(String imgURL) {
this.imgURL = imgURL;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
When the user presses on an item in the list of tweets, a child activity opens to display the selected tweet on a specific screen giving details about the tweet author. In order to do this, I defined a User object, so each tweet is defined by its body, image url, date and user.
Here’s the User class:
package com.streamdataio.stocktwits;
import java.io.Serializable;
public class User implements Serializable {
private String username, name, bio, join, avatarURL, location, followers, following, off, xpLevel, holdingPeriod, approach, webURL, ideas;
public String getUsername() {
return username;
}
public String getName() {
return name;
}
public String getBio() {
return bio;
}
public String getJoin() {
return join;
}
public String getAvatarURL() {
return avatarURL;
}
public String getLocation() {
return location;
}
public String getFollowers() {
return followers;
}
public String getFollowing() {
return following;
}
public String getOff() {
return off;
}
public String getXpLevel() {
return xpLevel;
}
public String getHoldingPeriod() {
return holdingPeriod;
}
public String getApproach() {
return approach;
}
public String getWebURL() {
return webURL;
}
public String getIdeas() {
return ideas;
}
public User (String uname, String name, String bio, String joined, String avatar, String loc,
String followers, String following, String off, String xp,
String holdingPeriod, String approach, String weburl, String Ideas) {
this.username = uname;
this.name = name;
this.bio = bio;
this.join = joined;
this.avatarURL = avatar;
this.location = loc;
this.followers = followers;
this.following = following;
this.off = off;
this.xpLevel = xp;
this.holdingPeriod = holdingPeriod;
this.approach = approach;
this.webURL = weburl;
this.ideas= Ideas;
}
}
We store an ArrayList of Tweet.
We update it on every new patch reception by calling the updateList() function.
Here is the implementation:
private void updateList() {
JsonNode jsonData = data.get("messages");
tweets.clear();
for (Iterator i = jsonData.iterator(); i.hasNext(); ) {
JsonNode jsonTweet = i.next();
// creating user from json object
User user = new User(
"@" + jsonTweet.get("user").get("username").asText(),
jsonTweet.get("user").get("name").asText(),
StringEscapeUtils.unescapeHtml4(jsonTweet.get("user").get("bio").asText()),
jsonTweet.get("user").get("join_date").asText(),
jsonTweet.get("user").get("avatar_url").asText(),
jsonTweet.get("user").get("location").asText(),
jsonTweet.get("user").get("followers").asText(),
jsonTweet.get("user").get("following").asText(),
jsonTweet.get("user").get("identity").asText(),
jsonTweet.get("user").get("trading_strategy").get("experience").asText(),
jsonTweet.get("user").get("trading_strategy").get("holding_period").asText(),
jsonTweet.get("user").get("trading_strategy").get("approach").asText(),
jsonTweet.get("user").get("website_url").asText(),
jsonTweet.get("user").get("ideas").asText()
);
// created_at format "[date]T[time]Z".
// Note: T = Tag and Z = Zeit
// build readable date time
String date = jsonTweet.get("created_at").asText().replace("T", " at ").replace("Z", "");
// unescape the HTML in the text
String body = StringEscapeUtils.unescapeHtml4(jsonTweet.get("body").asText());
String imageUrl = "";
if (!jsonTweet.path("entities").path("chart").path("large").isMissingNode()) {
// here you can get
// path("entities").path("chart").path("thumb")
// path("entities").path("chart").path("large")
// path("entities").path("chart").path("original")
// we pick "large" for better display result
imageUrl = jsonTweet.path("entities").path("chart").path("large").asText();
}
// Create new tweet object
Tweet tweet = new Tweet(body,date,imageUrl,user);
tweets.add(tweet);
}
runOnUiThread(new Runnable() {
@Override
public void run() {
mainListViewAdapter.updateData(tweets);
mainListViewAdapter.notifyDataSetChanged();
}
});
}
Note that tweetJson.path() returns MissingNode if the element specified is not found, while tweetJson.get() returns null.
All texts are encoded in HTML (except links, which are in clear text with no <a href=…>…<a/> markup), so it is important to properly unescape the HTML in the “body” of a tweet. For instance, the first library I found would let show “\n” in a tweet instead of going on a new line. The Jakarta Commons Lang library unescapes the HTML encoding perfectly.
To display images through urls without saving them on the phone, I had two options: either find a way to manually store them in cache, or use a library.
In order to display avatars, I chose to use ION. It displays a placeholder image (a default avatar image) while the avatar loads.
I ran into some trouble when trying to use it to display images embeded in the tweets using ION. I found another library, Picasso, which works perfectly well but takes a bit too much memory. I finally chose to use both ION and Picasso. ION for the avatar feature and Picasso for other image display.
Here are some screenshots of the end result:
On the left side the stocktwits thread with articles. On the right the detail of article number two.
5. Additional implementations
Error handling
Common errors are handled with a toast which is shown when:
- the app is not connected to a network,
- the app has performed too many API calls (200/hour for an unauthenticated usage of the stockTwits API).
More ideas
The purpose was to demonstrate the easy integration and usage of Streamdata.io proxy in an Android native app.
Now that this was done quickly, many more features may be added to improve the app itself:
- Add the capability to zoom images, display them full screen.
- Managing a list of securities (the app only shows one : $EURUSD).
- Handle navigation to the links present in the tweets.
6. Conclusion
By using streamdata.io, data usage is considerably reduced:
This has a lot of impact, not only on user experience, but also on the environment, since the phone battery is not drained doing unnecessary work.
With a few dependencies, and very few lines of code you can create your own real time app from a standard API.
You can clone the code for this demo app from the github repository here.
You can also check out this other tutorial which tells how to make a realtime updated Github Android Client using multiples APIs and dealing with multiple streams in one activity.
Follow us on social