Initial commit

This commit is contained in:
Iiro Krankka
2016-09-02 15:02:07 +03:00
parent 58919bb536
commit ce7a2d3737
53 changed files with 1534 additions and 0 deletions

41
.gitignore vendored Normal file
View File

@@ -0,0 +1,41 @@
# Built application files
*.apk
*.ap_
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# Intellij
*.iml
.idea/workspace.xml
.idea/libraries
# Keystore files
*.jks

1
.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
BookLibrary

22
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>

3
.idea/copyright/profiles_settings.xml generated Normal file
View File

@@ -0,0 +1,3 @@
<component name="CopyrightManager">
<settings default="" />
</component>

6
.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

23
.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="myModules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

62
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<component name="masterDetails">
<states>
<state key="ProjectJDKs.UI">
<settings>
<last-edited>1.8</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>

9
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/BookLibrary.iml" filepath="$PROJECT_DIR$/BookLibrary.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
</modules>
</component>
</project>

12
.idea/runConfigurations.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@@ -0,0 +1,35 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All Instrumentation Tests" type="AndroidTestRunConfigurationType" factoryName="Android Tests">
<module name="app" />
<option name="TESTING_TYPE" value="1" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="com.codemate.booklibrary.ui.MainActivityTest" />
<option name="PACKAGE_NAME" value="com.codemate.booklibrary" />
<option name="EXTRA_OPTIONS" value="" />
<option name="TARGET_SELECTION_MODE" value="SHOW_DIALOG" />
<option name="PREFERRED_AVD" value="" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="true" />
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
<option name="FORCE_STOP_RUNNING_APP" value="true" />
<option name="DEBUGGER_TYPE" value="Java" />
<option name="USE_LAST_SELECTED_DEVICE" value="true" />
<option name="PREFERRED_AVD" value="" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="2147483647" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
<Native>
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
</Native>
<Java />
<Hybrid>
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
</Hybrid>
<Profilers>
<option name="GAPID_DISABLE_PCS" value="false" />
</Profilers>
<method />
</configuration>
</component>

7
.idea/runConfigurations/All_Tests.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All Tests" type="CompoundRunConfigurationType" factoryName="Compound Run Configuration">
<toRun type="AndroidTestRunConfigurationType" name="All Instrumentation Tests" />
<toRun type="JUnit" name="All Unit Tests" />
<method />
</configuration>
</component>

View File

@@ -0,0 +1,29 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All Unit Tests" type="JUnit" factoryName="JUnit">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea">
<pattern>
<option name="PATTERN" value="com.codemate.booklibrary.ui.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<module name="app" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="com.codemate.booklibrary" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="directory" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$MODULE_DIR$" />
<option name="ENV_VARIABLES" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<envs />
<dir value="$PROJECT_DIR$/app/src/test" />
<patterns />
<method />
</configuration>
</component>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

65
README.md Normal file
View File

