We’re covering some “best practice” examples of how to set up a mobile application powered with Stream APIs. We’re happy to announce that after several weeks of development and testing that we have an Android example to share. As with all of our example code, the project is open sourced and available on GitHub. We have also submitted the application to the Google Play Store for ease of installation.
The goal of this blog post is to offer some of our best practices in general, but through the lens of a how we produced our mobile application example. The best practices we’ll cover here are equally applicable to mobile or other applications.
Security Best Practices
We want to address several things up front about building a mobile application with Stream. Most importantly, we do not recommend that you build an application where your API key and secret are embedded within or otherwise accessible to the mobile application -- our best practice is that your mobile application communicate with a fast backend API service hosted on your own systems, and that this backend application would communicate with Stream.
This Android app is meant to be our “best practices example” of how to perform asynchronous HTTP operations to fetch various kinds of feeds and present that data to your users. In the interest of time to develop this project, we took several shortcuts along the way:
- Our mobile application does not perform secure communication with the backend process; please incorporate secure best practices for something like JWT over SSL between your mobile app and your backend.
- We took a shortcut on authentication using only usernames and email addresses; again, please follow security best practices around authentication.
- We do not utilize feed filters to only pull the latest data; we do recommend that feed data be cached on the mobile device and refreshed using the newest/oldest activity identifier to minimize data transfer on mobile devices.
- There may be optimizations in how the Android application operates, manages photos and orientation, and we would be happy to review ideas and pull requests from our community.
The Mobile App
This project mimics an Instagram-style application where users can take photos, share them publicly, follow other users, see a notification feed and aggregated data along the way. We’ll describe each portion of the application below.
The Global Feed
When the mobile app starts, users see a global feed, which we recommend when you want a single activity feed of all activities of your users:
To accomplish this, our application calls the /feed/user/global endpoint on our Go backend. The backend retrieves the data from Stream, does some manipulation to show likes on the photo, the author details, and sends that back to the mobile client. We recommend tracking the newest activity retrieved and only retrieving new items “greater than” that activity ID when pulling feed data for the sake of mobile data limits. We describe this in our documentation for retrieving feed data.
Since we’ve only just started the application, the navigation menu at this point will only show options to reload the Global Feed and to Register/Login:
Registering and Logging in
To register/login with our backend application, you only need a unique username and an email address. We use your email address to load an avatar and we may contact you at this email address to thank you for trying out our application. As mentioned earlier, we took a shortcut on authentication: if your username/email match an existing entry, we log you in, otherwise we’ll create a new user entry if both username and email are unique within our system.
Once you register and log in, the global feed will refresh, which will show whether you’ve liked any photos or follow any of the users on the global feed. The navigation menu will also refresh to show additional options for feed types to view.
Most of these other feed views won’t hold any data until you take some photos and follow other users, but we’ll describe them in order.
Take a Photo
This launches a new Android activity to take a photo. Once you take a photo and approve it, you can upload it to Stream. We discuss the image processing later in our blog post about our Go example service. Our backend shrinks the photos within a 1024x768 constraint so the mobile app is fast and responsive; we discuss this more in our Go service post.
This endpoint requests your “timeline” feed from Stream, which shows a history of photos by other users you follow. The first time you follow a user, our backend application will retrieve a number of their previous photos. For this endpoint, if Matthisk had followed Ian, we’d retrieve “timeline:matthisk” which would contain any photos posted on Ian’s user feed, “user:ian”. If you unfollow a user, the backend will remove their activities from your timeline feed. Our SDKs have methods or flags to allow you to change the behavior about pre-loading and removing old activities onto a feed when following and unfollowing.
This endpoint does not access Stream at all, but simply returns a list of users from our backend application’s database. How you load and paginate this is up to you.
In the screenshot below, we remove the follow/unfollow button for Ian since he’s the one logged in right now (we recommend blocking users from following themselves) and we see that he follows users named “ian2”, “josh” and “nick”.
This endpoint calls your own "user" feed (not your "timeline" feed), which will photos that you've submitted and allows you to see how many likes each photo received. In this case, if Peter was viewing his own profile, we’d be retrieving “user:peter” from Stream. Our backend application also looks up additional information from its own database regarding a count of how many users follow Peter, how many users Peter follows, and a total count of photos Peter has posted).
Another shortcut we took on this Android view is reusing the feed item layout from the global feed and timeline feed, but since these are your own photos, we hide the author gravatar, author name, and follow button.
As users interact with the app and follow users or like photos, we push activities with a “like” or “follow” verb to the target’s notification feed. In the case of a "like" we also push the photo’s UUID as the "object" in the activity. This allows our backend to retrieve Tommaso’s notification feed, then retrieve each photo’s details based on the "object" reference, and merge that data together to pass to the Android client. In this case, Ian and 3 other users liked a photo, and the Android client aggregates it together like this:
While the data feed to the Android client would contain a list of all users following you, the Android client aggregates the data into a simpler representation:
Since our users’ timeline aggregated feed follow other users’ flat “user” feeds, our aggregated feed will only contain a summary of photos added to our application. For example, since Ian followed Thierry and Nick, his aggregated feed could contain data like this:
Interesting Blocks Along the Way
Android development has come a long way in the past few years, and the inclusion of Android Studio based on JetBrains’ IntelliJ IDE made development much smoother. That doesn’t mean we didn’t hit a few interesting bumps along the way.
Material Design Styling
Google has published some excellent styling guides for Material Design, which on a web application is very easy to re-style if you really need to customize something: you simply define your custom CSS last in the chain and then your styles override anything previously defined. With Android it seems to be the complete opposite. We found, for example, that styling a primary toolbar was simple enough, we could write our text in black font, but when triggering a new activity intent to build the “back arrow” toolbar, the font color would change to what Material Design had defined, and restricted us from overriding this further.
We’ve since simplified our font colors and let Material Design dictate that the toolbar font color would be a dark gray.
GridView Heights are not Automatic
GridViews are great for showing something like a gallery of photos on a layout, and you can customize the number of columns and the width of each column, but if you add more entries than columns across your layout, the height will not automatically adjust. In this case the aggregated feed for user “ian2” had 3 photos but only two would display.
The solution lies in determining how many resulting rows your GridView would need and adjust the height of your GridView on the fly.
We’d Love Your Feedback
As with all of our open-source projects and examples, we would love to hear your questions, ideas for improvements and so on by posting a new issue on GitHub.
Also published on Medium.