Back to Home

Android best practices

mobile developement · android development

Android best practices

Original author: Antti Lammi, Joni Karppinen, Peter Tackage, Timo Tuominen, Vera Izrailit, Vihtori Mäntylä, Mark Voit, Andre Medeiros, Paul Houghton and other Futurice developers
  • Transfer
We would like to share with you the experience that we, in Futurice , have gained while developing Android applications. We hope these tips save you from creating your own bikes. If you are interested in iOS or Windows Phone development, pay attention to the relevant documents on our website.

Briefly about the main thing


  • Use the Gradle build system and standard project structure
  • Store passwords and other important data in the gradle.properties file
  • Do not write your own HTTP client, better use the Volley or OkHttp libraries
  • Use the Jackson Library to Pars JSON Data
  • Avoid using Guava and generally save - try not to exceed the limit of 65k methods
  • Use snippets to display the user interface.
  • Use activity to manage fragments only
  • XML markup is code, write it carefully
  • Use styles to avoid duplicating attributes in XML markup
  • Better multiple style files than one hefty one that will be difficult to read.
  • Try to avoid duplicates in colors.xml and make it shorter - mainly for storing the color palette
  • The same goes for diameter.xml, define only the basic constants there
  • Do not make the hierarchy of ViewGroup elements too deep
  • Avoid client side mining when using WebView, be aware of possible leaks
  • Use Robolectric for unit testing, and Robotium for testing UI
  • Use Genymotion as an Emulator
  • Always use ProGuard or DexGuard

Android SDK


Place your Android SDK in a home directory or other place not related to the application. Some IDEs are bundled with the SDK, and can install it in their directory. This can prevent you from updating (or reinstalling) the IDE, or when you switch to another IDE. Avoid installing the SDK in a different system directory, as this may require administrative privileges if your IDE runs as a user rather than an administrator.

Build system


The default selection should be Gradle. Ant is much more modest in features, and its instructions are also less compact. With Gradle, you can easily:

  • Create different variations and builds of your application
  • Create simple tasks as a script
  • Manage dependencies and load them automatically
  • Configure keystore
  • And many other useful things.

Also note that the Gradle plugin for Android is being actively developed by Google as a new standard for build systems.

Project structure


There are two common options: the old Ant & Eclipse ADT project structure - or the new Gradle & Android Studio. It is better to choose the second option. If your project uses the old structure, we recommend porting it.

Old structure
old-structure
├─ assets
├─ libs
├─ res
├─ src
│  └─ com/futurice/project
├─ AndroidManifest.xml
├─ build.gradle
├─ project.properties
└─ proguard-rules.pro


New structure
new-structure
├─ library-foobar
├─ app
│  ├─ libs
│  ├─ src
│  │  ├─ androidTest
│  │  │  └─ java
│  │  │     └─ com/futurice/project
│  │  └─ main
│  │     ├─ java
│  │     │  └─ com/futurice/project
│  │     ├─ res
│  │     └─ AndroidManifest.xml
│  ├─ build.gradle
│  └─ proguard-rules.pro
├─ build.gradle
└─ settings.gradle


The main difference is that the new structure explicitly shares 'resource sets' ( main, androidTest), this is one of the Gradle concepts. You can, for example, add the folders 'paid' and 'free' to your folder 'src', and they will contain the source code for the paid and free versions of your application.

Having the application folder appat the top of the hierarchy helps to separate it from the libraries (for example, library-foobar) that the application uses. The file then settings.gradlestores a list of these library projects that it can reference app/build.gradle.

Gradle Configuration


General structure. Follow Google's instructions for Gradle for Android .

Small build tasks. Unlike other scripting languages ​​(shell, Python, Perl, etc.), you can create build tasks in Gradle. See the Gradle documentation for details .

Passwords In your application file, build.gradleyou need to define the signature parameters ( signingConfigs) for the release build. This error should be avoided:

Do not do so. This information will appear in the version control system.

signingConfigs {
    release {
        storeFile file("myapp.keystore")
        storePassword "password123"
        keyAlias "thekey"
        keyPassword "password789"
    }
}

It would be more correct to create a file gradle.propertiesthat will not be added under the control of the version control system:

KEYSTORE_PASSWORD=password123
KEY_PASSWORD=password789

This data is automatically imported into gradle, and you can use it in the build.gradlefollowing way:

