Android πŸ€–

Hero image for Android πŸ€–

Reference Resources

Formatting

  • Indentation:
    • Only using spaces * never tabs.
    • Use 4 space indents for blocks and 8 space indents for line wraps.
    • It’s also flexible to change the rules too, as there is a recommendation from Google to use 2 space for blocks and 4 space for line wraps too, so, depending on the start of the project, we should always follow the base structure.
  • Line length: it shouldn’t exceed the screen width, ideally should be no longer than 100 characters.

  • Line amount per class: should be no longer than 600 lines per class.

  • Vertical spacing: it’s very important that you should keep alignment on your new codes to be consistent with current code base in order to have good experience when doing the code review.

  • Brace style:
// Bad
public void main()
{
    // Not this
}

// Good
public void main() {
    // Your code goes here
}
  • Conditional statements are always required to be enclosed with braces:
// Bad
if (conditionPassed)
    doSomething();

// Good
if (conditionPassed) {
    doSomething();
}
if (someTest) { doSomethingElse(); }

if (someTest) doSomethingElse(); // Still acceptable

  • 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 .. For example:

SomeClass.Builder.with(context)
        .setAlpha("1")
        .setCancelable(false)
        .setExtras(bundle)
        .build();
  • RxJava chaining style:
public Observable<Location> syncLocations() {
    return mLocationHelper.getAllLocations()
            .concatMap(new Func1<Location, Observable<? extends Location>>() {
                @Override
                 public Observable<? extends Location> call(Location location) {
                     return mLocationService.getLocation(location.id);
                 }
            })
            .retry(new Func2<Integer, Throwable, Boolean>() {
                 @Override
                 public Boolean call(Integer numRetries, Throwable throwable) {
                     return throwable instanceof Error;
                 }
            });
}

Naming

Packages

Package names are all lower-case, multiple words concatenated together, without hypens or underscores:

// Bad
com.nimble.android_demo

// Good
com.nimble.androiddemo

Variables

BAD:

// Bad
private int mTemp;
private boolean mA;

private void check() {
    // Do a bunch of logics here
}

// Good
private int mTemporary;
private boolean mIsValid;

private void checkStyle() {
    // or private void validateStyle() and do your logics here
}

Classes and Interfaces

  • Written in UpperCamelCase.
// Bad
BaseACTIVITY

// Good
BaseActivity

Methods

  • Written in lowerCamelCase.
// Bad
setvalue

// Good
setValue

Fields

  • Normally be written in lowerCamelCase.
  • Non-public, non-static field names start with an m.
  • Static field names start with an s.
// Bad
private int Value //or anything else.
public static Singleton singleTon;

// Good
private int mValue; // Note: in Kotlin it could be different.
public static Singleton sSingleton;
  • Static final fields should be written in uppercase:
// Bad
public static final int extra_value = 42;

// Good
public static final int EXTRA_VALUE = 42;
  • Acronyms should be treated as words:
// Bad
XMLHTTPRequest
String URL
findPostByID

// Good
XmlHttpRequest
String url
findPostById
  • Example:
public class ExampleClass {
    public static final int SOME_CONSTANT = 42;
    public int publicField;
    protected int mProtected;
    private String mUrl;

    private int findRateById() {
        // Your code goes here
    }
}
class ExampleClass {
    val publicFinalField: Int
    var publicField: Int = 0
    protected val protectedField: Int
    private val url: String

    private fun findRateById(): Int {
        // Your code goes here
    }
}

const val SOME_CONSTANT = 42

Declaration

  • Access level modifiers should be explicitly defined for classes, methods and member variables:
// Bad
boolean isValid;
void method() {
    // Nope
}

// Good
private boolean isValid;
public void method() {
    // Your code
}
  • Classes should be one class per source file, although inner classes are still encouraged in some situations.
  • Enum: should be avoided where possible, due to a large memory overhead. Static constants are preferred. See more or here.

Comments

// Bad

// Two line
// comments

/**
  * Block comment but mis-aligned
 */

// Good

// One line comment