@@ -0,0 +1,65 @@
# Behavior-Driven Development with Cucumber
This is a sample Android app that has tests mostly written using the Cucumber framework. The app acts like a library book search, allowing you to search for books by title, author or year. **The test coverage is 97%**.
[Cucumber](https://cucumber.io/) is a BDD testing framework that allows people without programming background write specifications **that can be translated to unit tests almost automatically**.
Writing tests turns more fun and it's no more a pain in the ass to write them first. It's also easier to specify the requirements.
## An example
The Oulu City Library has an application for book management.
The application is working fine, but they don't have a search feature. For the first version of the search feature, there's only one thing to implement:
* the employees must be able to search for books by authors.
This translates to the following **Gherkin** syntax, which could be even written by the customer or project lead.
**booksearch.feature:**
```gherkin
Feature: Book Search
Scenario: Search books by author
Given there's a book called "Tips for horrible hangovers" written by "John Smith"
And there's a book called "Bananas and their many colors" written by "James Brown"
And there's a book called "Mama look I'm a rock star" written by "John Smith"
When an employee searches by author "John Smith"
Then 2 books should be found
And Book 1 has the title "Tips for horrible hangovers"
And Book 2 has the title "Mama look I'm a rock star"
```
Simple, isn't it? This can easily be read and understood by non-programmers!
From that file, Cucumber pretty much autogenerates the Regular Expressions and Annotations needed to match the specific criterias. Then we only need to add the test logic and if we want, modify the method names.
**BookSearchSteps.java (annotations and methods auto-generated):**
```java
public class BookSearchSteps {
private Library library = new Library();
private List<Book> searchResults = new ArrayList<>();
@Given("^there's a book called \"([^\"]*)\" written by \"([^\"]*)\"$")
public void addBook(String title, String author) throws Throwable {
Book book = new Book(title, author);
library.addBook(book);
}
@When("^an employee searches by author \"([^\"]*)\"$")
public void searchForAuthor(String authorQuery) throws Throwable {
searchResults = library.findBooksByAuthor(authorQuery);
}
@And("^Book (\\d+) has the title \"([^\"]*)\"$")
public void verifyBookFound(int position, String title) throws Throwable {
int realPosition = position - 1;
assertEquals(title, searchResults.get(realPosition).getTitle());
}
}
```
Now that we have the tests in place, we just implement the functionality to make the tests pass!

1
app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

41
app/build.gradle Normal file
View File

@@ -0,0 +1,41 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "com.codemate.booklibrary"
minSdkVersion 19
targetSdkVersion 24
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
testCoverageEnabled true
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile ('com.android.support.test.espresso:espresso-core:2.2.1') {
exclude module: 'support-annotations'
}
testCompile 'junit:junit:4.12'
testCompile 'info.cukes:cucumber-java:1.2.4'
testCompile 'info.cukes:cucumber-junit:1.2.4'
testCompile 'org.mockito:mockito-core:1.+'
compile 'com.android.support:appcompat-v7:24.2.0'
compile 'com.android.support:recyclerview-v7:24.2.0'
}

17
app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/ironman/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -0,0 +1,53 @@
package com.codemate.booklibrary;
import android.support.test.espresso.NoMatchingViewException;
import android.support.test.espresso.ViewAssertion;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
* Created by ironman on 02/09/16.
*/
public class RecyclerViewAssertions {
public static ViewAssertion adapterItemCountEquals(int count) {
return new ItemCountAssertion(ItemCountAssertion.MODE_EQUALS, count);
}
public static ViewAssertion adapterItemCountLowerThan(int count) {
return new ItemCountAssertion(ItemCountAssertion.MODE_LESS_THAN, count);
}
private static class ItemCountAssertion implements ViewAssertion {
private static final int MODE_EQUALS = 0;
private static final int MODE_LESS_THAN = 1;
private final int mode;
private final int expectedChildCount;
ItemCountAssertion(int mode, int exptectedChildCount) {
this.mode = mode;
this.expectedChildCount = exptectedChildCount;
}
@Override
public void check (View view, NoMatchingViewException noViewFoundException) {
if (noViewFoundException != null) {
throw noViewFoundException;
}
RecyclerView recyclerView = (RecyclerView) view;
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (mode == MODE_EQUALS) {
assertThat(expectedChildCount, is(adapter.getItemCount()));
} else if (mode == MODE_LESS_THAN) {
assertTrue(expectedChildCount > adapter.getItemCount());
}
}
}
}

View File

@@ -0,0 +1,43 @@
package com.codemate.booklibrary.ui;
import android.support.test.espresso.action.ViewActions;
import android.support.test.espresso.matcher.ViewMatchers;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.widget.EditText;
import com.codemate.booklibrary.R;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static com.codemate.booklibrary.RecyclerViewAssertions.adapterItemCountEquals;
import static com.codemate.booklibrary.RecyclerViewAssertions.adapterItemCountLowerThan;
/**
* Created by ironman on 02/09/16.
*/
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
@Rule
public ActivityTestRule<MainActivity> mainActivityTestRule =
new ActivityTestRule<>(MainActivity.class);
@Test
public void allBooksDisplayedInUI() {
onView(ViewMatchers.withId(R.id.bookRecycler))
.check(adapterItemCountEquals(45));
}
@Test
public void onSearchTermEntered_NonMatchingItemsNotShown() {
onView(isAssignableFrom(EditText.class))
.perform(ViewActions.typeText("2"));
onView(withId(R.id.bookRecycler))
.check(adapterItemCountLowerThan(45));
}
}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.codemate.booklibrary">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".ui.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,30 @@
package com.codemate.booklibrary.data;
import java.util.Date;
/**
* Created by ironman on 01/09/16.
*/
public class Book {
private final String title;
private final String author;
private final Date publishDate;
public Book(String title, String author, Date publishDate) {
this.title = title;
this.author = author;
this.publishDate = publishDate;
}
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
public Date getPublishDate() {
return publishDate;
}
}