signingConfigs {
    release {
        try {
            storeFile file("myapp.keystore")
            storePassword KEYSTORE_PASSWORD
            keyAlias "thekey"
            keyPassword KEY_PASSWORD
        }
        catch (ex) {
            throw new InvalidUserDataException("You should define KEYSTORE_PASSWORD and KEY_PASSWORD in gradle.properties.")
        }
    }
}

Try to use Maven dependencies rather than importing jar files. If you include external jar files in your project, they will be preserved in the version at which the import occurred, for example 2.1.1. Manually downloading jar files and updating them is a rather time-consuming operation, and Maven can perfectly solve this problem for us by including the result in the assembly. For instance:

dependencies {
    compile 'com.squareup.okhttp:okhttp:2.2.0'
    compile 'com.squareup.okhttp:okhttp-urlconnection:2.2.0'
}

Avoid using dynamic Maven dependencies Avoid specifying dynamically generated versions, for example 2.1.+, as this can lead to instability of assemblies, depending on uncontrolled differences between different versions of libraries. Using static version numbers, for example, 2.1.1will help create more stable builds with predictable behavior.

Development Environment (IDE) and text editor


Use any editor that you like, but it should be well compatible with the structure of the project. The editor is your personal choice, and you should choose one that will be convenient to work with as part of your project structure and build system.

The most popular IDE at the moment is Android Studio , since it is developed by Google, integrated with Gradle, uses the new project structure by default, is in a state of stable assembly and is tuned for Android development.

You can use Eclipse ADT if you like it, but you have to configure it, because it works by default with the old project structure and Ant build system. You can even use Vim, Sublime Text, or Emacs text editors. In this case, you have to use Gradle and adb from the command line. If you cannot make friends with Eclipse Gradle, you also have to use the command line to build. Given that the ADT plugin has recently been deprecated, it's best to just upgrade to Android Studio.

Whatever you use, keep in mind that Gradle and the new project structure are the officially recommended way to build applications, and do not add your editor-dependent configuration files to the version control system. For example, do not add an Ant filebuild.xml. Also remember to update build.gradlewhen you change the build configuration in Ant. In general, do not force other developers to use unusual tools.

Libraries


Jackson is a Java library for converting objects to JSON and vice versa. Gson is the most common way to solve this problem, but according to our observations, Jackson is more productive because it supports alternative methods of processing JSON: streaming , a tree model in RAM, and the traditional connection of JSON-POJO formats. Keep in mind, however, that Jackson is larger than GSON in size, so you might prefer GSON to avoid the 65k limit. Other options are Json-smart and Boon JSON .

Networking, caching and pictures.There are a couple of proven solutions for productive requests to backend servers that you should consider before developing your own client. Use Volley or Retrofit . Volley also provides tools for loading and caching images. If you choose Retrofit, take Picasso to upload or cache images, and OkHttp for efficient HTTP requests. All these libraries - Retrofit, Picasso and OkHttp are developed by one company, so that they complement each other perfectly. OkHttp can also be used with Volley .

Rxjava- a library for Reactive Programming, in other words, for processing asynchronous events. This is a powerful and promising concept that can confuse with its unusualness. We recommend that you think carefully before using this library as the foundation of the architecture of the entire application. There are projects created using RxJava, and you can turn to one of these people for help: Timo Tuominen, Olli Salonen, Andre Medeiros, Mark Voit, Antti Lammi, Vera Izrailit, Juha Ristolainen. We wrote several articles on our blog about this: [1] , [2] , [3] , [4] .

If you haven’t worked with Rx before, start by using the API. Or you can start by using it to handle simple user interface events, such as clicking or typing in a search field. If you are confident in your skills in using Rx and want to use it throughout the architecture, write Javadocs regarding the most difficult points. Keep in mind that a programmer who does not have experience using RxJava will curse you, may have big problems supporting the project. Try to help him understand your code and Rx.

RetrolambdaIs a Java library for using lambda expressions on Android and other platforms with JDKs below version 8. It will help you make your code compact and well readable, especially if you use a functional style, such as with RxJava. To use it, install JDK8, specify it in the path to the SDK in Android Studio in the dialog for describing the project structure, and set the environment variables JAVA8_HOMEand JAVA7_HOMEthen in the root build.gradleproject write:

