====================== Android Best Practices ====================== .. contents:: We will take a look at some standard practices we should apply when building Android applications. :local: .. _Android SDK: https://developer.android.com/studio/install?pkg=tools .. _Gradle: https://gradle.org/ .. _Android Gradle plugin: https://developer.android.com/studio/build/ .. _default structure: https://developer.android.com/studio/build/#sourcesets .. _Gradle for Android: https://developer.android.com/studio/build/ Android SDK ----------- Place your `Android SDK`_ somewhere in your home directory or some other application-independent location. Some distributions of IDEs include the SDK when installed, and may place it under the same directory as the IDE. This can be bad when you need to upgrade (or reinstall) the IDE, as you may lose your SDK installation, forcing a long and tedious redownload. Also avoid putting the SDK in a system-level directory that might need root permissions, to avoid permissions issues. Build system ------------ Your default option should be Gradle_ using the `Android Gradle plugin`_. It is important that your application's build process is defined by your Gradle files, rather than being reliant on IDE specific configurations. This allows for consistent builds between tools and better support for continuous integration systems. Project structure ----------------- Although Gradle offers a large degree of flexibility in your project structure, unless you have a compelling reason to do otherwise, you should accept its `default structure`_ as this simplify your build scripts. Gradle configuration -------------------- General structure. Follow Google's guide on `Gradle for Android`_. **minSdkVersion: 21** We recommend to have a look at the Android version usage chart before defining the minimum API required. Remember that the statistics given are global statistics and may differ when targeting a specific regional/demographic market. It is worth mentioning that some material design features are only available on Android 5.0 (API level 21) and above. And also, from API 21, the multidex support library is not needed anymore. **Small tasks**. Instead of (shell, Python, Perl, etc) scripts, you can make tasks in Gradle. Just follow Gradle's documentation for more details. Google also provides some helpful Gradle recipes, specific to Android. **Passwords**. In your app's build.gradle you will need to define the signingConfigs for the release build. Here is what you should avoid: Don't do this. This would appear in the version control system. .. code-block:: Groovy signingConfigs { release { // DON'T DO THIS!! storeFile file("myapp.keystore") storePassword "password123" keyAlias "thekey" keyPassword "password789" } } Instead, make a **keystore.properties** file which should not be added to the version control system: .. code-block:: Groovy keyAlias=App keyPassword=password123 storeFile=/../keystore_app.jks storePassword=password789 That file is automatically imported by Gradle, so you can use it in build.gradle as such: .. code-block:: Groovy def keystorePropertiesFile = rootProject.file("keystore.properties") def keystoreProperties = new Properties() keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) signingConfigs { config { keyAlias keystoreProperties['keyAlias'] keyPassword keystoreProperties['keyPassword'] storeFile file(keystoreProperties['storeFile']) storePassword keystoreProperties['storePassword'] } } **Prefer Maven dependency resolution to importing jar files**. If you explicitly include jar files in your project, they will be a specific frozen version, such as 2.1.1. Downloading jars and handling updates is cumbersome and is a problem that Maven already solves properly. Where possible, you should attempt to use Maven to resolve your dependencies. **Avoid Maven dynamic dependency resolution** Avoid the use of dynamic dependency versions, such as 2.1.+ as this may result in different and unstable builds or subtle, untracked differences in behavior between builds. The use of static versions such as 2.1.1 helps create a more stable, predictable and repeatable development environment. **Use different package name for non-release builds** Use applicationIdSuffix for debug build type to be able to install both debug and release apk on the same device (do this also for custom build types, if you need any). This will be especially valuable after your app has been published. Android gradle app should have product flavors for each environment as shown below: .. code-block:: Groovy .... flavorDimensions "default" defaultConfig { .... } buildTypes { debug { .... } release { .... } } productFlavors { svil { resValue "string", "app_name", "App Name Svil" applicationIdSuffix ".svil" versionNameSuffix "-svil" signingConfig signingConfigs.config } demo { resValue "string", "app_name", "App Name Demo" applicationIdSuffix ".demo" versionNameSuffix "-demo" signingConfig signingConfigs.config } prod { resValue "string", "app_name", "App Name" signingConfig signingConfigs.config } } .... Use different icons to distinguish the builds installed on a device—for example with different colors or an overlaid "debug" label. Gradle makes this very easy: with default project structure, simply put debug icon in app/src/debug/res and release icon in app/src/release/res. You could also change app name per build type, as well as versionName (as in the above example). **Share debug app keystore file** Sharing the debug APK keystore file via the app repository saves time when testing on shared devices and avoids the uninstalling/reinstalling of the app. It also simplifies the processing of working with some Android SDKs, such as Facebook, which require the registration of a single key store hash. Unlike the release key file, the debug key file can safely be added to your repository. **Share code style formatting defintions** Sharing the code style and formatting definitions via the app repository helps ensure a visually consistent code base and makes code comprehension and reviews easier. Java packages structure ----------------------- We recommend using a feature based package structure for your code. This has the following benefits: - Clearer feature dependency and interface boundaries. - Promotes encapsulation. - Easier to understand the components that define the feature. - Reduces risk of unknowingly modifying unrelated or shared code. - Simpler navigation: most related classes will be in the one package. - Easier to remove a feature. - Simplifies the transition to module based build structure (better build times and Instant Apps support) The alternative approach of defining your packages by how a feature is built (by placing related Activities, Fragments, Adapters etc in separate packages) can lead to a fragmented code base with less implementation flexibility. Most importantly, it hinders your ability to comprehend your code base in terms of its primary role: to provide features for your app. Naming ------ All resource names follow a simple convention. ``___`` Let’s first describe every element briefly. After the advantages, we’ll see how this applies to each resource type. **** Indicate what the resource actually represents, often a standard Android view class. Limited options per resource type. (e.g. MainActivity -> activity) **** Describe where it logically belongs in the app. Resources used in multiple screens use all, all others use the custom part of the Android view subclass they are in. (e.g. MainActivity -> main, ArticleDetailFragment -> articledetail) **** Differentiate multiple elements in one screen. (e.g. title) **** (optional) Either a precise size or size bucket. Optionally used for drawables and dimensions. (e.g. 24dp, small)) .. image:: img/resourcenaming_cheatsheet.png :scale: 60 % **Advantages** - **Ordering of resources by screen** The WHERE part describes what screen a resource belongs to. Hence it is easy to get all IDs, drawables, dimensions,… for a particular screen. - **Strongly typed resource IDs** For resource IDs, the WHAT describes the class name of the xml element it belongs to. This makes is easy to what to cast your findViewById() calls to. - **Better resource organizing** File browsers/project navigator usually sort files alphabetically. This means layouts and drawables are grouped by their WHAT (activity, fragment,..) and WHERE prefix respectively. A simple Android Studio plugin/feature can now display these resources as if they were in their own folder. - **More efficient autocomplete** Because resource names are far more predictable, using the IDE’s autocomplete becomes even easier. Usually entering the WHAT or WHERE is sufficient to narrow autocomplete down to a limited set of options. - **No more name conflicts** Similar resources in different screens are either all or have a different WHERE. A fixed naming scheme avoids all naming collisions. - **Cleaner resource names** Overall all resources will be named more logical, causing a cleaner Android project. - **Tools support** This naming scheme could be easily supported by the Android Studio offering features such as: lint rules to enforce these names, refactoring support when you change a WHAT or WHERE, better resource visualisation in project view,… Layouts ------- Layouts are relatively simple, as there usually are only a few layouts per screen. Therefore the rule can be simplified to: ``_.xml`` Where ```` is one of the following: +----------+--------------------------------------------+ | Prefix | Usage | +----------+--------------------------------------------+ | activity | contentview for activity | +----------+--------------------------------------------+ | fragment | view for a fragment | +----------+--------------------------------------------+ | view | inflated by a custom view | +----------+--------------------------------------------+ | item | layout used in list/recycler/gridview | +----------+--------------------------------------------+ | layout | layout,layout reused using the include tag | +----------+--------------------------------------------+ Examples: - **activity_main**: content view of the MainActivity - **fragment_articledetail**: view for the ArticleDetailFragment - **view_menu**: layout inflated by custom view class MenuView - **item_article**: list item in ArticleRecyclerView - **layout_actionbar_backbutton**: layout for an actionbar with a backbutton (too simple to be a customview) Strings ------- The ```` part for Strings is irrelevant. So either we use to indicate where the string will be used: ``_.xml`` or ``all`` if the string is reused throughout the app: ``all_.xml`` Examples: - **articledetail_title**: title of ArticleDetailFragment - **feedback_explanation**: feedback explanation in FeedbackFragment - **feedback_namehint**: hint of name field in FeedbackFragment - **all_done**: generic “done” string - **all_connection_error**: generic “connection error” string ```` obviously is the same for all resources in the same view. Don't write string values in all uppercase. Stick to normal text conventions (e.g., capitalize first character). If you need to display the string in all caps, then do that using for instance the attribute **textAllCaps** on a TextView. Bad .. code-block:: xml CALL FAILED Good .. code-block:: xml Call failed Drawables --------- The ```` part for Drawables is irrelevant. So either we use ```` to indicate where the drawable will be used: ``__.xml`` or all if the drawable is reused throughout the app: ``all__.xml`` Optionally you can add a ```` argument, which can be an actual size “24dp” or a size qualifier “small”. Examples: - **articledetail_placeholder**: placeholder in ArticleDetailFragment - **all_infoicon**: generic info icon - **all_infoicon_large**: large version of generic info icon - **all_infoicon_24dp**: 24dp version of generic info icon IDs --- For IDs, ```` is the class name of the xml element it belongs to. Next is the screen the ID is in, followed by an optional description to distinguish similar elements in one screen. ``__.xml`` Examples: - **tablayout_main** -> TabLayout in MainActivity - **imageview_menu_profile** -> profile image in custom MenuView - **textview_articledetail_title** -> title TextView in ArticleDetailFragment Dimensions ---------- Apps should only define a limited set of dimensions, which are constantly reused. This makes most dimensions ``all`` by default. Therefore you should mostly use: ``_all__.xml`` and optionally use the screen specific variant: ``___.xml`` Where ```` is one of the following: +-----------+------------------------------------------------+ | Prefix | Usage | +-----------+------------------------------------------------+ | width | width in dp | +-----------+------------------------------------------------+ | height | height in dp | +-----------+------------------------------------------------+ | size | if width == height | +-----------+------------------------------------------------+ | margin | margin in dp | +-----------+------------------------------------------------+ | padding | padding in dp | +-----------+------------------------------------------------+ | elevation | elevation in dp | +-----------+------------------------------------------------+ | keyline | absolute keyline measured from view edge in dp | +-----------+------------------------------------------------+ | textsize | size of text in sp | +-----------+------------------------------------------------+ Note that this list only contains the most used ```` s. Other dimensions qualifiers like: rotation, scale,... are usually only used in drawables and as such less reused. Examples: - **height_toolbar**: height of all toolbars - **keyline_listtext**: listitem text is aligned at this keyline - **textsize_medium**: medium size of all text - **size_menu_icon**: size of icons in menu - **height_menu_profileimage**: height of profile image in menu Styles ------ Almost every project needs to properly use styles, because it is very common to have a repeated appearance for a view. At least you should have a common style for most text content in the application. Add a styles file for each elements, for example: - **style_text_view.xml** - **style_button.xml** - **style_edit_text.xml** and each file should be organized in this way, for example: ``styles.xml`` .. code-block:: xml ``styles_text_view.xml`` .. code-block:: xml usage them in this way: .. code-block:: xml Colors ------ There should be nothing in your ``colors.xml`` other than a mapping from a color name to an RGBA value. This helps avoid repeating RGBA values and as such will make it easy to change or refactor colors, and also will make it explicit how many different colors are being used. Normally for a aesthetic UI, it is important to reduce the variety of colors being used. So, don't define your colors.xml like this: .. code-block:: xml #FFFFFF #2A91BD Instead, do this: .. code-block:: xml #FFFFFF #2A91BD Ask the designer of the application for this palette. The names do not need to be plain color names as "green", "blue", etc. Names such as "brand_primary", "brand_secondary", "brand_negative" are totally acceptable as well. By referencing the color palette from your styles allows you to abstract the underlying colors from their usage in the app, as per: - ``colors.xml`` - defines only the color palette. - ``styles.xml`` - defines styles which reference the color palette and reflects the color usage. (e.g. the button foreground is white). - ``activity_main.xml`` - references the appropriate style in ``styles.xml`` to color the button. If needed, even further separation between underlying colors and style usage can be achieved by defined an additional color resource file which references the color palette. As per: .. code-block:: xml @color/white @color/blue Then in styles.xml: .. code-block:: xml This approach offers improved color refactoring and more stable style definitions when multiple related styles share similar color and usage properties. However, it comes at the cost of maintaining another set of color mappings.