13 Android development best practices we follow at Innofied

Swarnendu De August 10, 2015

The popularity of Android has created a humongous demand for applications. As developers, it’s our responsibility to ensure that users don’t have a bad experience while using our apps. Here, at Innofied, there are a few Android development tips that we follow to ensure that our consumers/ clients get the optimum experience from the products. Lets take a look:

1. Switch to Android Studio

Android’s official website explicitly states “Android Studio is now the official IDE for Android”. If you’re still on Eclipse and that’s not enough reason to switch, here are few exclusive features of Android Studio that might change your mind:

  1. Android Studio uses the Gradle build system. Gradle has features like support for Maven repositories, multiple build types, multiple app flavors (for eg., demo & paid), apk splits (by screen density or ABI) & custom apk signing configurations.
  2. Contains built-in 9-patch creator.
  3. You can view previews for drawables, strings, colors & other resources.
  4. A color picker to pick colors in layouts and drawables.
  5. Almost all navigation & keyboard shortcuts from IntelliJ IDEA.

Furthermore, Google has ended development of the ADT plugin for Eclipse.

2. Use strings.xml

Adding text as String resources is always useful in the long-run especially when support for new languages need to be added.

3. Create a separate layout for UI elements that will be re-used

The <include /> tag makes it possible to have a single layout, re-used across multiple activities and fragments.
For example, for a uniquely styled button that must be shown in many activities of your application, a separate layout can be created. That layout can then be included in each activity’s layout.

<Button
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/unique_button"
    android:textStyle="bold"
    android:text="Unique button" />
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="See button below" />

    <include layout="@layout/unique_button.xml" />
</LinearLayout>

 

Another handy tag is the <merge /> tag. It acts as a pseudo-parent and helps get rid of an unneeded root ViewGroup.
For example, if your re-usable layout contains two Buttons placed vertically, you can put them inside a LinearLayout with a vertical orientation. But this LinearLayout becomes redundant if the layout is included (using <include />) into another LinearLayout. In this case, our re-usable layout can have <merge /> as the root ViewGroup instead of LinearLayout.

<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Submit" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Reset" />
</merge>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Enter text" />

    <include layout="@layout/buttons.xml" />
</LinearLayout>

4. Include resources for xxxhdpi screens

Many devices (and launcher apps) use launcher icons from the xxxhdpi drawable folder. Moreover, the LG G3 and Nexus 6 use all resources from xxxhdpi folders. There will definitely be many xxxhdpi devices in the future. So, you should start developing a habit of including them.

5. Place launcher icons in mipmap- folders

When building separate apks for different densities, drawable folders for other densities get stripped. This will make the icons appear blurry in devices that use launcher icons of higher density.
Since, mipmap folders do not get stripped, it’s always best to use them for including the launcher icons.

6. Use separate resources for debugging and release

Gradle allows configuring custom build types. By default, two build types, debug and release are provided.
We can provide separate files (source, resources & assets) for each build type by creating their corresponding folders, adjacent to the main folder, in the project structure. These folders follow the same structure as main & should contain only the overriding files.

An example of its usefulness is when you need to have a configuration file for Google Analytics. Separate XML files containing the Tracking ID can be created for both build types. During compilation, the file corresponding to the build will automatically be pulled.

Something to note here is that resource files/values from the main folder are always used. But a resource file/value in main will be overridden if it also exists in the build folder.

The image shows how to place the configuration file, ga_tracker.xml, for Google Analytics for debugging and release build types.

  • ga_tracker.xml in debug contains a tracking Id for debugging purposes
  • ga_tracker.xml in release contains the actual tracking Id
  • ga_tracker.xml contains rest of the configuration

file_structure

7. Use shapes and selectors instead of images as much as possible

Basic shapes and gradients can easily be drawn using the <shape /> tag without the use of images. The resulting shapes that are drawn are always sharp and do not need to be created for multiple densities.

A basic circle can be created in the following way and saved as circle.xml in the drawables folder

<shape android:shape="oval" >
    <solid android:color="#ff01aef0" />
</shape>

The <selector /> tag can be used to add different visual states (like pressed, disabled, checked) to Views.

A simple selector, to add a pressed state background to a button, can be created in the following way and saved in the drawables folder

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/button_pressed" android:state_pressed="true"/>
    <item android:drawable="@drawable/button_normal"/>