dependencies {
    classpath 'me.tatarka:gradle-retrolambda:2.4.1'
}

and in the file of build.gradleeach module add

apply plugin: 'retrolambda'
android {
    compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}
retrolambda {
    jdk System.getenv("JAVA8_HOME")
    oldJdk System.getenv("JAVA7_HOME")
    javaVersion JavaVersion.VERSION_1_7
}

Android Studio will start supporting lambda expression syntax. If you have not used them before, you can start by understanding the statements:

  • Any interface with one method is compatible with lambda expressions and can be simplified in writing
  • If you don’t understand how to describe the parameters, write a regular inner class and let Android Studio translate it into a lambda expression.

Remember to limit the dex file to the number of methods, and avoid using a large number of libraries. Android applications, when packaged in a dex file, have a strict limit of 65,536 reference methods [1] [2] [3] . If you exceed the limit, you will receive a fatal compilation error. So we recommend using the minimum possible number of libraries, and pay attention to the utility for calculating the number of methods in a dex file . It will help determine which set of libraries can be used without exceeding the limit. Be especially careful when using the Guava library, which contains more than 13k methods.

Activity and fragments


In the community of Android developers (as in Futurice) there is no consensus on the question of how best to build the architecture of an Android application in terms of using fragments and activity. Square even released a library for building architecture mainly using view, thus minimizing the need for fragments, but this method has not yet become generally accepted.

Based on the development history of the Android API, you can consider fragments as part of the screen user interface. In other words, fragments usually refer to the UI. Activities are usually considered as controllers; they are especially important in terms of their life cycle and for state management. However, it can be another way: activity can perform functions related to the UI (state transition between screens), and fragments can only be used as controllers. We would advise making a balanced decision, bearing in mind that an architecture based on using only fragments, or only activity, or only view, can have a number of drawbacks. Here are a few tips to consider, but be critical of them:

  • Avoid intensive use of nested fragments , because of the possibility of the appearance of errors such as " nested dolls" . Use nested fragments only if it makes sense (for example, fragments in a horizontally scrolling ViewPager inside a fragment of the screen) or if you well understand what you are doing.
  • Do not put too much code in activity. If possible, use them as lightweight containers that exist in your application mainly for managing the life cycle and other important functions of the Android API. An activity with a single fragment is better than just activity - place the code related to the user interface in the fragment. This will make it possible to reuse it in case you need to place it in the layout with tabs, or on the tablet screen with several fragments. Avoid creating activity without related fragments, unless you do it on purpose.
  • You should not abuse the Android-level API, for example, blindly relying on the Intent mechanism for the internal operation of the application. You can affect the Android operating system or other applications, causing errors or freezes. For example, it is known that if your application uses the Intent mechanism for internal communication between application packages, you can cause a freeze in a few seconds if the application was opened immediately after loading the operating system.

Java Package Architecture


The Java architecture for Android applications resembles the Model-View-Controller pattern . In Android, fragments and activity represent Conroller's classes . On the other hand, they are part of the user interface, so they are also part of the View.

Therefore, it is difficult to attribute fragments (or activity) uniquely to a Controller or View. Better put them in your own package fragments. Activity in this case can be left in the top-level package. If you have more than two or three activities, you can also place them in a separate package.

On the other hand, the architecture may look like regular MVC, with a package modelscontaining POJOs generated by the JSON parser from the API responses, and a packageviewscontaining copyright View, alerts, View classes associated with the action bar, widgets, etc. Adapters are the link between data and views. Given that they usually use View, exported through the method getView(), you can include adapters as a child package adaptersin views.

Some controller classes are used throughout the application and work directly with the Android operating system. They can be put in a bag managers. Various classes for processing data, such as DateUtils, can be stored in a package utils. The classes responsible for interacting with the backend are in the package network.

All of the above packages, in order from backend to user interface:

com.futurice.project
├─ network
├─ models
├─ managers
├─ utils
├─ fragments
└─ views
   ├─ adapters
   ├─ actionbar
   ├─ widgets
   └─ notifications

Resources


Naming. Follow the convention on using an object type as a prefix to a file name, as in type_foo_bar.xml. Examples: fragment_contact_details.xml, view_primary_button.xml, activity_main.xml.

