In continuation of the series of posts with our friends on the Nordstrom Technology team we’re going to be creating a simple StackOverflow client utilizing the technologies discussed in Part 1 – Tools & Libraries.
The image to the right is the final screen that will be presented to the user.
You can access part 1 on GitHub at:
git clone https://github.com/Liffft/StackRX.git
cd StackRX
git checkout stack_rx_1_architecture_basics
Before we jump into code let’s take a look at the anatomy and organization of the project.
StackRX Client Application vs StackRX Services
As mentioned in Part 1 we completely decouple the service layer from the view layer. Projects that combine these become extremely unwieldy, limit code reusability and are much harder to test.
Android specific logic will never exist within the service layer. Keep in mind that another team may use the service layer for their project so keep it clean.
To create the services project simply right click File–>New–>New Module
In the editor be sure to select ‘Android Library’. Also make sure the library is included in your app build.gradle as a dependency
dependencies {
compile project(‘:stackrx-services’)
}
The services build.gradle should include any libraries shared between the application front-end and the services layer.
build.gradle (app) build.gradle (services)
Notice how we have the new Material Design cardview library only in the app dependencies but the services library contains Roboguice which is used both in the services and app.
Package Structure
Android applications are often organized by object type but we organize by feature.
We do this for the following reasons:
- As projects get more complex, standard naming is imperative. As we progress in the project development you will notice that all packages resemble each other in both the unit tests, client code and service layer. This prevents problems like a fragment package with 30 fragment files with no context or classes being in packages for no rhyme or reason.
- Coming into a large Android project can be hell. As a new developer getting your first commit can be overwhelming. However if the application is broken into features it’s much easier to get started. Need to make changes to the shopping page? Most likely it will be in the shop->fragment package. Some oversight will be needed for shared objects.
- We will touch on this further, but fragment classes should be as small as possible. Complex things such as adapters should be moved into their own packages and classes when possible. This leads to more testable code in general.
Let’s Write Clean Code Together. Android Code Best Practices.
We’ve spent a lot of time thinking of ways to keep our code base as uniform and readable as possible. Following good practices from day #1 will reduce code complexity, tech debt and refactoring time. Many projects race to release but are so disorganized and non-uniform a rewrite is almost necessary afterwards.
Code Grouping and Organization
Android Studio supports regions; a little known feature that really helps organize classes.
Pulling open StackRXActivity (or any other classes in the view layer) you will see our code regions defined:
Figure 1 Figure 2
These are just like pragma marks in other languages. To collapse the regions hit
Command->Shift->Minus
to grow the region
Command->Shift->Plus
The categories above are the same for every fragment, adapter, activity etc.
This won’t be crystal clear initially, but I’ll explain each region and its purpose.
INJECTED CLASSES
This section is for dependency Roboguice injected classes
@Inject UserSession private UserSession mUserSession;
INJECTED VIEWS
This section is for injected Roboguice views. There should be no view.findByViewId’s littered throughout the code
@InjectView(R.id.drawer_layout)
private DrawerLayout mDrawerLayout;
LOCAL CONSTANTS
Local constants are immutable constants pertaining to the class. If they are string constants and used in other classes they should live in an AppConstants
private static final String TAG = QuestionFragment.class.getSimpleName();
CLASS VARIABLES
Local class variables. We tend to perserve the ‘m’ prefix.
private QuestionFragment mQuestionFragment;
CONSTRUCTOR
Any constructors associated with the class
public StackRXActivity () {}
LIFE CYCLE METHODS
All life cycle methods. For fragments and activities order matters. Ex: onCreate should never be after onDestroy
onAttach(Activity)
onCreate(Bundle)
onCreateView(LayoutInflater, ViewGroup, Bundle)
onActivityCreated(Bundle)
onViewStateRestored(Bundle)
onStart()
onResume()
onPause()
onStop()
onDestroyView()
onDestroy()
onDetach()
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); }
WIDGET
Widget is used as a catch-all for widgets within the screen.
@Override public boolean onCreateOptionsMenu(Menu menu) { }
LISTENERS
Listeners are all component type listeners in the view (OnClickListener, OnTextChangeListener)
Listeners should always be separate classes and never be inline in the life cycle methods. Also set the listener in the code and never implement or extend on the class level.
private class OnLoginButtonClickListener extends View.OnClickListener { @Override public void onClick (View v) { } }
EVENTS
Events are triggered by the EventBus. All fragment/activity level uses of the event bus are captured here
public void OnButtonSelectEvent(ButtonSelectEvent buttonSelectEvent) { }
LOCAL METHODS
Local methods pertaining to the fragment
public void AddItemToList(String item) { ... }
OBSERVERS
Observers are listening for observables that have sent down a single typically from network calls
private class GetQuestionObserver implements Obvserver { … }
ACCESSORS
Getters and setters the class
public void getItemCount () { return itemCount; } <b>INNER CLASSES</b> Inner classes that are not discussed above private class SomeInnerClass { }
Variable Naming
Let Android Studio name your variables for you! Rather than using a shorthand like "adaptor" or "qda" for a “QuestionDetailAdapter” simply start typing the first letter of the class name to have it automatically named that.
We use the m prefix on all class level variables. While this is always up for debate, you never have any of the time wasting this.variableName on constructors.
Ex:
public QuestionDetailAdapter(Context context) { context = context; // no! mContext = context; // nice! }
You can configure Android Studio to automatically prepend the ‘m’ right when you type the variable name
Android Studio-->Preferences-->Edit-->Code Style-->Java-->Code Generation-->Field-->Name Prefix-->m
Layout Naming Conventions
Too many projects do not name layouts or layout ids consistently. Our convention is as follows:
The layout name always matches the class exactly. This makes it is incredibly clear which layout file is for which class.
Ex: QuestionFragment.java → question_fragment.xml
Variable naming should always match the layout name appended with what it is and its type.
How many times have we seen:
R.id.question or R.id.sign_in
This gives no context.
Instead:
R.id.question_fragment_question_list_view or
R.id.question_fragment_sign_in_button
Do you have any other tips / tricks for keeping your project sane and beautiful? Drop us a comment! Stay turned for Part 3: Dependency Injection, RxJava, DAO’s and more!
Thanks for reading. If you have any questions or would like to know more about working with the Nordstrom Technology team feel free to shoot us an email!
The build.gradle (services) and the build.gradle (app) images seemed to be mislabeled. Great series of post!
Thanks Todiou, Just updated the labels