</selector>

8. Avoid deep levels in layouts

Having a deep hierarchy of Views makes the UI slow, not to mention a harder to manage layout.

Deep hierarchies can mostly be avoided by using the correct ViewGroup.

For example, a view hierarchy like this:

layout

 

 

can be created in either of these ways:

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <ImageView
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
        android:src="@drawable/magnifying_glass" />

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="top text" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="bottom text" />
    </LinearLayout>
</LinearLayout>

OR

<RelativeLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/magnifying_glass" />

    <TextView
        android:id="@+id/top_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/image"
        android:text="top text" />

    <TextView
        android:id="@+id/bottom_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/top_text"
        android:layout_toRightOf="@id/image"
        android:text="bottom text" />
</RelativeLayout>

The second way should be preferred since it has a single level hierarchy.

9. Don’t add the whole Google Play Services library package

The Google Play Services library is a package that contains all the native Google libraries for Android. Google also provides these libraries individually so that we can avoid adding the whole Google Play Services package. They can be added in build.gradle.

A list of the available libraries can be found here.

10. Use an HTTP library like Volley, Retrofit

When building a big application, our networking code can get huge because of boilerplate code. Not only does that make it difficult to maintain but also makes debugging harder. Libraries like Volley and Retrofit reduce a lot of boilerplate code and we have fewer things to worry about.

11. Use the Parcelable class instead of Serializable when passing data in Intents/Bundles

Serialization of an object that implements the Parcelable interface is much faster than using Java’s default serialization.

A class the implements the Serializable interface is marked as serializable and Java serializes it using reflection (which makes it slow).

When using the Parcelable interface, the whole object doesn’t get serialized automatically. Rather, we can selectively add data from the object to a Parcel using which the object is later deserialized.

12. Use an AsyncTaskLoader instead of an AsyncTask

A caveat while using an AsyncTask is that if the Activity gets destroyed before the AsyncTask has completed, it will still keep running and deliver the result in it’s onPostExecute() method, which could cause unexpected behavior. A typical example of this situation is when a device is rotated while an AsyncTask is loading content.

Loaders were introduced in Honeycomb but can also be used in pre-Honeycomb versions using the support library.

Loaders are managed by a LoaderManager which is tied to the lifecycle of its Activity or Fragment. Each Activity or Fragment contains an instance of LoaderManager. If the Activity/Fragment is destroyed, the LoaderManager destroys the Loaders and frees up resources. In case of a configuration change, it retains its Loaders.

We can get a LoaderManger instance and initialize a Loader in the following way:

getLoaderManager().initLoader(LOADER_ID, null, this);

A simple AsyncTaskLoader can be created in the following way:

class CustomLoader extends AsyncTaskLoader<String> {

    public CustomLoader(Context context) {
        super(context);
    }

    public String loadInBackground() {
        String result = null;
        // Load result string
        return result;
    }
}

You might also want to override onStartLoading(), onForceLoad(), onReset(), onCancelled(), onStopLoading(), onAbandon(), cancelLoadInBackground(), onCancelLoad() according to your needs.

When calling the initLoader() method of a LoaderManager, we must pass an implementation of LoaderManager.LoaderCallbacks as the third parameter. It’s a callback for the events occurring in a Loader.

class MyActivity extends Activity implements LoaderManager.LoaderCallbacks<String> {
    private static final int LOADER_ID = 1;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // set layout and stuff
        getLoaderManager().initLoader(LOADER_ID, null, this);
    }

    @Override
    public Loader<String> onCreateLoader(int id, Bundle args) {
        // Return a loader according to the Loader id
        return new CustomLoader(context);
    }

    @Override
    public void onLoadFinished(Loader<String> loader, String data) {
        // Process result here
    }

    @Override
    public void onLoaderReset(Loader<String> loader) {
            
    }
}

13. Perform file operations on the UI thread

File operations should always be performed on a worker thread typically using an AsyncTask/Loader. They take time and if done on the UI thread can make the interface feel sluggish. And in case it blocks the UI thread for 5 seconds, an ANR will be triggered.

These are some strong tips that one should always remember while developing an Android application. There are numerous other optimizations that can be done and much more that might be specific to your application. Following good practices not only makes it easier to manage and maintain your code but also reduces the number of potential bugs in your application.

If you have any questions don’t forget to drop them in the comments below.