Jennifer is a productivity enthusiast who loves to start her day with a steaming cup of coffee. Every morning, she walks over to her friendly neighbourhood Starbucks and orders the same cup of coffee every day - a delicious cappuccino with extra hot oat milk and a splash of sugar syrup. Her morning ritual has stayed consistent for many many years.
John, the barista who takes her order every morning, is very passionate about his craft and wants to go above and beyond. This often leads to his excitement getting the better of him. Whenever a customer starts describing their order, he excitedly starts presenting them with some of the possible options that they might be interested in. Even though Jennifer knows exactly what she wants, she is presented with a few options which causes her to lose some of her valuable time. On first glance, this might feel like nothing. However, when this is happening every single day, these inefficiencies tend to add up over time.
As someone who takes her productivity very seriously, Jennifer wishes that her morning ritual could be more efficient 😢
I hate to break it to you, but this is happening to you every single day. Not only is it happening every day, it's in fact happening tens of times each day. This might not be obvious so let's emulate a simple task that you are doing very frequently - we will create a simple Composable function that references some existing functions that are a part of the Compose framework itself.
As I start typing an API that I intend to use, I'm presented with multiple options by Android Studio's autocomplete feature for my convenience. Fortunately enough, Android Studio is smart enough to elevate my most frequently used functions and classes to the top of the list. However, I'm also presented with some more options that I know I'm never going to use. In the example above, here's what I was being presented in addition to the actual API that I was searching for-
// When searching for the Surface composable android.view.Surface // When searching for the Row composable android.inputmethodservice.Keyboard.Row // When searching for the correct alignment to pass to the Row composable android.text.layout.Alignment android.widget.GridLayout.Alignment // When searching for the Text composable org.w3c.dom.Text
Don't get me wrong, auto-complete is a really critical tool for API discoverability. However, each option that's presented in front of you adds cognitive load and takes away a few seconds from your very valuable time because you are presented options you will never use. Optimising this workflow is even more important for your team as you start embracing a completely new framework like Jetpack Compose because you want to minimize the guesswork needed to do development as teams are still figuring out how things work.
Thankfully, there's a really straightforward way to improve this experience. Android Studio/IntelliJ ships with the ability to limit what you can see during code auto-complete.
It's available under
Preferences > Editor > General > Auto Import.
It allows you to specify exclusions at various granularity levels like packages, classes, members and even wild cards. This is really useful in practice and here are some examples at how you specify these auto import exclusions at each level of granularity.
// Packages org.w3c.dom // Classes org.w3c.dom.Text // Members kotlin.text.substring // Wildcards org.w3c.dom.*
As you can probably guess, this is a really powerful lever for not just reducing noise but also guiding developers to make the right choices in your codebase. For example, imagine that you've created your own design system in your codebase. Since you don't want to reinvent the wheel, you've also taken the Material Design dependencies to use as a starting point for some of your components. However, you don't want to allow your teammates to use the Material Design components directly. Code completion exclusions are a very effective way of doing just that. You probably also want to add some lint/ktlint rules to have strict checks in place but adding it as part of code complete exclusions is a useful way of documenting your API (not directly but in some ways if you think about it).
One thing to keep in mind is the scope of these exclusions -
IDE scope will restrict these rules to just your local development environment. In practice, you will probably
want to ensure that all your teammates also benefit from them. You do that by keep the scope of these exclusions
When you add exclusion rules with
Project scope, Android Studio generates a file called
codeInsightSettings.xml under the
.idea folder. Make sure that you commit this
file to version control. Given that this is a Jetpack Compose focused website, here is a ready to use
codeInsightSettings.xml that you can use for Compose development in your
codebase as well.
<!-- codeInsightSettings.xml --> <?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="JavaProjectCodeInsightSettings"> <excluded-names> <name>android.app.LauncherActivity.ListItem</name> <name>android.graphics.drawable.Icon</name> <name>android.graphics.fonts.FontFamily</name> <name>android.inputmethodservice.Keyboard.Row</name> <name>android.text.layout.Alignment</name> <name>android.widget.GridLayout.Alignment</name> <name>android.view.RoundedCorner</name> <name>android.view.Surface</name> <name>java.lang.reflect.Modifier</name> <name>java.nio.file.WatchEvent.Modifier</name> <name>java.time.format.TextStyle</name> <name>org.w3c.dom.Text</name> <!-- Optional: If you have your own design system and don't want to promote Material Design usage within your team --> <name>androidx.compose.material.*</name> <!-- Optional: I think Compose is a much better fit for any custom Views, so hide the Canvas API from the classic android view system to nudge developers to use Jetpack Compose instead --> <name>android.graphics.Canvas</name> <!-- Optional: Just for demonstration purposes for the use case where you have created your own variant of the Text composable and don't want developers to not use the ones provided by Compose by default --> <name>androidx.compose.foundation.text.BasicText</name> <name>androidx.compose.material.Text</name> </excluded-names> </component> </project>
Note: Make sure to verify that these exclusions make sense even for your codebase. Every codebase is different and you might need some of these APIs for day-to-day development. Regardless, this should be a good starting point.
Note: These merely remove things from code completion. Nothing is stopping an engineer from manually copy-pasting the import in their file and using an API. Hence you probably also want to add lint/ktlint checks to have stricter checks if that's what you desire. In my opinion, you want the combination of both these systems.
I'm sure a lot of you are wondering what I meant by the title of this article. I don't have conclusive data to back this but I made some reasonable assumptions and did some back-of-the-envelope estimates. Hopefully you can see where I'm coming from and why this simple change that takes less than 5 mins to add to your codebase can have a really meaningful (and unexpected) return on investment (ROI).
With that said, let's talk through some assumptions I made:
- You are working at a mid sized company with ~50 engineers working daily on the Android app
- You work 20 days/month on an average
- This optimization saves you just 10 seconds every day due to the streamlined decision making that we enabled with this change. Over time you will add more classes for exclusion as you learn more about your needs. You can audit and do this for more API's in your codebase.
With these assumptions, here's how many engineering hours you will save every year -
10 seconds x 20 days/month x 50 engineers x 12 months/year = 120,000 seconds = 33.3 engineering hours = 4+ engineering days every year (assuming 8 hour work days)
I don't know about you, but when the ROI can be this high for almost no effort, I would happily take it. This is not going to be completely accurate by any means but the point I'm trying to make is that for very little effort you have a lot to gain. In addition, once you lay this foundation, you will optimize this incrementally and hopefully see even more productivity gains.
On a side note, a lot of engineers make improvements without measuring its impact. You'd be surprised at how many times you didn't take the win when you totally deserved it. When there's no easy way to quantify and measure something, making reasonable assumptions and doing back-of-the-envelope math is a good strategy to quantify the impact. Do make sure to call out the assumptions clearly - hopefully you're peers will be able to help you fine tune your assumptions and reach a number that's realistic if it wasn't already.
Spending a little time in optimizing things that you are doing multiple times each day goes a long way in improving your day-to-day developer workflow. There are tools available at your disposal that make this super easy to do. Even though the focus of this article (and the site) is Jetpack Compose, you don't need to limit the code completion exclusions to just Jetpack Compose APIs. Use this as a starting point and figure out other packages/classes/members that should be excluded when doing Android development. I'm also open to hearing about more API's that we should be excluding to optimize our Compose development workflow. Do tweet at me with your suggestions and I'll be happy to append them to this article (and give you a shoutout).
I hope I was able to teach you something new today. There's more articles in the pipeline that I'm excited to share with y'all and if you are interested in getting early access to them, consider signing up to the newsletter that's linked below. Until next time!
Sign up to our newsletter for exclusive content and early access 👇