Develop scalable android apps with perfect design architecture and libraries.

Tushar Kawadkar
LHD India
Published in
8 min readMar 16, 2018

As we all know that Android has covered a huge market in the world accounting 87.7% of sales in Q2 2017. That means most of the population have Android phones. Due to it’s popularity, Android community grew hugely and is the biggest among its rivals. Thus, the developers have a huge audience to target and are viable to do necessary changes very frequently according to the feedback received, new features, etc. For that a clean architecture is required to organize your project in a proper format. So the article is all about following a perfect path to develop great scalable apps and why is it necessary to follow geed practices.

In this article we will discuss about:

  • Managing project/codes.
  • Working with MVP+Retrofit+dagger

Android by default doesn’t have a clean architecture pattern. We have components like activities, layouts, services, etc which can be implemented in any pattern. The beginners may find it comfortable to develop the project without separating packages, modules, functionalities, components, etc and this leads to an unorganized and unscalable project. A big project can be made without following the best practices and may get a good response.

So why does your project need a good architecture?

A simple answer is to make it scalable, readable, organized, debug friendly and versatile.

An unorganized project looks like this:

  • Logics will be harder to implement and keeping its record inside a big class would be a tough task.
  • Debugging and changing codes will be a headache.
  • Unreadable and hard to maintain codes.
  • Most important problem would be the Unit Testing. All codes won’t get covered in it.
  • No modularity.
  • Skipped frames and “Too much work on the UI thread ” errors.

An organized project looks like this:

  • Modularity will be maintained at its best.
  • Load on UI thread will be reduced and almost no frames will be skipped.
  • Logics will be easily to maintain and readable.
  • Unit Tests would be easy to perform as everything would be in segments.
  • Debugging and changing the codes or adding/removing any feature would be an easy task.
  • Other developers working on the same code may able to understand it at a glance.

Now, a lot of questions may boom around your mind. Like

  • Which architecture to follow?
  • How to manage the project?
  • How to modularise components?

First, let’s look at managing the project. We have several components like activities, layouts, services, data classes, etc. To manage it, we need to place each components into separate packages. For eg., have a look at this hierarchy-

Doesn’t it looks like a clean organized project? This practice is necessary for a professional app development. This sets a great modularity level and ease in maintenance . So the key point is to make packages for every component, functionalities, etc.

Now moving on to design architecture and pattern. There are many design patterns laid out for development and are seemed as a standard practice. Followings are:

  • MVC ( Model — View — Controller)
  • MVP ( Model — View — Presenter)
  • MVVM (Model — View — ViewModel)

Here, MVP is the most recommended because of its popularity and suitability for android. Even, Google also provide its best practice example on Github. So, let’s see how MVP works.

This pattern have Model, View and Presenter. These all serves as classes. Let’s have an overview of these.

View: Android have activities that extends AppCompatActivity class which serve as a View. These are and should be only used for user interactions. It’s components should be only limited for input and output i.e to take input from user and show information as output in a graphical form. View classes only communicates with the Presenter by demanding operations and getting response from it.

Presenter: These classes are and should be only used for logic implementation and information transfer. These classes communicates with the View and Model. It’s an operational unit between View and Model. View calls a function from the Presenter and passes information to it. The presenter then calls a function from Model which passes information back to the it. This information is then operated and transferred back to the View. Presenter also controls the elements of the View.

Model: These classes are and should be only used for sending and receiving data from resources like local database, sessions, server, etc. The data received is then passed back to the presenter.

All 3 components communicate with each other using objects and interfaces.

Have you seen the level of modularity? It is well maintained here. Each functionality is separated into an individual unit. Thus, the project is easy to understand and debug. Any changes can be made easily without touching any other components. Developing everything into segments is best for Unit testing. You can prefer the Android architecture blueprint examples from here.

MVP+RETROFIT+DAGGER

Now, moving on to another section of our article. Big apps have a lots of features and functionalities. One such is networking with the server. Although now Android officially have Volley library for network calls. Its simple and efficient. But there are few areas on which the developers concentrate upon the most. Its centralizing the networking with a single gateway and serializing the data from the server. Volley also have a lot of options to do this but certainly its not the best one. So, here comes the Retrofit library which is a type-safe client for Android and Java. Few questions may burst in your mind like Why Retrofit? Maybe for the following reasons:

  • Type-Safe client for Android. That means serializing and parsing objects can be done easily.
  • Centralized gateway by turning HTTP API into a java interface.
  • It generates an implementation of interface.
  • It uses annotations to describe the request like URL parameter replacement and query parameter support, object conversion to request body (e.g., JSON, protocol buffers), multipart request body and file upload. Annotations on the interface methods and its parameters indicate how a request will be handled.
  • Synchronous and Asynchronous calls.
  • Retrofit is the class through which your API interfaces are turned into callable objects. By default, Retrofit will give you sane defaults for your platform but it allows for customization.
  • Converters: By default, Retrofit can only deserialize HTTP bodies into OkHttp’s ResponseBody type and it can only accept its RequestBody type for ‘@Body’. Converters can be added to support other types. Six sibling modules adapt popular serialization libraries for your convenience.
  • Gson: com.squareup.retrofit2:converter-gson
  • Jackson: com.squareup.retrofit2:converter-jackson
  • Moshi: com.squareup.retrofit2:converter-moshi
  • Protobuf: com.squareup.retrofit2:converter-protobuf
  • Wire: com.squareup.retrofit2:converter-wire
  • Simple XML: com.squareup.retrofit2:converter-simplexml
  • Scalars (primitives, boxed, and String).

