Skip to content

Instantly share code, notes, and snippets.

@scruffyfox
Last active December 30, 2015 04:09
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save scruffyfox/7774093 to your computer and use it in GitHub Desktop.
Save scruffyfox/7774093 to your computer and use it in GitHub Desktop.
Documentation of default coding style for 3SC

#README

This is the default struture of all projects (starting from Jan 2013) for 3 SIDED CUBE.

Last updated: 11/07/14 by Callum Taylor

Note: These guidelines are for everyone's benifit, to ensure consistency between code and projects, allowing quick understanding of the code with minimal problems.

##Structure

The structure of all projects are as follows:

/project
+-build.gradle
+-Codebase/
| +-libs/
| +-src/
|   +-main/
|     +-AndroidManifest.xml
|     +-java/
|     | +-com/
|     |   +-cube/
|     |     +-{3 letter company code}/
|     |       +-controller/
|     |       |    +-adapter/
|     |       |    |    +-DeleateAdapter.java
|     |       |    +-handler/
|     |       +-data/
|     |       +-lib/
|     |       |    +-adapter/
|     |       |    +-event/
|     |       |    +-helper/
|     |       |    +-manager/
|     |       |    +-receiver/
|     |       |    +-service/
|     |       |    +-util/
|     |       |    |    Views.java
|     |       |    +-Constants.java
|     |       +-model/
|     |       +-view/
|     |       |    +-delegate/
|     |       |    |    +-AdapterDelegate.java
|     |       |    +-holder/
|     |       |    |    +-Holder.java
|     |       +-{project name}/
|     |       |    +-fragment/
|     |            +-BootActivity
|     |            +-MainActivity
|     |            +-MainApplication
|     `-res/
|       +-drawable/
|       +-drawable-nodpi/
|       +-drawable-hdpi/
|       +-drawable-mdpi/
|       +-drawable-xhdpi/
|       +-drawable-xxhdpi/
|       +-values/

####src/main/java/com/cube/xxx/controller/adapter/ All list/model adapters should go here and extend the base delegate adapter

####src/main/java/com/cube/xxx/controller/handler/ All response handlers or handlers to do with model manipulation go here

####src/main/java/com/cube/xxx/data/ General data structures that are not necessarily models should go here

####src/main/java/com/cube/xxx/lib/adapter/ Other adapters such as view pager adapters and other type adapters should go here

####src/main/java/com/cube/xxx/lib/event/ Otto/broadcast events go here

####src/main/java/com/cube/xxx/lib/helper/ Any helper class goes here. Helper classes generally have static methods.

####src/main/java/com/cube/xxx/lib/manager/ Manager classes go here. Example manager class

####src/main/java/com/cube/xxx/lib/receiver/ Push, broadcast, alarm receivers go here

####src/main/java/com/cube/xxx/lib/service/ Intent services go here

####src/main/java/com/cube/xxx/lib/util/ Any utility type classes go here

Required class for Holder: Views.java

####src/main/java/com/cube/xxx/model/ All API/data models go here

####src/main/java/com/cube/xxx/view/ All custom views go here

####src/main/java/com/cube/xxx/view/delegate/ View delegates go here adapter delegate

####src/main/java/com/cube/xxx/view/holder/ View holders for the delegates go here

####src/main/java/com/cube/xxx/xxx/ Main activities go here

####src/main/java/com/cube/xxx/xxx/fragment/ Fragments for main activities go here

N.B: You can have sub packages for grouping certain functionalitys such as src/main/java/com/cube/xxx/xxx/locator/ and src/main/java/com/cube/xxx/xxx/locator/fragment/ but the structure follows the same with the main activities in the root package and a sub package for the fragments.

####res/drawable/ For all XML drawables

####res/drawable-nodpi/ For SVG/9 patch drawables that apply to all DPIs

####res/layout/ Global layout resources.

  1. All layouts for views must suffix "view"
  2. All layouts for sections of views must suffix "stub"
  3. All layouts for templates (reusables) must suffix "template"
  4. All list view views must suffix "item"
  5. List header/footer views must suffix "header" or "footer"

####res/layout-small/ Small screen specific layout resources

  1. All layouts for views must suffix "view"
  2. All layouts for sections of views must suffix "stub"
  3. All layouts for templates (reusables) must suffix "template"
  4. All list view views must suffix "item"

####res/raw/ Any raw items such as MP3s or uncompressed drawables

####res/values/ All value XML files placed in here

  1. attr.xml - Used for custom view attributes
  2. colors.xml - Used for colours
  3. ids.xml - Used for defining ids for views or TAGs
  4. integers.xml - Used for integer values
  5. strings.xml - Used for global localisation and string constants such as "app_version", "app_name"
  6. styles.xml - Used for view styles AND theme styles
  7. themes.xml - Used for activity themes only

####res/xml/ Used for any other xml resources

##Code Style

Without being too harsh on the style of the code, here is the (strongly) recommended style in which to code in. Following this style will not only make your life easier, but anyone else's who has to work on the project, following the same style. Time will not be wasted trying to figure out what hte other developer is doing.

###Libraries

There are a set of standard libraries that are commonly used throughout projects.

####Storm

This library is used in most apps which are long term clients (such as BRC, ARC, ASPCA etc) who have the potential to have multiple apps. Apps such as OPSM VT, Boots and Essilor VT will not use storm or any of its sub libraries.

####AsyncHttpClient

This library is also part of Storm, but can be used standalone. This is a standard http client which is maintained by Callum. You can fork this and use in projects you start, but make sure that any changes you make gets merged backinto its upstream.

####View Injector

Based off Butterknife, this is a runtime library used for injecting views into variables and adding onclick listeners by using annotations.

####Debugger

Also included as standard (D.java) in Storm, this is a debug class used throughout every 3SC app. It is Strongly recommended to use this class over the Log class as part of Android, as it has the ability to disable debugging by setting the debug variable, also includes a stacktrace (class and line number) for all output, also includes a proper variable type conversion for output.

###Style

####Readability

  1. Tabbing

    1. All code must use tabs to seperate code. That way all code is consistent and customizable with each different IDE.

    2. All code must line up and have consistent tabbing.

    3. XML should use tabs for its indentation, and if has more than 2 or so internal properties, should be new line seperated and the ending bracket matching with the starting bracket.

    4. XML properties should be ordered by priority

      1. (optional) xmlns
      2. layout_width
      3. layout_height
      4. id
      5. orientation
      6. everything else
      7. style

RIGHT

public void test()
{
	if (this)
	{
		//TODO: that
	}
	
	//TODO: add code here
}
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:id="@+id/content"
	android:orientation="vertical"
>
	<TextView
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
	/>
	
	<ImageView style="@style/divider" />
</LinearLayout>

WRONG

public void test()
{
if (this)
	{
	//TODO: that
	}
	
	//TODO: add code here
}
public void test() {
if (this){
	//TODO: that
	}
	
	//TODO: add code here
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:orientation="vertical"
	android:id="@+id/content" >
	<TextView
		android:layout_width="match_parent"
		android:layout_height="wrap_content" />
		
	<ImageView style="@style/divider" />
</LinearLayout>

####Braces

Braces should always be on a new line, and should always exist for control statements, even one line if blocks.

RIGHT

public void test()
{
	// brace on newline, single space after comment on its own line.
}

WRONG

public void test(){
	
}

####Array Initializers

RIGHT

// No space after or before braces, single space after comma
public String[] array = {"test", "test1", "test2"};
// Braces on new line, *single* tab indentation for array items if there are many items in the array
public String[] array = 
{
	"test1",
	"test2",
	"test3",
	"test4",
	"test5"
};

WRONG

public String[] array = { "test", "test1", "test2" };
public String[] array = {
		"test1",
		"test2",
		"test3",
		"test4",
		"test5"
};

####New lines

New lines are the trickiest thing to get right, but isnt a massive thing that can be forced apon. Try to keep relevant code grouped together, and seperate these groups by a new line.

There should never be a new line after a new brace, or before an ending brace.

RIGHT

public static void main(String[] args)
{
	int count = args.length;
	for (int index = 0; index < count; index++)
	{
		// TODO: Add functionality
	}
	
	for (String arg : args)
	{
		// TODO: Add functionality
	}
	
	return;
}

WRONG

public static void main(String[] args)
{

	int count = args.length;
	for (int index = 0; index < count; index++)
	{
		// TODO: Add functionality
	}
	for (String arg : args)
	{
		// TODO: Add functionality
	}
	return;
	
}

####Annotations

Annotations should be on the same line as the block being annotated, but there are certain scenarios where this is not applicible (such as when the annotation has many variables)

Having many members with the same annotation (in this case) has to be done on the same line, having them on new lines gunks up the code.

RIGHT

@Getter @Setter private int id = 0;
@ToString(callSuper = true, includeFieldNames = true)
public class TestClass
{
	// TODO: Add functionality
}

WRONG

@Getter
@Setter
private int id = 0;
@ToString(callSuper = true, includeFieldNames = true) public class TestClass
{
	// TODO: Add functionality
}

####Getters/Setters

The Lombok annotation library should be used in all projects with the annotations @Getter and @Setter. These annotations automatically create basic getters/setters for the right members for you. You can also override these annotations by creating the getter/setter method yourself.

This forces you to use better member names.

####Member names

In a model, the members of that model should always be private, with a getter/setter. The member names should not have any prefixes and should match as closly as possible to its data source (json). Models should always use protected for variables that apply to any subclass, and private for any variables that will only ever be used by that class.

RIGHT

public class TestModel
{
	@Getter @Setter protected int id = 0;
	@Getter @Setter protected String name = "";
	@Getter @Setter protected String email = "";	
	
	// Using the primitive type boolean means the getter method is `isEnabled()`
	@Getter @Setter private boolean enabled = false;
	
	// Using the class boolean means the getter method is now `getHasAccess()`
	@Getter @Setter private Boolean hasAccess = 0;
}

WRONG

public class TestModel
{
	@Getter @Setter protected int mId = 0;
	@Getter @Setter protected String mName = "";
	@Getter @Setter protected String mEmail = "";	
	
	// Using the primitive type boolean means the getter method is `isMEnabled()`
	@Getter @Setter private boolean mEnabled = false;
	
	// Using the class boolean means the getter method is now `getMHasAccess()`
	@Getter @Setter private Boolean mHasAccess = 0;
}

Prefixes should only be used for private members who do not have a getter/setter annotation.

Single letter member names should never be used barring the expection for co-ordinate system (x, y, and z)

####Casing

  1. All properties, members and variables should follow the camelCase standard where the first character is lowercase and the following first letter of each word is uppercase. Example: myVariableTest.

  2. Database Table names should be Capitalized. Example: MagazineIssues

  3. Database Column names should be camelCase

  4. Class names should be Capitalized

  5. Ids in xml should be underscored. Example: @+id/test_id

####Method names

Method names need to be descriptive of what they do and the parameters that they take.

RIGHT

public int getId()
{
	//TODO: return integer
	return 0;
}

WRONG

public int id()
{
	//TODO: return integer
	return 0;
}

They can also trail with parameters to complete a sentence

RIGHT

public Model createFrom(JsonObject object)
{
	//TODO: parse json
	return null;
}

WRONG

public Model createFromJsonObject(JsonObject object)
{
	//TODO: parse json
	return null;
}

####Line wrapping

Line wrapping should be turned off or set to a visual wrap rather than a code wrap.

Methods with too many arguments that would require wrapping should be refactored. A method should never need more than 5 or so arguments.

####Comments

  1. Method Comments

    1. Method names should be description and have a method comment.
    2. Method comments are only needed on methods that do not override another method (@Override)
    3. Method comments should be descriptive in what the method is doing
  2. File Comments

    1. Every file in your source code should have a header which explains what the file does, how it interfaces with the app and what parameters it takes.
  3. Code Comments

    1. You should not need to comment code explaining what is happening
    2. You may use TODO comments as placeholders/stubs
    3. FIXMEs, OPTIMIZEs, and BUGs can be used anywhere at any point

EXAMPLE HEADER

/**
* This is the test model. It is used to hold the values for the list adapter in MyListView
* 
* Requires ID
* 
* @author Callum Taylor
* @project Project Name
*/
public class TestClass
{
	private int mId = 0;
	
	public TestClass(int id)
	{
		this.mId = id;
	}
	
	public int getId()
	{
		return mId;
	}
	
	public void setId(int id)
	{
		this.mId = id;
	}
	
	/**
	 * Processes the class and does some cool stuff which is explained in
	 * the method header.
	 * 
	 * TODO: Any method TODOs or FIXMEs
	 *
	 * @param param The param
	 *
	 * @return void
	 */
	public void processClass()
	{
	}
}

####Filesystem

  1. Naming

    1. Class names should be descriptive in what they do
    2. Activities should suffix "Activity"
    3. Fragments should suffix "Fragment"
    4. List adapters should suffix "Adapter"
    5. Custom views should suffix "View" or "Layout"
    6. Handlers should suffix "Handler"
    7. Helpers should suffix "Helper"
    8. Managers should suffix "Manager"
    9. Utils should suffix "Util"

####Constants and String checking

When doing string comparisons you sould always use the equals() method to compare the content of each string. When comparing with a defined constant, you should implement the yoda style coding where the constant is the left most variable in the operation. This allows for null pointer exceptions to be avoided when making the comparison.

RIGHT

public static final String CONSTANT = "test";

public void test()
{
	if (CONSTANT.equals(myVariable))...
}

WRONG

public static final String CONSTANT = "test";

public void test()
{
	if (myVariable.equals(CONSTANTS))...
}

###Practices

####Models

Models should generally extends a base class called Model which will implement a serializable/parcelable functionality to allow every subclass to be passed/stored to disc. Members of the model should not be prefixed and should use the protected keyword unless that field is used specifically for that model. The model should also use the @Data annotation to allow auto-generation of getters/setters for each of the protected/public members. Member names should closely relate to what it's JSON counterpart is to allow GSON to correctly populate the model.

Example:

package com.cube.arc.model;

import lombok.Data;

/**
 * User Model
 */
@Data
public class User extends Model
{
	protected String username;
	protected String firstname;
	protected String lastname;
	
	@Expose private String tempVariable;
}

####For loops

When iterating an array, a for each block is always prefered to a for block, but if you require index counting, a for block should be used.

The variable name i should never be used. ever. You should always use a descriptive variable name or index

Counts of array lists or other collections (not mutable arrays) should be defined before the loop

int count = array.size();
for (int index = 0; index < count; index++)
{
	// TODO: Add functionality
}

or

for (int index = 0, count = array.size(); index < count; index++)
{
	// TODO: Add functionality
}

or

for (int index = 0; index < array.length; index++)
{
	// TODO: Add functionality
}

Using array.length is fine because the length parameter is pre-calculated due to the array being mutable. Calling array.size() on ever iteration causes performance issues.

####OnClick listeners

Anonymous onclick listeners should be avoided if possible. Instead, set the listener to be the current context and override onClick. In this method, check for the id in an if block.

public class TestClass implements View.OnClickListener
{
	@Override public void onClick(View v)
	{
		if (v.getId() == R.id.item)
		{
			// TODO: Add functionality
		}
	}
}

Or if you're using the View injection class, then

@OnClick public void onButtonIdClick(View v)
{
	// TODO: Add functionality
}

####View injection

View injection should be used where possible for all view references or onClick listeners. Views.reset(this) should also be called in the onDestroyView method in fragments to clean up any references.

##Class Templates

###DelegateAdapter.java

package com.cube.xxx.controller.adapter;

import android.content.Context;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

import com.cube.xxx.view.delegate.AdapterDelegate;

import lombok.Getter;

public abstract class DelegateAdapter<T> extends BaseAdapter
{
	@Getter private Context context;
	@Getter private SparseArray<AdapterDelegate> viewTypes = new SparseArray(2);

	public DelegateAdapter(Context context)
	{
		this.context = context;
		getViewTypes(viewTypes);
	}

	public abstract void getViewTypes(SparseArray<AdapterDelegate> toFill);

	@Override public int getViewTypeCount()
	{
		return viewTypes.size();
	}

	@Override public View getView(int position, View convertView, ViewGroup parent)
	{
		int viewType = getItemViewType(position);
		T item = (T)getItem(position);

		convertView = viewTypes.get(viewType).getView(item, position, convertView, parent, LayoutInflater.from(getContext()));

		return convertView;
	}
}

###AdapterDelegate.java

package com.cube.xxx.view.delegate;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

import lombok.Getter;
import lombok.Setter;

public abstract class AdapterDelegate<T>
{
	@Getter @Setter private BaseAdapter adapter;

	public AdapterDelegate(BaseAdapter adapter)
	{
		this.adapter = adapter;
	}

	public abstract View getView(T item, int position, View convertView, ViewGroup parent, LayoutInflater inflater);

	public boolean onItemLongClick(int position, View view)
	{
		return false;
	}
}

###Holder.java

package com.cube.xxx.view.holder;

import android.view.View;

import com.cube.xxx.lib.util.Views;
import com.cube.xxx.lib.util.Views.Injectable;

@Injectable
public abstract class Holder<T>
{
	public Holder(View view)
	{
		Views.inject(this, view);
	}

	public abstract void populate(T model);
}

###Manager.java

All manager classes should be singletons with a public static method called getInstance() which returns the static instance of the manager class. The class should also have a private constructor to prevent instantiations of the manager.

package com.cube.xxx.lib.manager;

public class APIManager
{
	private static APIManager instance;

	public static APIManager getInstance()
	{
		if (instance == null)
		{
			synchronized (APIManager)
			{
				if (instance == null)
				{
					instance = new APIManager();
				}
			}
		}
		
		return instance;
	}

	private APIManager()
	{
		Constants.API_BASE_URL = com.cube.storm.ModuleSettings.CONTENT_BASE_URL;
		Constants.API_VERSION = com.cube.storm.ModuleSettings.CONTENT_VERSION;
	}
}

##Source control

We use a standard git flow (as documented here).

  1. Feature branches must follow the style feature/feature-name-here should be brief and descriptive.
  2. When merging into develop, make sure you have any changes pulled from origin first, merge (do not auto-commit) and resolve any conflicts
  3. Test the merged changes by running your code again if there were any conflicts/potential issues
  4. Commit the merge (should include all commit information from that branch) and include the ticket number if applicible

The only time it is acceptable to commit on develop is if you are updating submodules or documentation.

###Commit requirements

When commiting, you must have PGP commit signing turned on. Create a PGP key using your 3SC email address and sync it with any PGP key server (preferably MIT).

Your commit message should be descriptive in what it is changing. You may use any tense language, as long as it is descriptive in what the commit does/is doing.

RIGHT

Updated gradle file for new version

WRONG

Stuff

You may also use the long description style commit where the first line is a brief title and the second line+ is the detailed description

RIGHT

Fixes BUG1, BUG2, BUG3
Fixes the issue which was caused sometimes by BUG1 which affected BUG2

You must push all of your changes/branches at the end of each working day.

###Ticket flow

 New +---------> Accepted +-------> In Progress
  +                                >      +     
  |                               /       |     
  |                              /        |     
  |                             +         v     
  |                         Invalid <- Completed 
  |                                       +     
  |                                       |     
  |                                       |     
  |            Wont-fix                   v     
  +----------------+----------------> Resolved  
  1. Tickets added default to "new"
  2. Once approved as valid, will change to "Accepted" and assigned
  3. When begining, status will change to "In Progress" via commit, or ticket update
  4. When finished, will change to "completed" for validation.
  5. When the validation is complete, the ticket will change to Resolved. If not, it will become invalid until it has been accpeted and worked on, where it will change to in-progress
  6. An invalid ticket or unreplicatable bug will change from new -> resolved with the message "wont fix" or similar

#App submission

This is the documentation for Android app submissions. This document must be followed meticulously

Last updated: 03/07/14 by Callum Taylor

##Pre-flight checklist

[x] App size is less that 50mb

[x] Check all FIXMEs have been resolved

[x] Set "install location" in manifest to "auto"

[x] Check bundle (if applicible) is up to date with latest timestamp

[x] Commit ALL changes and create a release branch (if not already)

[x] Has the app been tested on various devices

[x] App includes appropriate sized icons? (mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi)

[x] App version set correctly

[x] External API requests versions correct

[x] Google anayltics working correctly?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment