Whether it's opening night for a Broadway musical or launch day for your app, both are thrilling times for everyone involved. Our agency, Posse, collaborated with Hamilton to design, build, and launch the official Hamilton app... in only three short months.
We decided to use Firebase, Google's mobile development platform, for the backend and infrastructure, while we used Flutter, a new UI toolkit for iOS and Android, for the front-end. In this post, we share how we did it.
We love to spend time designing beautiful UIs, testing new interactions, and iterating with clients, and we don't want to be distracted by setting up and maintaining servers. To stay focused on the app and our users, we implemented a full serverless architecture and made heavy use of Firebase.
A key feature of the app is the ticket lottery, which offers fans a chance to get tickets to the constantly sold-out Hamilton show. We used Cloud Functions for Firebase, and a data flow architecture we learned about at Google I/O, to coordinate the lottery workflow between the mobile app, custom business logic, and partner services.
For example, when someone enters the lottery, the app first writes data to specific nodes in Realtime Database and the database's security rules help to ensure that the data is valid. The write triggers a Cloud Function, which runs business logic and stores its result to a new node in the Realtime Database. The newly written result data is then pushed automatically to the app.
Because of Hamilton's intense fan following, we wanted to make sure that app users could get news the instant it was published. So we built a custom, web-based Content Management System (CMS) for the Hamilton team that used Firebase Realtime Database to store and retrieve data. The Realtime Database eliminated the need for a "pull to refresh" feature of the app. When new content is published via the CMS, the update is stored in Firebase Realtime Database and every app user automatically sees the update. No refresh, reload, or pull required!
Besides powering our lottery integration, Cloud Functions was also extremely valuable in the creation of user profiles, sending push notifications, and our #HamCam — a custom Hamilton selfie and photo-taking experience. Cloud Functions resized the images, saved them in Cloud Storage, and then updated the database. By taking care of the infrastructure work of storing and managing the photos, Firebase freed us up to focus on making the camera fun and full of Hamilton style.
With only three months to design and deliver the app, we knew we needed to iterate quickly on the UX and UI. Flutter's hot reload development cycle meant we could make a change in our UI code and, in about a second, see the change reflected on our simulators and phones. No rebuilding, recompiling, or multi-second pauses required! Even the state of the app was preserved between hot reloads, making it very fast for us to iterate on the UI with our designers.
We used Flutter's reactive UI framework to implement Hamilton's iconic brand with custom UI elements. Flutter's "everything is a widget" approach made it easy for us to compose custom UIs from a rich set of building blocks provided by the framework. And, because Flutter runs on both iOS and Android, we were able to spend our time creating beautiful designs instead of porting the UI.
The FlutterFire project helped us access Firebase Analytics, Firebase Authentication, and Realtime Database from the app code. And because Flutter is open source, and easy to extend, we even built a custom router library that helped us organize the app's UI code.
We enjoyed building the Hamilton app (find it on the Play Store or the App Store) in a way that allowed us to focus on our users and experiment with new app ideas and experiences. And based on our experience, we'd happily recommend serverless architectures with Firebase and customized UI designs with Flutter as powerful ways for you to save time building your app.
For us, we already have plans how to continue and develop Hamilton app in new ways, and can't wait to release those soon!
If you want to learn more about Firebase or Flutter, we recommend the Firebase docs, the Firebase channel on YouTube, and the Flutter website.
With a DIY approach, you'd be faced with having to build user management, data storage, server side logic, and more. This will take a lot of your time, and importantly, it would take critical resources away from what you really want to do: build that amazing new mobile game!
Our Firebase SDKs for Unity and C++ provide you with the tools you need to add these features and more to your game with ease. Plus, to help you better understand how Firebase can help you build your next chart-topper, we've built a sample game in Unity: MechaHamster. Check it out on Google Play or download the sample project at Github to see how easy it is to integrate Firebase into your game.
Before you dive into the sample code for MechaHamster, here's a rundown of the Firebase products that can help your game be successful.
One of the best tools you have to maintain a high-performing game is your analytics. With Google Analytics for Firebase, you can see where your players might be struggling and make adjustments as needed. Analytics also integrates with Adwords and other major ad networks to maximize your campaign performance. If you monetize your game using AdMob, you can link your two accounts and see the lifetime value (LTV) of your players, from in-game purchases and AdMob, right from your Analytics console. And with Streamview, you can see how players are interacting with your game in realtime.
Before releasing updates to your game, you'll want to make sure it works correctly. However, manual testing can be time consuming when faced with a large variety of target devices. To help solve this, we recently launched Firebase Test Lab for Android Game Loop Test at Google I/O. If you add a demo mode to your game, Test Lab will automatically verify your game is working on a wide range of devices. You can read more in our deep dive blog post here.
Another thing you'll want to be sure to take care of before launch is easy sign-in, so your users can start playing as quickly as possible. Firebase Authentication can help by handling all sign-in and authentication, from simple email + password logins to support for common identity providers like Google, Facebook, Twitter, and Github. Just announced recently at I/O, Firebase also now supports phone number authentication. And Firebase Authentication shares state cross-device, so your users can pick up where they left off, no matter what platforms they're using.
As more players start using your game, you realize that there are few spots that are frustrating for your audience. You may even see churn rates start to rise, so you decide that you need to push some adjustments. With Firebase Remote Config, you can change values in the console and push them out to players. Some players having trouble navigating levels? You can adjust the difficulty and update remotely. Remote Config can even benefit your development cycle; team members can tweak and test parameters without having to make new builds.
Now that you have a robust player community, you're probably starting to see a bunch of great player-built levels. With Firebase Realtime Database, you can store player data and sync it in real-time, meaning that the level builder you've built can store and share data easily with other players. You don't need your own server and it's optimized for offline use. Plus, Realtime Database integrates with Firebase Auth for secure access to user specific data.
A few months go by and your game is thriving, with high engagement and an active community. You're ready to release your next wave of new content, but how can you efficiently get the word out to your users? Firebase Cloud Messaging lets you target messages to player segments, without any coding required. And Firebase Dynamic Links allow your users to share this new content — or an invitation to your game — with other players. Dynamic Links survive the app install process, so a new player can install your app and then dive right into the piece of content that was shared with him or her.
At Firebase, our mission is to help mobile developers build better apps and grow successful businesses. When it comes to games, that means taking care of the boring stuff, so you can focus on what matters — making a great game. Our mobile SDKs for C++ and Unity are available now at firebase.google.com/games and don't forget to check out our sample game project, MechaHamster, on GitHub.
Until recently, developers were required to update their Android tools to make use of new versions of the local maven repository that contains Play services compile dependencies. Only after updating were the Android build tools able to locate them. Now, the dependencies are available directly from maven.google.com. You can update your app's Gradle build scripts to use this repository by simply configuring the build like this:
allprojects { repositories { jcenter() maven { url 'https://maven.google.com' } } }
Note the new Google maven repository. This is where dependencies are now hosted. Using Gradle 4.0 and later, you can simply specify google() as a shortcut instead. Once configured like this, Gradle will be able to locate, download, and cache the correct Play services dependencies without requiring an update to the Android build tools. Play services SDKs going back to version 3.1.36 will be available in this repo.
google()
You can read more about Google's maven repo here.
compileSdkVersion
When you upgrade your app's Play services dependencies to 11.2.0 or later, your app's build.gradle must also be updated to specify a compileSdkVersion of at least 26 (Android O). This will not change the way your app runs. You will not be required to update targetSdkVersion. If you do update compileSdkVersion to 26, you may receive an error in your build with the following message referring to the Android support library:
build.gradle
targetSdkVersion
This support library should should not use a different version (25) than the compileSdkVersion (26).
This error can be resolved by upgrading your support library dependencies to at least version 26.0.0. Generally speaking, the compileSdkVersion of your app should always match the major version number of your Android support library dependencies. In this case, you'll need to make them both 26.
Please join us in Amsterdam on October 31st for a day of talks, codelabs, and office hours, as well as (of course) an after-party.
Three months ago, thousands of developers joined us at Google I/O to hear about improvements to the Firebase platform, like Performance Monitoring, Phone Authentication, and our newly open sourced SDKs. We haven't slowed down since then and now we're excited to bring the Firebase and Fabric teams to Amsterdam to talk about a bunch of new announcements, as well as hear your feedback on how we can improve Firebase to help you build even more extraordinary experiences for your users.
Registration is now open, but keep in mind that space will be filled on a first-come, first-serve basis, so make sure to request an invitation today.
The Firebase Dev Summit is full day event for app developers that will focus on solving core infrastructure and growth challenges in app development. We'll have deep dive sessions, as well as introductory overviews, so all levels of Firebase familiarity are welcome!
We also want you to get your hands dirty with Firebase. You'll get a chance to put your new knowledge into practice with instructor-led codelabs, as well as ask our team any questions you have at our #AskFirebase lounge.
The day isn't just about us talking to to you, though. Our product managers and engineering team (including me!) are excited to meet you in person and hear your feedback about what is and isn't working in Firebase. Our community is what makes Firebase great, so we couldn't be more excited to get your help in shaping the future of Firebase.
As a native Dutchie, I'm thrilled that we'll be combining two of my favorite things at the Dev Summit this year: Firebase & The Netherlands! If you'll be traveling to Amsterdam for the conference, I highly recommend you stay an extra day. Take a canal tour, visit one of the many museums, rent a bike, or just take a stroll and say hi to a local. We're friendly, I promise :-).
We're looking forward to meeting you in person. Dank je en tot gauw!
If you haven't tried Firebase Performance Monitoring yet, many Firebase developers have found it to be a helpful way to get a sense of some of the performance characteristics of their iOS or Android app, without writing many extra lines of code. To get more detailed information beyond what's collected automatically, you'll eventually have to write some custom traces and counters. Traces are a report of performance data within a distinct period of time in your app, and counters let you measure performance-related events during a trace. In today's perf tip, I'll propose a way to add potentially many more traces to your Android app without writing very much code at all.
Android apps are typically made up of a collection of activities that present some task or data to the user. For the purpose of hunting down potential performance problems, it can be handy to define a trace for every Activity in your app, so you can study the results later in the Firebase console. If your app has lots of activities, it might be kind of a pain to write the code for all of them. Instead, you can write a little bit of code that instruments all of them with their own trace.
Android gives you a way to listen in on the lifecycle of every single Activity in your app. The listeners are implementations of the interface ActivityLifecycleCallbacks, and you can register one with the Application.registerLifecycleCallbacks() method. For measuring performance, I suggest creating a trace that corresponds to the onStart() and onStop() lifecycle methods. When an activity is "started", that means it's visible on screen, and when it's "stopped", it's no longer visible, so I think this is a good place to define a trace that tracks an activity while it's actually doing things. Here's the start of an implementation of ActivityLifecycleCallbacks that keeps track of traces for each of your activities. First we'll make it a singleton so it can be easily accessed everywhere (or you might want to use some form of dependency injection):
ActivityLifecycleCallbacks
Application.registerLifecycleCallbacks()
onStart()
onStop()
public class PerfLifecycleCallbacks implements Application.ActivityLifecycleCallbacks { private static final PerfLifecycleCallbacks instance = new PerfLifecycleCallbacks(); private PerfLifecycleCallbacks() {} public static PerfLifecycleCallbacks getInstance() { return instance; } }
Then, inside that class, I'll add some members that manage custom traces for each Activity:
private final HashMap<Activity, Trace> traces = new HashMap<>(); @Override public void onActivityStarted(Activity activity) { String name = activity.getClass().getSimpleName(); Trace trace = FirebasePerformance.startTrace(name); traces.put(activity, trace); } @Override public void onActivityStopped(Activity activity) { Trace trace = traces.remove(activity); trace.stop(); } // ...empty implementations of other lifecycle methods...
This will start a trace when any activity is started, and stop the same trace when the activity is stopped. For the name of the trace, I'm using the simple class name of the activity object, which is just the class name without the full java package. (Note: if you do this, make sure that your Activity class names are unique, if they're spread across Java packages!)
I'll add one more method to it that will return the trace of a given Activity object. That can be used in any activity to get a hold of the current trace so that counters can be added to it:
@Nullable public Trace getTrace(Activity activity) { return traces.get(activity); }
This class should be registered before any Activity starts. A ContentProvider is a good place to do that. If you're not familiar with how that works, you can read about how Firebase uses a ContentProvider to initialize.
public class PerfInitContentProvider extends ContentProvider { @Override public boolean onCreate() { context = getContext(); if (context != null) { Application app = (Application) context.getApplicationContext(); app.registerActivityLifecycleCallbacks( PerfLifecycleCallbacks.getInstance()); } } }
Don't forget to add the ContentProvider to your app's manifest! This will ensure that it gets created before any Activity in your app.
Once this ContentProvider is in place, your app will automatically create traces for all your activities. If you want to add counters to one of them, simply use the getTrace() method from the PerfLifecycleCallbacks singleton using the current Activity object. For example:
getTrace()
private Trace trace; @Override protected void onCreate(Bundle savedInstanceState) { trace = PerfLifecycleCallbacks.getInstance().getTrace(this); // use the trace to tally counters... }
Be sure to think carefully about the counters you want to log! You'll want to measure things that will give you information that helps inform a decision about how the user experience could be improved in your app. For example, you could record the ratio of cache hits to misses to help tune the amount of memory for the cache. And be sure to follow Firebase on Twitter to get more Firebase Performance Monitoring tips.