View File

@@ -0,0 +1,95 @@
package com.codemate.booklibrary.data;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
/**
* Created by ironman on 01/09/16.
*/
public class Library {
private List<Book> inventory = new ArrayList<>();
public void addBook(Book book) {
inventory.add(book);
}
public void addBooks(List<Book> books) {
inventory.addAll(books);
}
public List<Book> getAllBooks() {
return inventory;
}
/**
* This is a stupid search. In a real scenario, we would have something that
* would be much better.
*/
public List<Book> search(String searchQuery) {
List<Book> results = new ArrayList<>();
if (searchQuery.matches("^[1-9][0-9]{0,3}$")) {
results.addAll(findBooksByYear(searchQuery));
}
results.addAll(findBooksByAuthor(searchQuery));
results.addAll(findBooksByTitle(searchQuery));
return results;
}
public List<Book> findBooksByAuthor(String authorQuery) {
List<Book> results = new ArrayList<>();
for (Book candidate : inventory) {
String normalizedAuthor = candidate.getAuthor().toLowerCase();
String normalizedSearchQuery = authorQuery.toLowerCase();
if (normalizedAuthor.contains(normalizedSearchQuery)) {
results.add(candidate);
}
}
return results;
}
public List<Book> findBooksByTitle(String titleQuery) {
List<Book> results = new ArrayList<>();
for (Book candidate : inventory) {
String normalizedTitle = candidate.getTitle().toLowerCase();
String normalizedSearchQuery = titleQuery.toLowerCase();
if (normalizedTitle.contains(normalizedSearchQuery)) {
results.add(candidate);
}
}
return results;
}
public List<Book> findBooksByYear(String searchedYear) {
List<Book> results = new ArrayList<>();
for (Book candidate : inventory) {
Calendar candidateCalendar = Calendar.getInstance();
candidateCalendar.setTime(candidate.getPublishDate());
int candidateYear = candidateCalendar.get(Calendar.YEAR);
if (candidateYear == Integer.valueOf(searchedYear)
|| yearsStartSimilarly(candidateYear, searchedYear)) {
results.add(candidate);
}
}
return results;
}
private boolean yearsStartSimilarly(int candidateYear, String searchedYear) {
String candidate = String.valueOf(candidateYear);
return candidate.startsWith(searchedYear);
}
}

View File

@@ -0,0 +1,78 @@
package com.codemate.booklibrary.data;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
* Created by ironman on 02/09/16.
*/
public class RandomBookGenerator {
private RandomBookGenerator() {}
private static final String[] SUBJECTS = {
"Chicken", "Pig", "Hippo", "Dinosaur", "Giraffe", "Orangutan",
"Bat", "Lion", "Daddy", "Grandpa", "Donald Trump", "Coffee",
"Dog", "Rat", "Pokemon", "Cat", "Senile", "Pensioner",
"Project manager", "Salesperson"
};
private static final String[] SECOND_WORDS = {
"strip club", "that knew too little", "Strategy", "parking space",
"Office", "Banana", "Toilet", "Poop", "Fart", "Rain", "Job",
"Thing", "Radio", "Zoo", "Office", "House", "Village",
"swimming pool", "Computer"
};
private static final String[] FIRST_NAMES = {
"Bob", "Anna", "Tim", "Bethany", "Donald", "Mickey", "John",
"George", "Dick", "James", "Timothy", "Joanne", "Angus"
};
private static final String[] LAST_NAMES = {
"Doe", "Trump", "Bond", "Jackson", "Jordan", "Young", "Page",
"Johnson", "Springsteen", "Kauffman", "Schmidt", "Jokinen"
};
public static List<Book> randomBooks(int howMany) {
List<Book> books = new ArrayList<>();
for (int i = 0; i < howMany; i++) {
books.add(randomBook());
}
return books;
}
public static Book randomBook() {
String title = randomTitle();
String author = randomAuthor();
Date date = randomDate();
return new Book(title, author, date);
}
private static String randomTitle() {
String subject = RandomUtils.randomFromArray(SUBJECTS);
String secondWord = RandomUtils.randomFromArray(SECOND_WORDS);
return subject + " " + secondWord;
}
private static String randomAuthor() {
String firstName = RandomUtils.randomFromArray(FIRST_NAMES);
String lastName = RandomUtils.randomFromArray(LAST_NAMES);
return firstName + " " + lastName;
}
private static Date randomDate() {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, RandomUtils.randBetween(1995, 2016));
calendar.set(Calendar.MONTH, RandomUtils.randBetween(0, 11));
calendar.set(Calendar.DAY_OF_MONTH, RandomUtils.randBetween(1, 25));
return calendar.getTime();
}
}