A library with so many features can be implemented in a lot of ways, like the instances can be called or developed in many ways. But will it be good to make or call instance without centralizing retrofit?. Without centralizing, returning and creating the objects will become like reinventing the wheel.

Here comes the concept of Dependency Injection. It is a technique whereby one object (or static method) supplies the dependencies of another object. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client’s state. Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.

This fundamental requirement means that using values (services) produced within the class from new or static methods is prohibited. The client should accept values passed in from outside. This allows the client to make acquiring dependencies someone else’s problem.

For this operation Dagger library would be the best to use. Now, how MVP + Retrofit + Dagger works altogether.

@Module
class NetModule(val baseUrl: String) {
@Provides
@Singleton
fun provideOkHttpCache(application: Application): Cache {
val cacheSize: Long = 10 * 1024 * 1024
val cache = Cache(application.cacheDir, cacheSize)
return cache
}
@Provides
@Singleton
fun provideOkHttpClient(cache: Cache): OkHttpClient {
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder()
client.addInterceptor(logging)
client.cache(cache)
client.readTimeout(30, TimeUnit.SECONDS)
return client.build()
}
@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(baseUrl)
.client(okHttpClient)
.build()
return retrofit
}
@Provides
@Singleton
fun provideLhdService(retrofit: Retrofit): LhdService {
return retrofit.create(LhdService::class.java)
}
}
mNetComponent = DaggerNetComponent.builder()
.appModule(new AppModule(this))
.netModule(new NetModule(Constants.INSTANCE.getBaseUrl()))
.build();

As you can see the above code, its the NetModule class which is passed to Dagger builder. Dagger build these components at compile time. With this, we can have an instance of LHDService class in any other class. In the above code, you can see how the methods provideOkHttpCache, provideOkHttpClient, provideRetrofit, provideLhdService are dependent on each other. With the use of Dagger, dependent objects are provided automatically. Thus, you can code any module of a program anywhere without worrying of its instance and dependency. For eg, you can see the code below.

@Inject
lateinit var service:LhdService
init {
BaseApplication.getNetComponent().inject(this)
}
override fun getOrderInfo(orderNumber: String, onResponseReceiveListener: OnResponseReceiveListener<GetOrderInfoResponse>) {
var request= GetOrderInfoRequestData(orderNumber)
service.getOrderInfo(SessionManager.instance.getTokenID(),request).enqueue(object :Callback<GetOrderInfoResponse>{
override fun onFailure(call: Call<GetOrderInfoResponse>?, t: Throwable?) {
onResponseReceiveListener.onFailure(t!!)
}
override fun onResponse(call: Call<GetOrderInfoResponse>?, response: Response<GetOrderInfoResponse>?) {
if(response!!.code()==200){
onResponseReceiveListener.onResponse(response.code(),response.body() as GetOrderInfoResponse)
}else{
onResponseReceiveListener.onFailure(Throwable(response.errorBody().toString()))
}
}
})
}

The code is in Kotlin language. We have used ‘@Inject’ and BaseApplication.getNetComponent().inject(this) to get the instance of LHDService class by passing this model class to inject() method. This registers the class at compile time and provides the instance to it. This centralizes your instances and network callings. Model class here have functions that only perform network calls. The data received is passed back to the presenter via interfaces. So separating the model class and doing only network calls in it lightens the class and also helps in the binding process.

Thus the MVP + Retrofit + Dagger altogether makes a perfect scalable project. Dividing the project into packages partitions the modules. MVP takes care of partitioning and placing functionalities into individual units. Retrofit provides a gateway for network calls and serializing objects. Dagger provides object dependencies so as to make object calling easy and prohibits the creation of new object everytime when required.

Thus by following these standards, a developer may easily develop big scale, maintainable, readable, change ready projects.

--

--

Tushar Kawadkar
LHD India

4 years of experience in Android, iOS apps and website development.