/**
 * Block comment
 * that is longer than 2 lines
 */

 /**
  * Class/method description
  *
  * @param  url  an absolute URL giving the base location of the image
  * @param  name the location of the image, relative to the url argument
  * @return      the image at the specified URL
  * @see         Image
  */

Project Structure

Typical structure we usually have:

com.nimble.project
β”œβ”€ other-module
β”œβ”€ app
β”‚  β”œβ”€ libs
β”‚  β”œβ”€ src
β”‚  β”‚  β”œβ”€ androidTest
β”‚  β”‚  β”‚  └─ java
β”‚  β”‚  β”‚     └─ com/nimble/InstrumentalTest
β”‚  β”‚  β”‚  
β”‚  β”‚  β”œβ”€ main
β”‚  β”‚  β”‚  β”œβ”€ java
β”‚  β”‚  β”‚  β”‚   └─ com/nimble
β”‚  β”‚  β”‚  β”‚          β”œβ”€ model
β”‚  β”‚  β”‚  β”‚          β”‚  └─ Something.java
β”‚  β”‚  β”‚  β”‚          β”œβ”€ view
β”‚  β”‚  β”‚  β”‚          β”œβ”€ presenter
β”‚  β”‚  β”‚  β”‚          β”œβ”€ service
β”‚  β”‚  β”‚  β”‚          β”œβ”€ event
β”‚  β”‚  β”‚  β”‚          β”œβ”€ util
β”‚  β”‚  β”‚  β”‚          β”‚
β”‚  β”‚  β”‚  β”‚          └─ ApplicationClass.java
β”‚  β”‚  β”‚  β”œβ”€ res
β”‚  β”‚  β”‚  β”‚   β”œβ”€ anim
β”‚  β”‚  β”‚  β”‚   β”œβ”€ color
β”‚  β”‚  β”‚  β”‚   β”œβ”€ drawable
β”‚  β”‚  β”‚  β”‚   β”œβ”€ layout
β”‚  β”‚  β”‚  β”‚   └─ values
β”‚  β”‚  β”‚  └─ AndroidManifest.xml
β”‚  β”‚  β”‚  
β”‚  β”‚  β”œβ”€ test
β”‚  β”‚  β”‚  └─ java
β”‚  β”‚  β”‚     └─ com/nimble/UnitTest
β”‚  β”‚  β”‚  
β”‚  β”‚  └─ staging
β”‚  β”‚     └─ res
β”‚  β”‚        └─ values
β”‚  β”œβ”€ build.gradle
β”‚  └─ proguard-rules.pro
β”‚
β”œβ”€ spec
β”‚  └─ rspec_test.rb
β”‚
β”œβ”€ .travis.yml
β”œβ”€ phraseapp.yml
β”‚
β”œβ”€ build.gradle
β”œβ”€ keystore.jks
β”œβ”€ settings.gradle
└─ README.md

Regarding structuring App flavors, read on the section Gradle configuration.

Development Environment

Android SDK, IDE

Emulator

  • Use x86 emulator for significant speed up during development.

CI services such as Circle CI or Travis CI do not support x86 Emulator.

Gradle configuration

  • Gradle should be the default option for build system (for now, you may want to review some other later too such as Bazel, Buck…)
  • 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.
  • Building a Signed Release APK, you can choose either way:
    • AS Menu -> Build -> Generate Signed APK -> Manually adding the keystore directory / passwords -> Build.
    • Gradle config for Signing APK:
// Put this in your app module level gradle:
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.")
        }
    }
}

// And create another `gradle.properties` file to keep the password, don't PUSH this to the repo.
KEYSTORE_PASSWORD=real_keystore_password
KEY_PASSWORD=real_key_passw
  • Don’t ever expose the key secrets like this:
// Don't do this
signingConfigs {
    release {
        storeFile file("myapp.keystore")
        storePassword "storepassword"
        keyAlias "thekey"
        keyPassword "nicepassword"
    }
}

Gradle configuration for app flavors

The purpose is to separate the Develop environment (endpoints, library keys…) with the real Production environment. To achieve this:

  • First, define the flavor in app level gradle config:
productFlavors {
    production {
        applicationId "com.app.android"
    }

    staging {
        // Staging AppId should be different from the real production.
        applicationId "com.app.staging"
    }
}

