Use Android's @+id Notation With Care
Being sloppy when it comes to managing your application’s resource IDs can lead to subtle bugs that are difficult to find and debug. Imagine a scenario where foo_activity.xml holds the layout definition for FooActivity, and you define three different variants for different screen configurations:
res/layout/foo_activity.xml
res/layout-land/foo_activity.xml
res/layout-sw600dp/foo_activity.xml
Now, with some certainty these three layouts will share the same views, with the same IDs, just slightly differently styled or arranged. Let’s furthermore assume in all three layouts, we have a TextView:
FooActivity will of course retrieve a reference to this TextView via findViewById:
Now what happens if in the main layout file (layout/foo_activity) you change the view’s ID? You may be surprised to hear that your application will still compile. That’s because the old view ID, my_text, still exists in R.java, since while now gone from layout/foo_activity.xml, it’s still (re)created using the @+id notation in the other two layouts, thus continuing to exist in the ID pool. Whenever these layouts are loaded and you reference the new ID from FooActivity, then of course your application will crash.
The problem here stems from violating the DRY principle: we’re carelessly repeating the code which creates a resource ID, when ideally, it should only ever be found in one, and only one part of the application. To recall what @+id does, it’s an idempotent “create this ID” action. In other words, if that ID has not been defined yet, it will get defined, otherwise it will be used. So it’s safe to use this notation multiple times with the same ID, which may be the reason why people overuse it: it looks like a safe bet, when it’s actually not.
There are three approaches I have tried to deal with this issue:
1 - Pulling view IDs into styles
When redefining views multiple times in different layouts, one approach could be to extract the respective view IDs into a style, then apply the single shared style to all three variants of the view:
While I first favored this, there are several problems with this approach: first, it reduces the visibility of IDs, which can be confusing when dealing with views in RelativeLayout, where you reference views using IDs. Moreover, IntelliJ IDEA at least will get terribly confused and issue an error, since it doesn’t resolve styles to inspect the correctness of a layout file (it’ll assume the view is missing the ID attribute.) Lastly, and this is purely a style question, one could argue that styles should be concerned with only visual appearance, not structural attributes like IDs.
2 - Pulling view IDs into ids.xml
Another option is to pull the shared view IDs into a global resource file, e.g. in res/ids.xml:
This will turn the respective view IDs into first class resources themselves, and by extension make them reachable via R.id and in all layout files. Here, too, no @+-notation is required in any layout files anymore. You would define shared IDs in one, and only one location.
The problems with this approach are similar to 1, but it clears up with the stylistic problem of defining IDs in a style sheet.
3 - How We Do It (TM)
We ended up taking a third route, which is one of convention. We’ve agreed on establishing a rule which says that it’s fine to use @+id in layouts files, but only use it in the default layout file, i.e. the one located in res/layout. Whenever a layout is overloaded using different configuations, then even for the same views, @id should be used. That way we ensure that there is only a single location where an ID actually gets defined, without taking it completely out of context when working with layout XML. Moreover, changing the view’s ID will lead to compilation errors, since all overloaded variants now reference a non-existing ID.
I’d be interested to hear how everyone else deals with this.