View File

@@ -0,0 +1,24 @@
package com.codemate.booklibrary.data;
import java.util.Random;
/**
* Created by ironman on 02/09/16.
*/
public class RandomUtils {
private RandomUtils() {}
private static final Random random;
static {
random = new Random();
}
public static <T> T randomFromArray(T[] array) {
return array[randBetween(0, array.length - 1)];
}
public static int randBetween(int start, int end) {
return start + (int) Math.round(random.nextDouble() * (end - start));
}
}

View File

@@ -0,0 +1,68 @@
package com.codemate.booklibrary.ui;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.codemate.booklibrary.data.Book;
import com.codemate.booklibrary.R;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* Created by ironman on 02/09/16.
*/
public class BookAdapter extends RecyclerView.Adapter<BookAdapter.ViewHolder> {
private final SimpleDateFormat dateFormat;
private List<Book> items = new ArrayList<>();
public BookAdapter() {
dateFormat = new SimpleDateFormat("yyyy", Locale.getDefault());
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View itemView = inflater.inflate(R.layout.item_book, parent, false);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Book book = items.get(position);
holder.title.setText(book.getTitle());
holder.author.setText(book.getAuthor());
holder.date.setText(dateFormat.format(book.getPublishDate()));
}
@Override
public int getItemCount() {
return items.size();
}
public void updateItems(List<Book> books) {
this.items = books;
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder {
final TextView title;
final TextView author;
final TextView date;
public ViewHolder(View itemView) {
super(itemView);
title = (TextView) itemView.findViewById(R.id.bookTitle);
author = (TextView) itemView.findViewById(R.id.bookAuthor);
date = (TextView) itemView.findViewById(R.id.bookDate);
}
}
}

View File

@@ -0,0 +1,57 @@
package com.codemate.booklibrary.ui;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import com.codemate.booklibrary.data.Book;
import com.codemate.booklibrary.data.Library;
import com.codemate.booklibrary.R;
import java.util.List;
public class MainActivity extends AppCompatActivity implements MainView, SearchView.OnQueryTextListener {
private MainPresenter presenter;
private BookAdapter bookAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initializeViews();
presenter = new MainPresenter(this, new Library());
presenter.loadAllBooks();
}
private void initializeViews() {
bookAdapter = new BookAdapter();
RecyclerView bookRecycler = (RecyclerView) findViewById(R.id.bookRecycler);
bookRecycler.setLayoutManager(new LinearLayoutManager(this));
bookRecycler.setAdapter(bookAdapter);
SearchView searchView = (SearchView) findViewById(R.id.searchView);
searchView.setOnQueryTextListener(this);
}
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
presenter.searchForBooks(newText);
return false;
}
@Override
public void showBooks(List<Book> books) {
bookAdapter.updateItems(books);
}
}

View File