sourceSets {
    staging {
        res.srcDirs = ['src/staging/res']
    }
    // production doesn't need to clarify the resource directory as it uses the default main one.
}
  • Second, create a proper staging directory for the staging resources (endpoints, different keys…):
β”‚  β”‚  β”œβ”€ main
β”‚  β”‚  β”‚  β”œβ”€ java
β”‚  β”‚  β”‚  └─ res
β”‚  β”‚  β”‚     β”œβ”€ anim
β”‚  β”‚  β”‚     β”œβ”€ color
β”‚  β”‚  β”‚     β”œβ”€ drawable
β”‚  β”‚  β”‚     β”œβ”€ layout
β”‚  β”‚  β”‚     └─ values
β”‚  β”‚  β”‚  
β”‚  β”‚  β”‚  
β”‚  β”‚  └─ staging
β”‚  β”‚     └─ res
β”‚  β”‚        β”œβ”€ anim
β”‚  β”‚        β”œβ”€ color
β”‚  β”‚        β”œβ”€ drawable
β”‚  β”‚        β”œβ”€ layout
β”‚  β”‚        └─ values

ABI filter

  • The ABI defines, with great precision, how an application’s machine code is supposed to interact with the system at runtime. You must specify an ABI for each CPU architecture you want your app to work with. Applying an incorrect ABI choice on a mismatched CPU architecture won’t make it work. For example: running on x86 emulator requires you to use the proper x86 apk.
  • You can read more from here, but in this example, we’re just simply splitting the app into 2 different flavors:
    • production_x86 for x86 chipset.
    • production for armeabi, armeabi-v7a chipset.
// Adding this to your app module level gradle:
production_x86 {
    // Append automatically a different code version to identify the different version when app is published and we observe the tracking info.
    versionCode Integer.parseInt("6" + defaultConfig.versionCode)
    applicationId "com.redplanet.android"
    ndk {
        abiFilters "x86"
    }
}

production {
    applicationId "com.redplanet.android"
    ndk {
        abiFilters "armeabi", "armeabi-v7a"
    }
}

// Don't forget to bring the proper native library files (.so) to the main/jniLibs/

This is only needed when the project is using native libraries.

Proguard

Proguard is normally used on Android projects to shrink and obfuscate the packaged code. We do this usually for release Apk (it helps saving time of downloading for users, hence increase the app download rates).

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

Always check the Release Build whenever we add a new project config/dependency. You won’t want to have a litle surprise within a short time when preparing for the final step and countering problem!

XML Files

  • View-based XML files should be prefixed with the type of view that they represent:

Bad:

  • login.xml
  • main_screen.xml
  • rounded_edges_button.xml

Good:

  • activity_login.xml
  • fragment_main_screen.xml
  • button_rounded_edges.xml

  • Use Context-Specific XML Files wherever possible XML resource files could be used:
    • Strings => res/values/strings.xml
    • Styles => res/values/styles.xml
    • Colors => res/color/colors.xml
    • Animations => res/anim/
    • Drawable => res/drawable
  • XML Attribute Ordering

Where appropriate, XML attributes should appear in the following order:

* `style` attribute (if there is)
* `id` attribute
* `layout_*` attributes
* style attributes such as `gravity` or `textColor`
* value attributes such as `text` or `src`

Within each of these groups, the attributes should be ordered alphabetically.

  • Use styles

It helps styling management easier, or at least we should have some common styles clarifications that are being used throughout the app:

In activity_main_layout.xml:

<Button
    style="@style/AppButtonStyle"
    android:id="@+id/image_profile"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:text="Button Text" />

style.xml:

<style name="AppButtonStyle">
    <item name="android:textSize">@dimen/font_normal</item>
    <item name="android:textColor">@color/basic_black</item>
</style>

What’s next?

Application structure pattern? There are various learning sources nowaday talking about MVP, MVVM….clean architecture in general. The goal is to have a clear view, good readability on the code base, decouples the code for reusability and testability. Always consider to apply any of these pattern at the beginning of the project. I won’t write much about them in here as it’s depending on the leader decision for each project, in the different situations, and many things to mention but shouldn’t be in this guideline.