Falcon Pro 3 is the much anticipated rewrite of one of the most popular Twitter apps in Android history. In just 3 weeks, Joaquim Vergès rewrote the app from scratch, and the new version generated 60,000 installs in 20 days with no advertising. In this talk, presented at the Bay Area Android Developer Group, Joaquim shared what he learned about some of the newest libraries available on Android, rendering at 60fps, making sure people pay for your app, seeding your beta, and much more.
Background & Prep (0:00)
Falcon Pro 3 took me three weeks to write, completely from scratch, from the moment I wrote the first line of code to the moment I published it on the Play Store. It was during Christmas holidays because my wife broke her leg, and so I had to cancel all of my plans and stay at home in San Francisco during awful weather. I had a little prototype of a new version of Falcon Pro, so I gave myself a two week deadline.
Before I wrote any line of code, I geared up. I always add these four Square libraries to my project. Picasso is one of the best image loading libraries right now, there’s no one beating it. Retrofit is a magical REST client for Android that’s really easy to use. Otto is kind of optional, it’s an event bus that could be useful sometimes. Butter Knife is basically a view injection library that allows you to note write any findViewById, but injects your fields with the idea of the view and you have it in your class.
Start Coding (4:47)
I more or less knew what I wanted for this app. I wanted a ViewPager at the beginning with lists that the user could swipe through. Clicking on one item of the list would take you to another activity, maybe a detail. Clicking on one of the profile pictures would take you to a profile screen.
My rule in making this app was whenever you had a new screen that was supposed to be on the back stack, you make a new activity. Especially with the new activity transition API on Lollipop, I thought this was a good rule. The back stack for activities is really easy to manage. Insade an activity, do not add back state states, but you can have fragments. The advantage of this is that you can enter the user flow from other events. You also get easier restoration logic, so you can return to an app at the screen the app gets killed on.
Caching (7:56)
After figuring out more or less how I want to architect the app, I need extremely good caching. This is a Twitter app, so I wanted to start up immediately and show something on the screen, the thing being whatever last came from the server. For caching, I searched on the internet and found two libraries high in the results: ORMLite and Realm. ORMLite is completely in Java and is all open source. It’s based on the classic SQLite. The other one is Realm, which has a closed course C++ core with a Java binding, which is open source. It’s based on a proprietary database which is called TightDB.
The first thing I wanted to look at was how to make Java objects persistable with each library. ORMList has a very nice way to deal with this: you simply annotate your class with a database table, give it a name, and that’s going to project to a SQL table. You annotate each of your fields and give a bunch of parameters, and that is basically your database schema. This is very straightforward; your objects are completely independent.
@DatabaseTable(tableName = "accounts")
public class Account {
@DatabaseField(id = true)
private String name;
@DatabaseField(canBeNull = false)
private String password;
// getters & setters
}
On Realm, all you have to do to make your objects persistable is extend a RealmObject. This is really nice because you only have to write two words, but it also limits you a little. You can’t extend your own objects if they’re already an extended RealmObject. I felt like Realm is trying to make your life as a developer easier, while compromising a little bit on the flexibility.
public class Account extends RealmObject {
private String name;
private String password;
// getters & setters
}
Writes (10:36)
How do you persist those objects on disk? In ORMLite, you have to manually create the table for each of your classes that you want to persist. You have to acquire a certified object, called a Dao. What’s really nice is that you create your objects just like you would normally, and at the very end, you simple take that third Dao object and call create to write to disk.
// if you need to create the 'accounts' table make this call
TableUtils.createTable(connectionSource, Account.class);
// instantiate the DAO to handle Account with String id
Dao<Account, String> accountDao = databaseHelper.getDao(Account.class);
// create an instance of Account
String name = "Jim Smith";
Account account = new Account(name, "_secret");
// persist the account object to the database
accountDao.create(account);
Realm has one object that you always deal with called Realm. All of your writes have to be wrapped in a transaction, so you always start with beginTransaction and you always have to remember to close it. Everything between that code is what will be written to disk. This means that you don’t have your own constructors They actually had a version released a couple weeks ago where now, you can write your objects normally with your constructor and save it later on with a Realm object. So, the two seem pretty similar with the writes. Realm may be a little bit nicer with the transaction wrapping, because it makes it feel more efficient just to have all your writes wrapped up like that.
// instantiate
Realm realm = Realm.getInstance(this);
// begin transaction
realm.beginTransaction();
// create and fill objects to persist
Account account = realm.createObject(Account.class);
account.setUsername("Jim Smith");
account.setPassword("_secret");
// commit the transaction
realm.commitTransaction();
Queries (12:35)
How do I get back those objects that I wrote on disk? In ORMLite, I have to again get the Dao object. From the Dao object, I can get a query builder and build my query. In Falcon, I needed lots of different queries to run on my cache. Here, I wanted to get accounts where name equals foo and password equals _secret, or name equals bar and password equals qwerty. When you read the code, it’s not as straightforward as the plain English for the querty. You have to read the or first and realise that it’s wrapping the two ands, which are wrapping the two equals. So, I feel like this API could be a little bit nicer, although it’s still very powerful.
QueryBuilder queryBuilder = databaseHelper
.getDao(Account.class).queryBuilder();
Where where = queryBuilder.where();
where.or(
where.and(
where.eq("name", "foo"),
where.eq("password", "_secret")),
where.and(
where.eq("name", "bar"),
where.eq("password", "qwerty")
)
);
where.query();
Realm has a really nice query for an API, very modern. You can write the query in the same order that you think it, which is a really important point for me. The API has to be as natural as possible, and Realm does a really good job on that.
RealmQuery query = realm.where(Account.class);
query.beginGroup()
.equalTo("name", "foo")
.equalTo("password", "_secret")
.endGroup()
.or()
.beginGroup()
.equalTo("name", "bar")
.equalTo("password", "_qwerty")
.endGroup();
query.findAll();
When I was trying to decide which one to go with, I saw Realm had a little bit less flexibility in the query, but I didn’t need that much complexity in Falcon. The benchmarks for Realm claimed it was seven times faster than SQLite when doing queries, and so I tried it out. I queried ten thousand Tweets on the main thread, and it performed so well. So, I went with Realm.
Threading (16:24)
I used a wrapper for AsyncTasks called NanoTasks, which provided a quick way to do something in the background and post it back. The code was separated into two interfaces: one that does stuff in the background, and one that calls you back on the main thread with an onSuccess and onError. It guarantees that the context you get in this callback is never null, or you just don’t get called back. This saves you so many crashes, and the API is pretty nice and easy to use.
Tasks.executeInBackground(context, new BackgroundWork<Data>() {
@Override
public Data doInBackground() throws Exception {
return fetchData(); // expensive operation
}
}, new Completion<Data>() {
@Override
public void onSuccess(Context context, Data result) {
display(result);
}
@Override
public void onError(Context context, Exception e) {
showError(e);
}
});
UI (18:07)
For the UI, I looked at this thing called RecyclerView, which seems worth it in the end to use if you’re starting a new app. If you have an existing app with lots of ListViews, don’t bother because the API is not that great. The class has over eight thousand lines of code, and requires work to port from listview. When you start a new app, you have to attach a LayoutManager to a new RecyclerView, but Google doesn’t just put a default one in the RecyclerView object. It’s also unclear what methods should be called in either the LayoutManager or the RecyclerView.
RecyclerView recyclerView = new RecyclerView(getActivity());
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
layoutManager.scollToPosition(0);
recyclerView.scrollToPosition(0);
The good thing about RecyclerView is that you get free insert and remove animations. It handles position changes much better than ListView, and the default implementation performs well. API aside, RecyclerView offers nice improvements over ListView, so definitely go with it for new apps.
Animations (23:26)
Animations, specifically shared elements, are my favorite API of Android 5. Activity transitions are nice, but the built-in ones aren’t revolutionary. It becomes really powerful when you do your own, and shared element transitions are awesome. The only thing you have to remember is that on the receiving activity, you only start the enter animation when the view that you set the TransitionName to is completely laid out and ready to be displayed. If you do it out of the box with images loaded from network, the animation won’t play when you click. This only works with Android 5, but there’s a little static method in the support library called versionUtils.isAtLeastL that makes it easy to fold back.
Another thing that people tend to forget, which I use quite a lot, is LayoutTransitions. It literally takes three lines of code. One, set the LayoutTransition on the parent; the other two are set one view to gone and the other view to gone. It’s not perfect, but it’s better than having no animation at all. The last little thing I do is to always have the class animUtils, which are static methods to grow a view, or shrink, slide, fade, expand, or collapse everything. I use these all the time in my projects.
Scrolling is also really important. One thing that always infuriated me was when iOS developoers would say “Oh, Android is so janky”. Android can be really smooth, but you need to knowk what not to do. What you don’t want to do is have a deep view hierachy in the ListView, so keep your items and the layouts flat. Don’t have allocations on a critical path, and don’t overdraw. Make sure you have few solid backgrounds as possible overlaying each other, because the GPU has to draw all of those over and over again. Some tools that I find really useful are GPU rendering profiling and Tracer for OpenGL. There’s also new tools in Android Studio which are awesome - there’s the memory monitor, which is a graph of your memory in real time, and also an allocation tracker, which records all the allocations that you’re doing.
Design (35:33)
For this project, I had no designer. I did this in two weeks, I didn’t have time to interact with a designer. Before I did a screen, I would always sketch it realy quick to make sure items fit where I thought they would fit. For the colors, I used this website called coolors.co which generates color palettes for you really quickly. For icons, Google released around two hundred material icons stuff, but it was hard to navigate in their zip file. Instead I found this little plugin for Android Studio, where you get this neat thing that autocompletes what you want. It generates all the formats for you and everything, you don’t even have to create your ID.
Text is also underrated. You can make text look really good with just a few tweaks. In Falcon, I heavily used the Android font_family XML attribute, no custom fonts. You can play with the weight and the colour of the font, which will help make the text nicer. Another trick I found was to make your lines of text look spaced on, and there’s an XML attribute for that on TextView called LineSpacingExtra.
Another rule I have is that content is king. If you have images, make sure they’re as big as possible; edge to edeg is preferable. That will take a big part of your screen that you don’t have to design for. Finally, you have to think where to add little details that will make all the difference. I added a separator between the Tweets comprised of one line darker than the background and one lighter line. This simple trick gave the nice effect of volume and made the app go from prototype to polished. I also like to add outlines on my images, so it makes them pop out and make the contrast a bit nicer.
Crash Reporting & Analytics (40:30)
I went straight to Crashlytics for crash reporting and analytics. I imported it into my project and they do it all for you. In one line of code, you get an awesome crash reporting tool with a dashboard and everything. The only downside I could say is that you don’t have custom events, like knowing when people click somewhere.
Security (41:41)
Another thing I had to think about was how to bring back money with this app, but I heard experiences from some game developers where they released their awesome games but only had 5% of installs be paid users. You want to obfuscate your code a bit so it’s not too easy to reverse engineer. If you have an app that’s paid up front and your code is cracked, the cracked version can be distributed.
I decided to go with a free app that had in-app purchases, because those are much easier to control. In-app purchases by themselves are also not enough. You need a little server site to make some verifications that, without it, the user cannot really use the app. So what I did in Falcon is, you have to buy an account. I do some checks through my server, and once I make sure everything is okay, I send back the thing that allows users to actually use the app.
Beta & Publish (44:30)
Once I had everything ready, with a stable and functional app, I had to decide whether or not to publish it directly. From a previous experience, don’t do private Google+ communities. You can only accept people 40 by 40, and to make things even harder, you had to refresh the page in order to invite another forty. If you’re going to release a beta to a public Google+ community, you might as well push it directly, right?
After I decided to just publish the app, I needed some quick marketing material. The video is important because you want people to see your app. I use the adb shell screenrecord. You can make a simple banner made from one of your nicest screenshots, and frame your screenshots. The hardest but most important to me was the launcher icon, which is like the first impression.
So I released super early, after a few weeks of development, and actually got a bit of drawback because I didn’t put beta or anything in the name. One day after, I had a rating of about 3.0, which probably was due to the lack of beta tag.
User Driven Development! (48:58)
I really love the concept of user driven development. You release as soon as possible, and you still lack features, but that’s okay as long as your app is stable. What you want is user feedback, because it will not only give you insight about your app, but also make your user feel like they’re part of the project. Your to-do list is set by the requests you get, and once you’ve added a feature or fixed something that the users were asking, they become more engaged than a user that never had a problem in the first place. Motivation is a big part of this - when you have a ton of users asking for a feature, you’re going to do it and fast.
Q&A (52:36)
Q: I saw you pass the context into Realm, when does it release it?
Joaquim: You can open as many instances as you want. Under the hood, it’s going to keep a map and keep opening instances. To make sure you release that context, you have to go close on the Realm instance. If you have objects that don’t have life cycle, you don’t really know where you need to call close, but it forces to have one main Realm instance that you try to reuse everywhere.
Q: How thread safe is Realm?
Joaquim: It is so thread safe, you wouldn’t believe it, but it’s good and bad. It’s so thread safe that you cannot use any object that you query on a thread from another thread. That will actually crash on runtime. It makes you think a bit more about what you’re doing, but there’s no safer way to do this. That’s why I did it on the main thread. If you have big stuff to query or write, do it on a background thread and then re-query on the main thread because the technology makes it really cheap to re-query it once you’ve already warmed up all the cache. Tim: We’re also working on giving you a convenience method to do that and handle the results on the UI thread!
Q: How did you do push notifications? Did you have to write server code for that?
Joaquim: I actually don’t do push notifications. It’s all pulling. This was two weeks of work, I didn’t have time to write any server code. Doing push notifications for Twitter is quite the investment, so I thought it would be better to use this feature called smart refresh, which adapts pulling to your usage.
Q: When you were designing this application, did you have tablets in mind when you were designing your layouts?
Joaquim: I actually had a tablet layout in mind for this design, but usually I don’t. I just think for phone firstly, because I use my apps on the phone most of the time. But actually, for this app, I have a tablet layout in mind which is going to work perfectly since it’s column-based.
Q: How did you think about orientation?
Joaquim: I just did a simple thing, nothing special. Re-layout, it’s a bit stretch out, but it works. Maybe when I work on the tablet layout, I’ll tweak the layout for the phone as well, especially with the size of phones today.
Q: Could you elaborate on how you used the in-app purchases as a way to avoid Twitter’s token limit?
Joaquim: On the last version of Falcon, I really got screwed when I added the multi-user feature because people bought it once but had eight accounts. With in-app purchases, I can now have people with multiple accounts pay a bit more than the person who only wants one account. That way, I can also limit the people who actually have eight accounts, so now they think twice before adding them. With a free app, I would hit the limit immediately, so I put the price a little higher to make sure it goes slowly.
Receive news and updates from Realm straight to your inbox