background-shape

Tips to keep your android project files and code organized

Overview

Android applications should always be neatly organized with a clear folder structure that makes your code easy to read. In addition, proper naming conventions for code and classes are important to ensure your code is clean and maintainable.

The importance of an organized code structure

It’s all about readability. Big projects can be difficult to organize due to the amount of classes, resource files and libraries used.

Having an organized code structure involves separating classes into different packages according to their purpose and having proper names for your resource files.


1. Project guidelines

1.1 Project structure

        New projects should follow the Android Gradle project structure that is defined on         the Android Gradle plugin user guide.

1.2 File naming

        Java Classes

  • Class names are written in UpperCamelCase.

  • Android classes should be named with a particular convention that makes their         purpose clear in the name. For example all activities should end with Activity as         in MoviesActivity. The following are the most important naming conventions:

Name Convention Inherits
Activity CreateTodoItemActivity AppCompatActivityActivity
List Adapter TodoItemsAdapter BaseAdapterArrayAdapter
Database Helper TodoItemsDbHelper SQLiteOpenHelper
Fragment TodoItemDetailFragment Fragment
Service FetchTodoItemService ServiceIntentService
         Resources Files

        Resources file names are written in lowercase_underscore.

  • Drawable files

    Naming conventions for drawables:

    Asset Type Prefix Example
    Button btn_ btn_send_pressed.png
    Dialog dialog_ dialog_top.png
    Divider divider_ divider_horizontal.png
    Action bar ab_ ab_stacked.png
    Icon ic_ ic_star.png
    Menu menu_ menu_submenu_bg.png
    Notification notification_ notification_bg.png
    Tabs tab_ tab_pressed.png
    Naming conventions for icons (taken from Android iconography guidelines):

Asset Type Prefix Example
Icons ic_ ic_star.png
Launcher icons ic_launcher ic_launcher_calendar.png
Menu icons and Action Bar icons ic_menu ic_menu_archive.png
Status bar icons ic_stat_notify ic_stat_notify_msg.png
Tab icons ic_tab ic_tab_recent.png
Dialog icons ic_dialog ic_dialog_info.png
  • Layout files

    Layout files should match the name of the Android components that they are intended for but moving the top level component name to the beginning. For example, if we are creating a layout for the SignInActivity, the name of the layout file should be activity_sign_in.xml.

Component Class Name Layout Name
Activity UserProfileActivity activity_user_profile.xml
Fragment SignUpFragment fragment_sign_up.xml
Dialog ChangePasswordDialog dialog_change_password.xml
  • Values files

    Resource files in the values folder should be plural, e.g. strings.xml, styles.xml, colors.xml, dimens.xml, attrs.xml

2. Code guidelines

2.1 Java language rules

  • Don’t ignore exceptions:

It can be tempting to write code that ignores an exception, such as:

  void setServerPort(String value) {
      try {
          serverPort = Integer.parseInt(value);
      } catch (NumberFormatException e) { }
  }

Don’t do this. While you may think your code will never encounter this error condition or that it isn’t important to handle it, ignoring this type of exception creates mines in your code for someone else to trigger some day. You must handle every exception in your code in a principled way. (Android code style guidelines)

  • Don’t catch generic exception

You should not do this:

try {
    someComplicatedIOFunction();        // may throw IOException
    someComplicatedParsingFunction();   // may throw ParsingException
    someComplicatedSecurityFunction();  // may throw SecurityException
} catch (Exception e) {                 // I'll just catch all exceptions
    handleError();                      // with one generic handler!
}

In almost all cases, it’s inappropriate to catch generic Exception or Throwable (preferably not Throwable because it includes Error exceptions).

It obscures the failure- handling properties of your code, meaning if someone adds a new type of exception in the code you’re calling, the compiler won’t point out that you need to handle the error differently. In most cases you shouldn’t be handling different types of exceptions in the same way.

Catch each exception separately as part of a multi-catch block, for example:

try {
    ...
} catch (ClassNotFoundException | NoSuchMethodException e) {
    ...
}
  • Fully qualify imports

This is bad: import foo.*;

This is good: import foo.Bar;

2.2 Java library rules

There are conventions for using Android’s Java libraries and tools. In some cases, the convention has changed in important ways and older code might use a deprecated pattern or library. When working with such code, it’s okay to continue the existing style. When creating new components however, never use deprecated libraries.

2.3 Java style rules

  • Use Javadoc standard comments

