How We Rethought our Complete Package Structure for Buffer on Android and the Awesome Effect It’s Had on Our Workflows

For the past five years, our Android project has maintained a similar package structure from when it was first created in 2012. There have been a few changes here and there, but the general structure has remained the same.

When most applications are created, the class count is small. However, as the app grows and features get added, it can be easy for packages to become bloated, potentially to the point where it makes a workspace feel unorganized and difficult to navigate.

This was starting to be true of our Android app.

As well as keeping our code clean, we also want to keep our package structure clean. This rethought is about keeping the workspace that we interact with on a daily basis both tidy and organized.

So we set about to rethink the entire package structure of the Buffer Android app. I’m excited to share what our process looked like and all that we learned, and it’d be great to hear your thoughts and questions, too.

The prevailing structure of the Buffer Android app (what things looked like before we reorganized)

Before we get into the details of the restructure, let’s take a look at the structure of the app before any improvements began.

The package structure was essentially built around the classes being grouped by their corresponding type: activities in an activities directory, fragments in a fragments directory and so on. It looked something like this:

It’s still quite common to come across projects being structured in this way. It does seem logical doesn’t it? Our class names always end with their corresponding type, so why not group them like that!

Well, all may appear well-and-good while the application class count per directory remains small, but the tricky part is that the count will grow as your app grows. This causes the packages to become bloated and unorganized, and in time, it makes it difficult to navigate and feel any sense of structure to a project.

Our new structure helps to achieve the complete opposite effect of the older, traditional setup. Within the new structure, packages are lean and organized, and navigation within the project stays crisp and smooth.

For example, with the improved navigation, now when we’re working on a feature, we often remain with the directory that we need to work in without the need to switch back and forth between directories. While there are still iterations and improvements to be made, we’re hoping that this new approach to organizing our project will save us from pains in the long run. ?

Here’s a look at the new version of our overall base package structure for the Buffer Android app:

Now we essentially have four layers of organization for our classes.

– Data
– UI
– Injection
– Util

While the names may speak a little for themselves, let’s take a look at the purpose of each package along with the kind of classes and child directories that they may contain.

The four new layers of organization for our classes

1. data

The data package contains any classes (and child packages) that are directly related to any kind of data or data management used within the app — be it networking classes and interfaces, preferences management, database classes, data models, network request and response model, or anything else directly tied to app data.

model

The model package is used to hold all of the model classes for any data objects that are used within the app. These could be models returned from network requests, or models that are dynamically created when we’re saving draft updates to the local database.

And because we deal with models from multiple networks (currently our networks include the Buffer API for our data and the Helpscout API for FAQ data), we have the model package separated into two child packages. This allows us to keep the two model families separated from one another as each child package holds its corresponding models. In the future if we add more, it’ll be simple to add another child package to the model directory.

local

The local package contains all of the classes that deal with data being persisted locally to the application. These are classes that handle both the storage and retrieval of local data. So in the case of Buffer for Android this is the PreferencesHelper (used to handle the storage and retrieval of preferences values) and DatabaseHelper (used to handle the saving of data to the local database).

remote

The remote package contains all classes that are responsible for holding any classes that are responsible for or involved in saving or retrieving data from an external source via a network request. This can include classes such as retrofit interfaces and factories, OKHTTP interceptors and error classes, or any other classes involved with remote data.

Within the remote package, there is a child response package which holds any response model classes that are by the retrofit service interfaces. Initially these classes were defined as inner classes within the service, but as the requests grew, it made sense to move them into a separate package to be defined within their own class. This removes the responsibility of the interfaces defining the response class and just giving it the single responsibility of providing the interface methods.

manager

The manager package is used to hold a collection of DataManager classes, each of which holds methods returning Observable instances which can be used to interact with the corresponding methods in the remote / local packages to save and retrieve data. We currently have UserDataManager, ComposerDataManager, and HelpDataManager classes, which help to split up the operations from different features within the app. As the app evolves, we will add corresponding DataManager classes to this package.

service

We keep all of our services inside a separate package, again to separate them from other packages within our project. We currently only have 4 services within our app which have no relation to one another, so there is no child packaging within the service package itself.

receiver

The receiver package is used to hold any BroadcastReceiver instances that we have defined for use within our app. Just like the services package, there is no child packaging with the receiver package as we only have several receiver classes being used within our app.

2. ui

The UI package is responsible for holding any classes that are related to the UI components of the application. Within this package we also have child packages that are organized per-feature. This makes it super tidy and extremely easy to navigate when working with a specific feature. This looks a little like so:

