_Sup for Android_ Part 2: Electric Boogaloo
Monday, December 1, 2014 :: Tagged under: engineering pablolife. â° 8 minutes.
Hey! Thanks for reading! Just a reminder that I wrote this some years ago, and may have much more complicated feelings about this topic than I did when I wrote it. Happy to elaborate, feel free to reach out to me! đ
Welcome back! Part 1 covered architecture, and Part 3 covers testing and miscellany. Here weâll talk about --
Libraries: âthe cause of, and solution to, all of Androidâs Problems.â
The first version of Sup was written in about 4 weeks. Being a developer with little Android experience, I was able to produce this quickly in large part due to extensive, wonderful libraries. The quote in the title illustrates how these libraries are both extremely helpful, and also serve to remind you that all software is broken, and weâre trying to get to the moon by stacking chairs.
The following are some libraries every Android developer should at least know about, whether or not I used it, and why.
Dagger
Iâll start with the most bittersweet: Dagger is a dependency-injection framework for Java. The other notable player in this space is Guice. Dagger builds on the ideas of Guice but is better-suited for mobile by both doing more work statically, and having the programmer specify some relationships more explicitly.
Many programmers donât know what DI is or why itâs useful, so Iâll point you to Guiceâs Motivation Page, which explains the utility behind DI pretty well.
To be clear, I love Dagger. Iâm using it in this project because I wholeheartedly believe it provides a major net savings. But like any major abstraction in software, thereâs an investment to learning and understanding it before you can cash in on that benefit. For many engineers, the cost of investment feels too high for what we think weâll get out, so we donât bother to invest in the first place (recall the Blub Paradox).
It can be a challenge to determine when an abstraction or tool is really worth it. Remember that even using version control at all was an item on The Joel Test because there was a time that many vendors thought it wasnât worth the trouble of learning it, setting it up, and using it consistently.
There is a learning curve to Dagger, and while the homepage is full of simple,
sweet examples, there will be times you will want to do something seemingly
simple and Dagger wonât let you. Youâll read the options to the @Module
annotation several times to get it working, and even after it is working, you
may not exactly know why.
I feel pretty confident now in my knowledge of how it works, but it wasnât easy, and when I had another team member join me for part of 2.0, this was a pain point for them. Fair warning!
That said:
- All object initialization and dependency satisfaction is easy as cake.
- Singletons and their distribution/management, especially, are an absolute breeze.
- I love the amount of control I get over different flavors of builds.
If youâd like a good example for how to use Dagger (and a myriad of other libraries) in a Gradle project with different configurations for other build flavors, do take a look at u2020.
All that said, Dagger relies heavily on Annotations and code generation. This has repercussions for your project:
- ProGuard is very difficult for Dagger 1.x (the current version). If you use Dagger (at least until 2.0), youâre opting not to use ProGuard.
- Kotlin also becomes a no-go, or at least very hard, for the same reason. If you want to try the new hotness that is Kotlin (the Swift of Android!) this wonât play nicely immediately.
So, think carefully when deciding to use Dagger.
Picasso, ButterKnife
After the hoo-haa of Dagger, here are two unambiguously good additions to your project. There is absolutely no reason to use anything other than Picasso for loading network images into your ImageViews. The Transformation interface made the circle crop in Sup profiles an afterthought, caching makes sure images Just Work when offline, and it handles all its network requests in a separate thread. Just use it.
ButterKnife similarly exists only to make your code more readable and save
you a ton of typing. If youâre working with classes that have a dozen or so views
that need configuring, ButterKnife.inject(this)
is all you need to wire up your
classes.
Otto
We use Otto for our Event Bus. Guava has one, but as Ottoâs homepage points out, itâs not especially suited for mobile. I trusted it because Square Open Source, but in retrospect I think I should have investigated its competitors more.
Otto is great, here are some gotchas:
- Per Dagger, it relies on annotations, so the same repercussions apply.
- Unregister your objects. Registering an object like a Service, Activity, or Fragment is an easy way for it to be retained when itâs not needed, since the Otto bus will retain a reference to it. Beware the memory leak.
- They explain this on the splash page but itâs easy to get bitten: events will
not look for Subscription handler code up an inheritance chain, vtable-style.
If you have an abstract class and want all its children to receive and handle
events a certain way, you canât just register the object and put the
@Subscribe
methods in the superclass. It violates DRY, but youâll need it both places.
Retrofit, OkHttp
Use these for your network requests. Volley over Retrofit is worthy of consideration, but there seems to be no major benefit and I prefer the interfaces of Retrofit.
The only caveat Iâd mention for Retrofit is that it may take some tweaking if
the service youâre querying has made certain design decisions on its data, as
Sup did. Retrofit is great if the data being returned to you follows a proper,
consistent resource model â almost all of their examples use return objects like
List<User>
on a GET /users
call, Project
for GET /project/{projectId}
, etc.
Supâs server was originally designed to accomodate the iOS client, which
represented the JSON object returned by the server as a queryable dictionary. So
POST /sups
returned a JSON dictionary with certain naked values the client may
have wanted, but GET /sups/statuses
returned another dictionary of its own
naked values. While theyâre both on /sups
, itâs not a clean interface, and required
the Java client to define a fair number of 'shell classes' -- POJOs whoâs only
purpose is to hold the precise, naked values of each and every call. We have
something like 40 of them.
The other caveat would be if your response objects use their data to describe themselves. Sit tight, because this oneâs a doozy:
Given that
- SPEED IS EVERYTHING in startups SHIP SHIP SHIP.
- The App Store can take one or two weeks to approve updates.
- Any update will require users to install the update.
- We didnât have product/market fit and wanted to experiment -- would users Sup places? Suggested Users for people with no contacts? What if we wanted to show people who had them in their contacts?
The solution was to let the the JSON object returned by the server describe its own contents, and program the client to parse the response object and render it according to its own spec. So something like:
{
sections : [
{
name : "contacts",
title : "Contacts",
type : "suppable cell"
},
{
name : "suggested",
title : "Suggested Users",
type : "suppable cell"
},
{
name : "places",
title : "Places",
type : "nonsuppable"
}
],
contacts : [ /* list of contacts */ ],
suggested : [ /* list of suggested */ ],
places : [ /* list of places */ ]
}
This is great for the bullet points above! This is terrible if your library counts on the response being represented by a POJO and not a dictionary!
For that case I ended up just getting the response data raw and using a JSON parser on it. Not the end of the world, but gross. Be aware!
OkHttp is a lot more simple. I use it for non-API network operations like uploading to S3. They have a great recipes page that engineer Jesse Wilson said they were quite proud of in his OkHttp talk, and I would say rightfully so.
Realm
A new one! If youâre an Android vet, you may have been nodding along at the mentions of these previous libraries, but Realm for Android has just shipped two months ago!
Realm for iOS has existed for a while, and it was something our iOS engineers were discussing as a solution to data persistence (apparently CoreData is terrible). Android isnât much better: youâve got Preferences, raw files that you write yourself on Internal or External storage, or raw SQLite.
Sup 1.0 had a pretty simple, but laborious and fraught scheme: all individual data that required persistence (particular strings, booleans, etc.) were stored in SharedPreferences, and any data of which there can be many elements (items on the Contacts Screen, pending Sups) were stored in SQLite tables. Migrations were a pain. There was lots of data conversion, especially since SQLite doesnât have a great representation for Dates.
Realmâs docs (correctly) make it seem pretty easy to operate. And it is! Go use it!
Some impressions, however:
- Realm ships with a fair bit of native code, and will add a few megabytes to
your application. I hit a delightfully gross bug because one of our other
dependencies shipped armeabi native libraries, but failed after including Realm.
The issue was that because Realm ships witharmeabi
andarmeabi-v7a
directories, armeabi-v7a devices saw the presence of a more specific directory, looked there for all their native dependencies, and failed to find the first library. - This also means you need to strip the Realm JAR of architectures you donât want to support, like x86 or MIPS.
- Realm is very new, so unlike a lot of these other libraries or Android itself, you wonât find many Stack Overflow questions on it, or blog posts. Use the mailing list! They are very friendly!
Lastly, and this is a harder point to articulate -- Realm, like any other abstraction, especially a recently released one -- can and will fail in mysterious ways. Its performance is achieved with the help of native libraries, but this means when things go wrong, it wonât always be immediately clear why as the crash can happen at the native level. Looking at my Play Store stats, Iâve got a few (not many!) Realm-related crashes that I need a bit of time to pore over, and donât know how to reproduce.
Furthermore, Realm objects achieve much of their ease and performance with a few tricks that may come out to bite you. For example, your data is represented as a Java Object you define, but if you pass that object to another thread (say, the callback to a network request) and attempt to access its fields without the proper ceremony, Realm will crash.
Per the other tools, Iâm happily using it because I wholeheartedly believe that it has saved me time and effort. Just understand that thereâs some unexplored territory here, and while SQLite is laborious and gross, itâs generally easier to understand its use and failures.
Lesser-knowns: Phrase, Timber, pidcat, Hugo, NineOldAndroids, and ActionBarSherlock?
These Iâll just sprint through.
Phrase does one small thing, and does it well. Itâs explained here. I
prefer it to String.format
for user-facing Strings but itâs hardly world-changing.
Timber is great, if only because you no longer have to add public static final String TAG = "ClassName";
to every class and subsequently use it in every logging
call. That you can âplantâ different trees based on your build type is icing on
the cake.
pidcat is my preferred logging view. Just have a full-screen tmux pane with your logs, beats having to resize your IDE window.
Hugo: frankly I discovered it late and never used it much, but looks delightful.
NineOldAndroids is less and less important now that 4.x phones are gaining
more users. That said, the Android Dashboards still place about 10% of users on API
10, and NineOldAndroids is handy for the very least for back-implementing
View.getX
and View.getY
, inexplicably missing from API 10. Being able to use
ObjectAnimators without fear is an added bonus.
Finally, ActionBarSherlock. We used to use it, but now with the Toolbar being the recommended approach I think you should probably just use AppCompat (we switched).
Thanks for the read! Disagreed? Violent agreement!? Feel free to join my mailing list, drop me a line at , or leave a comment below! I'd love to hear from you đ