Android Project Guidelines
1. Project Guidelines
1.1 Project Structure
When contributing work, the project should maintain the following structure:
src/androidTest : Directory containing functional
tests.src/test : Directory containing unit
tests.src/commonTest : Directory containing shared test code for
AndroidTest & Test.
src/main : Directory containing application code.
The structure of the project should remain as defined above whenever you are
modifying or adding new features.
Using this structure allows us to keep the application code separated from any test-related
code. The CommonTest directory allows us to share classes between the functional and unit tests,
such as mock model creation and dagger test configuration classes.
1.2 File Naming
1.2.1 Class Files
Any classes that you define should be named using UpperCamelCase, for example:
- AndroidActivity, NetworkHelper, UserFragment, PerActivity
Any classes extending an Android framework component should always end with the
component name. For example:
- UserFragment, SignUpActivity, RateAppDialog, PushNotificationServer,
NumberView.
We use UpperCamelCase as this helps to seperate the words used to create the name,
making it easier to read. Naming classes to end with the framework component makes it super clear
as to what the class is used for. For example, if you're looking to make changes to the
RegistrationDialog then this naming convention makes it really easy to locate that class.
1.2.1 Resource Files
When naming resource files you should be sure to name them using lowercase letters and
underscores instead of spaces, for example:
- activity_main, fragment_user, item_post
1.2.2 Drawable Files
Drawable resource files should be named using the ic_ prefix along with the size and color
of the asset. For example, white accept icon sized at 24dp would be named:
- ic_accept_24dp_white
And a black cancel icon sized at 48dp would be named:
- ic_cancel_48dp_black
Other drawable files should be named using the corresponding prefix, for example:
Type Prefix Example
Selector selector_ selector_button_cancel
Background bg_ bg_rounded_button
Circle circle_ circle_white
Progress progress_ progress_circle_purple
Divider divider_ divider_grey
When creating selector state resources, they should be named using the corresponding
suffix:
State Suffix Example
Normal _normal btn_accept_normal
Pressed _pressed btn_accept_pressed
Focused _focused btn_accept_focused
Disabled _disabled btn_accept_disabled
Selected _selected btn_accept_selected
1.2.3 Layout Files
When naming layout files, they should be named starting with the name of the Android
Component that they have been created for. For example:
Component Class Name Layout Name
Activity MainActivity activity_main
Fragment MainFragment fragment_main
Dialog RateDialog dialog_rate
Widget UserProfileView view_user_profile
AdapterView Item N/A item_followe
1.2.4 Menu Files
Menu files do not need to be prefixed with the menu_ prefix. This is because they are
already in the menu package in the resources directory, so it is not a requirement.
1.2.5 Values Files
All resource file names should be plural, for example:
- attrs.xml, strings.xml, styles.xml, colors.xml, dimens.xml
2. Code Guidelines
2.1 Java Language Rules
2.1.1 Never ignore exceptions
Avoid not handling exceptions in the correct manner. For example:
public void setUserId(String id) {
try {
mUserId = Integer.parseInt(id);
} catch (NumberFormatException e) { }
}
This gives no information to both the developer and the user, making it harder to
debug and could also leave the user confused if something goes wrong. When catching an
exception, we should also always log the error to the console for debugging purposes and if
necessary alert the user of the issue. For example:
public void setCount(String count) {
try {
count = Integer.parseInt(id);
} catch (NumberFormatException e) {
count = 0;
Log.e(TAG, "There was an error parsing the count " + e);
DialogFactory.showErrorMessage(R.string.error_message_parsing_count);
}
}
Here we handle the error appropriately by:
• Showing a message to the user notifying them that there has been an error
• Setting a default value for the variable if possible
• Throw an appropriate exception
2.1.2 Never catch generic exceptions
Catching exceptions generally should not be done:
public void openCustomTab(Context context, Uri uri) {
Intent intent = buildIntent(context, uri);
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.e(TAG, "There was an error opening the custom tab " + e);
}
}
2.1.3 Grouping exceptions
public void openCustomTab(Context context, @Nullable Uri uri) {
Intent intent = buildIntent(context, uri);
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e | NullPointerException e) {
Log.e(TAG, "There was an error opening the custom tab " + e);
} catch (SomeOtherException e) {
// Show some dialog
}
}
2.1.4 Using try-catch over throw exception
Using try-catch statements improves the readability of the code
where the exception is taking place. This is because the error is
handled where it occurs, making it easier to both debug or make a
change to how the error is handled.
2.1.6 Fully qualify imports
When declaring imports, use the full package declaration. For example:
Don’t do this:
- import android.support.v7.widget.*;
Instead, do this
- import android.support.v7.widget.RecyclerView;
2.1.7 Don't keep unused imports
Sometimes removing code from a class can mean that some imports are no longer needed.
If this is the case then the corresponding imports should be removed alongside the code.
2.1.8 Logging
Use Timber for console logs.( implementation 'com.jakewharton.timber:timber:4.7.1')
This is a logger with a small, extensible API which provides utility on top of
Android's normal Log class.
We can set the Tag for the log as a static final field at the top of the class,
for example:
private static final String TAG = MyActivity.class.getName();
2.1.9 Field Ordering
Any fields declared at the top of a class file should be ordered in the following order:
1. Enums
2. Constants
3. Dagger Injected fields
4. Butterknife View Bindings
5. private global variables
6. public global variables
public static enum {
ENUM_ONE, ENUM_TWO
}
public static final String KEY_NAME = "KEY_NAME";
public static final int COUNT_USER = 0;
@Inject SomeAdapter someAdapter;
@BindView(R.id.text_name) TextView nameText;
@BindView(R.id.image_photo) ImageView photoImage;
private int userCount;
private String errorMessage;
public int someCount;
public String someString;
2.1.10 Class member ordering
1. Constants
2. Fields
3. Constructors
4. Override methods and callbacks (public or private)
5. Public methods
6. Private methods
7. Inner classes or interfaces
public class MainActivity extends Activity {
private int count;
public static newInstance() { }
@Override
public void onCreate() { }
public setUsername() { }
private void setupUsername() { }
static class AnInnerClass { }
interface SomeInterface { }
}
2.1.11 Method parameter ordering
Context parameters always go first and Callback parameters always go last.
public Post loadPost(Context context, int postId);
public void loadPost(Context context, int postId, Callback callback);
2.2.1 Resource naming
All resource names and IDs should be written using lowercase and underscores, for
example:
text_username, activity_main, fragment_user, error_message_network_connection
The main reason for this is consistency, it also makes it easier to search for views within
layout files when it comes to altering the contents of the file.
2.2.1.1 ID naming
All IDs should be prefixed using the name of the element that they have been declared for.
Element Prefix
ImageView image_
Fragment fragment_
RelativeLayout layout_
Button button_
TextView text_
View view_
2.2.1.2 Strings
All string names should begin with a prefix for the part of the application that they are
being referenced from. For example:
Screen String Resource Name
Registration Fragment “Register now” registration_register_now
Sign Up Activity “Cancel” sign_up_cancel
Rate App Dialog “No thanks” rate_app_no_thanks
If it’s not possible to name the referenced like the above, we can use the following rules:
Prefix Description
error_ Used for error messages
title_ Used for dialog titles
action_ Used for option menu actions
msg_ Used for generic message such as in a dialog
label_ Used for activity labels
Two important things to note for String resources:
• String resources should never be reused across screens. This can cause issues when it
comes to changing a string for a specific screen. It saves future complications by having a
single string for each screens usage.
• String resources should always be defined in the strings file and never hardcoded in
layout or class files.
2.2.2 Attributes ordering
1. View Id
2. Style
3. Layout width and layout height
4. Other layout_ attributes, sorted alphabetically
5. Remaining attributes, sorted alphabetically
For example:
<Button
android:id="@id/button_accept"
style="@style/ButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
android:padding="16dp"
android:text="@string/button_skip_sign_in"
android:textColor="@color/bluish_gray" />
2.3 Tests style rules
2.3.1 Unit tests
Any Unit Test classes should be written to match the name of the class that the test are
targeting, followed by the Test suffix. For example:
Class Test Class
DataManager DataManagerTest
UserProfilePresenter UserProfilePresenterTest
PreferencesHelper PreferencesHelperTest
All Test methods should be annotated with the @Test annotation, the methods should be
named using the following template:
@Test
public void methodNamePreconditionExpectedResult() { }
So for example, if we want to check that the signUp() method with an invalid email
address fails, the test would look like:
@Test
public void signUpWithInvalidEmailFails() { }
Tests should focus on testing only what the method name entitles, if there’s extra
conditions being tested in your Test method then this should be moved to it’s own individual test.
If a class we are testing contains many different methods, then the tests should be split
across multiple test classes - this helps to keep the tests more maintainable and easier to locate. For
example, a DatabaseHelper class may need to be split into multiple test classes such as :
- DatabaseHelperUserTest
- DatabaseHelperPostsTest
- DatabaseHelperDraftsTest
2.4.2 Espresso tests
Each Espresso test class generally targets an Activity, so the name given to it should match
that of the targeted Activity, again followed by Test. For example:
Class Test Class
MainActivity MainActivityTest
ProfileActivity ProfileActivityTest
DraftsActivity DraftsActivityTest
When using the Espresso API, methods should be chained on new lines to make the
statements more readable, for example:
onView(withId(R.id.text_title))
.perform(scrollTo())
.check(matches(isDisplayed()))
3. Gradle Style
3.1 Dependencies
3.1.1 Versioning
Where applicable, versioning that is shared across multiple dependencies should be defined
as a variable within the dependencies scope. For example:
final SUPPORT_LIBRARY_VERSION = '23.4.0'
implementation "com.android.support:support-v4:$SUPPORT_LIBRARY_VERSION"
implementation "com.android.support:recyclerview-v7:$SUPPORT_LIBRARY_VERSION"
implementation "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION"
implementation "com.android.support:design:$SUPPORT_LIBRARY_VERSION"
implementation "com.android.support:percent:$SUPPORT_LIBRARY_VERSION"
implementation "com.android.support:customtabs:$SUPPORT_LIBRARY_VERSION"
This makes it easy to update dependencies in the future as we only need to change the
version number once for multiple dependencies.
3.1.2 Grouping
Where applicable, dependencies should be grouped by package name, with spaces in-
between the groups. For example:
implementation "com.android.support:percent:$SUPPORT_LIBRARY_VERSION"
implementation "com.android.support:customtabs:$SUPPORT_LIBRARY_VERSION"
implementation 'io.reactivex:rxandroid:1.2.0'
implementation 'io.reactivex:rxjava:1.1.5'
implementation 'com.jakewharton:butterknife:7.0.1'
implementation 'com.jakewharton.timber:timber:4.1.2'
implementation 'com.github.bumptech.glide:glide:3.7.0'
Both of these approaches makes it easy to locate specific dependencies when required as it
keeps dependency declarations both clean and tidy
3.1.3 Independent Dependencies
Where dependencies are only used individually for application or test purposes, be sure to
only compile them using Implementation , testImplementation or androidTestImplementation . For
example, where the robolectric dependency is only required for unit tests, it should be added using:
testImplementation 'org.robolectric:robolectric:3.0'
General Informations
- Avoid using public static variables to pass parameters between classes. 100%
- Remove any unecessary code when copy/ pasting. 100%
- All layout files should be using ConstraintLayout. 80%
- All strings should be declared in the srting.xml files. 100%
- Make sure to check "Reformat code", "Rearrange code" and "Optimize imports" when
commiting and pushing your code using git. 100%