The UI package is fairly simple as it’s unlikely that there will be any classes that do not sit inside of a child package. Here’s a look at what the child packages are used for.

base

The base package is used to hold any base classes that can be extended by other classes within the UI package. For example, we have BaseActivity and BaseFragment classes which are used to hold common logic for the activities and fragment used by our app, meaning we don’t have to repeat the same code over and over for each new activity or fragment that we create. This package also holds base classes for any Presenter classes and MvpView interfaces that may exist within the child feature packages.

common
The common package is used to hold any classes that may be used across the different features within the UI package. For example, we have general ErrorView and RefreshingView classes that are shared across several different screens, so placing this inside a common package feels natural.

If necessary, then classes within the common package are also grouped into child packages for further organization, like so:

composer

Each feature child package contains all of the classes that are specific to that feature. We’re only going to look at one feature (the composer), but each feature follows the same approach. The package will always hold any Activities, Presenters, Fragments, or MvpViews that are specific to that feature. For example, the composer package currently holds a ComposerActivity, ComposerPresenter, and ComposerMvpView.

The package also contains two child packages, which contain classes specific to the feature itself. This is because the Composer users a profile selection fragment (displayed as a bar above the composer), as well as a couple of widgets, both of which only contain components that are not used elsewhere in the app. This structure looks like so:

The profiles package above is its own feature package essentially. But it’s specific to the composer, so this is where it currently lives. The profiles package contains its own Activity, Fragment, and Presenter as well as its own child packages for the adapters and other classes that are used and specific to the profiles feature itself.

3. injection

The injection package holds all of our dependency injection classes. This helps to keep any DI configuration and responsibilities separated from the rest of the application. The package itself holds several child packages to further simplify the organization of our DI files.

component

The component package holds any Component interfaces that have been defined for use within our DI setup. We currently have an ActivityComponent for activity level dependencies, FragmentComponent for fragment level dependencies, ApplicationComponent for application level dependencies, ViewComponent for view level dependencies, and a Configuration Component for persistence across orientation changes. For this reason, they live in this package to avoid bloating the parent injection package.

module

This package holds any @Module annotated classes that are used to provide dependencies for our dependency injection setup. We currently have an ActivityModule, FragmentModule, and ApplicationModule, so having this child package helps us to keep the modules contained from the rest of the DI files.

scope

The scope package holds any @Scope annotated classes used alongside the other DI classes. Because we have several defined scopes, we are using the package for the same reason as the other child packages within this injection package.

4. util

The util package is used to contain any kind of Helper class or Utility class that we may use for things such as Dialog creation, Snackbar creation, Display metrics, Connectivity checks, Custom Tabs, or any other form of task that falls within a utility class. Currently, there is no feeling of similar responsibility within this package, so no child packages exist while the package class count remains so small.

———-

And that’s the change we’ve made for now! No doubt we’ll iterate and improve on what we currently have as we learn along the way, but we’re looking forward to and excited to be working with a more organized project structure.

I’m an Android Engineer and I love creating beautiful, clean and functional applications that help to make peoples lives easier – I love to be constantly learning and I enjoy writing about Android things / working on open-source projects over on GitHub. I have a bit of a love for Android TV which I sometimes talk about at conferences too! I first found my passion for Android whilst I was at university and have been working with it non-stop since – I find it an amazing platform. I love how it’s constantly evolving, it’s open-ness and the community around it!

Where should I place both MyResponseType and MyRequestType? Inside the respective data/remote/[response|request] folder or inside the data/model?

2) Where exactly do you guys place your Retrofit interface classes? In the root of data/remote?

3) All the above assumes a single family/network. Let’s say we are making an app that integrates with both GitHub and GitLab and we have different Retrofit interfaces and response/requests for each. How would you organize this?

4) I have two custom exceptions for network related operations, UnexpectedResponseException.java and UnexpectedStatusException.java. I’m not sure where to put them… If above we opt for Solution A, putting them in a “common” folder on the root data/remote folder alongside with response/request folders, seems odd. But if we opt for Solution B, putting them inside a “common” folder alongside with “github” and “gitlab” folders seems a better choice. But I’m not entirely sure about the “common” folder itself for exceptions. Perhaps an “exceptions” folder or something else suits better. What do you guys think?

5) Last one… I have a few custom annotations on my app and I currently store them in a root “annotations” folder. I’m not sure about this thought but I don’t know where else to put stuff like this. Perhaps I need some kind of generic folder like “app” or “core” or something in the root folder alongside with “data”, “ui”, “injection” and “util”. And then have an “annotations” package inside this one. Not sure…