Showcases object oriented programming in Java, Java Swing, Kotlin, and Android
The purpose of this project is to demonstrate how to build Java applications from the ground up using clean architecture principles, Java best practices, and design patterns.
It is less about the application content and more about the code and architecture.
The goal is to showcase object oriented programming in Java, Java Swing, Kotlin, and Android, including:
This project has the following derivative projects, which showcases / walks through different portions of this project's architecture.
A simple business search application using the Yelp Fusion (v3) REST API.
This simple business search application is able to:
Notes
The application has several different presentations. In other words, there are several different runnable applications that can be installed in different platforms.
All of the above presentations use the same domain and data layer.
JDK 8 is required to build and run this project.
You may open this project in Android Studio (v2.3.1).
For plain Java applications, you must Build -> Make Project in order to build them. There are several plain Java applications:
For Android applications, click Build -> Rebuild Project is enough. However, it will not build plain Java applications as mentioned above. There are several Android applications:
Since this project contains a mix of plain Java modules and Android modules, the best way to ensure that all modules are built properly is to
Running the applications
After building/making the project, the applications may then be run.
To build all modules,
./gradlew build
Note that building all modules may result in parallel build failure due to concurrent file
access resulting in file deadlocks. See FIXME about org.gradle.parallel
in gradle.properties.
To build all variants of a specific module,
./gradlew :<module>:build
For example, to build the presentation-java-nogui-mvp application,
./gradlew :presentation-java-nogui-mvp:build
To list all tasks for this project,
./gradlew tasks
To list all tasks for a specific module,
./gradlew :<module>:tasks
For example, to list all tasks for the presentation-java-nogui-mvp module,
./gradlew :presentation-java-nogui-mvp:tasks
Running the applications
For plain Java applications,
./gradlew :<java-presentation-module>:run
For example, to run the presentation-java-nogui-mvp application,
./gradlew :presentation-java-nogui-mvp:run
You may also generate and run via jar,
./gradlew :<java-presentation-module>:jar
java -jar <java-presentation-module>/build/libs/<name-of-jar>.jar
For example, to generate and run the jar of the presentation-java-nogui-mvp application,
./gradlew :presentation-java-nogui-mvp:jar
java -jar presentation-java-nogui-mvp/build/libs/presentation-java-nogui-mvp-1.0.0.jar
For Android applications,
./gradlew :<android-presentation-module>:install<build_variant>
For example, to install the presentation-java-android-mvp debug application,
./gradlew :presentation-java-android-mvp:installDebug
Known Issues
If building fails due to a read/write process lock or any file related error, this may be due to org.gradle.parallel set to true in gradle.properties. This allows for parallel tasks which could result in several gradle processes accessing the same files at the same time.
For example, ./gradlew build
builds all projects in parallel, which may result in the
following error,
:presentation-java-android-mvp:transformClassesAndResourcesWithProguardForRelease FAILED :presentation-java-android-mvvm:transformClassesAndResourcesWithProguardForRelease FAILED
You will need to stop the existing gradle daemons and try again (without cleaning).
./gradlew --stop
You may want to build a specific variant of a specific module to avoid this synchronization issue.
./gradlew :<presentation-module>:build<build_variant>
If anything unexpected occurs, clean the project first and try again.
./gradlew clean
This project's adaptation of the clean architecture guidelines is inspired by the Clean Architecture sample app by android10. Take a look at the Clean Architecture sample app for links to blogs discussing the architecture.
Not one project is perfect. Not one architectural pattern is without flaws. There is no 1 size fits all architecture. The same goes for this project. With that said, there are a couple of things to consider about this project's architecture;
Does there need to be separate entities/models for each layer? That is, Entity
in the data layer,
DomainObject
in the domain layer, and Model
in the presentation layers?
Keeping these objects separate allows each layer to be completely independent from the other layers. It follows one of the clean architecture principles' on the separation of layers. In practical terms, each layer have complete control on what these objects should be. Furthermore, the amount of inter-layer imports are kept to a minimum. This increases the modules's modularity. A change from one layer will not affect the other layers, except for "mappers".
However, this abstraction comes at the cost of code duplication and having to create "mappers". Given the current state of the project, the advantage of this abstraction does not seem to outweigh its costs. A more pragmatic approach is to only have a data layer and a presentation layer where the data layer provides the entities for the presentation layer to use.
Currently, the domain layer provides interactors / use cases that simply call the repository implementations provided by the data layer. So, why not get rid of the domain layer and just use the data layer directly?
As mentioned in point 1, this would be the more pragmatic approach. However, the domain layer is important in future-proofing. What if a reporting call needs to be fired when a use case is executed? What if all use cases need to be logged? What if API X needs to be used in use case Y? The domain layer serves as the middleman between the presentation layer and other layers / APIs.
All work has been logged, including:
See more in docs/DEVELOPMENT_LOGS.md.
JUnit4, AssertJ, Mockito2, and Robolectric3 are used for unit testing Android and pure Java classes.
To run all unit tests,
./gradlew test
To run all unit tests for a specific module,
./gradlew :<presentation-module>:test
Espresso is used for Android instrumentation tests.
To run all instrumentation tests,
./gradlew connectedAndroidTest
To run all instrumentation tests for a specific Android module,
./gradlew :<android-presentation-module>:connectedAndroidTest
Note that only debug build types support Android instrumentations tests.
Code coverage is available through the use of the jacoco plugin.
To generate test reports for all modules,
./gradlew jacocoTestReport
To generate test reports for a specific module,
./gradlew :<module>:jacocoTestReport
The following static analysis checks are used to enforce high code quality and compliance to standard Java (and Android) code style and patterns:
You can read more about these tools in this blog post.
To run all static analysis checks for all modules,
./gradlew checkQuality
To run all static analysis checks for a specific module,
./gradlew :<module>:checkQuality
Javadoc is available. To generate the Javadocs,
To generate Javadocs for all modules,
./gradlew javadoc
To generate Javadocs for a specific module,
./gradlew :<module>:javadoc
Styling is very important in maintaining a codebase with multiple (and large amounts) of contributors. A set of code style guidelines are enforced by this project, most of which are checked by the static analysis tool checkstyle. Read more in docs/STYLE.md.
Copyright 2018 Vandolf Estrellado
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.