@@ -0,0 +1,39 @@
package com.codemate.booklibrary.ui;
import com.codemate.booklibrary.data.Book;
import com.codemate.booklibrary.data.Library;
import com.codemate.booklibrary.data.RandomBookGenerator;
import java.util.List;
/**
* Created by ironman on 02/09/16.
*/
public class MainPresenter {
private final MainView mainView;
private final Library library;
public MainPresenter(MainView mainView, Library library) {
this.mainView = mainView;
this.library = library;
}
public void searchForBooks(String searchQuery) {
List<Book> searchResults = library.search(searchQuery);
loadBooks(searchResults);
}
public void loadAllBooks() {
// Populate the library with fake dummy data. In a real app
// we would have an interactor that would fetch the books from
// a real API.
List<Book> books = RandomBookGenerator.randomBooks(45);
library.addBooks(books);
loadBooks(library.getAllBooks());
}
public void loadBooks(List<Book> books) {
mainView.showBooks(books);
}
}

View File

@@ -0,0 +1,12 @@
package com.codemate.booklibrary.ui;
import com.codemate.booklibrary.data.Book;
import java.util.List;
/**
* Created by ironman on 02/09/16.
*/
public interface MainView {
void showBooks(List<Book> books);
}

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ui.MainActivity">
<android.support.v7.widget.SearchView
android:id="@+id/searchView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:iconifiedByDefault="false"
app:queryHint="Search by title, author or year..." />
<android.support.v7.widget.RecyclerView
android:id="@+id/bookRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:clipToPadding="false"
android:layout_below="@id/searchView"
android:scrollbars="vertical"/>
</RelativeLayout>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/bookTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
tools:text="Book Title" />
<TextView
android:id="@+id/bookAuthor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
tools:text="Book Author" />
<TextView
android:id="@+id/bookDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
tools:text="2016" />
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>

View File

@@ -0,0 +1,5 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Book Library</string>
</resources>

View File

@@ -0,0 +1,11 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

View File

@@ -0,0 +1,16 @@
package com.codemate.booklibrary;
import junit.framework.TestCase;
import org.junit.runner.RunWith;
import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
/**
* Created by ironman on 01/09/16.
*/
@RunWith(Cucumber.class)
@CucumberOptions(features = "src/test/resources")
public class CucumberTests extends TestCase {
}

View File

@@ -0,0 +1,20 @@
package com.codemate.booklibrary.data;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
/**
* Created by ironman on 02/09/16.
*/
public class LibraryTest {
@Test
public void addMultipleBooks_PersistsThemInLibrary() {
List<Book> books = RandomBookGenerator.randomBooks(3);
Library library = new Library();
library.addBooks(books);
Assert.assertEquals(books, library.getAllBooks());
}
}

View File

@@ -0,0 +1,27 @@
package com.codemate.booklibrary.data;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
/**
* Created by ironman on 02/09/16.
*/
public class RandomBookGeneratorTest {
@Test
public void generatingRandomBooks_ReturnsNonEmptyBookList() {
List<Book> books = RandomBookGenerator.randomBooks(15);
assertEquals(15, books.size());
for (Book book : books) {
assertNotNull(book.getTitle());
assertNotNull(book.getAuthor());
assertNotEquals(0, book.getPublishDate().getTime());
}
}
}

View File

@@ -0,0 +1,87 @@
package com.codemate.booklibrary.steps;
import com.codemate.booklibrary.data.Book;
import com.codemate.booklibrary.data.Library;
import com.codemate.booklibrary.ui.MainPresenter;
import com.codemate.booklibrary.ui.MainView;
import org.junit.Assert;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import cucumber.api.Format;
import cucumber.api.PendingException;
import cucumber.api.java.en.And;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Created by ironman on 01/09/16.
*/
public class BookSearchSteps {
private Library library = new Library();
private List<Book> results = new ArrayList<>();
/**
* Test data populations
*/
@Given(".*a book with title \"([^\"]*)\", written by \"([^\"]*)\", published in (.+)$")
public void addNewBook(String title, String author, @Format("dd MMMM yyyy") Date publishDate) throws Throwable {
Book book = new Book(title, author, publishDate);
library.addBook(book);
}
/**
* Use cases
*/
@When("^the customer wants to know all books in the library$")
public void getAllBooks() throws Throwable {
results = library.getAllBooks();
}
@When("^the customer searches for books by author \"([^\"]*)\"$")
public void searchByAuthor(String authorQuery) throws Throwable {
results = library.search(authorQuery);
}
@When("^the customer searches for books with title \"([^\"]*)\"$")
public void searchByTitle(String title) throws Throwable {
results = library.search(title);
}
@When("^the customer searches for books published in year (.*)$")
public void searchByYear(String year) throws Throwable {
results = library.search(year);
}
/**
* Assertions
*/
@Then("^(\\d+) books should be found$")
public void verifyAmountOfBooksFound(int booksFound) throws Throwable {
assertEquals(booksFound, results.size());
}
@And("^Book (\\d+) should have the title \"([^\"]*)\"$")
public void verifyBookAtPosition(int position, String title) throws Throwable {
int realPosition = position - 1;
Assert.assertEquals(title, results.get(realPosition).getTitle());
}
@And("^Books should be (.+)$")
public void verifyBookTitlesEqualTo(String titles) throws Throwable {
for (Book result : results) {
assertThat(titles, containsString(result.getTitle()));
}
}
}

