activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED);
activity[0] = Iterables.getOnlyElement(activities);
}
});
return (A) activity[0];
}
}
================================================
FILE: appIt/src/main/java/com/example/project/espresso/EspButton.java
================================================
package com.example.project.espresso;
import android.support.test.espresso.action.ViewActions;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
public class EspButton {
private int resourceId;
public EspButton(int resourceId) {
this.resourceId = resourceId;
}
public P click() {
onView(withId(resourceId)).perform(ViewActions.click());
return null;
}
}
================================================
FILE: appIt/src/main/java/com/example/project/espresso/EspListView.java
================================================
package com.example.project.espresso;
import android.view.View;
import android.widget.ListView;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
public class EspListView {
private int resourceId;
public EspListView(int resourceId) {
this.resourceId = resourceId;
}
public int count() {
final int[] counts = new int[1];
onView(withId(resourceId)).check(matches(new TypeSafeMatcher() {
@Override
public boolean matchesSafely(View view) {
ListView listView = (ListView) view;
counts[0] = listView.getCount();
return true;
}
@Override
public void describeTo(Description description) {
}
}));
return counts[0];
}
}
================================================
FILE: appIt/src/main/java/com/example/project/espresso/EspMenuItem.java
================================================
package com.example.project.espresso;
import android.support.test.espresso.action.ViewActions;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
public class EspMenuItem {
int resourceId;
public EspMenuItem(int resourceId) {
this.resourceId = resourceId;
}
public P click() {
onView(withId(resourceId)).perform(ViewActions.click());
return null;
}
}
================================================
FILE: appIt/src/main/java/com/example/project/espresso/EspTextEdit.java
================================================
package com.example.project.espresso;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
public class EspTextEdit {
int resourceId;
public EspTextEdit(int resourceId) {
this.resourceId = resourceId;
}
public void insert(String text) {
onView(withId(resourceId)).perform(typeText(text));
}
}
================================================
FILE: appIt/src/main/java/com/example/project/pages/EspContactListPage.java
================================================
package com.example.project.pages;
import com.example.project.R;
import com.example.project.espresso.EspListView;
import com.example.project.espresso.EspMenuItem;
public class EspContactListPage {
public EspMenuItem createContact() {
return new EspMenuItem(R.id.action_add_contact) {
@Override
public EspEditContactPage click() {
super.click();
return new EspEditContactPage();
}
};
}
public EspMenuItem syncContacts() {
return new EspMenuItem(R.id.action_sync_contacts) {
@Override
public EspContactListPage click() {
super.click();
return EspContactListPage.this;
}
};
}
public EspListView contactList() {
return new EspListView(R.id.listView);
}
}
================================================
FILE: appIt/src/main/java/com/example/project/pages/EspEditContactPage.java
================================================
package com.example.project.pages;
import com.example.project.R;
import com.example.project.espresso.EspButton;
import com.example.project.espresso.EspTextEdit;
public class EspEditContactPage {
public EspTextEdit firstName() {
return new EspTextEdit(R.id.first_name);
}
public EspTextEdit lastName() {
return new EspTextEdit(R.id.last_name);
}
public EspButton confirm() {
return new EspButton(R.id.confirm) {
@Override
public EspContactListPage click() {
super.click();
return new EspContactListPage();
}
};
}
public EspTextEdit birthDate() {
return new EspTextEdit(R.id.birth_date);
}
}
================================================
FILE: appIt/src/main/java/com/example/project/test/CreateContactTest.java
================================================
package com.example.project.test;
import com.example.project.EspressoTestCase;
import com.example.project.pages.EspContactListPage;
import com.example.project.pages.EspEditContactPage;
import com.example.project.views.contact_list.ContactListActivity_;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class CreateContactTest extends EspressoTestCase {
EspContactListPage contactListPage = new EspContactListPage();
@Test
public void testCreateNewContact() {
givenListHasNoContacts();
whenAddContact();
thenListHasContacts();
}
private void thenListHasContacts() {
assertThat(contactListPage.contactList().count()).isPositive();
}
private void whenAddContact() {
EspEditContactPage editContactPage = contactListPage.createContact().click();
editContactPage.firstName().insert("My First Name");
editContactPage.lastName().insert("My Last Name");
editContactPage.birthDate().insert("1984-11-11");
contactListPage = editContactPage.confirm().click();
}
private void givenListHasNoContacts() {
assertThat(contactListPage.contactList().count()).isZero();
}
}
================================================
FILE: appIt/src/main/java/com/example/project/test/SyncContactsTest.java
================================================
package com.example.project.test;
import com.example.project.EspressoTestCase;
import com.example.project.pages.EspContactListPage;
import com.example.project.pages.EspEditContactPage;
import com.example.project.views.contact_list.ContactListActivity_;
import org.junit.Test;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.assertj.core.api.Assertions.assertThat;
public class SyncContactsTest extends EspressoTestCase {
EspContactListPage contactListPage = new EspContactListPage();
@Test
public void testSyncContacts() {
givenListHasNoContacts();
whenSyncContacts();
onView(withText("Sync done")).inRoot(withDecorView(not(is(activityRule.getActivity().getWindow().getDecorView())))).check(matches(isDisplayed()));
thenListHasContacts();
}
private void thenListHasContacts() {
assertThat(contactListPage.contactList().count()).isPositive();
}
private void whenSyncContacts() {
contactListPage.syncContacts().click();
}
private void givenListHasNoContacts() {
assertThat(contactListPage.contactList().count()).isZero();
}
}
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
// plugin for reporting code coverage to coveralls
classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.4.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
// set flag whether we are on a build server
ext.isCi = ("true".equals(System.getenv("TRAVIS")) || "true".equals(System.getenv("CIRCLECI")))
// current robolectric has no support for android M (v23) https://github.com/robolectric/robolectric/issues/1865
ext.projectAndroidVersion = 22
// latest supported version by google playstore app is v8 (august 2015)
// latest supported version by joda time library is v10
ext.projectAndroidMinVersion = 10
// sometimes support tools and build tools have same version but support tools version grow faster with time
// current robolectric has no support for android M (v23) stuff
ext.projectAndroidBuildToolsVersion = "22.0.1"
ext.projectAndroidSupportToolsVersion = "22.2.1"
// not all jacoco versions work without issues
// TODO try which is the latest working version without issues, with 0.7.5 is saw some
ext.projectJacocoVersion = "0.7.0.201403182114"
allprojects {
repositories {
jcenter()
}
}
// ensure clean is also triggered for root build folder
apply plugin: 'java'
// support to see all reports on one page
apply plugin: 'build-dashboard'
buildDashboard {
reports.html.destination = "build/"
}
buildDashboard << {
println file("build/index.html").absolutePath
}
// just clean up dashboard from not generated reports
test.reports.html.enabled = false
test.reports.junitXml.enabled = false
// support combined test code coverage for all levels (unit, component, integration)
apply from: "build.jacoco-test-report.gradle"
================================================
FILE: build.jacoco-test-report.gradle
================================================
apply plugin: "jacoco"
apply plugin: 'com.github.kt3k.coveralls'
jacoco {
toolVersion = projectJacocoVersion
}
task configureCoverageReport {
doFirst {
def includeAppSrc = false;
def includeCoreSrc = false;
def includeDatabaseSrc = false;
def coverageFiles = []
def coverageSourceDirs = []
// exclude generated classes from code coverage report because not all generated methods will be used
def defaultExcludes = ['**/R.class', '**/R$*.class', '**/BuildConfig.class' // generated by android
, '**/*_*' // generated by androidannotaions
, '**/*InjectAdapter.class', '**/*ModuleAdapter*.class' // generated by dagger
, '**/database/provider/*' // generated database handling
]
// Don't take not existing coverage report files or the report will be empty
def currentCoverageFile = 'app/build/jacoco/testDebugUnitTest.exec'
if (file(currentCoverageFile).exists()) {
println "found app unit test coverage"
includeAppSrc = true;
coverageFiles += currentCoverageFile
}
currentCoverageFile = 'appIt/build/outputs/code-coverage/connected/coverage.ec'
if (file(currentCoverageFile).exists()) {
println "found app integration test coverage"
includeAppSrc = true;
coverageFiles += currentCoverageFile
}
currentCoverageFile = 'appCt/build/jacoco/testDebug.exec'
if (file(currentCoverageFile).exists()) {
println "found app component test coverage"
includeAppSrc = true;
coverageFiles += currentCoverageFile
}
// Don't include classes from modules we haven't executed
if (includeAppSrc) {
coverageSourceDirs += 'app/src/main/java'
tasks.jacocoTestReport.classDirectories += fileTree(dir: 'app/build/intermediates/classes/debug', excludes: defaultExcludes)
}
tasks.jacocoTestReport.additionalSourceDirs = files(coverageSourceDirs)
tasks.jacocoTestReport.executionData = files(coverageFiles)
// Send code coverage report to coveralls with coveralls-gradle-plugin. To get the plugin working
// we need to include the coverageSourceDirs as main sources. But this conflicts with android
// studio and also with jacocoTestReport task.
coveralls {
if (isCi) { // avoid android studio source root conflicts
sourceSets.main.java.srcDirs += coverageSourceDirs
}
}
}
}
compileJava.enabled = false
tasks.jacocoTestReport.dependsOn tasks.configureCoverageReport
tasks.coveralls.dependsOn tasks.configureCoverageReport
jacocoTestReport {
reports {
xml.enabled = true
html.enabled = true
}
}
================================================
FILE: circle.yml
================================================
machine:
java:
# latest wiremock version needs java 8
version: oraclejdk8
environment:
# auth for the coveralls report
COVERALLS_REPO_TOKEN: mHJjy4h8zt9RIIBKg8sBosF5JkKsFk139
# chooce network device for tests with wiremock support and emulator
TEST_MOCK_IFACE: eth0
test:
pre:
# emulator startup need some minutes that's why start it before first build/test steps for build speed up
- emulator -avd circleci-android22 -no-audio -no-window:
background: true
parallel: true
override:
# Build the code
- ./gradlew app:assembleDebug
# Execute Unit Tests
- ./gradlew app:test
# start wiremock to support tests with network communication
- tools/src/main/resources/start-wiremock.sh:
# start at background to use wiremock in next build steps
background: true
# Execute Component Tests
- ./gradlew appCt:test
# Check lint hints
- ./gradlew app:lint
# Build instrumented test code
# fix apk not build https://code.google.com/p/android/issues/detail?id=180689
- ./gradlew appIt:assembleDebug
# ensure that the emulator is ready to use
- circle-android wait-for-boot
# the necessary sleep duration may change with time and depends on the pre tasks length.
# When all pre tasks are run long enough then waiting will not be necessary anymore.
# This sleep should avoid the com.android.builder.testing.api.DeviceException: com.android.ddmlib.ShellCommandUnresponsiveException
- sleep 60
# at least remove the look screen
- adb shell input keyevent 82
# later we collect the logs from test run
- adb logcat -d
# execute tests on emulator
- ./gradlew appIt:connectedCheck
# Create coverage report
- ./gradlew jacocoTestReport
# copy test results
- mv app/build/reports/tests/debug $CIRCLE_TEST_REPORTS/AppUnitTests
- mv appCt/build/test-report $CIRCLE_TEST_REPORTS/ComponentTests
- mv appIt/build/reports/androidTests/connected $CIRCLE_TEST_REPORTS/AndroidTests
- mv build/reports/jacoco/test/html $CIRCLE_TEST_REPORTS/CodeCoverageReport
# copy lint report
- mkdir $CIRCLE_TEST_REPORTS/Lint
- mv app/build/outputs/lint-results_files $CIRCLE_TEST_REPORTS/Lint
- mv app/build/outputs/lint-results.html $CIRCLE_TEST_REPORTS/Lint/lint-app-results.html
# circleCi proper test value feature
- mkdir $CIRCLE_TEST_REPORTS/junit
- find */build/test-results -name "*.xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \;
- find */build/outputs/androidTest-results/ -name "*.xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \;
# collect logs from emulator
- adb logcat -d > $CIRCLE_ARTIFACTS/logcat_emulator.txt
# Only executed if all tasks are successful
deployment:
# report coverage to coveralls
coverage:
branch: [master, rewrite]
commands:
- ./gradlew coveralls
================================================
FILE: docs/build.gradle
================================================
apply plugin: "java"
================================================
FILE: docs/src/main/resources/adjust_project_to_your_needs.md
================================================
[Back to Index](index.md)
# Adjust the project to your needs
Start by cloning the project `git clone https://github.com/nenick/android-gradle-template.git myAppName` and navigate into it `cd myAppName`.
### Create fresh git history
Remove the git folder `rm -rf .git` and create a new history by `git init`.
You can connect it with an already existing remote repository by:
```
git remote add origin
git push --all --set-upstream origin
```
### Prototyping
For fast prototyping just remove appCt, appIt, tools and docs for a clean code base.
### Remove database support
* clean up the app/build.gradle file from stuff which is added for [database example](database.md)
* delete the app/src/main/java/com/example/project/database package
* now delete or adjust other code with compile errors
### Remove network support
* clean up the app/build.gradle file from stuff which is added for [network example](network.md)
* delete the app/src/main/java/com/example/project/database package
* now delete or adjust other code with compile errors
---
[Back to Index](index.md)
================================================
FILE: docs/src/main/resources/concepts/function_class.md
================================================
[Back to Index](../index.md)
# Function Class
I don't know if this style has a name.
I took this idea from some scala developer.
The base idea is to split code for hiding details and easy unit testing.
Whenever you have a more complex class function then create a own class for it with a single public method.
Additional convenience methods are also possible.
public class TaskToDoFunction {
public apply() {
}
}
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/concepts/model_view_presenter.md
================================================
[Back to Index](../index.md)
# Model View Presenter
* [Wikipedia.org - Model View Presenter](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter)
* [AntonioLeiva.com - MVP for Android](http://antonioleiva.com/mvp-android/)
Classic approach of MVP for android is to use the activity/fragment as the view which is controlled by a presenter.
## Android MVP - An Alternate Approach
Original article (until site is back see [cached version](http://webcache.googleusercontent.com/search?q=cache:OgfVapRjNRYJ:blog.cainwong.com/android-mvp-an-alternate-approach/))
and github project
**The statement about activities and fragments as view:**
*".. Activities send Intents, start Services, create and execute FragmentTransisitons, etc. All of these complexities are, in my opinion, outside of the scope of what a "View" should be concerned with. A View's job is to present data and get input from the user. Ideally, a View should be devoid of business logic, making a unit test of the View unnecessary. .."*
This results in activities/fragments/adapter are treated as presenters and moving all view code into a separated class.
## Following the alternate approach
I like the idea, often I wrote boilerplate code to inform the presenter about the current life cycle.
This approach give you still clean separated code but faster and more intuitive development thinking.
The base for the MVP can be found at *com.example.project.view.common.mvp* for the usage see the example implementations.
The activity is most time only the glue between fragments.
When fragments has something to show they will tell it back through a Show...Listener to the current activity.
The root activity may differ between portrait and landscape mode.
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/concepts/package_structure.md
================================================
[Back to Index](../index.md)
# Package structure
The project package structure follows mainly the package by feature approach.
**With strict package by feature approach it becomes hard to distinguish between business feature and view packages.**
For me I recognised that my work is mostly view and business driven so I need to find fast the related view
or business package which need to be changed.
With pure package by layer its mostly very easy to decide where to put new classes (new Activity into package activities)
but for package by feature it can become complicated.
Often you will be confronted with the problem that different features need the same classes.
Create new feature package for shared classes may a solution but will end in many packages
inside one package or in a more structured way with sub packages.
After this step it looks like many developer must think long time about where to put new code because
they can't distinguish easy between business and view logic related packages and this may result in a bad mix.
*It's the nature of a layers to use many different features of the lower layer.*
I recommend to use a mix of package by layer and by feature in the following way:

### ..view.concrete_view
*(replace 'concrete_view' with a describing name for your view, e.g. contact_editor.)*
Shows the reader all kinds of views which this application present.
The main entry point for UI related development.
Don't put any business logic into view layer, presentation logic is never business logic but decision logic may be business logic.
Can be the a full view (activity, fragment, etc) or a partial view (dialog, view group, etc).
When something is changed here the changed view and depending views must be checked.
### ..business.business_feature
*(replace 'business_feature' with a describing name for your feature, e.g. contact.)*
Its often that multiple views need the same business features.
Shows all the business features supported by this application.
The main entry point for business related development.
When something is changed here the changed business feature must checked with the related views.
### ..database.table
*(replace 'table' with a describing name for your table, e.g. contact.)*
Its often that multiple business features access the same database tables.
When something is changed here there must be a migration script and the database version must be increased.
The app update process should be tested for data issues and all related business features.
Content should not change often like other do. The most parts of this package content may be generated.
### ..network.api
*(replace 'api' with a describing name for your used api, e.g. facebook.)*
Its not often that two business feature use the same network calls but sometimes they do.
When something is changed here the interaction with real services must be tested and all related business features.
Content should not change often like other do. The most parts of this package content may be generated.
## Some articles about the package topic
* [CodingTheArchitecture.com - Package by component and architecturally-aligned testing](http://www.codingthearchitecture.com/2015/03/08/package_by_component_and_architecturally_aligned_testing.html
* [Oracle.com - Naming a Package](https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html
* [JavaPractices.com - Package by feature, not layer](http://www.javapractices.com/topic/TopicAction.do?Id=205
* [StackOverflow.com - What strategy do you use for package naming in Java projects and why?](http://stackoverflow.com/questions/533102/what-strategy-do-you-use-for-package-naming-in-java-projects-and-why
## Additional suggestions
### ..view.common.stuff
*(replace 'stuff' with a describing name for your , e.g. contact.)*
Put here basic stuff used by different views.
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/concepts/project_structure.md
================================================
[Back to Index](../index.md)
# Project structure
### app - Application
contains the app with views, business logic, etc ...
Checks the details of each unit.
A unit is the smallest meaningful part of a app.
### appCt - Component Tests
Tests which will check the integration of different units.
Most high level logic is proved here.
### appDt - Device Tests
Runs the app on device/emulator to check that all work in a real environment.
### Modules which could be simple folders
*Some modules only are modules instead of simple folders to support the Android Project View. Without they would not be visible.*
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/concepts/testing.md
================================================
[Back to Index](../index.md)
# Testing Strategies
## Unit Tests
Check that your code work technically in isolation like you expected it.
Your have the choice between different unit tests variants.
**Android Unit Tests** (Historical this was the first existing possibility for unit tests)
This is the most realistic testing by deploy and run your code on device/emulator.
Is also the slowest approach because of the device/emulator deployment time.
**Robolectric Unit Tests** (Historical this was the second existing possibility for unit tests)
You can run tests at JVM but have access functional android code. You will have less issues with
android code compared to pure unit tests and faster then deploy an apk on device/emulator.
**Pure Java Unit Tests** (since android gradle plugin 1.1.0)
The fastest and most known style of unit testing,
**Its your choice**
I prefer a mix of pure unit tests and robolectric supported unit tests. Whenever possible write
unit test without robolectric this give you the fastest development cycle (TDD).
## Component Tests
Check that units work together in combination with database, network, android services, business logic, views, etc...
Some guys say Robolectric is just for unit testing but you can also do your main integration tests.
Test execution is up to 20x faster than on device/emulator with espresso.
But not all android features are supported or must first be configured to your needs.
## Integration Tests
At least check that your app does run on device.
Again you can choose between different approaches, see .
One concept may be to navigate through each page and feature and take a screenshot.
After each runs you can compare the layout and style to look same the effort. This can be improved with automatic image compare.
My favourite is Espresso because it is the tool with most stable and has fastest test execution.
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/getting_started.md
================================================
[Back to Index](index.md)
# Getting Started
## Start Wiremock
Without the example app sync process will fail.
Execute from project root
* Linux/Mac `tools/src/main/resources/start-wiremock.sh`
* Windows `java -jar wiremock\wiremock-1.57-standalone.jar --port 1337 --https-port 1338 --verbose --root-dir 'wiremock\src\main\resources'`
The app get your current ip at compile time and then try to connect to your wiremock instance.
## Start from commandline
Start an emulator or connect with device.
Then on commandline navigate into the project root and execute from there following script and check that all works.
* Linux/Mac `tools/src/main/resources/test-all-with-coverage.sh`
* Windows `tools\src\main\resources\test-all-with-coverage.bat`
#### Gradle Commands
**unit tests:** `./gradlew app:test`
* append `app:jacocoTestReport` for coverage report
**component tests:** `./gradlew appCt:test`
* append `appCt:jacocoTestReport` for coverage report
**acceptance tests:** `./gradlew appIt:connectedAndroidTest` *(Use `connectedCheck` until bug closed )*
* replace by `appIt:connectedCheck` for coverage report
**combined coverage report:** `./gradlew jacocoTestReport` after you run all tests
## Start with Android Studio
Import project into Android Studio.
#### Build Variants settings
Change the build variants test artifact type to unit tests.
Since we can use a test module for instrumentation tests we don't need to switch between them anymore.
#### Android Studio - Espresso
When you are on `Test Artifact: Unit Tests` then you don't get the possibility to start instrumented tests.
Switch to `Test Artifact: Instrumentation Tests` and then start/create test run configuration.
Later this created configuration can still be used when you are on `Test Artifact: Unit Tests` without changing it.
I hope this behavior can be adjusted so that the test module only support instrumented tests and we need no more to switch the test artifact type
#### Android Project View
On the left top about your project structure you can switch between different project content views.
I like the minimal Android View, but at project view you can see more files.
Switch there to you preferred style.
*Some modules only are modules instead of simple folders to support the Android Project View. Without they would not be visible.*
---
[Back to Index](index.md)
================================================
FILE: docs/src/main/resources/index.md
================================================
[Back to Project](https://github.com/nenick/android-gradle-template)
# Template explanation
## Some basis information
* [Getting Start](getting_started.md)
* [Adjust Project to your needs](adjust_project_to_your_needs.md)
## Concepts explanation
* [MVP Pattern](concepts/model_view_presenter.md)
* [Project Structure](concepts/project_structure.md)
* [Package Structure](concepts/package_structure.md)
* [Function classes](concepts/function_class.md)
* [How and what to Test](concepts/testing.md)
## Used tools
* Generate Database Handling [ContentProvider Generator](tools/android_contentprovider_generator.md)
* Generate Android Boilerplate Code [AndroidAnnotations](tools/androidannotations.md)
* Tests with [Espresso](tools/espresso.md)
* Tests with [Espresso tests in own module](tools/espresso_test_module.md)
* Fluent Assertions [Fest Assertions](tools/fest_assertions.md)
* Code Coverage report [Coveralls](tools/coveralls.md)
* Code Coverage report [Jacoco](tools/jacoco.md)
* Better Time Handling [Joda TimeDate](tools/joda_timedate.md)
* Generate Json Mapping [Json](tools/jsonschema2pojo.md)
* Test with robolectric [Robolectric](tools/robolectric.md)
* Test with robolectric [Robolectric tests in own module](tools/robolectric_test_module.md)
* Mock REST Services [Wiremock](tools/wiremock.md)
---
[Back to Project](https://github.com/nenick/android-gradle-template)
================================================
FILE: docs/src/main/resources/tools/android_contentprovider_generator.md
================================================
[Back to Index](../index.md)
# Android ContentProvider Generator
Create tool for generating all necessary database files.
* Table classes with constants
* ContentProvider implementation
* CursorWrapper
* Hook for database updates
* and many more ...
## Info
* [Project](https://github.com/BoD/android-contentprovider-generator)
Current there is an important bug with joins and ids .
To avoid this issue you can give a specific projection.
private final String[] ALL_JOINED_COLUMNS = (String[]) ArrayUtils.addAll(
AddressColumns.ALL_COLUMNS,
ContactColumns.ALL_COLUMNS);
// multi join example
private final String[] ALL_JOINED_COLUMNS = (String[]) ArrayUtils.addAll(
ArrayUtils.addAll(
AddressColumns.ALL_COLUMNS,
ContactColumns.ALL_COLUMNS),
MoreColumns.ALL_COLUMNS);
The ArrayUtils comes with *compile 'commons-lang:commons-lang:2.6'*
## Add Android ContentProvider Generator to your project
Add the following snippet to your build.gradle file:
// this block must be above the first plugin line
plugins {
id "de.undercouch.download" version "1.2"
}
// this block can be at the end of the build.gradle or in a separate file
def dbSchemaPath = "src/main/json/database/schema"
def dbClassesPath = "src/gen/java"
android.sourceSets.main.java.srcDirs += dbClassesPath
android.sourceSets.main.java.srcDirs += "src/main/json"
task generateDatabaseFiles << {
def generatorVersion = "1.9.2"
# download android contentprovider generator
if (!file("$buildDir/android_contentprovider_generator-${generatorVersion}-bundle.jar").exists()) {
download {
src "https://github.com/BoD/android-contentprovider-generator/releases/download/v${generatorVersion}/android_contentprovider_generator-${generatorVersion}-bundle.jar"
dest buildDir
}
}
# delete last generated files
file(dbClassesPath).deleteDir()
# execute the generator
exec {
executable "java"
args = ["-jar", "$buildDir/android_contentprovider_generator-${generatorVersion}-bundle.jar", "-i", dbSchemaPath, "-o", dbClassesPath]
}
}
This snippet add the new task **generateDatabaseFiles** to you gradle tasks.
This new Task reads the schema files from $dbSchemaPath and write them to $dbClassesPath.
After the generation you will get a hint how to register your new database provider at AndroidManifest.xml
### subclass for dependency injection
@EBean
public class ExampleDbProvider extends ExampleProvider {
}
### Move SQLiteOpenHelper Callbacks
By default this class would not be overwritten but with some custom changes to the generation process it does.
Best is to move it to src/main/java instead of src/gen/java within the same package to keep custom callback implementations.
After each you must delete
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/tools/androidannotations.md
================================================
[Back to Index](../index.md)
# AndroidAnnotations
I decided me for androidannotations instead of dagger, butterknife and co because it just fit perfectly into all my need.
* dependency injection
* view injection
* view event
* background / ui thread
* REST
* and many more
## Infos
* [Documentation](https://github.com/excilys/androidannotations/wiki)
* [Available Annotations](https://github.com/excilys/androidannotations/wiki/AvailableAnnotations)
* [Latest Release Version](https://github.com/excilys/androidannotations/releases)
## Add AndroidAnnotations to your project
They have a create documentation so read
More is not necessary, all code generation is automatically be done by a build.
Short overview:
* **buildscript:** classpath 'com.neenbedankt.gradle.plugins:android-apt:1.7'
* **apply plugin:** 'com.neenbedankt.android-apt'
* **add config:**
apt {
arguments {
androidManifestFile variant.outputs[0].processResources.manifestFile
}
}
* **dependency:** apt "org.androidannotations:androidannotations:3.3.2"
* **dependency:** compile "org.androidannotations:androidannotations-api:3.3.2"
When you don't use the http support then you must adjust your proguard config to avoid proguard issues.
-dontwarn org.androidannotations.api.rest.*
## REST Support
Andorid Annotations use Spring for network communication
latest version
* **dependency:** (optional) compile spring
https://github.com/excilys/androidannotations/wiki/Rest-API
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/tools/circleci.md
================================================
[Back to Index](../index.md)
# Circle CI
For my continues integration I prefer Circle CI because of nice separation of each build step and a way to collect build results.
## emulator
The emulator is not ready after it is started, it just need one minute more after the script `circle-android wait-for-boot` report ready.
And its not guaranted that the lock screen is disabled `adb shell input keyevent 82`
## Max memory
preDex use mutiple dex instances which in cobnation fast exceed the 4G memory limit.
## collecting build artifacts
copy all what you like to the $CIRCLE_TEST_REPORTS location to see them later like the reason why a test has failed.
## proper test value
circle ci offers possibility to read and show the tests results on a nice screen when you provide
test result xml files into $CIRCLE_TEST_REPORTS/junit
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/tools/coveralls.md
================================================
[Back to Index](../index.md)
# Cloud Service to host code coverage reports
classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.4.0'
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/tools/espresso.md
================================================
[Back to Index](../index.md)
Getting startes wiki: https://code.google.com/p/android-test-kit/wiki/EspressoSetupInstructions
Resolve issue http://stackoverflow.com/questions/28999124/resolved-versions-for-app-22-0-0-and-test-app-21-0-3-differ
### insert text issue
http://stackoverflow.com/questions/20436968/espresso-typetext-not-working
### Idling Resources to avoid sleep/wait/flaky
compile 'com.android.support.test.espresso:espresso-contrib:2.2'
see CountingIdlingResource
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/tools/espresso_test_module.md
================================================
[Back to Index](../index.md)
module under test need to publish variants: publishNonDefault true
Initial need dummy AndroidManifest.xml at src/main
base build.gradle
apply plugin: 'com.android.test'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
minSdkVersion 8
}
targetProjectPath ':app'
targetVariant 'debug'
}
### APK not build issue
workaround is to call https://code.google.com/p/android/issues/detail?id=180689
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/tools/fest_assertions.md
================================================
[Back to Index](../index.md)
# Fluent Assertion with Android Support
I prefer the fluent style because you must not think so much.
Just use the smart code completion by pressing "." and select what to check.
## Android Support
The assertJ-android provides extra some checks for android elements like `assertThat( textField ).hasText( "expected text" )`
## Setup
Add to your test dependencies `'com.squareup.assertj:assertj-android:1.1.0'` and use `assertThat( actual ).isEqualTo( expected )` instead of `assertThat( actual, is( expected ))`
Import both assertThat methods to use automatic the correct one.
import static org.assertj.android.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThat;
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/tools/jacoco.md
================================================
[Back to Index](../index.md)
# Jacoco unit tests
simple unit tests in app module and robolectric supported
apply plugin: "jacoco"
add custom jacocoTestReport task (see app/build-jacoco-test-report.gradle)
result at: app/build/reports/jacoco/index.html
# jacoco component tests
apply plugin: "jacoco"
to get a report adjust some properties (see appCt/build-jacoco-test-report.gradle)
result at: appCt/build/reports/jacoco/test/html/com.example.project/index.html
# jacoco integration tests
enable them and use connectedCheck instead of connectedAndroidTest
buildTypes {
debug {
testCoverageEnabled = true
}
}
results at: result at: appCt/build/reports/jacoco/test/html/com.example.project/index.html
# combined coverage report
see projectRoot/build-jacoco-test-report.gradle
# Jacoco Version
looks like some version does no work with android. The version "0.7.0.201403182114" looks good.
# Replace AndroidAnnotation with Dagger and ButterKnife
* clean up the app/build.gradle file from stuff which is added for [AndroidAnnotations](tools/androidannotations.md)
* add dagger and butterknife to the app/build.gradle
* replace all annotations
* for jacoco code coverage you will need the following workaround to avoid issues with the generated $$ classes
task jacocoReport(type: JacocoReport) {
doFirst {
applyDaggerWorkaround('app/build/intermediates/classes/')
}
// avoid some side effects through revert renaming
doLast {
revertDaggerWorkaround('app/build/intermediates/classes/')
}
}
def applyDaggerWorkaround(String pathWithDaggerClasses) {
def filePath = new File(pathWithDaggerClasses)
if (filePath.exists()) {
filePath.eachFileRecurse { file ->
if (file.name.contains('$$')) {
file.renameTo(file.path.replace('$$', '$'))
}
}
}
}
def revertDaggerWorkaround(String pathWithDaggerClasses) {
def filePath = new File(pathWithDaggerClasses)
if (filePath.exists()) {
filePath.eachFileRecurse { file ->
if (file.name.contains('$ModuleAdapter')) {
file.renameTo(file.path.replace('$ModuleAdapter', '$$ModuleAdapter'))
}
}
}
}
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/tools/joda_timedate.md
================================================
[Back to Index](../index.md)
http://stackoverflow.com/questions/5059663/android-java-joda-date-is-slow
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/tools/jsonschema2pojo.md
================================================
[Back to Index](../index.md)
https://github.com/joelittlejohn/jsonschema2pojo
https://github.com/joelittlejohn/jsonschema2pojo/wiki/Reference
https://github.com/joelittlejohn/jsonschema2pojo/tree/master/jsonschema2pojo-gradle-plugin
classpath 'org.jsonschema2pojo:jsonschema2pojo-gradle-plugin:0.4.14'
apply plugin: 'jsonschema2pojo'
// Required if generating equals, hashCode, or toString methods
compile 'commons-lang:commons-lang:3.4'
compile 'com.fasterxml.jackson.core:jackson-databind:2.6.1'
jsonSchema2Pojo {
source = files("${project.rootDir}/json/network/schema")
targetDirectory = file("${project.rootDir}/src/gen/java")
targetPackage = 'com.example.project.network'
useCommonsLang3 = true
}
https://github.com/joelittlejohn/jsonschema2pojo/blob/37a98588312679391f7e660962a8d40de3c826b5/jsonschema2pojo-gradle-plugin/example/android/app/build.gradle
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/tools/robolectric.md
================================================
[Back to Index](../index.md)
# Robolectric
Is a create tool for testing your app inside jvm insdead to deploy it on a device or emulator.
## Add Robolectric to your project
They have getting started site and some sample projects at .
Some more basic examples can be found at
Short overview:
* dependency testCompile 'org.robolectric:robolectric:3.0'
* use @RunWith(RobolectricGradleTestRunner.class)
* use @Config(constants = BuildConfig.class, sdk = 21)
### Base class for tests
Instead of adding the annotations every time to you test class you can use a base test.
Adding following snippet to your base class make writing tests more convenient.
protected Context context;
@Before
public void roboSetup() {
context = RuntimeEnvironment.application;
}
### Reset Singletons
You will get strange behavior when you forget to reset your tests. Most times you will get them when you use a database in your tests.
@After
public void finishRobolectricTest() {
resetSingleton(ExampleSQLiteOpenHelper.class, "sInstance");
}
private void resetSingleton(Class clazz, String fieldName) {
java.lang.reflect.Field instance;
try {
instance = clazz.getDeclaredField(fieldName);
instance.setAccessible(true);
instance.set(null, null);
} catch (Exception e) {
throw new RuntimeException();
}
}
### Use extra shadow modules
List of available extra modules
For apps using classes from v4 support must add *testCompile 'org.robolectric:shadows-support-v4:3.0'* or it may result in unstable tests.
### debugging with shadows
They may a bit confuse when you try to debug.
When you see are in RobolectricInternals#methodInvoked(...) then use step out to reach the expected method.
When you reach ShadowWrangler#ShadowMethodPlan#run then step over until *return shadowMethod.invoke(shadow, params);*
and then step into then you reach the shadowed method and can debug there.
### Create shadows for non android classes
Register a class which could be shadowed (android classes can be shadowed without extra registration)
class CustomRobolectricTestRunner {
public InstrumentationConfiguration createClassLoaderConfig() {
InstrumentationConfiguration.Builder builder = InstrumentationConfiguration.newBuilder();
builder.addInstrumentedClass(MyClass.class.getName());
return builder.build();
}
}
Register a Shadow for the class
@Config(constants = BuildConfig.class, sdk = 21, shadows = {ShadowMyClass.class})
class RobolectricTestCase {}
More informations at
### Avoid jumping virtual desktops
I detected this behaviour just on mac os. Every time i run robolectric tests at IDE or on command line it was jumping to another desktop.
On command line you can avoid it with `export JAVA_TOOL_OPTIONS='-Djava.awt.headless=true'`
In your IDE set as VM option in your run configuration `-Djava.awt.headless=true`
---
See also
* [Testing Concept](../concepts/testing.md)
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/tools/robolectric_test_module.md
================================================
[Back to Index](../index.md)
# gradle-android-test-plugin
Move some of the robolectric supported tests into an own module to separate simple units from component tests.
For Robolectric basics see also [Robolectrc](robolectric.md)
### avoid release builds for test runs, and conflict between lint and test runs
** check if issue is gone**
afterEvaluate {
def isLintRun = false
def isTestRun = false
gradle.startParameter.taskNames.each {
if (it.contains("lint")) {
isLintRun = true
}
if (it.contains("test")) {
isTestRun = true
}
}
if (isLintRun && isTestRun) {
println "WARNING: tests for release type are disabled for supporting jacoco"
println "WARNING: run test and lint at same time is not supported"
exit 1
}
if (isTestRun) {
tasks.each {
if (it.name.contains("Release")) {
it.enabled = false
}
}
}
}
---
[Back to Index](../index.md)
================================================
FILE: docs/src/main/resources/tools/wiremock.md
================================================
[Back to Index](../index.md)
# Mocking REST Service
http://wiremock.org/running-standalone.html
### NoSuchMethodError on Java 7
Latest Wiremock versions needs Java 8. For Java 7 we need older wiremock version. Or you will see errors like:
ava.lang.NoSuchMethodError: java.util.concurrent.ConcurrentHashMap.keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;
at com.github.tomakehurst.wiremock.global.ThreadSafeRequestDelayControl.cancelAllDelays(ThreadSafeRequestDelayControl.java:51)
at com.github.tomakehurst.wiremock.global.ThreadSafeRequestDelayControl.clearDelay(ThreadSafeRequestDelayControl.java:38)
at com.github.tomakehurst.wiremock.core.WireMockApp.resetMappings(WireMockApp.java:141)
at com.github.tomakehurst.wiremock.core.WireMockApp.resetToDefaultMappings(WireMockApp.java:151)
at com.github.tomakehurst.wiremock.admin.ResetToDefaultMappingsTask.execute(ResetToDefaultMappingsTask.java:26)
at com.github.tomakehurst.wiremock.http.AdminRequestHandler.handleRequest(AdminRequestHandler.java:55)
at com.github.tomakehurst.wiremock.http.AbstractRequestHandler.handle(AbstractRequestHandler.java:38)
at com.github.tomakehurst.wiremock.jetty6.Jetty6HandlerDispatchingServlet.service(Jetty6HandlerDispatchingServlet.java:98)
---
[Back to Index](../index.md)
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Sun Aug 16 11:19:08 CEST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.6-all.zip
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: readme.md
================================================
# Android - Rapid Test Driven Development
* Combine tools to generate most of the boilerplate code.
* Examples how to test different aspects of an android application.
* Clean architecture approach with MVP and package by feature.
For details see [Project Documentations](docs/src/main/resources/index.md)
**Wishes, improvements, questions and discussions about the stuff here are welcome.**
[](https://circleci.com/gh/nenick/android-gradle-template) [](https://coveralls.io/r/nenick/android-gradle-template?branch=master)
### Last successful tests with
* Android Studio 1.3.2
* Gradle Build Tools 1.3.0
* Gradle 2.6
* Android v22
* Java 8
### Getting Started
Import the project into Android Studio and start developing. For more details see [Getting Started](docs/src/main/resources/getting_started.md)
================================================
FILE: settings.gradle
================================================
include 'app', 'appCt', 'appIt'
include 'wiremock', 'docs', 'tools'
================================================
FILE: tools/build.gradle
================================================
apply plugin: "java"
================================================
FILE: tools/src/main/resources/rename-packages(in development).sh
================================================
#!/bin/bash
#
# Change easy the base package to your needs
#
function printUsage {
echo ""
echo "Failed: Start script from the project root folder with target package name."
echo "> sh scripts/src/main/resources/rename-packages.sh \"my.target.name\" "
echo ""
}
initial_base_package="com.example.project"
if [ -z "$1" ]; then
echo "Error: Started without target base package."
printUsage
exit 1
fi
if !([ -d app ]); then
echo "Error: Not started from project root because folder app not found."
printUsage
exit 1
fi
# rename folders
# change files content
================================================
FILE: tools/src/main/resources/start-wiremock.sh
================================================
#!/bin/bash
function killCurrentWiremockInstance {
[ $(ps aux | grep -v grep | grep -c "wiremock.*standalone") -gt 0 ] && \
kill -9 $(ps aux | grep -v grep | grep "wiremock.*standalone" | awk 'NR==1{print $2}') || \
echo "No wiremock found to kill"
}
killCurrentWiremockInstance
if [ "$1" != "kill" ]; then
java -jar wiremock/wiremock-1.57-standalone.jar --port 1337 --https-port 1338 --verbose --root-dir 'wiremock/src/main/resources' &
else
echo "only kill servers"
fi
================================================
FILE: tools/src/main/resources/test-all-with-coverage.bat
================================================
gradlew.bat clean app:test appCt:test appIt:assembleDebug appIt:connectedCheck jacocoTestReport
================================================
FILE: tools/src/main/resources/test-all-with-coverage.sh
================================================
./gradlew clean app:test appCt:test appIt:assembleDebug appIt:connectedCheck jacocoTestReport
================================================
FILE: wiremock/build.gradle
================================================
apply plugin: "java"
================================================
FILE: wiremock/src/main/resources/__files/contacts-get.json
================================================
{
"contacts": [
{
"id": "k345lbhjt5er",
"firstName": "Max",
"lastName": "FromServer",
"birthDate": "1984-11-11",
"lastModification" : "2345678"
},
{
"id": "v4cn478cn8bz7",
"firstName": "Susi",
"lastName": "FromServer",
"birthDate": "1984-11-11",
"lastModification" : "2345678"
}
]
}
================================================
FILE: wiremock/src/main/resources/mappings/contcts-delete.json
================================================
{
"request": {
"method": "DELETE",
"urlPattern": "/contacts/.*"
},
"response": {
"status": 204
}
}
================================================
FILE: wiremock/src/main/resources/mappings/contcts-get.json
================================================
{
"request": {
"method": "GET",
"url": "/contacts"
},
"response": {
"status": 200,
"bodyFileName": "/contacts-get.json",
"headers": {
"Content-Type": "application/json;charset=UTF-8"
}
}
}
================================================
FILE: wiremock/src/main/resources/mappings/contcts-post.json
================================================
{
"request": {
"method": "POST",
"url": "/contacts"
},
"response": {
"status": 201
}
}
================================================
FILE: wiremock/src/main/resources/mappings/contcts-put.json
================================================
{
"request": {
"method": "PUT",
"urlPattern": "/contacts/.*"
},
"response": {
"status": 204
}
}