Billion dollar tips for every Android developer
Architectural Patterns
One of the most important things that you should focus on while developing a world-class app is the separation of concerns. It is good to separate and define clear roles for each component in your app. A class shouldn’t be a multi-tasking component. Many developers make a mistake of writing almost all their code in an Activity
or Fragment
. Any code that doesn’t handle a UI or operating system interaction should not be in those classes.
Having a good design pattern saves you lots of problems. A good pattern ensures that all your code is organized and thoroughly covered by Unit tests. It makes debugging easier, since you know where to look for a particular problem. It also greatly improves the maintainability of your code.
There several architectural patterns, but the most popular being;
- MVC (Model View Controller)
- MVP (Model View Presenter)
- MVVM (Model View ViewModel)
I’m going to casually introduce each of the above architectural patterns.
i. MVC (Active Model)
MVC is commonly known as active model. The model notifies the view, when it is changed by the controller.
credit: codeproject.com
It also notifies other classes through the help of the Observer pattern. The Model contains a collection of observers that are interested in updates. The View implements the observer interface and registers as an observer to the Model.
credit: upday.github.io
Model: This is the data layer. It is responsible for managing the business logic and network or database API requests.
View: This is the UI layer. Its responsible for rendering the user interface. It is responsible for visualizing data from the data model.
Controller: This is the logic layer. It gets notified of the user’s behavior and updates the view accordingly. It is also in charge of updating the model as needed.
For more information about MVC, this blog has you covered.
ii. MVP (Model View Presenter)
This is also known as the passive model. The presenter is the only class that manipulates the model. The model doesn’t not notify the view when it is changed by the presenter. Notification is done by the presenter. So the model is referred to as inactive or passive.
Model-View-Controller — passive Model — behavior (credit: upday.github.io)
Model: This is similar to what we have defined above. It is the data layer responsible for managing the business logic and network or database API requests.
View: This is responsible for presenting data in a way decided by the presenter. The view is usually implemented by Activities or Fragments.
Presenter: The presenter acts as a link between the model and the view. All presentation logic must be defined here. It is responsible for querying the model and updating the view. It is also responsible for reacting to user interactions and updating the model accordingly.
This architectural pattern aims at ensuring that the view does as little work as possible. In other words, we have to keep the view as dumb as possible.
The presenter should also be framework-independent and shouldn’t depend on any Android classes. It should be a simple POJO class.
The backbone of this architectural pattern is an interface called a contract
that describes the communication between the view and the presenter. It cleans up the interactions between your view and presenter.
An example of MVP implementation;
Here are some sample code snippets from a simple stories App.
The contract file is an interface file that contains the presenter
and view
interfaces. The view
interface provides methods that are responsible for drawing the user interface, whilst the presenter
interface contains methods that are responsible for the doing logic that is not dependent on the Android framework. It also contains methods that fetch data to be displayed on the user interface.
Below is how the contract file StoriesFragmentContract
is structured.
public interface StoriesFragmentContract {
interface View {
// methods for rendering the UI
void showLoading();
void hideLoading();
void displayStories(List<Story> storiesList);
void displayNoStories();
void showError();
}
interface Presenter {
// methods that contain business logic
void setView(StoriesFragmentContract.View view);
void getAndDisplayStories();
void pullToRefreshStories();
}
}
StoriesFragmentContract.java
The presenter file implements the Presenter interface. The presenter interface defines the business logic of the application.
public class StoriesFragmentPresenter implements StoriesFragmentContract.Presenter {
private StoriesFragmentContract.View mView;
@Override public void setView(StoriesFragmentContract.View view) {
mView = view;
}
@Override public void getAndDisplayStories() {
List<Story> storiesList = new ArrayList<Story>();
view.showLoading();
// Logic for getting stories
view.displayStories(storiesList);
view.hideLoading();
}
@Override public void pullToRefreshStories() {
// Logic for updating displayed stories
}
}
StoriesFragmentPresenter.java
Finally, we have a Fragment (StoriesFragment
) which is responsible for displaying our stories. This fragment implements the view interface, which is responsible for drawing the android UI.
public class StoriesFragment extends Fragment implements StoriesFragmentContract.View {
// declare your layout widgets
private RecyclerView recyclerView;
private ProgressBar progressBar;
private LinearLayout error_layout;
private StoriesAdapter adapter;
private StoriesFragmentPresenter presenter;
public StoriesFragment() {
// Required empty public constructor
}
public static StoriesFragment newInstance() {
StoriesFragment fragment = new StoriesFragment();
return fragment;
}
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// inflate the fragment layout view
View view = inflater.inflate(R.layout.fragment_stories, container, false);
// provide view to the presenter
presenter.setView(this);
return view;
}
@Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Code for UI Binding
recyclerView = (RecyclerView) view.findViewById(R.id.stories_recycler);
progressBar = (ProgressBar) view.findViewById(R.id.storiesLoadingProgressBar);
error_layout = (LinearLayout) view.findViewById(R.id.stories_error_layout);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
// Call to the presenter to provide stories
presenter.getAndDisplayStories();
}
@Override public void showLoading() {
progressBar.setVisibility(View.VISIBLE);
}
@Override public void hideLoading() {
progressBar.setVisibility(View.GONE);
}
@Override public void displayStories(List<Story> storiesList) {
recyclerView.setVisibility(View.VISIBLE);
adapter = new StoriesAdapter(storiesList, getContext());
recyclerView.setAdapter(adapter);
}
@Override public void displayNoAssignments() {
error_layout.setVisibility(View.VISIBLE);
}
@Override public void showError() {
error_layout.setVisibility(View.VISIBLE);
}
}
StoriesFragment.java
However, the above is a very simple and basic example of the MVP architectural pattern. you can refer to this blog post for further details about the same.
iii. MVVM (Model View ViewModel)
This is a very useful pattern that was introduced together with the Android Architecture Components.
With the help of the ViewModel
class, this pattern enables your app to survive configuration changes that come up as a result of device orientation changes.
This is because it combines the advantages of separation of concerns as provided by MVP while leveraging the advantages of data bindings. With this pattern, the model takes care of as many operations as possible while minimizing the logic in the view.
Unlike in MVP where the presenter holds a reference to the view, the ViewModel
doesn’t need to hold a reference to the view, because it exposes streams of events to which the Views can bind. The View however, has a reference to the ViewModel, but the ViewModel
has no information about the view, hence the principle; The consumer of the data should know about the producer, but the producer (ViewModel
) doesn’t know about who consumes the data.
The main players herein are;
credit: en.wikipedia.org
Model: This is also referred to as the DataModel. Its main purpose is to abstract the data source. The ViewModel
works with the DataModel
to get and save data. This is the component in charge of business logic.
View: The job of this component is to inform the ViewModel about the user’s action. It is also responsible for drawing the UI. In the Android framework, this can be an Activity
or Fragment
. This shouldn’t have any logic. Any underlying logic should be handled by the ViewModel
.
ViewModel: This component is responsible for exposing streams of data relevant to the View. It retrieves data from the Model, applies the UI logic and then provides the relevant data for the view to consume.
The biggest plus on the above pattern is that it makes your app very easy to test. Unit tests can be easily written for the ViewModel
and Espresso tests for the View
are also easy to do. This is a broad pattern which can’t be wholly introduced here. More information about the same can be found here.
Libraries
Ben Jakuben, in his post about Android Libraries clearly states that;
“A good developer knows to never reinvent the wheel, unless you plan on learning more about wheels!”
There are tons of open source libraries that developers can freely use in their apps. Some of these are so helpful that you shouldn’t start any App without them. One line of code in your app’s build.gradle
is sufficient to include any library of choice automatically.
Below, I’m going to casually introduce some must know libraries that make your Android development life fun and much easier.
1. Retrofit
credit: tsurutan.com
This is my favorite library. It is the best library for making REST API calls. It makes HTTP Requests easy through annotations and automatically handles JSON parsing through the use of POJO classes.
All you need to do is to define an API interface and a Retrofit class that automatically generates an implementation of your API interface. In your interface, you use annotations to describe the HTTP request.
A simple example of an API interface is;
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
GitHubService.java
The retrofit class that generates an implementation of the GithubService
interface shall look like this;
public class GithubApi {
// Constructor to create the GithubApi
public GithubApi() {
this.createApi();
}
// Method that creates the Retrofit object
private void createApi() {
Gson gson = new GsonBuilder().create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create(gson));
.build();
GitHubService service = retrofit.create(GitHubService.class);
}
}
GithubApi.java
I won’t dive so deep into the intricacies of the Retrofit implementation. You can however go read about it from here;
2. GSON
credit: welbornmachado.com
In the Retrofit example above, I introduced GSON
while creating the Retrofit
object. I guess you wondered why!
GSON is a library that coverts Java Objects (POJOs) into their JSON representation. It can also be used to convert a JSON string to an equivalent Java Object. The library provides simple toJson()
and fromJson()
methods to convert Java objects to JSON and vice versa.
However, when using the Retrofit
library, you never have to make direct calls to GSON. The Retrofit class handles all that automatically when you add GSON as the converterFactory
. More information about the same can be found here.
3. ButterKnife
credit: appcodelibrary.com
Butterknife uses annotations to inject views by creating boilerplate code for you. It is small, simple and lightweight. It makes your code more readable on top of being easy to use.
Typically, you would call references to views in your Activity or fragment like so;
public class MainActivity extends AppCompatActivity {
private TextView mWelcomeLabel;
private EditText mUsernameField;
private EditText mPasswordField;
private Button mSubmitButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWelcomeLabel = (TextView) findViewById(R.id.welcomeLabel);
mUsernameField = (EditText) findViewById(R.id.usernameField);
mPasswordField = (EditText) findViewById(R.id.passwordField);
mSubmitButton = (Button) findViewById(R.id.submitButton);
}
}
MainActivity.java
However, when we use Butterknife, our code becomes much more readable and concise. The same Activity code shall hence look like this;
public class MainActivity extends AppCompatActivity {
@BindView(R.id.welcomeLabel) TextView mWelcomeLabel;
@BindView(R.id.usernameField) EditText mUsernameField;
@BindView(R.id.passwordField) EditText mPasswordField;
@BindView(R.id.submitButton) Button mSubmitButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
}
MainActivity.java (with ButterKnife)
For more information about ButterKnife refer to the Butterknife documentation here.
4. Dagger2
Dagger2 is a dependency injection framework, built on a simple concept called Inversion of Control. According to that concept, a class should not configure its dependencies statically, but should be configured from the outside. In Java, a class has a dependency on another class, if it uses an instance of that class. This is referred to as class dependency.
Since dependency injection in most world class Apps is a must know, Dagger2 is a true solution for problems that may arise during providing dependencies to particular classes. Ideally, classes should be as independent as possible from other classes.
Dagger2 uses code generation and is based on annotations. The generated code is very easy to read and debug. The annotations used are; @Module
, @Provides
, @Inject
and @Component
among others.
For more information about dependency injection, Vogella has got you covered here.
5. Glide
credit: ssaurel.com
If you have used Picasso before, trust me, Glide is a better option for fast and efficient image loading.
Glide is a fast and efficient media management and image loading framework that wraps media decoding, memory and disk caching, and resource pooling into a simple and easy to use interface.
In simple terms, Glide takes up the burden of managing your media files especially if you are loading files from a network to itself. It automatically manages memory consumed by the image loading process and also automatically does caching for you, in a guise of reducing the pinch on network resources.
An image or drawable resource can easily be loaded into an image view by just calling;
GlideApp .with(CONTEXT) .load(URL) .into(IMAGE_VIEW);
Glide supports fetching, decoding, and displaying of video stills, images, and animated GIFs. For more information about the same, refer to Glide’s Comprehensive documentation here.
6. Room
credit:cdn-images
In order to build world-class apps, you need a clean ORM mapping library. Room was introduced during Google I/O 2017 as a persistence library aimed at cleaning up architecture in developing Android Apps.
Room in simple terms is one of the best SQLite object mapping libraries. It provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.
Persisting data offline greatly improves the experience of your users in cases where the internet connection is saggy. The goal of every app developer is to ensure that users have a seamless experience when using your App. Room hence bridges the gap by providing the easiest mechanism for data persistence.
@Entity(tableName = "user")
public class User {
@PrimaryKey(autoGenerate = true)
private int userID;
@ColumnInfo(name = "first_name")
private String firstName;
@ColumnInfo(name = "last_name")
private String lastName;
@ColumnInfo(name = "email")
private String email;
// getters and setters
}
User Model Class
You can find more information about Room here.
7. RxJava
credit: mytrendin.com
RxJava is one of the best libraries for enabling Reactive Programming in Android development. It is a framework for simplifying concurrency or asynchronous tasks. Multi-threading most times is a pain to developers and if not correctly implemented, it can cause some very difficult bugs to fix. RxJava comes in to make your life easier and help you avoid the nasty memory leaks.
Consider a case where you want to obtain data over the network and update the UI. One approach to achieve that is to use an inner AsyncTask
subclass in our Activity or Fragment.
public class MainActivity extends AppCompatActivity {
// class attributes
private Button mButton;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// activity configurations
mButton = (Button) findViewById(R.id.user_details_btn);
// make call to asyncTask
mButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View view) {
new GetDataTask(1001).execute();
}
});
}
public class GetDataTask extends AsyncTask<Void, Void, User> {
private final int userId;
public GetDataTask(int userId) {
this.userId = userId;
}
@Override protected User doInBackground(Void... params) {
return userService.getUserData(userId);
}
@Override protected void onPostExecute(User user) {
// do something after getting user data
}
}
}
Making a network call with an AsyncTask
The above seems like a safe approach, but it is not. It has some issues and limitations. Memory/context leaks are easily created by the above approach since GetDataTask
is an inner class and holds an implicit reference to the outer class. Notwithstanding, if you wanted to perform another long operation with the returned data, that may result in nesting another AsyncTask
which greatly reduces readability.
However, with RxJava, performing the same network call may look like this;
public class MainActivity extends AppCompatActivity {
// class attributes
private Subscription mSubscription;
private Button mButton;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// activity configurations
mButton = (Button) findViewById(R.id.user_details_btn);
// make a network call
mButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View view) {
mSubscription = userService.getObservableUser(1001)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<User>() {
@Override public void call(User user) {
// do something with the user data
}
});
}
});
}
@Override protected void onDestroy() {
if (mSubscription != null && !mSubscription.isUnsubscribed()) {
mSubscription.unsubscribe();
}
super.onDestroy();
}
}
Network call with RxJava
The RxJava approach solves the problem of potential memory leaks that may be a result of running a thread holding a reference to the outer context, by keeping a reference to the returned Subscription
object.
For more information about RxJava you can refer to this awesome piece.
In order to keep the article short enough, I’ll stop with the above libraries. But, you can read about; Ciceron, a nice navigation library, SlimAdapter a lightweight, slim, clean and typeable adapter without a need for a ViewHolder
, and Spruce Android Android Animation library among many.
Tests
Developing an App without Tests is like driving at 100mph without a seatbelt.
From the look of things, everything may seem fine and okay till you create a billion dollar bug.
In order to ensure quality Apps and a peace of mind when you launch your production ready apps, it is very important that you write sufficient unit tests for your business logic and espresso tests for UI tests. These tests save you from lots of unseen precedencies that may result in large uninstalls of your app due to unforeseen crashes. A thoroughly tested app gives you confidence that your app functions as expected.
Here is an espresso cheat sheet that shall save you tons of time when writing your UI tests.
Do’s & Don’ts
credit: tech.co
Do’s,
- Design for multiple screens
- Consider supporting multiple languages
- Provide your users with a seamless user experience
- Do performance tests
- Use user analytics tools
- Do Performance & Memory usage monitoring
Don’ts
- Develop for you device
- Reinventing the wheel
- Not using intents
- Not using fragments
- Blocking the main thread
- Not assuming success
Those are what I deemed as Billion $ tips for every Android developer. Hope you had a good read.