Every file should have a copyright statement at the top, followed by package and import statements (each block separated by a blank line), and finally the class or interface declaration. In the Javadoc comments, describe what the class or interface does.

/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.foo;

import android.os.Blah;
import android.view.Yada;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * Does X and Y and provides an abstraction for Z.
 */

public class Foo {
    ...
}
  • Fields definition and naming

Fields should be defined at the top of the file and they should follow the naming rules listed below.

  1. Private, non-static field names start with m.
  2. Private, static field names start with s.
  3. Other fields start with a lower case letter.
  4. Static final fields (constants) are ALL_CAPS_WITH_UNDERSCORES.

Example:

public class MyClass {
    public static final int SOME_CONSTANT = 42;
    public int publicField;
    private static MyClass sSingleton;
    int mPackagePrivate;
    private int mPrivate;
    protected int mProtected;
}
  • String constants, naming, and values

Many elements of the Android SDK such as SharedPreferencesBundle, or Intentuse a key-value pair approach so it’s very likely that even for a small app you end up having to write a lot of String constants.

When using one of these components, you must define the keys as a static final fields and they should be prefixed as indicated below.

Element Field Name Prefix
SharedPreferences PREF_
Bundle BUNDLE_
Intent Extra EXTRA_
Intent Action ACTION_

2.4 Logging guidelines

Use the logging methods provided by the Log class to print out error messages or other information that may be useful for developers to identify issues:

  • Log.v(String tag, String msg) (verbose)
  • Log.d(String tag, String msg) (debug)
  • Log.i(String tag, String msg) (information)
  • Log.w(String tag, String msg) (warning)
  • Log.e(String tag, String msg) (error)

As a general rule, we use the class name as tag and we define it as a static final field at the top of the file. For example:

public class MyClass {
    private static final String TAG = MyClass.class.getSimpleName();

    public myMethod() {
        Log.e(TAG, "My error message");
    }
}

VERBOSE and DEBUG logs must be disabled on release builds. It is also recommended to disable INFORMATION, WARNING and ERROR logs but you may want to keep them enabled if you think they may be useful to identify issues on release builds. If you decide to leave them enabled, you have to make sure that they are not leaking private information such as email addresses, user ids, etc.

To only show logs on debug builds:

if (BuildConfig.DEBUG) Log.d(TAG, "The value of x is " + x);

2.5 Line length limit

Code lines should not exceed 100 characters. If the line is longer than this limit there are usually two options to reduce its length:

  1. Extract a local variable or method (preferable).
  2. Apply line-wrapping to divide a single line into multiple ones.

There are some methods with the help of which we can organize the lines with much characters:

  • Method chain case

    When multiple methods are chained in the same line - for example when using Builders - every call to a method should go in its own line, breaking the line before the .

    Picasso.with(context).load("foo.jpg").into(imageView);
    
    Picasso.with(context)
            .load("foo.jpg")
            .into(imageView);
    
  • Long parameters case

    When a method has many parameters or its parameters are very long, we should break the line after every comma ,

    loadPicture(context, "foo.jpg", mImageViewProfilePicture, clickListener, "Title of the picture");
    
    loadPicture(context,
            "foo.jpg",
            mImageViewProfilePicture,
            clickListener,
            "Title of the picture");
    

2.6 Methods and Classes limits

  • Any method should not contain more than 30 lines of code (not counting line spaces and comments). If your method goes above the limit then it is highly recommended to devide your task into sub-tasks and create new methods to handle them.

  • Any class should not contain more than 20 methods (resulting in up to 600 lines of code in a class). If your code breaks the limit then create a new class.

  • Any package should not contain more than 30 classes, thus comprising 18,000 lines of code.

  • Also, packages should be created according to the tasks. Like, a seperate package for making queries upon data on the cloud, a package that handle all the activity classes, a package for model classes, and-so-on.


Final Words

Each project is unique, and each case is a case but believe that keeping classes single-purpose and organized in a clean and easy to understand way, makes development easier and more enjoyable.

The important thing is that in the end you’ll be able to look at your code and feel that sweet light-weighted sensation of a well done job.

I hope you find it useful or at least it gives you some fresh ideas.

Thanks for reading 🙌🏼

Happy coding! 😇

BTW, I, am an Android Developer, solve real-world problems by building useful apps. Have a look at our portfolio.

Connect with me on Github and LinkedIn