Almost every Android app in existence is using Java Packages incorrectly! Consider the typical structure for Android app Packages:
In short, almost every Android project uses Packages to create buckets for Objects that provide a similar type of utility.
The important details when establishing Packages is not the type of utility, but instead is about establishing meaningful encapsulation boundaries.
What Are Encapsulation Boundaries?
Encapsulation boundaries are sets of behaviors that define the manner in which the state of a system can be mutated. Encapsulation boundaries also establish de-facto data structures for the system’s input and output.
Encapsulation boundaries go to the core of Object-Oriented Programming. In fact, the “object” in “Object-Oriented Programming” is not the same thing as a Java Object or a Java Class. The “object” in “Object-Oriented Programming” means “encapsulation boundary.” You can think of Object-Oriented Programming as really being Encapsulation Boundary-Oriented Programming.
Once upon a time, all programming was Procedural Programming. But Procedural Programming sucked because shared state would inevitably destroy the lives of all programmers. In a reaction to the terrible experience of Procedural Programming, two new paradigms were born. Functional Programming was offered as a solution to shared state by simply saying “No shared state is allowed!” Object-Oriented Programming was offered as a solution to the same problem, but from a different angle — Object-Oriented Programming said “We will hide all state behind concise, constrained encapsulation boundaries to minimize state management complexity.”
Therefore, encapsulation boundaries are the building blocks of all Object-Oriented applications. If developers shirk the responsibility of establishing strong encapsulation boundaries then what they are really engaging in is Procedural Programming with Java Objects.
In practice, encapsulation boundaries help developers:
- Segment risk
- Drastically reduce state management complexity
- Facilitate the ability to adapt to changing requirements, and
- Promote effective code re-use
Packages Are Encapsulation Boundaries
Package are not directories, and directories are not Packages. If Packages really were about dividing Class files into directories then Java would not have created a special language construct for Packages. Each OS already provides the concept of directories.
Additionally, if Packages are just buckets then why does Java provide the “Package private” access modifier? What sense would it make to have one or more Views in your views Package as “Package private”? It wouldn’t — that would prevent the View from being used by any Activity, or Fragment, or anything else except for another View.
Packages aren’t about creating buckets at all, they are about creating encapsulation boundaries.
Definition: A package is a grouping of related types providing access protection and name space management. Note that types refers to classes, interfaces, enumerations, and annotation types.
- Official Oracle Definition
Packages represent the encapsulation boundary level that exists above classes. Looking at encapsulation boundaries from the bottom up we see that Methods are encapsulation boundaries around related lines of code, Classes are encapsulation boundaries around related Methods, and Packages are encapsulation boundaries around related Classes.
Packages deserve as much API consideration as do individual Classes!
As with any other encapsulation boundary, Packages should strive to minimize out-going dependencies, and should never leak internal state/implementation details. Yes, that means you should strive to privatize everything in your packages that you can. If a Class doesn’t represent a public behavior that other Packages should be able to call then it should be private to its Package.
How Do I Find The Package Boundaries?
Package boundaries and responsibilities are defined by the domain logic of the application that you are building. As Bob Martin says about application architecture, “[Your application architecture] should scream its intent.”
Consider the following top-level Package structure of an application:
What kind of application is this? It shouldn’t take you long to guess that this Package structure probably applies to some kind of medical application. Maybe this application handles all patient management for a private practice. We can make that assumption after seeing nothing but the top level Packages — is that true of most Android apps?
Packages, like Classes, should be defined based on related behaviors, not related state. Remember, we’re trying to hide the state. Notice in the aforementioned medical Package breakdown that the breakdown is around behavior. Billing, HIPAA compliance, prescriptions, scheduling, and reporting are all things that you do, they are not things that somehow exist and persist.
To establish an effective Package structure, find the behaviors of your application that can generally be added to/removed from your application as a single unit. You can’t conceivably remove all Activitys from your application and still have an application, but you can conceivably remove the prescriptions behavior from a medical patient management app and still have a medical patient management app.
But Where Do Android Things Go?
To continue Bob Martin’s previous quote, Bob Martin claims that things like IO channels should be “details” in your application architecture, and they should rightfully be “difficult to find.” Bob Martin says he should “have to search for those details.”
Applying Bob Martin’s rationale to Android apps, not only are things like databases and network communication considered “details,” but the Android integration points themselves are “details.” Activitys, Fragments, Services, and Views do not require nor deserve some kind of special spotlight treatment. These artifacts are simply Objects that help implement a use-case, just like every other Object — treat them as such.
If we assume the aforementioned medical application is an Android app, the prescriptions package might look like the following.
Again, it shouldn’t be difficult to surmise what the Objects in this Package are doing. There is clearly behavior centered around filling prescriptions, as well as behavior centered around ensuring patients do not miss medication doses. The fact that a couple of these Objects are Activitys is simply a “detail” that you have to look for. In fact, this package even includes a couple of BroadcastReceivers, though we don’t draw any special attention to that fact.
Create all the Android artifacts just as you normally would, but group those artifacts with the other Objects that are involved in the given behavior. Don’t let your Android Classes be elitist!
But Where Do Databases and Network Comms Go?
Databases, network communication, and other implementation details should be spread across the codebase based on their behavior just like Android artifacts. However, there is one caveat.
Unlike Android artifacts, databases and networking logic may require a substantial infrastructure that applies to the implementation technology and has no behavior significance to the business area. For example, HTTP communication requires significant code infrastructure long before we ever start to discuss the medical domain of the app. In this case, it makes perfect sense to have something like an “http” Package. This Package would then establish an encapsulation boundary around the technical behavior of HTTP communication. Just like any other Package, the HTTP Package would likely contain many package private Classes that play roles in orchestrating HTTP requests, but have no business being used by other systems.
Continuing with the medical app as an example, imagine that the app had a top-level http package providing generic HTTP comm behavior to other areas of the app. We already looked at a possible set of classes in the prescriptions Package, one of which was PrescriptionRefiller. We might choose to offer an implementation of that Interface called a HttpPrescriptionRefiller which sits in a prescriptions.http Package and utilizes the top-level http package to implement its HTTP communication. In this manner, medically-oriented HTTP artifacts sit in the medical Packages, but pure HTTP comm logic sits in the general-purpose http package.
Details Should Be Plugins
Details should be grouped by business behavior, but how should those details interact with the business-specific logic?
Details (Android artifacts, databases, network comms, etc) should integrate with business-specific logic as “plugins.” Plugins are an implementation of the Dependency Inversion principle.
Instead of making your prescription refill behavior depend on your MySQL database, you should wrap your MySQL database in a system that depends on your prescription refill behavior. Then this MySQL-specific plugin is connected to the business-oriented prescription refill system.
Think of the prescription refill system like Android Studio, and think of the MySQL system as an Android Studio plugin. The Android Studio plugin depends entirely on the APIs of Android Studio, but Android Studio does not depend at all on the APIs of the plugin. You should apply the same restrictions when connecting details to your business systems.
In the case of network requests, you might choose to define generic requests within your business logic and then provide dependency injected implementations of those requests that happen to use HTTP or any other desired communication protocol. Note, there does not need to be a 1-to-1 relationship between business requests and network requests — a single business request might require a series of network requests, one after the other — or even multiple parallel requests that aggregate data from multiple sources.
Android artifacts are easier to use in a plugin manner because Android artifacts tend to represent the most user-facing aspects of the application. The way to use Android as a plugin is to ensure that business systems never reference or otherwise know about any Activitys, Fragments, Services, Views, or any other Android construct. Use Android artifacts to bootstrap and initiate business logic, but from there make sure that no Android artifacts are required.
Decoupling business logic from IO details will greatly improve your apps ability to adapt to changing requirements, as well as changing technical realities such as the need to switch database implementations or network stacks.
Architecture: The Lost Years
Bob Martin discusses, among other things, that “architecture should scream its intent.” The linked video will start around 29 mins in.
Google IO 2015 App
Exemplifies a typical ineffective package scheme.
Exemplifies Package access controls and separation of Retrofit (business) behavior from HTTP (detail) behavior.
South Park: Hello Mr UPS Man
Funny Bane-themed South Park segment (viewer discretion is advised).