Ten #AndroidLifeHacks You Can Use Today
I’ve been kicking around the idea of a new type of Android blog post. A blog post that’s simple and applicable to your apps today. No new architecture pattern. No shiny new library. Simpler. Just a few tips and code snippets that we, at Tonal, have found useful that you can use today.
If you find #AndroidLifeHacks useful, let me know. I would love to see this become a trend where people and companies start sharing their own #AndroidLifeHacks. That way, we can all benefit from each other’s work.
Tip 1: Green Gutter Culture
Okay, I cheated. Tip 1 isn’t code at all, it’s a culture. On top of unit tests and lint, Android Studio provides a ton of built-in inspections. Unfortunately, there isn’t a good way to ensure that there are no warnings on CI (yet) but instead, we have our inspection profile checked into the repo (here is ours) so everybody sees the same set of warnings. Then, we built in the culture of always fixing or suppressing ALL inspection warnings. If any new warnings are encountered (from refactors or the addition of new inspections), we follow the boy scout rule to fix or at the very least, suppress, the warning for the next person so the gutter remains green.
One trickier case to handle is entry points such as Fragments that are only referenced via reflection. To get around this, we created @TonalEntryPoint. We can’t make that suppress unused warnings automatically but if you put it on a class or constructor marked as unused and press alt-enter, Android Studio will give you the option to add that annotation as a list of “entry points” which will prevent it from showing up as unused.
Tip 2: Treat all Kotlin warnings as errors
In line with Tip 1, we want to treat all Kotlin warnings as errors so they are either fixed or explicitly suppressed. To do this, we added the following to our root build.gradle file:
Tip 3: ViewBinding Delegate
We are big fans of View Binding. It has worked extremely well for us and we have already completely migrated from butterknife/Kotlin synthetics to view binding. However, we have found the syntax for creating it to be less than ideal so we made our own delegate to create it for us in a lifecycle aware way and it looks like this:
We have used this 440 times so far.
You can view the full code here.
Tip 4: fadeTo(visible)
Inevitably, there will be times when you need to change the visibility of a view. However, snapping views to visible/gone can create a jarring user experience. To get around this, we created fadeTo() which is a drop-in replacement for View.setVisibility() or View.isVisible = true/false and often provides a better user experience than immediately showing or hiding a view.
We have used this 152 times so far.
You can view the full code here.
Tip 5: mapDistinct()
This next one is really simple. It’s a shorthand for Flow.map { } followed by a distinctUntilChanged().
We have used this 79 times so far, 54 of which were added since I made the first draft of this blog post…
The full code is… The single line above…
Tip 6: uniqueObservable()
Kotlin’s Delegates.observable is pretty useful. However, we found that:
- Often, you only want to do something when a value has changed.
- When that is all you want, writing the lambda with 3 parameters and checking for equality felt like too much boilerplate.
Instead, we wrote uniqueObservable() which functions the same way except it only called your lambda with the new value when it has changed.
We have used this 276 times so far.
You can view the full code here.
Tip 7: Debug + Coroutine Broadcast Receivers
Broadcast receivers are seldom used in Android these days. However, we have found them to be incredibly useful for testing things in debug. We set up debug-only broadcast receivers that essentially gives us a CLI to set/update parameters in code without having to bring up a UI debug menu or rebuild the app. We use this for a variety of actions from simulating cable/arm position changes, completing reps, achieving PRs, and dozens of other things. I originally wrote about this approach here.
Since then, we have updated our code to be coroutine friendly and it looks like this:
We have used this 87 times so far.
You can view the full code here.
Tip 8: ConflatedJob
A common action with coroutines is to cancel a previous instance of a job before launching a new one such as if a user pulls to refresh. ConflatedJob automatically cancels the previous job when launching a new one.
NOTE: this doesn’t currently join the existing job, it cancels the old one and launches the new one right away. We are exploring some options for situations in which you need to wait for cancellation prior to launching the new job.
We have used this 86 times so far.
You can view the full code here.
Tip 9: Timber Property Delegate
We use Timber for logging but find the syntax of calling Timber.tag(TAG).i(…) to be cumbersome. Similar to the view binding delegate, we created a delegate that simplified things and it looks like this:
It automatically creates a TAG once time per property (not once per log line like Timber.DebugTree, automatically truncates common suffixes such as “Impl” and “ViewModel” and trims tags to 23 characters but you can override it if you would like.
We have used this 157 times so far.
You can viewthe full code here.
Tip 10: Number.dp
It’s common to need to convert from dps to pixels in code. Most designs use dps yet most View properties take pixels. It is not surprising that this StackOverflow post has 873 points and 34 answers. To make this easier, we created the following extension function:
With this extension, updating padding in code (using the ktx updatePadding function) is as simple as this:
We have used this 551 times so far.
The full code is… The single line above…
These are just ten of many #AndroidLifeHacks that we have built up to make writing Android at Tonal safe, fun, and fast. We are always looking to improve at Tonal and want to share what we’re working on because we think it could be helpful for pretty much every team. At the same time, we would love to hear about how any of these tips could be improved or even about other tips that aren’t mentioned here that you have found useful!
P.S. Tonal runs Android but our mobile companion app (Android, iOS) is built with Flutter, our backend is written in Go, and we have an incredible data science team. We’re hiring across all platforms and functions so please reach out to me or apply directly if you want to be a part of the team! If you are interested in a role but in a different location DM me on Twitter.