#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.
- All layouts for views must suffix "view"
- All layouts for sections of views must suffix "stub"
- All layouts for templates (reusables) must suffix "template"
- All list view views must suffix "item"
- List header/footer views must suffix "header" or "footer"
####res/layout-small/ Small screen specific layout resources
- All layouts for views must suffix "view"
- All layouts for sections of views must suffix "stub"
- All layouts for templates (reusables) must suffix "template"
- 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
- attr.xml - Used for custom view attributes
- colors.xml - Used for colours
- ids.xml - Used for defining ids for views or TAGs
- integers.xml - Used for integer values
- strings.xml - Used for global localisation and string constants such as "app_version", "app_name"
- styles.xml - Used for view styles AND theme styles
- 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
-
Tabbing
-
All code must use tabs to seperate code. That way all code is consistent and customizable with each different IDE.
-
All code must line up and have consistent tabbing.
-
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.
-
XML properties should be ordered by priority
- (optional) xmlns
- layout_width
- layout_height
- id
- orientation
- everything else
- 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
-
All properties, members and variables should follow the
camelCase
standard where the first character is lowercase and the following first letter of eachword
is uppercase. Example:myVariableTest
. -
Database Table names should be
Capitalized
. Example:MagazineIssues
-
Database Column names should be
camelCase
-
Class names should be
Capitalized
-
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
-
Method Comments
- Method names should be description and have a method comment.
- Method comments are only needed on methods that do not override another method (@Override)
- Method comments should be descriptive in what the method is doing
-
File Comments
- 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.
-
Code Comments
- You should not need to comment code explaining what is happening
- You may use TODO comments as placeholders/stubs
- 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
-
Naming
- Class names should be descriptive in what they do
- Activities should suffix "Activity"
- Fragments should suffix "Fragment"
- List adapters should suffix "Adapter"
- Custom views should suffix "View" or "Layout"
- Handlers should suffix "Handler"
- Helpers should suffix "Helper"
- Managers should suffix "Manager"
- 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).
- Feature branches must follow the style
feature/feature-name-here
should be brief and descriptive. - When merging into develop, make sure you have any changes pulled from origin first, merge (do not auto-commit) and resolve any conflicts
- Test the merged changes by running your code again if there were any conflicts/potential issues
- 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
- Tickets added default to "new"
- Once approved as valid, will change to "Accepted" and assigned
- When begining, status will change to "In Progress" via commit, or ticket update
- When finished, will change to "completed" for validation.
- 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
- An invalid ticket or unreplicatable bug will change from new -> resolved with the message "wont fix" or similar