XML markup structure. If you're not sure how to format XML markup, the following tips may help.

  • One attribute per line, indented with 4 spaces
  • android:id always comes first
  • android:layout_**** attributes at the beginning
  • stylelast attribute
  • The closing tag />is on its line, to facilitate ordering and adding attributes.
  • Instead of writing manually android:text, you can use the Visual Attribute Editor for Android Studio.

Example


Experience has shown that an attribute android:layout_****must be defined in XML markup, and other attributes android:****must be defined in XML styles. There are exceptions to this rule, but overall it works well. The point is to store only markup attributes (position, margins, size) and content attributes in the markup file, and the display details of visual components (colors, indents, fonts) should be in the style files.

Exceptions:
  • android:id of course should be in the markup file
  • android:orientation for a LinearLayout object it usually makes sense in a markup file
  • android:text must be in the markup file because it describes the content
  • Sometimes it makes sense to define in a general style android:layout_widthand android:layout_height, but by default these attributes should be in the markup file

Use styles. Almost every project should use styles correctly, since there are usually duplicate display attributes for the view. At a minimum, you should have a common style for most of the text content of the application, for example:


Applies to TextView:


You probably have to do the same for the buttons, but don't stop there. Develop this concept and bring all groups of repeating attributes android:****related to certain types of visual components into styles.

Separate a large file with styles into several smaller ones. There is no need to store all styles in one file styles.xml. The Android SDK supports various file names by default, so there is no problem naming files with styles — they just need to contain the XML “style” tag. So you can create files styles.xml, styles_home.xml, styles_item_details.xml, styles_forms.xml. Unlike directory names, which are important for the build system, file names in res/valuescan be arbitrary.

colors.xmlThis is a color palette . Do not put incolors.xmlnothing but the association of a color name with its RGBA value. Do not use it to determine RGBA values ​​for different types of buttons.

So wrong
#FFFFFF#2A91BD#5F5F5F#939393#FFFFFF#FF9D2F
    ...
    #323232


With this approach, it’s very simple to create duplicate RGBA values, and changing colors is much more difficult. In addition, these colors refer to certain content, “button” or “comment,” and should be described in the style of a button, not in colors.xml.

And so - right
#FFFFFF#DBDBDB#939393#5F5F5F#323232#27D34D#2A91BD#FF9D2F#FF432F


The color palette is determined by the application designer. Colors do not have to be called "green", "blue", etc. Names like "brand_primary", "brand_secondary", "brand_negative" are also quite acceptable. This formatting of colors makes them easy to change or refactor, and also makes it easy to understand how many colors are used. To create a beautiful user interface, it is important to reduce the number of colors used whenever possible.

Style the diameter.xml as colors.xml . For the same reason, it is also worth defining the “palette” of typical sizes of objects and fonts.

An example of a good structure
22sp18sp15sp12sp40dp24dp14dp10dp4dp60dp40dp32dp


We recommend not writing numeric values ​​in duplicate markup attributes (margins and indents), but using view constants spacing_****(roughly the way you usually do to localize string values).
This will make the markup clearer and make it easier to change it.

strings.xml

Use keys in line naming, as in package naming - this will allow you to solve the problem with the same constant names and better understand the context of their use.

Bad example

Network errorCall failedMap loading failed

Good example

Network errorCall failedMap loading failed

Do not write string values ​​in lower case. You can use normal text conversions (including converting the first letter to uppercase). If you want to write the entire string in lowercase letters, use the textAllCaps attribute of the TextView object.

Bad example

CALL FAILED

Good example

Call failed

Avoid the deep view hierarchy . Sometimes you will be tempted to add another LinearLayout to solve your view description problem.

The result may be something like the following


Even if you do not see a clearly increased nesting in the markup file, it can occur when you include (in Java) view in other views.

There may be a couple of problems. You may get performance issues because the processor is forced to process a complex description of the user interface component tree. Another, more serious problem is the possibility of a StackOverflowError error .

So try to make your view hierarchy as flat as possible: see how to use RelativeLayout , how to optimize your layout and use a tag.

Be careful when using WebView. Когда вам нужно показать web-страницу, например новостную статью, избегайте исполнения кода на клиентской стороне для формирования HTML, лучше попросите backend-программистов предоставить «чистый» HTML. WebView также могут вызывать утечку памяти при сохранении ссылки на Activity, к которой привязаны вместа ApplicationContext. Избегайте использования WebView для создания простого текста или кнопки, лучше используйте объекты TextView или Button.

Фреймворки для тестирования


The Android SDK test framework is still in an unfinished state ( are they about
Espresso 2.1?! - approx. Per.), Especially with regard to user interface tests. The Android Gradle, by default, contains a connectedAndroidTest task that runs the JUnit tests you created using the JUnit extension with Android utilities . This means that you have to run tests on a device or emulator. Use official instructions [1] [2] for testing.

Use Robolectric only for unit tests, not for UI.This framework provides the ability to run tests without a device, to increase the speed of their execution, and is ideal for unit tests of data models and view. However, the user interface of Robolectric does not test completely and inaccurately. You will have problems when testing user interface elements related to animations, dialogs, etc., and the testing process will go “blindly” (without seeing the screen).

Robotium makes user interface testing easy. You can run UI tests without Robotium, but it is very useful due to utilities for analyzing view and controlling the screen. Testing scripts will look very simple:

solo.sendKey(Solo.MENU);
solo.clickOnText("More"); // searches for the first occurence of "More" and clicks on it
solo.clickOnText("Preferences");
solo.clickOnText("Edit File Extensions");
Assert.assertTrue(solo.searchText("rtf"));

Emulators


If you are a professional Android developer, buy a license for the Genymotion emulator. It works faster than a regular AVD emulator. The Genymotion emulator allows you to record a video demonstrating the operation of your application, emulates various quality of network connections, GPS signals and much more. It is ideal for running tests. You will have access to many (though not all) images of Android devices, so the cost of a Genymotion license is much cheaper than buying many devices.

Pitfalls: Genymotion does not allow the use of Google services such as the Google Play Store or Maps in the application. And if you need to test Samsung API functions, you have to buy a real device.

Proguard Configuration


ProGuard is commonly used in Android projects to compress and obfuscate code.

The terms of use of ProGuard depend on the settings of your project. You typically configure gradle to use ProGuard to build the release version.

buildTypes {
    debug {
        minifyEnabled false
    }
    release {
        signingConfig signingConfigs.release
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

To indicate which part of the code needs processing, you must mark one or more entry points. Usually these are classes with basic methods, applets, midlets, activities, etc. The default configuration that the Android framework uses is located at SDK_HOME/tools/proguard/proguard-android.txt. You can define your own rules for the configuration of ProGuard by placing them in a file my-project/app/proguard-rules.proand they will complement the default configuration.

The most common problem associated with ProGuard is the crash of the application upon launch with errors ClassNotFoundExceptionor NoSuchFieldExceptioneven if the project build team (for example assembleRelease) worked without errors. This can mean one of two things:

  • ProGuard deleted the class, enum, method, field or annotation, believing that it is not needed.
  • ProGuard renamed the class, enum or field, but it is called using its old name, including through the Java reflection mechanism.

Check the file app/build/outputs/proguard/release/usage.txtfor a mention of the remote object. If it has been renamed, its name is in the file app/build/outputs/proguard/release/mapping.txt.

In order to protect the necessary classes and the method from being deleted by ProGuard, add the option to its configuration keep:

-keep class com.futurice.project.MyClass { *; }

To protect against renaming, use the option keepnames:

-keepnames class com.futurice.project.MyClass { *; }

Other possible modifications to the ProGuard configuration can be found in this example . More Proguard configuration examples here .

At the beginning of your project, create a release build to verify that the rules for ProGuard are described correctly. When connecting new libraries, create a release build and check the executable on the device. Do not wait for version “1.0” to create a release build, otherwise you may get some unpleasant surprises in the absence of time to fix them.

Tip. Save the mapping.txt file for each release. Having a copy of the mapping.txt file for each assembly, you can be sure that you can find the problem, the user will catch a bug and send you an obfuscated error log.

DexGuard. If you want to cool optimize your code and obfuscate it in a special way, try using DexGuard, a commercial analogue of ProGuard. It can easily split a Dex file into several to circumvent the limit of 65k methods.

Acknowledgments


Antti Lammi, Joni Karppinen, Peter Tackage, Timo Tuominen, Vera Izrailit, Vihtori Mäntylä, Mark Voit, Andre Medeiros, Paul Houghton and other Futurice developers for sharing their Android knowledge.

License


Futurice Oy Creative Commons Attribution 4.0 International (CC BY 4.0)

Read Next