While recently working in the luxurious upstairs loft that is home to the Aeris Weather software development team, I ran into an issue that had me stumped for a bit. Thank goodness for Google and Stack Overflow, but even with the help of our Android benefactor and the developer’s crisis hotline we know as SO, it took me a little digging to find all the pieces of the puzzle. So this episode of our Aeris developer’s blog is devoted to all of you Android devs who are searching for an answer to “The Mystery of the Duplicate Icon”.
The mission was simple, even for an Android newb like myself — “Update the Android Wear app to better accommodate the newer round devices (Moto360 Gen 2, LG Urbane, etc.) that have become popular since our Aeris Wear product was initially released.” I set about rebuilding the app and deploying a new build to my test devices, when I noticed a strange new issue: a duplicate application icon had appeared. A few Google searches later, I determined that the problem was actually stemming from an external library used within the app.
One of the first things we learn with Android development is that specifying an activity with the category LAUNCHER tells Android that we want to start the app here. Android creates a shortcut on the device using the name and icon specified (sometimes through a theme, but that’s a subject for another blog post).
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
What isn’t so obvious, though, is that if a library we’ve included in our build has also specified an activity as launchable, Android is kind enough to automatically drop an icon on our device for that too.
At this point you may be screaming “Libraries aren’t supposed to declare launchable activities!”, and you’re absolutely correct. However, even though in this case it was my own fault (the library in question here was one that I created <sigh> <head slap>), it seems that this is a fairly common occurrence. Run a Google search on “duplicate app icon android” and you’ll see what I mean.
Since I created the problem with my own library in this case, I assumed I could resolve it quickly and move on. In fact, it was pretty painless — I was able to just remove the entire <application> section from the manifest file et voila! I pushed a staging build of the library to my repo and triumphantly clicked the Rebuild button to watch my app restore itself to shiny working goodness. Or not. As you might expect, it didn’t end there. Android Studio was now showing an error that was previously unseen (that never happens, right?):
Gradle finished with non-zero exit value 1 (ic_launcher.png: error: Duplicate file)
I found that although my library was no longer declaring itself as a launchable entity, there was still one thing I had missed. If you make the mistake of building a library as an application, Android Studio will help you out by tossing in a default icon: the little Android guy that you see when you first build your app. Being the default icon, it naturally has the default name of ic_launcher.png, which is great except that we already have one of those in our app. When the compiler merges the library and application resources together, it finds two ic_launcher.png files and spits out the error shown above.
Since we don’t need a launcher, or launcher icon, or any of that stuff in our library, the answer was to just simply delete the png file from the library project. I like how Ivan put it in the Stack Overflow thread “Launcher icons have no business being in a library”. Make sure to do this from Android Studio, because it has copies of the png file in several folders and will clean up after itself nicely. With this completed and the library finally put together like it should be, the build finished without error and I could finally get back to the actual task of updating the Aeris Wear application.
That’s it — remove the application definition from the library manifest and delete the ic_launcher.png file from its project. Seems easy enough, but it’s amazing how much time it can take to unravel a little mystery like this. Hopefully this post will help save someone out there from a head slap or two.