View File

@@ -0,0 +1,56 @@
package com.codemate.booklibrary.ui;
import com.codemate.booklibrary.data.Book;
import com.codemate.booklibrary.data.Library;
import com.codemate.booklibrary.data.RandomBookGenerator;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.List;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Created by ironman on 02/09/16.
*/
public class MainPresenterTest {
@Mock
private MainView mainView;
@Mock
private Library library;
private MainPresenter presenter;
private List<Book> randomBooks;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
presenter = new MainPresenter(mainView, library);
randomBooks = RandomBookGenerator.randomBooks(5);
}
@Test
public void searchForBooks_ShowsThemInUI() {
when(library.search(anyString()))
.thenReturn(randomBooks);
presenter.searchForBooks("test_search");
verify(mainView).showBooks(randomBooks);
}
@Test
public void loadAllBooks_ShowsThemInUI() {
when(library.getAllBooks())
.thenReturn(randomBooks);
presenter.loadAllBooks();
verify(mainView).showBooks(randomBooks);
}
}

View File

@@ -0,0 +1,43 @@
Feature: Book Search
A customer must be able to search books by year, author or title
Background:
Given the library has a book with title "How to be awesome", written by "Iiro Krankka", published in 16 May 2016
And a book with title "My life as an awesome guy", written by "Iiro Krankka", published in 27 July 2016
And a book with title "I think my teacher is an asshat", written by "John Doe", published in 01 January 2010
Scenario: List all books
When the customer wants to know all books in the library
Then 3 books should be found
And Book 1 should have the title "How to be awesome"
And Book 2 should have the title "My life as an awesome guy"
And Book 3 should have the title "I think my teacher is an asshat"
Scenario: Search books by year
When the customer searches for books published in year 2016
Then 2 books should be found
And Book 1 should have the title "How to be awesome"
And Book 2 should have the title "My life as an awesome guy"
Scenario Outline: Search books by author
When the customer searches for books by author <author>
Then 2 books should be found
And Book 1 should have the title "How to be awesome"
And Book 2 should have the title "My life as an awesome guy"
Examples:
| author |
| "Iiro" |
| "Krankka" |
| "Iiro Krankka" |
Scenario Outline: Search books by title
When the customer searches for books with title <searched_title>
Then <search_results_count> books should be found
And Books should be <search_results>
Examples:
| searched_title | search_results_count | search_results |
| "Awesome" | 2 | "How to be awesome" and "My life as an awesome guy" |
| "asshat" | 1 | "I think my teacher is an asshat" |
| "How to be" | 1 | "How to be awesome" |

23
build.gradle Normal file
View File

@@ -0,0 +1,23 @@
// 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:2.1.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

18
gradle.properties Normal file
View File

@@ -0,0 +1,18 @@
# 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

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#Thu Sep 01 16:32:45 EEST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

160
gradlew vendored Executable file
View File

@@ -0,0 +1,160 @@
#!/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
# 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\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
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"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# 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 "$@"

90
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,90 @@
@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

1
settings.gradle Normal file
View File

@@ -0,0 +1 @@
include ':app'