├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── apidez │ │ └── com │ │ └── databinding │ │ ├── TestApplication.java │ │ ├── TestRunner.java │ │ ├── utils │ │ ├── ApplicationUtils.java │ │ └── MatcherEx.java │ │ └── view │ │ ├── activity │ │ └── PurchaseActivityIntegrateTest.java │ │ └── fragment │ │ └── PlacesFragmentIntegrateTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── apidez │ │ │ └── com │ │ │ └── databinding │ │ │ ├── ComponentBuilder.java │ │ │ ├── MyApplication.java │ │ │ ├── dependency │ │ │ ├── component │ │ │ │ ├── AppComponent.java │ │ │ │ ├── PlacesComponent.java │ │ │ │ └── PurchaseComponent.java │ │ │ ├── module │ │ │ │ ├── AppModule.java │ │ │ │ ├── PlacesModule.java │ │ │ │ └── PurchaseModule.java │ │ │ └── scope │ │ │ │ └── ViewScope.java │ │ │ ├── model │ │ │ ├── api │ │ │ │ ├── IPlacesApi.java │ │ │ │ ├── IPurchaseApi.java │ │ │ │ └── PurchaseApi.java │ │ │ └── entity │ │ │ │ ├── GoogleSearchResult.java │ │ │ │ ├── Place.java │ │ │ │ └── Purchase.java │ │ │ ├── utils │ │ │ ├── BindingUtils.java │ │ │ ├── NumericUtils.java │ │ │ ├── RetrofitUtils.java │ │ │ ├── RxUtils.java │ │ │ ├── StringUtils.java │ │ │ ├── TestDataUtils.java │ │ │ ├── TextWatcherAdapter.java │ │ │ └── UiUtils.java │ │ │ ├── view │ │ │ ├── activity │ │ │ │ ├── BaseActivity.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── PlacesActivity.java │ │ │ │ ├── PurchaseActivity.java │ │ │ │ └── TestActivity.java │ │ │ ├── adapter │ │ │ │ ├── BaseRecyclerViewAdapter.java │ │ │ │ └── PlacesAdapter.java │ │ │ ├── fragment │ │ │ │ ├── BaseFragment.java │ │ │ │ └── PlacesFragment.java │ │ │ └── handler │ │ │ │ ├── IMainHandler.java │ │ │ │ └── IPurchaseHandler.java │ │ │ └── viewmodel │ │ │ ├── IPlacesViewModel.java │ │ │ ├── IPurchaseViewModel.java │ │ │ ├── PlacesViewModel.java │ │ │ └── PurchaseViewModel.java │ └── res │ │ ├── drawable-hdpi │ │ ├── ic_place_36dp.png │ │ ├── ic_sort_white_24dp.png │ │ └── ic_sort_white_36dp.png │ │ ├── drawable-mdpi │ │ ├── ic_place_36dp.png │ │ ├── ic_sort_white_24dp.png │ │ └── ic_sort_white_36dp.png │ │ ├── drawable-xhdpi │ │ ├── ic_place_36dp.png │ │ ├── ic_sort_white_24dp.png │ │ └── ic_sort_white_36dp.png │ │ ├── drawable-xxhdpi │ │ ├── ic_place_36dp.png │ │ ├── ic_sort_white_24dp.png │ │ └── ic_sort_white_36dp.png │ │ ├── drawable │ │ ├── bg_inactive_submit.xml │ │ ├── bg_submit.xml │ │ ├── bg_submit_normal.xml │ │ ├── bg_submit_pressed.xml │ │ └── ic_place_24dp.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_places.xml │ │ ├── activity_purchase.xml │ │ ├── fragment_places.xml │ │ └── item_place.xml │ │ ├── menu │ │ └── menu_places.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── apidez │ └── com │ └── databinding │ ├── model │ ├── api │ │ └── PurchaseApiTest.java │ └── entity │ │ ├── PlaceTest.java │ │ └── PurchaseTest.java │ ├── utils │ ├── NumericUtilsTest.java │ ├── StringUtilsTest.java │ └── TestDataUtilsTest.java │ └── viewmodel │ ├── PlacesViewModelTest.java │ └── PurchaseViewModelTest.java ├── build.gradle ├── calabash-data-binding ├── .idea │ ├── .name │ ├── encodings.xml │ ├── inspectionProfiles │ │ ├── Project_Default.xml │ │ └── profiles_settings.xml │ ├── misc.xml │ ├── modules.xml │ └── workspace.xml ├── features │ ├── form.feature │ ├── recycler.feature │ ├── step_definitions │ │ └── calabash_steps.rb │ └── support │ │ ├── app_installation_hooks.rb │ │ ├── app_life_cycle_hooks.rb │ │ ├── env.rb │ │ └── hooks.rb ├── report.html ├── screenshot_0.png └── screenshot_1.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jacoco.gradle └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | 28 | # Android Studio Navigation editor temp files 29 | .navigation/ 30 | 31 | # Android Studio captures folder 32 | captures/ 33 | 34 | # build folder 35 | /build 36 | *.iml 37 | /.idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: 3 | - oraclejdk7 4 | android: 5 | components: 6 | - tools 7 | - build-tools-23.0.2 8 | - android-23 9 | - extra-android-support 10 | - extra-android-m2repository 11 | before_install: 12 | - export JAVA7_HOME=/usr/lib/jvm/java-7-oracle 13 | - export JAVA8_HOME=/usr/lib/jvm/java-8-oracle 14 | - export JAVA_HOME=$JAVA8_HOME 15 | script: 16 | - ./gradlew test --continue 17 | after_success: 18 | - ./gradlew jacocoTestReport coveralls 19 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Vu Huy Quan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # android-data-binding 2 | Sample use of Data Binding Library
3 | Apply MVVM for android
4 | Unit test ViewModel with JUnit4
5 | 6 | [![Coverage Status](https://coveralls.io/repos/nongdenchet/android-data-binding/badge.svg?branch=master&service=github)](https://coveralls.io/github/nongdenchet/android-data-binding?branch=master) 7 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | 28 | # Android Studio Navigation editor temp files 29 | .navigation/ 30 | 31 | # Android Studio captures folder 32 | captures/ 33 | 34 | # build folder 35 | /build 36 | *.iml 37 | /.idea -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'me.tatarka.retrolambda' 3 | apply plugin: 'com.neenbedankt.android-apt' 4 | apply plugin: 'jacoco' 5 | apply plugin: 'com.github.kt3k.coveralls' 6 | apply from: '../jacoco.gradle' 7 | 8 | android { 9 | compileSdkVersion 23 10 | buildToolsVersion '23.0.2' 11 | 12 | defaultConfig { 13 | applicationId "apidez.com.databinding" 14 | minSdkVersion 14 15 | targetSdkVersion 23 16 | versionCode 1 17 | versionName "1.0" 18 | testInstrumentationRunner "apidez.com.databinding.TestRunner" 19 | } 20 | lintOptions { 21 | checkReleaseBuilds false 22 | abortOnError false 23 | } 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 28 | } 29 | debug { 30 | testCoverageEnabled true 31 | } 32 | } 33 | dataBinding { 34 | enabled = true 35 | } 36 | compileOptions { 37 | sourceCompatibility JavaVersion.VERSION_1_8 38 | targetCompatibility JavaVersion.VERSION_1_8 39 | } 40 | } 41 | 42 | configurations { 43 | androidTestCompile.exclude group: 'com.android.support', module: 'support-v4' 44 | androidTestCompile.exclude group: 'com.android.support', module: 'recyclerview-v7' 45 | androidTestCompile.exclude group: 'com.android.support', module: 'appcompat-v7' 46 | androidTestCompile.exclude group: 'com.android.support', module: 'support-annotations' 47 | } 48 | 49 | dependencies { 50 | compile fileTree(include: ['*.jar'], dir: 'libs') 51 | 52 | // Support library 53 | compile 'com.android.support:appcompat-v7:23.1.1' 54 | compile 'com.android.support:design:23.1.1' 55 | compile 'com.android.support:recyclerview-v7:23.1.1' 56 | compile 'com.android.support:cardview-v7:23.1.1' 57 | 58 | // Dagger 59 | compile 'org.glassfish:javax.annotation:10.0-b28' 60 | compile 'com.google.dagger:dagger:2.0.2' 61 | apt 'com.google.dagger:dagger-compiler:2.0.2' 62 | 63 | // Others 64 | compile 'com.google.code.gson:gson:2.4' 65 | compile 'com.squareup.picasso:picasso:2.5.2' 66 | compile 'io.reactivex:rxandroid:1.1.0' 67 | compile 'io.reactivex:rxjava:1.1.0' 68 | 69 | // Retrofits 70 | compile 'com.squareup.retrofit:retrofit:2.0.0-beta2' 71 | compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' 72 | compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2' 73 | 74 | // Test 75 | testCompile 'junit:junit:4.12' 76 | testCompile 'org.mockito:mockito-core:2.0.31-beta' 77 | 78 | // Android Test 79 | androidTestCompile 'com.android.support.test:runner:0.4.1' 80 | androidTestCompile 'com.android.support.test:rules:0.4.1' 81 | androidTestCompile 'com.android.support:support-annotations:23.1.1' 82 | androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1' 83 | androidTestCompile 'com.google.dexmaker:dexmaker:1.2' 84 | androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' 85 | androidTestCompile 'org.mockito:mockito-core:1.10.19' 86 | } 87 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/nongdenchet/Android-SDK/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/apidez/com/databinding/TestApplication.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding; 2 | 3 | 4 | import apidez.com.databinding.dependency.component.AppComponent; 5 | 6 | /** 7 | * Created by nongdenchet on 10/23/15. 8 | */ 9 | public class TestApplication extends MyApplication { 10 | public void setComponent(AppComponent appComponent) { 11 | this.mAppComponent = appComponent; 12 | } 13 | 14 | public void setComponentBuilder(ComponentBuilder componentBuilder) { 15 | this.mComponentBuilder = componentBuilder; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/apidez/com/databinding/TestRunner.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.support.annotation.NonNull; 6 | import android.support.test.runner.AndroidJUnitRunner; 7 | 8 | /** 9 | * Created by nongdenchet on 10/23/15. 10 | */ 11 | public class TestRunner extends AndroidJUnitRunner { 12 | @Override 13 | public Application newApplication(@NonNull ClassLoader cl, String className, Context context) 14 | throws InstantiationException, IllegalAccessException, ClassNotFoundException { 15 | return super.newApplication(cl, TestApplication.class.getName(), context); 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/apidez/com/databinding/utils/ApplicationUtils.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.utils; 2 | 3 | import android.app.Instrumentation; 4 | import android.support.test.InstrumentationRegistry; 5 | 6 | import apidez.com.databinding.TestApplication; 7 | 8 | /** 9 | * Created by nongdenchet on 10/22/15. 10 | */ 11 | public class ApplicationUtils { 12 | public static TestApplication application() { 13 | Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 14 | return (TestApplication) instrumentation.getTargetContext().getApplicationContext(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/androidTest/java/apidez/com/databinding/utils/MatcherEx.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.utils; 2 | 3 | import android.annotation.TargetApi; 4 | import android.os.Build; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | import android.widget.TextView; 8 | 9 | import org.hamcrest.Description; 10 | import org.hamcrest.Matcher; 11 | import org.hamcrest.TypeSafeMatcher; 12 | 13 | import static android.support.test.espresso.Espresso.onView; 14 | import static android.support.test.espresso.assertion.ViewAssertions.matches; 15 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 16 | import static android.support.test.espresso.matcher.ViewMatchers.withText; 17 | 18 | /** 19 | * Created by nongdenchet on 10/3/15. 20 | */ 21 | public class MatcherEx { 22 | 23 | /** 24 | * Returns a matcher that matches {@link View}s is visible 25 | */ 26 | public static Matcher isVisible() { 27 | return new TypeSafeMatcher() { 28 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) 29 | @Override 30 | protected boolean matchesSafely(View view) { 31 | return view.getVisibility() == View.VISIBLE; 32 | } 33 | 34 | @Override 35 | public void describeTo(Description description) { 36 | description.appendText("is visible"); 37 | } 38 | }; 39 | } 40 | 41 | /** 42 | * Returns a matcher that matches {@link View}s has listener 43 | */ 44 | public static Matcher hasListener() { 45 | return new TypeSafeMatcher() { 46 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) 47 | @Override 48 | protected boolean matchesSafely(View view) { 49 | return view.hasOnClickListeners(); 50 | } 51 | 52 | @Override 53 | public void describeTo(Description description) { 54 | description.appendText("has onclick listener"); 55 | } 56 | }; 57 | } 58 | 59 | /** 60 | * Returns a matcher that matches {@link TextView}s has maxLines 61 | */ 62 | public static Matcher hasMaxLines(int max) { 63 | return new TypeSafeMatcher() { 64 | @Override 65 | public void describeTo(Description description) { 66 | description.appendText("has max lines"); 67 | } 68 | 69 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 70 | @Override 71 | public boolean matchesSafely(View view) { 72 | try { 73 | return ((TextView) view).getMaxLines() <= max; 74 | } catch (Exception exception) { 75 | return false; 76 | } 77 | } 78 | }; 79 | } 80 | 81 | /** 82 | * Returns a matcher that matches {@link RecyclerView}s has exactly item counts 83 | */ 84 | public static Matcher hasItemCount(int count) { 85 | return new TypeSafeMatcher() { 86 | @Override 87 | public void describeTo(Description description) { 88 | description.appendText("has items"); 89 | } 90 | 91 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 92 | @Override 93 | public boolean matchesSafely(View view) { 94 | try { 95 | return ((RecyclerView) view).getAdapter().getItemCount() == count; 96 | } catch (Exception exception) { 97 | return false; 98 | } 99 | } 100 | }; 101 | } 102 | 103 | /** 104 | * Perform action of waiting for a specific view text. 105 | */ 106 | public static void waitText(final String viewText, final long millis) throws Exception { 107 | Thread.sleep(millis); 108 | onView(withText(viewText)).check(matches(isDisplayed())); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /app/src/androidTest/java/apidez/com/databinding/view/activity/PurchaseActivityIntegrateTest.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.view.activity; 2 | 3 | import android.content.Intent; 4 | import android.support.test.rule.ActivityTestRule; 5 | import android.support.test.runner.AndroidJUnit4; 6 | import android.test.suitebuilder.annotation.MediumTest; 7 | 8 | import com.google.gson.Gson; 9 | 10 | import org.junit.Before; 11 | import org.junit.Rule; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | 15 | import apidez.com.databinding.ComponentBuilder; 16 | import apidez.com.databinding.R; 17 | import apidez.com.databinding.dependency.component.AppComponent; 18 | import apidez.com.databinding.dependency.component.PurchaseComponent; 19 | import apidez.com.databinding.dependency.module.PurchaseModule; 20 | import apidez.com.databinding.model.api.IPurchaseApi; 21 | import apidez.com.databinding.utils.ApplicationUtils; 22 | import apidez.com.databinding.utils.UiUtils; 23 | import rx.Observable; 24 | 25 | import static android.support.test.espresso.Espresso.onView; 26 | import static android.support.test.espresso.action.ViewActions.click; 27 | import static android.support.test.espresso.action.ViewActions.typeText; 28 | import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; 29 | import static android.support.test.espresso.assertion.ViewAssertions.matches; 30 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 31 | import static android.support.test.espresso.matcher.ViewMatchers.withId; 32 | import static android.support.test.espresso.matcher.ViewMatchers.withText; 33 | import static apidez.com.databinding.utils.MatcherEx.isVisible; 34 | import static apidez.com.databinding.utils.MatcherEx.waitText; 35 | import static org.hamcrest.Matchers.not; 36 | 37 | /** 38 | * Created by nongdenchet on 10/3/15. 39 | */ 40 | 41 | /** 42 | * Test the whole flow mocking long running task 43 | */ 44 | @MediumTest 45 | @RunWith(AndroidJUnit4.class) 46 | public class PurchaseActivityIntegrateTest { 47 | private String FAIL_CARD = "222222222222"; 48 | 49 | @Rule 50 | public ActivityTestRule activityTestRule = 51 | new ActivityTestRule<>(PurchaseActivity.class, true, false); 52 | 53 | @Before 54 | public void setUp() throws Exception { 55 | PurchaseModule mockModule = new PurchaseModule() { 56 | @Override 57 | public IPurchaseApi providePurchaseApi(Gson gson) { 58 | return (creditCard, email) -> Observable.create(subscriber -> { 59 | try { 60 | Thread.sleep(2000); 61 | if (creditCard.equals(FAIL_CARD)) 62 | throw new Exception("Invalid card"); 63 | subscriber.onNext(true); 64 | subscriber.onCompleted(); 65 | } catch (Exception ex) { 66 | ex.printStackTrace(); 67 | subscriber.onError(ex); 68 | } 69 | }); 70 | } 71 | }; 72 | 73 | // Setup test component 74 | AppComponent component = ApplicationUtils.application().component(); 75 | ApplicationUtils.application().setComponentBuilder(new ComponentBuilder(component) { 76 | @Override 77 | public PurchaseComponent purchaseComponent() { 78 | return component.plus(mockModule); 79 | } 80 | }); 81 | 82 | // Run the activity 83 | activityTestRule.launchActivity(new Intent()); 84 | } 85 | 86 | @Test 87 | public void noErrorAtTheBeginning() throws Exception { 88 | onView(withText(R.string.error_credit_card)).check(doesNotExist()); 89 | onView(withText(R.string.error_email)).check(doesNotExist()); 90 | } 91 | 92 | @Test 93 | public void hasNoErrorCreditCard() throws Exception { 94 | onView((withId(R.id.creditCard))).perform(typeText("411111111111")); 95 | Thread.sleep(1000); 96 | onView(withText(R.string.error_credit_card)).check(matches(not(isVisible()))); 97 | } 98 | 99 | @Test 100 | public void hasErrorCreditCard() throws Exception { 101 | onView(withId(R.id.creditCard)).perform(typeText("abcdefabcdef")); 102 | onView(withText(R.string.error_credit_card)).check(matches(isDisplayed())); 103 | onView(withText(R.string.error_credit_card)).check(matches(isVisible())); 104 | } 105 | 106 | @Test 107 | public void hasNoErrorEmail() throws Exception { 108 | onView(withId(R.id.email)).perform(typeText("abc@abc.com")); 109 | Thread.sleep(1000); 110 | onView(withText(R.string.error_email)).check(matches(not(isVisible()))); 111 | } 112 | 113 | @Test 114 | public void hasErrorEmail() throws Exception { 115 | onView(withId(R.id.email)).perform(typeText("abc___#!@...com")); 116 | onView(withText(R.string.error_email)).check(matches(isDisplayed())); 117 | onView(withText(R.string.error_email)).check(matches(isVisible())); 118 | } 119 | 120 | @Test 121 | public void cannotSubmit() throws Exception { 122 | onView(withId(R.id.creditCard)).perform(typeText("411111111111")); 123 | onView(withId(R.id.btnSubmit)).perform(click()); 124 | onView(withText(R.string.loading)).check(doesNotExist()); 125 | } 126 | 127 | @Test 128 | public void canSubmit() throws Exception { 129 | onView(withId(R.id.creditCard)).perform(typeText("411111111111")); 130 | onView(withId(R.id.email)).perform(typeText("rain@gmail.com")); 131 | onView(withId(R.id.btnSubmit)).perform(click()); 132 | waitText("Success", 3000); 133 | } 134 | 135 | @Test 136 | public void submitSuccess() throws Exception { 137 | onView(withId(R.id.creditCard)).perform(typeText("411111111111")); 138 | onView(withId(R.id.email)).perform(typeText("rain@gmail.com")); 139 | UiUtils.closeKeyboard(activityTestRule.getActivity()); 140 | onView(withId(R.id.btnSubmit)).perform(click()); 141 | waitText("Success", 3000); 142 | } 143 | 144 | @Test 145 | public void submitFail() throws Exception { 146 | onView(withId(R.id.creditCard)).perform(typeText(FAIL_CARD)); 147 | onView(withId(R.id.email)).perform(typeText("apidez@gmail.com")); 148 | UiUtils.closeKeyboard(activityTestRule.getActivity()); 149 | onView(withId(R.id.btnSubmit)).perform(click()); 150 | waitText("Error", 10000); // A little bit long because it has three retry 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /app/src/androidTest/java/apidez/com/databinding/view/fragment/PlacesFragmentIntegrateTest.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.view.fragment; 2 | 3 | import android.content.Intent; 4 | import android.support.test.rule.ActivityTestRule; 5 | import android.test.suitebuilder.annotation.MediumTest; 6 | 7 | import org.junit.Before; 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.junit.runners.JUnit4; 12 | import org.mockito.Mockito; 13 | 14 | import apidez.com.databinding.ComponentBuilder; 15 | import apidez.com.databinding.R; 16 | import apidez.com.databinding.dependency.component.AppComponent; 17 | import apidez.com.databinding.dependency.component.PlacesComponent; 18 | import apidez.com.databinding.dependency.module.PlacesModule; 19 | import apidez.com.databinding.model.api.IPlacesApi; 20 | import apidez.com.databinding.utils.ApplicationUtils; 21 | import apidez.com.databinding.utils.TestDataUtils; 22 | import apidez.com.databinding.view.activity.TestActivity; 23 | import rx.Observable; 24 | 25 | import static android.support.test.espresso.Espresso.onView; 26 | import static android.support.test.espresso.action.ViewActions.click; 27 | import static android.support.test.espresso.assertion.ViewAssertions.matches; 28 | import static android.support.test.espresso.matcher.ViewMatchers.withId; 29 | import static android.support.test.espresso.matcher.ViewMatchers.withText; 30 | import static apidez.com.databinding.utils.MatcherEx.hasItemCount; 31 | import static org.mockito.Mockito.when; 32 | 33 | /** 34 | * Created by nongdenchet on 10/22/15. 35 | */ 36 | @MediumTest 37 | @RunWith(JUnit4.class) 38 | public class PlacesFragmentIntegrateTest { 39 | 40 | @Rule 41 | public ActivityTestRule activityTestRule = 42 | new ActivityTestRule<>(TestActivity.class, true, false); 43 | 44 | @Before 45 | public void setUp() throws Exception { 46 | PlacesModule mockModule = new PlacesModule() { 47 | @Override 48 | public IPlacesApi providePlacesApi() { 49 | IPlacesApi placesApi = Mockito.mock(IPlacesApi.class); 50 | when(placesApi.placesResult()) 51 | .thenReturn(Observable.just(TestDataUtils.nearByData())); 52 | return placesApi; 53 | } 54 | }; 55 | 56 | // Setup test component 57 | AppComponent component = ApplicationUtils.application().component(); 58 | ApplicationUtils.application().setComponentBuilder(new ComponentBuilder(component) { 59 | @Override 60 | public PlacesComponent placesComponent() { 61 | return component.plus(mockModule); 62 | } 63 | }); 64 | 65 | activityTestRule.launchActivity(new Intent()); 66 | activityTestRule.getActivity() 67 | .getSupportFragmentManager() 68 | .beginTransaction() 69 | .replace(android.R.id.content, PlacesFragment.newInstance()) 70 | .commit(); 71 | 72 | // TODO: fix this problem 73 | // Sometime espresso cannot find the view with id: "R.id.action_sort" 74 | // Maybe the view has not been completed rendered 75 | // Currently put it to sleep 1000ms 76 | Thread.sleep(1000); 77 | } 78 | 79 | @Test 80 | public void testAllPlacesOnLayout() throws Exception { 81 | onView(withId(R.id.recycler_view)).check(matches(hasItemCount(10))); 82 | } 83 | 84 | @Test 85 | public void clickFood() throws Exception { 86 | onView(withId(R.id.action_sort)).perform(click()); 87 | onView(withText("Food")).perform(click()); 88 | onView(withId(R.id.recycler_view)).check(matches(hasItemCount(4))); 89 | } 90 | 91 | @Test 92 | public void clickCafe() throws Exception { 93 | onView(withId(R.id.action_sort)).perform(click()); 94 | onView(withText("Cafe")).perform(click()); 95 | onView(withId(R.id.recycler_view)).check(matches(hasItemCount(5))); 96 | } 97 | 98 | @Test 99 | public void clickStore() throws Exception { 100 | onView(withId(R.id.action_sort)).perform(click()); 101 | onView(withText("Store")).perform(click()); 102 | onView(withId(R.id.recycler_view)).check(matches(hasItemCount(4))); 103 | } 104 | 105 | @Test 106 | public void clickTheater() throws Exception { 107 | onView(withId(R.id.action_sort)).perform(click()); 108 | onView(withText("Theater")).perform(click()); 109 | onView(withId(R.id.recycler_view)).check(matches(hasItemCount(3))); 110 | } 111 | 112 | @Test 113 | public void clickRestaurant() throws Exception { 114 | onView(withId(R.id.action_sort)).perform(click()); 115 | onView(withText("Restaurant")).perform(click()); 116 | onView(withId(R.id.recycler_view)).check(matches(hasItemCount(3))); 117 | } 118 | 119 | @Test 120 | public void clickAll() throws Exception { 121 | onView(withId(R.id.action_sort)).perform(click()); 122 | onView(withText("All")).perform(click()); 123 | onView(withId(R.id.recycler_view)).check(matches(hasItemCount(10))); 124 | } 125 | 126 | @Test 127 | public void clickRestaurantAfterCafe() throws Exception { 128 | onView(withId(R.id.action_sort)).perform(click()); 129 | onView(withText("Cafe")).perform(click()); 130 | onView(withId(R.id.action_sort)).perform(click()); 131 | onView(withText("Restaurant")).perform(click()); 132 | onView(withId(R.id.recycler_view)).check(matches(hasItemCount(3))); 133 | } 134 | 135 | @Test 136 | public void clickAllAfterCafe() throws Exception { 137 | onView(withId(R.id.action_sort)).perform(click()); 138 | onView(withText("Cafe")).perform(click()); 139 | onView(withId(R.id.action_sort)).perform(click()); 140 | onView(withText("All")).perform(click()); 141 | onView(withId(R.id.recycler_view)).check(matches(hasItemCount(10))); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/ComponentBuilder.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding; 2 | 3 | 4 | /** 5 | * Created by nongdenchet on 10/24/15. 6 | */ 7 | 8 | import apidez.com.databinding.dependency.component.AppComponent; 9 | import apidez.com.databinding.dependency.component.PlacesComponent; 10 | import apidez.com.databinding.dependency.component.PurchaseComponent; 11 | import apidez.com.databinding.dependency.module.PlacesModule; 12 | import apidez.com.databinding.dependency.module.PurchaseModule; 13 | 14 | /** 15 | * Use to build subcomponent 16 | */ 17 | public class ComponentBuilder { 18 | private AppComponent appComponent; 19 | 20 | public ComponentBuilder(AppComponent appComponent) { 21 | this.appComponent = appComponent; 22 | } 23 | 24 | public PlacesComponent placesComponent() { 25 | return appComponent.plus(new PlacesModule()); 26 | } 27 | 28 | public PurchaseComponent purchaseComponent() { 29 | return appComponent.plus(new PurchaseModule()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/MyApplication.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import apidez.com.databinding.dependency.component.AppComponent; 7 | import apidez.com.databinding.dependency.component.DaggerAppComponent; 8 | import apidez.com.databinding.dependency.module.AppModule; 9 | 10 | 11 | /** 12 | * Created by nongdenchet on 10/2/15. 13 | */ 14 | public class MyApplication extends Application { 15 | private static Context mContext; 16 | protected AppComponent mAppComponent; 17 | protected ComponentBuilder mComponentBuilder; 18 | 19 | @Override 20 | public void onCreate() { 21 | super.onCreate(); 22 | mContext = this; 23 | 24 | // Create app component 25 | mAppComponent = DaggerAppComponent.builder() 26 | .appModule(new AppModule()) 27 | .build(); 28 | 29 | // Create component builder 30 | mComponentBuilder = new ComponentBuilder(mAppComponent); 31 | } 32 | 33 | public AppComponent component() { 34 | return mAppComponent; 35 | } 36 | 37 | public ComponentBuilder builder() { 38 | return mComponentBuilder; 39 | } 40 | 41 | public static Context context() { 42 | return mContext; 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/dependency/component/AppComponent.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.dependency.component; 2 | 3 | import javax.inject.Singleton; 4 | 5 | import apidez.com.databinding.dependency.module.AppModule; 6 | import apidez.com.databinding.dependency.module.PlacesModule; 7 | import apidez.com.databinding.dependency.module.PurchaseModule; 8 | import dagger.Component; 9 | 10 | /** 11 | * Created by nongdenchet on 10/2/15. 12 | */ 13 | @Singleton 14 | @Component(modules = {AppModule.class}) 15 | public interface AppComponent { 16 | PurchaseComponent plus(PurchaseModule purchaseModule); 17 | PlacesComponent plus(PlacesModule placesModule); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/dependency/component/PlacesComponent.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.dependency.component; 2 | 3 | import apidez.com.databinding.dependency.module.PlacesModule; 4 | import apidez.com.databinding.dependency.scope.ViewScope; 5 | import apidez.com.databinding.view.fragment.PlacesFragment; 6 | import dagger.Subcomponent; 7 | 8 | /** 9 | * Created by nongdenchet on 10/24/15. 10 | */ 11 | @ViewScope 12 | @Subcomponent(modules = {PlacesModule.class}) 13 | public interface PlacesComponent { 14 | void inject(PlacesFragment placesFragment); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/dependency/component/PurchaseComponent.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.dependency.component; 2 | 3 | import apidez.com.databinding.dependency.module.PurchaseModule; 4 | import apidez.com.databinding.dependency.scope.ViewScope; 5 | import apidez.com.databinding.view.activity.PurchaseActivity; 6 | import dagger.Subcomponent; 7 | 8 | /** 9 | * Created by nongdenchet on 10/24/15. 10 | */ 11 | @ViewScope 12 | @Subcomponent(modules = {PurchaseModule.class}) 13 | public interface PurchaseComponent { 14 | void inject(PurchaseActivity purchaseActivity); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/dependency/module/AppModule.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.dependency.module; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import javax.inject.Singleton; 6 | 7 | import apidez.com.databinding.utils.RxUtils; 8 | import dagger.Module; 9 | import dagger.Provides; 10 | import rx.Scheduler; 11 | import rx.android.schedulers.AndroidSchedulers; 12 | import rx.schedulers.Schedulers; 13 | 14 | /** 15 | * Created by nongdenchet on 10/3/15. 16 | */ 17 | @Module 18 | public class AppModule { 19 | @Singleton 20 | @Provides 21 | public Gson provideGson() { 22 | return new Gson(); 23 | } 24 | 25 | @Provides 26 | public RxUtils.SchedulerHolder provideSchedulerHolder() { 27 | return new RxUtils.SchedulerHolder(AndroidSchedulers.mainThread(), Schedulers.io()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/dependency/module/PlacesModule.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.dependency.module; 2 | 3 | import apidez.com.databinding.dependency.scope.ViewScope; 4 | import apidez.com.databinding.model.api.IPlacesApi; 5 | import apidez.com.databinding.utils.RetrofitUtils; 6 | import apidez.com.databinding.utils.RxUtils; 7 | import apidez.com.databinding.viewmodel.IPlacesViewModel; 8 | import apidez.com.databinding.viewmodel.PlacesViewModel; 9 | import dagger.Module; 10 | import dagger.Provides; 11 | import rx.Scheduler; 12 | 13 | /** 14 | * Created by nongdenchet on 10/21/15. 15 | */ 16 | @Module 17 | public class PlacesModule { 18 | @Provides 19 | @ViewScope 20 | public IPlacesApi providePlacesApi() { 21 | return RetrofitUtils.create(IPlacesApi.class, "https://maps.googleapis.com/maps/api/place/"); 22 | } 23 | 24 | @Provides 25 | @ViewScope 26 | public IPlacesViewModel providePlacesViewModel(IPlacesApi placesApi, RxUtils.SchedulerHolder schedulerHolder) { 27 | return new PlacesViewModel(placesApi, schedulerHolder); 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/dependency/module/PurchaseModule.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.dependency.module; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import apidez.com.databinding.dependency.scope.ViewScope; 6 | import apidez.com.databinding.model.api.IPurchaseApi; 7 | import apidez.com.databinding.model.api.PurchaseApi; 8 | import apidez.com.databinding.viewmodel.IPurchaseViewModel; 9 | import apidez.com.databinding.viewmodel.PurchaseViewModel; 10 | import dagger.Module; 11 | import dagger.Provides; 12 | 13 | /** 14 | * Created by nongdenchet on 10/2/15. 15 | */ 16 | @Module 17 | public class PurchaseModule { 18 | @Provides 19 | @ViewScope 20 | public IPurchaseApi providePurchaseApi(Gson gson) { 21 | return new PurchaseApi(gson); 22 | } 23 | 24 | @Provides 25 | @ViewScope 26 | public IPurchaseViewModel providePurchaseViewModel(IPurchaseApi purchaseApi) { 27 | return new PurchaseViewModel(purchaseApi); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/dependency/scope/ViewScope.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.dependency.scope; 2 | 3 | import javax.inject.Scope; 4 | 5 | /** 6 | * Created by nongdenchet on 10/24/15. 7 | */ 8 | @Scope 9 | public @interface ViewScope { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/model/api/IPlacesApi.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.model.api; 2 | 3 | import apidez.com.databinding.model.entity.GoogleSearchResult; 4 | import retrofit.http.GET; 5 | import rx.Observable; 6 | 7 | /** 8 | * Created by nongdenchet on 10/21/15. 9 | */ 10 | public interface IPlacesApi { 11 | @GET("nearbysearch/json?location=10.7864422,106.677516&radius=500&types=food&key=AIzaSyBk3A8Q3pqVWYYmZhODbE-D2lf2ZHEoKuo") 12 | Observable placesResult(); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/model/api/IPurchaseApi.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.model.api; 2 | 3 | import rx.Observable; 4 | 5 | /** 6 | * Created by nongdenchet on 10/24/15. 7 | */ 8 | public interface IPurchaseApi { 9 | Observable submitPurchase(String creditCard, String email); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/model/api/PurchaseApi.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.model.api; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.google.gson.Gson; 6 | 7 | import apidez.com.databinding.model.entity.Purchase; 8 | import rx.Observable; 9 | 10 | /** 11 | * Created by nongdenchet on 10/3/15. 12 | */ 13 | public class PurchaseApi implements IPurchaseApi { 14 | 15 | private Gson mGson; 16 | 17 | public PurchaseApi(@NonNull Gson gson) { 18 | mGson = gson; 19 | } 20 | 21 | /** 22 | * Fake networking 23 | */ 24 | public Observable submitPurchase(String creditCard, String email) { 25 | Purchase purchase = new Purchase(creditCard, email); 26 | return Observable.create(subscriber -> { 27 | try { 28 | String json = mGson.toJson(purchase); 29 | Thread.sleep((json.length() % 3) * 1000); 30 | if (json.length() % 3 == 2) throw new Exception("fail"); 31 | subscriber.onNext(true); 32 | subscriber.onCompleted(); 33 | } catch (Exception e) { 34 | subscriber.onError(e); 35 | } 36 | }); 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/model/entity/GoogleSearchResult.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.model.entity; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by nongdenchet on 10/21/15. 9 | */ 10 | public class GoogleSearchResult { 11 | 12 | @SerializedName("status") 13 | public String status; 14 | 15 | @SerializedName("results") 16 | public List results; 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/model/entity/Place.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.model.entity; 2 | 3 | import android.databinding.BaseObservable; 4 | import android.databinding.Bindable; 5 | import android.os.Parcel; 6 | import android.os.Parcelable; 7 | 8 | import com.google.gson.annotations.SerializedName; 9 | 10 | import java.util.List; 11 | import java.util.UUID; 12 | 13 | /** 14 | * Created by nongdenchet on 10/21/15. 15 | */ 16 | public class Place extends BaseObservable { 17 | 18 | @SerializedName("icon") 19 | private String icon; 20 | 21 | @SerializedName("place_id") 22 | private String id; 23 | 24 | @SerializedName("name") 25 | private String name; 26 | 27 | @SerializedName("types") 28 | private List types; 29 | 30 | private Place(String icon, String id, String name, List types) { 31 | this.icon = icon; 32 | this.id = id; 33 | this.name = name; 34 | this.types = types; 35 | } 36 | 37 | @Bindable 38 | public String getIcon() { 39 | return icon; 40 | } 41 | 42 | public String getId() { 43 | return id; 44 | } 45 | 46 | @Bindable 47 | public String getName() { 48 | return name; 49 | } 50 | 51 | public List getTypes() { 52 | return types; 53 | } 54 | 55 | public static class Builder { 56 | private String id = UUID.randomUUID().toString(); 57 | private String icon; 58 | private String name; 59 | private List types; 60 | 61 | public Builder name(String name) { 62 | this.name = name; 63 | return this; 64 | } 65 | 66 | public Builder icon(String icon) { 67 | this.icon = icon; 68 | return this; 69 | } 70 | 71 | public Builder types(List types) { 72 | this.types = types; 73 | return this; 74 | } 75 | 76 | public Place build() { 77 | return new Place(icon, id, name, types); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/model/entity/Purchase.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.model.entity; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by nongdenchet on 10/2/15. 7 | */ 8 | public class Purchase { 9 | 10 | @SerializedName("creditCard") 11 | private String creditCard; 12 | 13 | @SerializedName("email") 14 | private String email; 15 | 16 | public Purchase() { 17 | } 18 | 19 | public Purchase(String creditCard, String email) { 20 | this.creditCard = creditCard; 21 | this.email = email; 22 | } 23 | 24 | public String getCreditCard() { 25 | return creditCard; 26 | } 27 | 28 | public void setCreditCard(String creditCard) { 29 | this.creditCard = creditCard; 30 | } 31 | 32 | public String getEmail() { 33 | return email; 34 | } 35 | 36 | public void setEmail(String email) { 37 | this.email = email; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/utils/BindingUtils.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.utils; 2 | 3 | import android.databinding.BindingAdapter; 4 | import android.databinding.BindingConversion; 5 | import android.graphics.drawable.Drawable; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.widget.ImageView; 8 | 9 | import com.squareup.picasso.Picasso; 10 | import com.squareup.picasso.RequestCreator; 11 | 12 | import java.util.List; 13 | 14 | import apidez.com.databinding.MyApplication; 15 | import apidez.com.databinding.view.adapter.BaseRecyclerViewAdapter; 16 | 17 | /** 18 | * Created by nongdenchet on 10/29/15. 19 | */ 20 | public class BindingUtils { 21 | @BindingConversion 22 | public static String convertIdToString(int stringId) { 23 | return MyApplication.context().getString(stringId); 24 | } 25 | 26 | @BindingAdapter(value = {"android:src", "placeHolder"}) 27 | public static void setImageUrl(ImageView view, String url, Drawable placeHolder) { 28 | RequestCreator requestCreator = Picasso.with(view.getContext()).load(url); 29 | requestCreator.placeholder(placeHolder); 30 | requestCreator.into(view); 31 | } 32 | 33 | @SuppressWarnings("unchecked") 34 | @BindingAdapter("items") 35 | public static void setItems(RecyclerView recyclerView, List items) { 36 | BaseRecyclerViewAdapter adapter = (BaseRecyclerViewAdapter) recyclerView.getAdapter(); 37 | if (adapter != null) adapter.setItems(items); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/utils/NumericUtils.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.utils; 2 | 3 | /** 4 | * Created by nongdenchet on 10/3/15. 5 | */ 6 | public class NumericUtils { 7 | /** 8 | * Check if a string is Integer 9 | */ 10 | public static boolean isNumeric(String number) { 11 | return number.matches("-?\\d+(\\.\\d+)?"); 12 | } 13 | 14 | /** 15 | * Check if a string is Integer 16 | */ 17 | public static boolean isNumeric(CharSequence number) { 18 | return isNumeric(number.toString()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/utils/RetrofitUtils.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.utils; 2 | 3 | import retrofit.GsonConverterFactory; 4 | import retrofit.Retrofit; 5 | import retrofit.RxJavaCallAdapterFactory; 6 | 7 | /** 8 | * Created by nongdenchet on 10/21/15. 9 | */ 10 | public class RetrofitUtils { 11 | public static T create(final Class clazz, final String endPoint) { 12 | final Retrofit restAdapter = new Retrofit.Builder() 13 | .baseUrl(endPoint) 14 | .addConverterFactory(GsonConverterFactory.create()) 15 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 16 | .build(); 17 | return restAdapter.create(clazz); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/utils/RxUtils.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.utils; 2 | 3 | import rx.Scheduler; 4 | 5 | /** 6 | * Created by nongdenchet on 1/17/16. 7 | */ 8 | public class RxUtils { 9 | 10 | public static class SchedulerHolder { 11 | public Scheduler mainScheduler; 12 | public Scheduler ioScheduler; 13 | 14 | public SchedulerHolder(Scheduler mainScheduler, Scheduler ioScheduler) { 15 | this.mainScheduler = mainScheduler; 16 | this.ioScheduler = ioScheduler; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.utils; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * Created by nongdenchet on 10/21/15. 7 | */ 8 | public class StringUtils { 9 | 10 | /** 11 | * Generate random string 12 | */ 13 | public static String generateString(String characters, int length) { 14 | Random rand = new Random(); 15 | char[] text = new char[length]; 16 | for (int i = 0; i < length; i++) { 17 | text[i] = characters.charAt(rand.nextInt(characters.length())); 18 | } 19 | return new String(text); 20 | } 21 | 22 | /** 23 | * Check empty string 24 | */ 25 | public static boolean isEmpty(String string) { 26 | return string == null || string.isEmpty(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/utils/TestDataUtils.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.utils; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | import apidez.com.databinding.model.entity.GoogleSearchResult; 8 | import apidez.com.databinding.model.entity.Place; 9 | 10 | /** 11 | * Created by nongdenchet on 10/22/15. 12 | */ 13 | public class TestDataUtils { 14 | 15 | /** 16 | * Google search nearby test data 17 | */ 18 | public static GoogleSearchResult nearByData() { 19 | List places = new ArrayList<>(); 20 | places.add(new Place.Builder().icon("url").name("A").types(Arrays.asList("food", "cafe")).build()); 21 | places.add(new Place.Builder().name("B").types(Arrays.asList("food", "movie_theater")).build()); 22 | places.add(new Place.Builder().name("C").types(Arrays.asList("store")).build()); 23 | places.add(new Place.Builder().name("D").types(Arrays.asList("store")).build()); 24 | places.add(new Place.Builder().name("E").types(Arrays.asList("cafe")).build()); 25 | places.add(new Place.Builder().name("F").types(Arrays.asList("food", "store", "cafe", "movie_theater")).build()); 26 | places.add(new Place.Builder().name("G").types(Arrays.asList("restaurant", "store")).build()); 27 | places.add(new Place.Builder().name("H").types(Arrays.asList("restaurant", "cafe")).build()); 28 | places.add(new Place.Builder().name("I").types(Arrays.asList("restaurant")).build()); 29 | places.add(new Place.Builder().name("K").types(Arrays.asList("movie_theater", "cafe", "food")).build()); 30 | GoogleSearchResult googleSearchResult = new GoogleSearchResult(); 31 | googleSearchResult.status = "OK"; 32 | googleSearchResult.results = places; 33 | return googleSearchResult; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/utils/TextWatcherAdapter.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.utils; 2 | 3 | import android.text.Editable; 4 | import android.text.TextWatcher; 5 | 6 | /** 7 | * Created by nongdenchet on 10/28/15. 8 | */ 9 | public class TextWatcherAdapter implements TextWatcher { 10 | @Override 11 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 12 | 13 | } 14 | 15 | @Override 16 | public void onTextChanged(CharSequence s, int start, int before, int count) { 17 | 18 | } 19 | 20 | @Override 21 | public void afterTextChanged(Editable s) { 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/utils/UiUtils.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.utils; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.support.v7.app.AlertDialog; 6 | import android.view.View; 7 | import android.view.inputmethod.InputMethodManager; 8 | 9 | /** 10 | * Created by nongdenchet on 10/2/15. 11 | */ 12 | public class UiUtils { 13 | public static void showDialog(String text, Context context) { 14 | new AlertDialog.Builder(context) 15 | .setMessage(text) 16 | .setPositiveButton(android.R.string.yes, (dialog, which) -> { 17 | }) 18 | .show(); 19 | } 20 | 21 | public static void closeKeyboard(Activity context) { 22 | // Check if no view has focus: 23 | View view = context.getCurrentFocus(); 24 | if (view != null) { 25 | InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 26 | imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/view/activity/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.view.activity; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | 5 | import rx.subjects.BehaviorSubject; 6 | 7 | /** 8 | * Created by nongdenchet on 10/1/15. 9 | */ 10 | public abstract class BaseActivity extends AppCompatActivity { 11 | 12 | private final BehaviorSubject preDestroy = BehaviorSubject.create(); 13 | 14 | protected BehaviorSubject preDestroy() { 15 | return preDestroy; 16 | } 17 | 18 | @Override 19 | protected void onDestroy() { 20 | preDestroy.onNext(this); 21 | super.onDestroy(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/view/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.view.activity; 2 | 3 | import android.content.Intent; 4 | import android.databinding.DataBindingUtil; 5 | import android.os.Bundle; 6 | import android.view.MenuItem; 7 | import android.view.View; 8 | 9 | import apidez.com.databinding.R; 10 | import apidez.com.databinding.databinding.ActivityMainBinding; 11 | import apidez.com.databinding.view.handler.IMainHandler; 12 | 13 | /** 14 | * Created by nongdenchet on 10/30/15. 15 | */ 16 | public class MainActivity extends BaseActivity implements IMainHandler { 17 | private ActivityMainBinding binding; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | binding = DataBindingUtil.setContentView(this, R.layout.activity_main); 23 | binding.setHandler(this); 24 | setSupportActionBar(binding.toolbar); 25 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 26 | } 27 | 28 | @Override 29 | public View.OnClickListener formSample() { 30 | return v -> startActivity(new Intent(this, PurchaseActivity.class)); 31 | } 32 | 33 | @Override 34 | public View.OnClickListener recyclerSample() { 35 | return v -> startActivity(new Intent(this, PlacesActivity.class)); 36 | } 37 | 38 | @Override 39 | public boolean onOptionsItemSelected(MenuItem item) { 40 | switch (item.getItemId()) { 41 | case android.R.id.home: 42 | onBackPressed(); 43 | return true; 44 | } 45 | return super.onOptionsItemSelected(item); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/view/activity/PlacesActivity.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.view.activity; 2 | 3 | /** 4 | * Created by nongdenchet on 10/21/15. 5 | */ 6 | 7 | import android.os.Bundle; 8 | 9 | import apidez.com.databinding.R; 10 | import apidez.com.databinding.view.fragment.PlacesFragment; 11 | 12 | /** 13 | * User this for testing purpose only 14 | */ 15 | public class PlacesActivity extends BaseActivity { 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_places); 20 | getSupportFragmentManager() 21 | .beginTransaction() 22 | .replace(android.R.id.content, PlacesFragment.newInstance()) 23 | .commit(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/view/activity/PurchaseActivity.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.view.activity; 2 | 3 | import android.app.ProgressDialog; 4 | import android.databinding.DataBindingUtil; 5 | import android.os.Bundle; 6 | import android.text.Editable; 7 | import android.text.TextWatcher; 8 | import android.util.Log; 9 | import android.view.MenuItem; 10 | import android.view.View; 11 | 12 | import javax.inject.Inject; 13 | 14 | import apidez.com.databinding.MyApplication; 15 | import apidez.com.databinding.R; 16 | import apidez.com.databinding.databinding.ActivityPurchaseBinding; 17 | import apidez.com.databinding.utils.TextWatcherAdapter; 18 | import apidez.com.databinding.utils.UiUtils; 19 | import apidez.com.databinding.view.handler.IPurchaseHandler; 20 | import apidez.com.databinding.viewmodel.IPurchaseViewModel; 21 | import rx.android.schedulers.AndroidSchedulers; 22 | import rx.schedulers.Schedulers; 23 | 24 | 25 | /** 26 | * Created by nongdenchet on 10/28/15. 27 | */ 28 | public class PurchaseActivity extends BaseActivity implements IPurchaseHandler { 29 | private ProgressDialog mProgressDialog; 30 | private ActivityPurchaseBinding binding; 31 | 32 | @Inject 33 | IPurchaseViewModel mViewModel; 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | setContentView(R.layout.activity_purchase); 39 | 40 | // Setup DI 41 | ((MyApplication) getApplication()) 42 | .builder() 43 | .purchaseComponent() 44 | .inject(this); 45 | 46 | // bind viewmodel 47 | bindViewModel(); 48 | 49 | // setup other views 50 | setUpView(); 51 | } 52 | 53 | private void bindViewModel() { 54 | binding = DataBindingUtil.setContentView(this, R.layout.activity_purchase); 55 | binding.setViewModel(mViewModel); 56 | binding.setHandler(this); 57 | } 58 | 59 | private void setUpView() { 60 | // Toolbar 61 | setSupportActionBar(binding.toolbar); 62 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 63 | 64 | // Progress dialog 65 | mProgressDialog = new ProgressDialog(this); 66 | mProgressDialog.setMessage(getString(R.string.loading)); 67 | mProgressDialog.setCancelable(false); 68 | } 69 | 70 | @Override 71 | public TextWatcher emailWatcher() { 72 | return new TextWatcherAdapter() { 73 | @Override 74 | public void afterTextChanged(Editable s) { 75 | mViewModel.setEmail(s.toString()); 76 | } 77 | }; 78 | } 79 | 80 | @Override 81 | public TextWatcher creditCardWatcher() { 82 | return new TextWatcherAdapter() { 83 | @Override 84 | public void afterTextChanged(Editable s) { 85 | mViewModel.setCreditCard(s.toString()); 86 | } 87 | }; 88 | } 89 | 90 | @Override 91 | public View.OnClickListener onSubmit() { 92 | return v -> mViewModel.submit() 93 | .subscribeOn(Schedulers.io()) 94 | .observeOn(AndroidSchedulers.mainThread()) 95 | .takeUntil(preDestroy()) 96 | .doOnSubscribe(mProgressDialog::show) 97 | .subscribe(success -> { 98 | UiUtils.showDialog(getString(R.string.success), this); 99 | mProgressDialog.hide(); 100 | }, throwable -> { 101 | UiUtils.showDialog(getString(R.string.error), this); 102 | mProgressDialog.hide(); 103 | }); 104 | } 105 | 106 | @Override 107 | public View.OnClickListener onInvalid() { 108 | return v -> { 109 | // Do something 110 | Log.d("APP", "Invalid"); 111 | }; 112 | } 113 | 114 | @Override 115 | public boolean onOptionsItemSelected(MenuItem item) { 116 | switch (item.getItemId()) { 117 | case android.R.id.home: 118 | onBackPressed(); 119 | return true; 120 | } 121 | return super.onOptionsItemSelected(item); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/view/activity/TestActivity.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.view.activity; 2 | 3 | /** 4 | * Created by nongdenchet on 10/30/15. 5 | */ 6 | 7 | /** 8 | * Using for testing fragment 9 | */ 10 | public class TestActivity extends BaseActivity { 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/view/adapter/BaseRecyclerViewAdapter.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.view.adapter; 2 | 3 | import android.databinding.ObservableList; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.support.v7.widget.RecyclerView; 7 | 8 | import java.lang.ref.WeakReference; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by nongdenchet on 10/29/15. 13 | */ 14 | public abstract class BaseRecyclerViewAdapter extends RecyclerView.Adapter { 15 | protected List mItems; 16 | private int mRecyclerViewRefCount = 0; 17 | private final WeakReferenceOnListChangedCallback callback = new WeakReferenceOnListChangedCallback<>(this); 18 | 19 | public void setItems(List items) { 20 | if (this.mItems == items) { 21 | return; 22 | } 23 | if (mRecyclerViewRefCount > 0) { 24 | if (this.mItems instanceof ObservableList) { 25 | ((ObservableList) this.mItems).removeOnListChangedCallback(callback); 26 | } 27 | if (items instanceof ObservableList) { 28 | ((ObservableList) items).addOnListChangedCallback(callback); 29 | } 30 | } 31 | mItems = items; 32 | notifyDataSetChanged(); 33 | } 34 | 35 | @Override 36 | public void onAttachedToRecyclerView(RecyclerView recyclerView) { 37 | if (mRecyclerViewRefCount == 0 && mItems != null && mItems instanceof ObservableList) { 38 | ((ObservableList) mItems).addOnListChangedCallback(callback); 39 | } 40 | mRecyclerViewRefCount += 1; 41 | } 42 | 43 | @Override 44 | public void onDetachedFromRecyclerView(RecyclerView recyclerView) { 45 | mRecyclerViewRefCount -= 1; 46 | if (mRecyclerViewRefCount == 0 && mItems != null && mItems instanceof ObservableList) { 47 | ((ObservableList) mItems).removeOnListChangedCallback(callback); 48 | } 49 | } 50 | 51 | private static class WeakReferenceOnListChangedCallback extends ObservableList.OnListChangedCallback> { 52 | final WeakReference> adapterRef; 53 | private final Handler mHandler = new Handler(Looper.getMainLooper()); 54 | 55 | WeakReferenceOnListChangedCallback(BaseRecyclerViewAdapter adapter) { 56 | this.adapterRef = new WeakReference<>(adapter); 57 | } 58 | 59 | @Override 60 | public void onChanged(ObservableList sender) { 61 | BaseRecyclerViewAdapter adapter = adapterRef.get(); 62 | if (adapter == null) { 63 | return; 64 | } 65 | adapter.notifyDataSetChanged(); 66 | } 67 | 68 | @Override 69 | public void onItemRangeChanged(ObservableList sender, final int positionStart, final int itemCount) { 70 | BaseRecyclerViewAdapter adapter = adapterRef.get(); 71 | if (adapter == null) { 72 | return; 73 | } 74 | adapter.notifyItemRangeChanged(positionStart, itemCount); 75 | } 76 | 77 | @Override 78 | public void onItemRangeInserted(ObservableList sender, final int positionStart, final int itemCount) { 79 | BaseRecyclerViewAdapter adapter = adapterRef.get(); 80 | if (adapter == null) { 81 | return; 82 | } 83 | adapter.notifyItemRangeInserted(positionStart, itemCount); 84 | } 85 | 86 | @Override 87 | public void onItemRangeMoved(ObservableList sender, final int fromPosition, final int toPosition, final int itemCount) { 88 | BaseRecyclerViewAdapter adapter = adapterRef.get(); 89 | if (adapter == null) { 90 | return; 91 | } 92 | for (int i = 0; i < itemCount; i++) { 93 | adapter.notifyItemMoved(fromPosition + i, toPosition + i); 94 | } 95 | } 96 | 97 | @Override 98 | public void onItemRangeRemoved(ObservableList sender, final int positionStart, final int itemCount) { 99 | BaseRecyclerViewAdapter adapter = adapterRef.get(); 100 | if (adapter == null) { 101 | return; 102 | } 103 | adapter.notifyItemRangeRemoved(positionStart, itemCount); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/view/adapter/PlacesAdapter.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.view.adapter; 2 | 3 | import android.content.Context; 4 | import android.databinding.DataBindingUtil; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import apidez.com.databinding.BR; 14 | import apidez.com.databinding.R; 15 | import apidez.com.databinding.databinding.ItemPlaceBinding; 16 | import apidez.com.databinding.model.entity.Place; 17 | 18 | /** 19 | * Created by nongdenchet on 10/21/15. 20 | */ 21 | public class PlacesAdapter extends BaseRecyclerViewAdapter { 22 | private Context mContext; 23 | 24 | public PlacesAdapter(Context context) { 25 | mContext = context; 26 | mItems = new ArrayList<>(); 27 | } 28 | 29 | @Override 30 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 31 | ItemPlaceBinding binding = DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout.item_place, parent, false); 32 | return new PlaceViewHolder(binding.getRoot()); 33 | } 34 | 35 | @Override 36 | public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) { 37 | PlaceViewHolder holder = (PlaceViewHolder) viewHolder; 38 | Place place = mItems.get(position); 39 | holder.getBinding().setVariable(BR.place, place); 40 | holder.getBinding().executePendingBindings(); 41 | holder.itemView.setOnClickListener(v -> { 42 | }); 43 | } 44 | 45 | @Override 46 | public int getItemCount() { 47 | return mItems.size(); 48 | } 49 | 50 | // this view holder hold the view of one particular card 51 | public static class PlaceViewHolder extends RecyclerView.ViewHolder { 52 | private ItemPlaceBinding binding; 53 | 54 | public PlaceViewHolder(View rowView) { 55 | super(rowView); 56 | binding = DataBindingUtil.bind(rowView); 57 | } 58 | 59 | public ItemPlaceBinding getBinding() { 60 | return binding; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/view/fragment/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.view.fragment; 2 | 3 | import android.support.v4.app.Fragment; 4 | 5 | import rx.subjects.BehaviorSubject; 6 | 7 | /** 8 | * Created by nongdenchet on 10/21/15. 9 | */ 10 | public abstract class BaseFragment extends Fragment { 11 | private final BehaviorSubject preDestroy = BehaviorSubject.create(); 12 | 13 | protected BehaviorSubject preDestroy() { 14 | return preDestroy; 15 | } 16 | 17 | @Override 18 | public void onDestroyView() { 19 | preDestroy.onNext(this); 20 | super.onDestroyView(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/view/fragment/PlacesFragment.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.view.fragment; 2 | 3 | import android.app.ProgressDialog; 4 | import android.databinding.DataBindingUtil; 5 | import android.os.Bundle; 6 | import android.support.v4.widget.SwipeRefreshLayout; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | import android.view.LayoutInflater; 10 | import android.view.Menu; 11 | import android.view.MenuInflater; 12 | import android.view.MenuItem; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.Toast; 16 | 17 | import javax.inject.Inject; 18 | 19 | import apidez.com.databinding.MyApplication; 20 | import apidez.com.databinding.R; 21 | import apidez.com.databinding.databinding.FragmentPlacesBinding; 22 | import apidez.com.databinding.view.adapter.PlacesAdapter; 23 | import apidez.com.databinding.viewmodel.IPlacesViewModel; 24 | import rx.android.schedulers.AndroidSchedulers; 25 | import rx.functions.Action0; 26 | import rx.functions.Action1; 27 | import rx.schedulers.Schedulers; 28 | 29 | public class PlacesFragment extends BaseFragment { 30 | private FragmentPlacesBinding binding; 31 | private ProgressDialog mProgressDialog; 32 | 33 | @Inject 34 | IPlacesViewModel mViewModel; 35 | 36 | public static PlacesFragment newInstance() { 37 | PlacesFragment fragment = new PlacesFragment(); 38 | Bundle args = new Bundle(); 39 | fragment.setArguments(args); 40 | return fragment; 41 | } 42 | 43 | public PlacesFragment() { 44 | } 45 | 46 | @Override 47 | public void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | setHasOptionsMenu(true); 50 | 51 | // Setup DI 52 | ((MyApplication) getActivity().getApplication()) 53 | .builder() 54 | .placesComponent() 55 | .inject(this); 56 | } 57 | 58 | @Override 59 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 60 | binding = DataBindingUtil.inflate(inflater, R.layout.fragment_places, container, false); 61 | binding.setViewModel(mViewModel); 62 | setupView(); 63 | return binding.getRoot(); 64 | } 65 | 66 | private void setupView() { 67 | // Toolbar 68 | ((AppCompatActivity) getActivity()).setSupportActionBar(binding.toolbar); 69 | ((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true); 70 | 71 | // Setup recycler view 72 | binding.recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 73 | binding.recyclerView.setAdapter(new PlacesAdapter(getContext())); 74 | 75 | // Swipe to refresh 76 | binding.swipeRefresh.setOnRefreshListener(this::downloadData); 77 | 78 | // Progress dialog 79 | mProgressDialog = new ProgressDialog(getContext()); 80 | mProgressDialog.setMessage(getString(R.string.loading)); 81 | mProgressDialog.setCancelable(false); 82 | } 83 | 84 | @Override 85 | public void onActivityCreated(Bundle savedInstanceState) { 86 | super.onActivityCreated(savedInstanceState); 87 | mViewModel.progress() 88 | .observeOn(AndroidSchedulers.mainThread()) 89 | .takeUntil(preDestroy()) 90 | .subscribe(done -> { 91 | if (done) { 92 | mProgressDialog.hide(); 93 | } else { 94 | mProgressDialog.show(); 95 | } 96 | }); 97 | downloadData(); 98 | } 99 | 100 | private void downloadData() { 101 | // fetch all places 102 | mViewModel.fetchAllPlaces() 103 | .takeUntil(preDestroy()) 104 | .subscribe(success -> { 105 | binding.swipeRefresh.setRefreshing(false); 106 | }, 107 | throwable -> { 108 | Toast.makeText(getContext(), "Problem occurs", Toast.LENGTH_SHORT); 109 | binding.swipeRefresh.setRefreshing(false); 110 | }); 111 | } 112 | 113 | @Override 114 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 115 | super.onCreateOptionsMenu(menu, inflater); 116 | inflater.inflate(R.menu.menu_places, menu); 117 | } 118 | 119 | @Override 120 | public void onDestroyView() { 121 | if (mProgressDialog != null) { 122 | mProgressDialog.dismiss(); 123 | } 124 | super.onDestroyView(); 125 | } 126 | 127 | @Override 128 | public boolean onOptionsItemSelected(MenuItem item) { 129 | switch (item.getItemId()) { 130 | case android.R.id.home: 131 | getActivity().onBackPressed(); 132 | return true; 133 | case R.id.action_cafe: 134 | case R.id.action_food: 135 | case R.id.action_store: 136 | case R.id.action_theater: 137 | case R.id.action_restaurant: 138 | case R.id.action_all: 139 | // Filter the items 140 | mViewModel.filterPlacesByType(item.getTitle().toString()); 141 | return true; 142 | } 143 | return super.onOptionsItemSelected(item); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/view/handler/IMainHandler.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.view.handler; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * Created by nongdenchet on 10/30/15. 7 | */ 8 | public interface IMainHandler { 9 | View.OnClickListener formSample(); 10 | View.OnClickListener recyclerSample(); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/view/handler/IPurchaseHandler.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.view.handler; 2 | 3 | import android.text.TextWatcher; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by nongdenchet on 10/28/15. 8 | */ 9 | public interface IPurchaseHandler { 10 | TextWatcher emailWatcher(); 11 | TextWatcher creditCardWatcher(); 12 | View.OnClickListener onSubmit(); 13 | View.OnClickListener onInvalid(); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/viewmodel/IPlacesViewModel.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.viewmodel; 2 | 3 | import android.databinding.ObservableArrayList; 4 | 5 | import apidez.com.databinding.model.entity.Place; 6 | import rx.Observable; 7 | 8 | /** 9 | * Created by nongdenchet on 10/21/15. 10 | */ 11 | public interface IPlacesViewModel { 12 | /** 13 | * Fetch all places from google 14 | */ 15 | Observable fetchAllPlaces(); 16 | 17 | /** 18 | * Observe current places 19 | */ 20 | ObservableArrayList getCurrentPlaces(); 21 | 22 | /** 23 | * Filter the places 24 | */ 25 | void filterPlacesByType(String type); 26 | 27 | /** 28 | * Porgressbar 29 | */ 30 | Observable progress(); 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/viewmodel/IPurchaseViewModel.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.viewmodel; 2 | 3 | import android.databinding.ObservableBoolean; 4 | import android.databinding.ObservableInt; 5 | 6 | /** 7 | * Created by nongdenchet on 10/29/15. 8 | */ 9 | public interface IPurchaseViewModel { 10 | /** 11 | * Observable validation of card 12 | */ 13 | ObservableInt creditCardError(); 14 | 15 | /** 16 | * Observable validation of email 17 | */ 18 | ObservableInt emailError(); 19 | 20 | /** 21 | * Observable validation of submit 22 | */ 23 | ObservableBoolean canSubmit(); 24 | 25 | /** 26 | * set new credit card 27 | */ 28 | void setCreditCard(String newCreditCard); 29 | 30 | /** 31 | * Set new email 32 | */ 33 | void setEmail(String newEmail); 34 | 35 | /** 36 | * Command submit 37 | */ 38 | rx.Observable submit(); 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/viewmodel/PlacesViewModel.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.viewmodel; 2 | 3 | import android.databinding.BaseObservable; 4 | import android.databinding.Bindable; 5 | import android.databinding.ObservableArrayList; 6 | import android.support.annotation.NonNull; 7 | 8 | import java.util.List; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | import apidez.com.databinding.model.api.IPlacesApi; 12 | import apidez.com.databinding.model.entity.Place; 13 | import apidez.com.databinding.utils.RxUtils; 14 | import rx.Observable; 15 | import rx.Scheduler; 16 | import rx.android.schedulers.AndroidSchedulers; 17 | import rx.schedulers.Schedulers; 18 | import rx.subjects.BehaviorSubject; 19 | 20 | /** 21 | * Created by nongdenchet on 10/21/15. 22 | */ 23 | public class PlacesViewModel extends BaseObservable implements IPlacesViewModel { 24 | private final int TIME_OUT = 5; 25 | private boolean firstTime = true; 26 | private List allPlaces; 27 | 28 | public Scheduler mMainThread; 29 | public Scheduler mIOThread; 30 | private IPlacesApi mPlacesApi; 31 | 32 | // Observable property 33 | private ObservableArrayList mPlaces = new ObservableArrayList<>(); 34 | private BehaviorSubject progress = BehaviorSubject.create(false); 35 | 36 | public PlacesViewModel(@NonNull IPlacesApi placesApi, @NonNull RxUtils.SchedulerHolder schedulerHolder) { 37 | mPlacesApi = placesApi; 38 | mMainThread = schedulerHolder.mainScheduler; 39 | mIOThread = schedulerHolder.ioScheduler; 40 | } 41 | 42 | /** 43 | * Return an Observable that emits the current places 44 | */ 45 | @Bindable 46 | public ObservableArrayList getCurrentPlaces() { 47 | return mPlaces; 48 | } 49 | 50 | /** 51 | * Command fetching all places 52 | */ 53 | @Override 54 | public Observable fetchAllPlaces() { 55 | return mPlacesApi.placesResult() 56 | .timeout(TIME_OUT, TimeUnit.SECONDS) 57 | .subscribeOn(mIOThread) 58 | .observeOn(mMainThread) 59 | .map(googleSearchResult -> { 60 | // update list 61 | allPlaces = googleSearchResult.results; 62 | mPlaces.clear(); 63 | mPlaces.addAll(allPlaces); 64 | 65 | // check first time 66 | if (firstTime) { 67 | progress.onNext(true); 68 | firstTime = false; 69 | } 70 | return true; 71 | }); 72 | } 73 | 74 | /** 75 | * Command filtering places 76 | */ 77 | @Override 78 | public void filterPlacesByType(String type) { 79 | mPlaces.clear(); 80 | if (type == null) return; 81 | if (type.equalsIgnoreCase("all")) { 82 | mPlaces.addAll(allPlaces); 83 | } else { 84 | Observable.from(allPlaces) 85 | .filter(place -> place.getTypes().contains(getApiType(type))) 86 | .subscribe(mPlaces::add); 87 | } 88 | } 89 | 90 | @Override 91 | public Observable progress() { 92 | return progress.asObservable(); 93 | } 94 | 95 | /** 96 | * Helpers change type to api_type 97 | */ 98 | private String getApiType(String type) { 99 | String newType = type.toLowerCase(); 100 | return newType.equals("theater") ? "movie_theater" : newType; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/databinding/viewmodel/PurchaseViewModel.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.viewmodel; 2 | 3 | import android.databinding.BaseObservable; 4 | import android.databinding.ObservableBoolean; 5 | import android.databinding.ObservableInt; 6 | import android.support.annotation.NonNull; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import apidez.com.databinding.R; 11 | import apidez.com.databinding.model.api.IPurchaseApi; 12 | import apidez.com.databinding.utils.NumericUtils; 13 | import apidez.com.databinding.utils.StringUtils; 14 | 15 | /** 16 | * Created by nongdenchet on 10/28/15. 17 | */ 18 | public class PurchaseViewModel extends BaseObservable implements IPurchaseViewModel { 19 | private final String EMAIL_REGEX = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@" 20 | + "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; 21 | private final int TIME_OUT = 5; 22 | private final int RETRY = 3; 23 | 24 | // Normal property 25 | private String mCreditCard; 26 | private String mEmail; 27 | private boolean mCreditCardValid; 28 | private boolean mEmailValid; 29 | 30 | // Observable property 31 | private ObservableBoolean mCanSubmit = new ObservableBoolean(false); 32 | private ObservableInt mEmailError = new ObservableInt(R.string.empty); 33 | private ObservableInt mCreditCardError = new ObservableInt(R.string.empty); 34 | 35 | // Dependency 36 | private IPurchaseApi mPurchaseApi; 37 | 38 | public PurchaseViewModel(@NonNull IPurchaseApi purchaseApi) { 39 | mPurchaseApi = purchaseApi; 40 | } 41 | 42 | @Override 43 | public ObservableInt creditCardError() { 44 | return mCreditCardError; 45 | } 46 | 47 | @Override 48 | public ObservableInt emailError() { 49 | return mEmailError; 50 | } 51 | 52 | @Override 53 | public ObservableBoolean canSubmit() { 54 | return mCanSubmit; 55 | } 56 | 57 | @Override 58 | public void setCreditCard(String newCreditCard) { 59 | if (StringUtils.isEmpty(newCreditCard)) return; 60 | mCreditCard = newCreditCard; 61 | mCreditCardValid = newCreditCard.length() == 12 && NumericUtils.isNumeric(newCreditCard); 62 | mCreditCardError.set(mCreditCardValid ? R.string.empty : R.string.error_credit_card); 63 | updateCanSubmit(); 64 | } 65 | 66 | @Override 67 | public void setEmail(String newEmail) { 68 | if (StringUtils.isEmpty(newEmail)) return; 69 | mEmail = newEmail; 70 | mEmailValid = newEmail.matches(EMAIL_REGEX); 71 | mEmailError.set(mEmailValid ? R.string.empty : R.string.error_email); 72 | updateCanSubmit(); 73 | } 74 | 75 | @Override 76 | public rx.Observable submit() { 77 | return mPurchaseApi.submitPurchase(mCreditCard, mEmail) 78 | .timeout(TIME_OUT, TimeUnit.SECONDS) 79 | .retry(RETRY); 80 | } 81 | 82 | private void updateCanSubmit() { 83 | mCanSubmit.set(mCreditCardValid && mEmailValid); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_place_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/drawable-hdpi/ic_place_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_sort_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/drawable-hdpi/ic_sort_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_sort_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/drawable-hdpi/ic_sort_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_place_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/drawable-mdpi/ic_place_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_sort_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/drawable-mdpi/ic_sort_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_sort_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/drawable-mdpi/ic_sort_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_place_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/drawable-xhdpi/ic_place_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_sort_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/drawable-xhdpi/ic_sort_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_sort_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/drawable-xhdpi/ic_sort_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_place_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/drawable-xxhdpi/ic_place_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_sort_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/drawable-xxhdpi/ic_sort_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_sort_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/drawable-xxhdpi/ic_sort_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_inactive_submit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_submit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_submit_normal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_submit_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_place_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | 15 | 16 | 26 | 27 | 37 | 38 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_places.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_purchase.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 12 | 13 | 14 | 19 | 20 | 30 | 31 | 41 | 42 | 49 | 50 | 51 | 52 | 62 | 63 | 71 | 72 | 73 | 74 | 86 | 87 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_places.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 16 | 17 | 27 | 28 | 33 | 34 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_place.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 20 | 21 | 24 | 25 | 34 | 35 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_places.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 15 | 20 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/test/java/apidez/com/databinding/model/api/PurchaseApiTest.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.model.api; 2 | 3 | import android.test.suitebuilder.annotation.SmallTest; 4 | 5 | import com.google.gson.Gson; 6 | 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.junit.runners.JUnit4; 11 | 12 | import rx.functions.Action0; 13 | import rx.functions.Action1; 14 | import rx.observers.TestSubscriber; 15 | 16 | import static org.junit.Assert.*; 17 | 18 | /** 19 | * Created by nongdenchet on 1/14/16. 20 | */ 21 | @SmallTest 22 | @RunWith(JUnit4.class) 23 | public class PurchaseApiTest { 24 | 25 | private PurchaseApi purchaseApi; 26 | 27 | @Before 28 | public void setUp() throws Exception { 29 | purchaseApi = new PurchaseApi(new Gson()); 30 | } 31 | 32 | @Test 33 | public void testSubmitPurchase() throws Exception { 34 | boolean res = purchaseApi.submitPurchase("124", "").toBlocking().single(); 35 | assertTrue(res); 36 | } 37 | 38 | @Test 39 | public void testSubmitPurchaseError() throws Exception { 40 | TestSubscriber testSubscriber = new TestSubscriber(); 41 | purchaseApi.submitPurchase("1", "").subscribe(testSubscriber); 42 | testSubscriber.assertError(Exception.class); 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/test/java/apidez/com/databinding/model/entity/PlaceTest.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.model.entity; 2 | 3 | import android.test.suitebuilder.annotation.SmallTest; 4 | 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.JUnit4; 8 | 9 | import java.util.Arrays; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Created by nongdenchet on 1/14/16. 15 | */ 16 | @SmallTest 17 | @RunWith(JUnit4.class) 18 | public class PlaceTest { 19 | 20 | @Test 21 | public void testGetIcon() throws Exception { 22 | Place place = new Place.Builder().icon("url").build(); 23 | assertEquals("url", place.getIcon()); 24 | } 25 | 26 | @Test 27 | public void testGetId() throws Exception { 28 | Place place = new Place.Builder().build(); 29 | assertNotNull(place.getId()); 30 | } 31 | 32 | @Test 33 | public void testGetName() throws Exception { 34 | Place place = new Place.Builder().name("name").build(); 35 | assertEquals("name", place.getName()); 36 | } 37 | 38 | @Test 39 | public void testGetTypes() throws Exception { 40 | Place place = new Place.Builder().types(Arrays.asList("123", "234")).build(); 41 | assertEquals(2, place.getTypes().size()); 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/test/java/apidez/com/databinding/model/entity/PurchaseTest.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.model.entity; 2 | 3 | import android.test.suitebuilder.annotation.SmallTest; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.junit.runners.JUnit4; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Created by nongdenchet on 1/14/16. 14 | */ 15 | @SmallTest 16 | @RunWith(JUnit4.class) 17 | public class PurchaseTest { 18 | 19 | private Purchase purchase; 20 | 21 | @Before 22 | public void setUp() throws Exception { 23 | purchase = new Purchase("credit", "number"); 24 | } 25 | 26 | @Test 27 | public void testGetCreditCard() throws Exception { 28 | assertEquals("credit", purchase.getCreditCard()); 29 | purchase = new Purchase(); 30 | assertNull(purchase.getCreditCard()); 31 | } 32 | 33 | @Test 34 | public void testSetCreditCard() throws Exception { 35 | purchase.setCreditCard("aaa"); 36 | assertEquals("aaa", purchase.getCreditCard()); 37 | } 38 | 39 | @Test 40 | public void testGetEmail() throws Exception { 41 | assertEquals("number", purchase.getEmail()); 42 | purchase = new Purchase(); 43 | assertNull(purchase.getEmail()); 44 | } 45 | 46 | @Test 47 | public void testSetEmail() throws Exception { 48 | purchase.setEmail("bbb"); 49 | assertEquals("bbb", purchase.getEmail()); 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/test/java/apidez/com/databinding/utils/NumericUtilsTest.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.utils; 2 | 3 | import android.test.suitebuilder.annotation.SmallTest; 4 | 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.JUnit4; 8 | 9 | import static org.junit.Assert.*; 10 | 11 | /** 12 | * Created by nongdenchet on 1/14/16. 13 | */ 14 | @SmallTest 15 | @RunWith(JUnit4.class) 16 | public class NumericUtilsTest { 17 | 18 | @Test 19 | public void testCreate() throws Exception { 20 | new NumericUtils(); 21 | } 22 | 23 | @Test 24 | public void testIsNumeric() throws Exception { 25 | assertTrue(NumericUtils.isNumeric("1000")); 26 | assertFalse(NumericUtils.isNumeric("hello")); 27 | } 28 | 29 | @Test 30 | public void testIsNumericCharacter() throws Exception { 31 | CharSequence sequence = "10000"; 32 | assertTrue(NumericUtils.isNumeric(sequence)); 33 | sequence = "hello"; 34 | assertFalse(NumericUtils.isNumeric(sequence)); 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/test/java/apidez/com/databinding/utils/StringUtilsTest.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.utils; 2 | 3 | import android.test.suitebuilder.annotation.SmallTest; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.junit.runners.JUnit4; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Created by nongdenchet on 1/14/16. 14 | */ 15 | @SmallTest 16 | @RunWith(JUnit4.class) 17 | public class StringUtilsTest { 18 | 19 | @Test 20 | public void testCreate() throws Exception { 21 | new StringUtils(); 22 | } 23 | 24 | @Test 25 | public void testGenerateString() throws Exception { 26 | assertEquals(StringUtils.generateString("aaa", 23).length(), 23); 27 | assertEquals(StringUtils.generateString("ccc", 10).length(), 10); 28 | } 29 | 30 | @Test 31 | public void testIsEmpty() throws Exception { 32 | assertTrue(StringUtils.isEmpty("")); 33 | assertTrue(StringUtils.isEmpty(null)); 34 | } 35 | 36 | @Test 37 | public void testIsNotEmpty() throws Exception { 38 | assertFalse(StringUtils.isEmpty("hello")); 39 | assertFalse(StringUtils.isEmpty("world")); 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/test/java/apidez/com/databinding/utils/TestDataUtilsTest.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.utils; 2 | 3 | import android.test.suitebuilder.annotation.SmallTest; 4 | 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.JUnit4; 8 | 9 | import static org.junit.Assert.*; 10 | 11 | /** 12 | * Created by nongdenchet on 1/14/16. 13 | */ 14 | @SmallTest 15 | @RunWith(JUnit4.class) 16 | public class TestDataUtilsTest { 17 | 18 | @Test 19 | public void testCreate() throws Exception { 20 | new TestDataUtils(); 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/test/java/apidez/com/databinding/viewmodel/PlacesViewModelTest.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.viewmodel; 2 | 3 | import android.test.suitebuilder.annotation.SmallTest; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.junit.runners.JUnit4; 9 | import org.mockito.Mockito; 10 | 11 | import java.util.Arrays; 12 | import java.util.Collections; 13 | import java.util.List; 14 | 15 | import apidez.com.databinding.model.api.IPlacesApi; 16 | import apidez.com.databinding.model.entity.GoogleSearchResult; 17 | import apidez.com.databinding.model.entity.Place; 18 | import apidez.com.databinding.utils.RxUtils; 19 | import apidez.com.databinding.utils.TestDataUtils; 20 | import rx.Observable; 21 | import rx.observers.TestSubscriber; 22 | import rx.schedulers.Schedulers; 23 | 24 | import static org.junit.Assert.assertEquals; 25 | import static org.mockito.Mockito.when; 26 | 27 | /** 28 | * Created by nongdenchet on 10/30/15. 29 | */ 30 | @SmallTest 31 | @RunWith(JUnit4.class) 32 | public class PlacesViewModelTest { 33 | private PlacesViewModel placesViewModel; 34 | private IPlacesApi placesApi; 35 | private TestSubscriber testSubscriber; 36 | 37 | @Before 38 | public void setUpViewModel() { 39 | placesApi = Mockito.mock(IPlacesApi.class); 40 | placesViewModel = new PlacesViewModel(placesApi, 41 | new RxUtils.SchedulerHolder(Schedulers.immediate(), Schedulers.immediate())); 42 | testSubscriber = TestSubscriber.create(); 43 | when(placesApi.placesResult()).thenReturn(testDataObservable()); 44 | } 45 | 46 | @Test 47 | public void fetchFirstTime() { 48 | TestSubscriber tester = TestSubscriber.create(); 49 | placesViewModel.progress().subscribe(tester); 50 | placesViewModel.fetchAllPlaces().subscribe(testSubscriber); 51 | placesViewModel.fetchAllPlaces().subscribe(testSubscriber); 52 | placesViewModel.fetchAllPlaces().subscribe(testSubscriber); 53 | tester.assertReceivedOnNext(Arrays.asList(false, true)); 54 | } 55 | 56 | @Test 57 | public void fetchAllPlacesSuccess() { 58 | placesViewModel.fetchAllPlaces().subscribe(testSubscriber); 59 | testSubscriber.assertNoErrors(); 60 | testSubscriber.assertCompleted(); 61 | testSubscriber.assertReceivedOnNext(Collections.singletonList(true)); 62 | } 63 | 64 | @Test 65 | public void fetchAllPlaces() { 66 | placesViewModel.fetchAllPlaces().subscribe(); 67 | assertEquals(placesViewModel.getCurrentPlaces().size(), 10); 68 | } 69 | 70 | @Test 71 | public void filterAll() { 72 | assertEquals(getAndFilterWith("all").size(), 10); 73 | getAndFilterWith("cafe"); 74 | assertEquals(getAndFilterWith("all").size(), 10); 75 | } 76 | 77 | @Test 78 | public void filterFood() { 79 | assertEquals(getAndFilterWith("food").size(), 4); 80 | getAndFilterWith("cafe"); 81 | assertEquals(getAndFilterWith("food").size(), 4); 82 | } 83 | 84 | @Test 85 | public void filterCafe() { 86 | assertEquals(getAndFilterWith("cafe").size(), 5); 87 | } 88 | 89 | @Test 90 | public void filterStore() { 91 | assertEquals(getAndFilterWith("store").size(), 4); 92 | } 93 | 94 | @Test 95 | public void filterRestaurant() { 96 | assertEquals(getAndFilterWith("restaurant").size(), 3); 97 | getAndFilterWith("cafe"); 98 | assertEquals(getAndFilterWith("restaurant").size(), 3); 99 | } 100 | 101 | @Test 102 | public void typeNull() { 103 | assertEquals(getAndFilterWith(null).size(), 0); 104 | } 105 | 106 | @Test 107 | public void filterTheater() { 108 | assertEquals(getAndFilterWith("theater").size(), 3); 109 | } 110 | 111 | private List getAndFilterWith(String type) { 112 | placesViewModel.fetchAllPlaces().subscribe(); 113 | placesViewModel.filterPlacesByType(type); 114 | return placesViewModel.getCurrentPlaces(); 115 | } 116 | 117 | private Observable testDataObservable() { 118 | return Observable.just(TestDataUtils.nearByData()); 119 | } 120 | } -------------------------------------------------------------------------------- /app/src/test/java/apidez/com/databinding/viewmodel/PurchaseViewModelTest.java: -------------------------------------------------------------------------------- 1 | package apidez.com.databinding.viewmodel; 2 | 3 | import android.test.suitebuilder.annotation.MediumTest; 4 | import android.test.suitebuilder.annotation.SmallTest; 5 | 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.junit.runners.JUnit4; 10 | import org.mockito.Mockito; 11 | 12 | import java.util.Collections; 13 | 14 | import apidez.com.databinding.R; 15 | import apidez.com.databinding.model.api.PurchaseApi; 16 | import rx.Observable; 17 | import rx.observers.TestSubscriber; 18 | 19 | import static junit.framework.Assert.assertEquals; 20 | import static junit.framework.Assert.assertFalse; 21 | import static junit.framework.Assert.assertTrue; 22 | import static org.mockito.Matchers.anyString; 23 | import static org.mockito.Mockito.verify; 24 | import static org.mockito.Mockito.when; 25 | 26 | /** 27 | * Created by nongdenchet on 10/29/15. 28 | */ 29 | @SmallTest 30 | @RunWith(JUnit4.class) 31 | public class PurchaseViewModelTest { 32 | private PurchaseViewModel purchaseViewModel; 33 | private PurchaseApi purchaseApi; 34 | private TestSubscriber testSubscriber; 35 | 36 | @Before 37 | public void setUpViewModel() { 38 | // Mock purchase api 39 | purchaseApi = Mockito.mock(PurchaseApi.class); 40 | when(purchaseApi.submitPurchase(anyString(), anyString())) 41 | .thenReturn(Observable.just(true)); 42 | 43 | // Create test viewmodel 44 | purchaseViewModel = new PurchaseViewModel(purchaseApi); 45 | testSubscriber = TestSubscriber.create(); 46 | } 47 | 48 | @Test 49 | public void inputValidCreditCard() throws Exception { 50 | purchaseViewModel.setCreditCard("412341234123"); 51 | assertEquals(purchaseViewModel.creditCardError().get(), R.string.empty); 52 | } 53 | 54 | @Test 55 | public void inputInvalidCreditCard() throws Exception { 56 | purchaseViewModel.setCreditCard("abcdabcdabcd"); 57 | assertEquals(purchaseViewModel.creditCardError().get(), R.string.error_credit_card); 58 | } 59 | 60 | @Test 61 | public void inputValidEmail() throws Exception { 62 | purchaseViewModel.setEmail("ndc@gmail.com"); 63 | assertEquals(purchaseViewModel.emailError().get(), R.string.empty); 64 | } 65 | 66 | @Test 67 | public void inputInvalidEmail() throws Exception { 68 | purchaseViewModel.setEmail("__123$$@gm.co"); 69 | assertEquals(purchaseViewModel.emailError().get(), R.string.error_email); 70 | } 71 | 72 | @Test 73 | public void canSubmit() throws Exception { 74 | purchaseViewModel.setCreditCard("412341234123"); 75 | purchaseViewModel.setEmail("ndc@gmail.com"); 76 | assertTrue(purchaseViewModel.canSubmit().get()); 77 | } 78 | 79 | @Test 80 | public void cannotSubmitInvalidEmail() throws Exception { 81 | purchaseViewModel.setCreditCard("412341234123"); 82 | purchaseViewModel.setEmail("ndc###@gmail.com"); 83 | assertFalse(purchaseViewModel.canSubmit().get()); 84 | } 85 | 86 | @Test 87 | public void cannotSubmitInvalidCreditCard() throws Exception { 88 | purchaseViewModel.setCreditCard("412123123341234123"); 89 | purchaseViewModel.setEmail("ndc@gmail.com"); 90 | assertFalse(purchaseViewModel.canSubmit().get()); 91 | } 92 | 93 | @Test 94 | public void cannotSubmitEmptyEmail() throws Exception { 95 | purchaseViewModel.setCreditCard("412341234123"); 96 | purchaseViewModel.setEmail(""); 97 | assertFalse(purchaseViewModel.canSubmit().get()); 98 | } 99 | 100 | @Test 101 | public void cannotSubmitEmptyCreditCard() throws Exception { 102 | purchaseViewModel.setEmail("ndc@gmail.com"); 103 | purchaseViewModel.setCreditCard(""); 104 | assertFalse(purchaseViewModel.canSubmit().get()); 105 | } 106 | 107 | @Test 108 | public void submit() throws Exception { 109 | purchaseViewModel.setCreditCard("412341234123"); 110 | purchaseViewModel.setEmail("ndc@gmail.com"); 111 | purchaseViewModel.submit().subscribe(testSubscriber); 112 | testSubscriber.assertReceivedOnNext(Collections.singletonList(true)); 113 | verify(purchaseApi).submitPurchase("412341234123", "ndc@gmail.com"); 114 | } 115 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:1.5.0' 10 | classpath 'me.tatarka:gradle-retrolambda:3.2.3' 11 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' 12 | classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.4.0' 13 | 14 | // NOTE: Do not place your application dependencies here; they belong 15 | // in the individual module build.gradle files 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | jcenter() 22 | mavenCentral() 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | -------------------------------------------------------------------------------- /calabash-data-binding/.idea/.name: -------------------------------------------------------------------------------- 1 | calabash-data-binding -------------------------------------------------------------------------------- /calabash-data-binding/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /calabash-data-binding/.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /calabash-data-binding/.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /calabash-data-binding/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /calabash-data-binding/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /calabash-data-binding/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | true 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 89 | 90 | 91 | 92 | 95 | 96 | 99 | 100 | 101 | 102 | 105 | 106 | 109 | 110 | 113 | 114 | 115 | 116 | 119 | 120 | 123 | 124 | 127 | 128 | 129 | 130 | 133 | 134 | 137 | 138 | 141 | 142 | 145 | 146 | 147 | 148 | 151 | 152 | 155 | 156 | 159 | 160 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 1452162271527 227 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | -------------------------------------------------------------------------------- /calabash-data-binding/features/form.feature: -------------------------------------------------------------------------------- 1 | Feature: Form feature 2 | 3 | Scenario: As a normal user I can use "Form Sample" 4 | When I press "Form Sample" 5 | Then I see "SUBMIT" 6 | 7 | Scenario: I enter invalid credit card 8 | When I press "Form Sample" 9 | Given I enter "5555" into input field number 1 10 | Then I see "Invalid Credit Card" 11 | Then I press "SUBMIT" 12 | Then I don't see the text "Success" 13 | 14 | Scenario: I enter invalid email 15 | When I press "Form Sample" 16 | Given I enter "nongdenchet" into input field number 2 17 | Then I see "Invalid Email" 18 | Then I press "SUBMIT" 19 | Then I don't see the text "Success" 20 | 21 | Scenario: I enter valid data 22 | When I press "Form Sample" 23 | Given I enter "555555555555" into input field number 1 24 | Given I enter "nongdenchet@gmail.com" into input field number 2 25 | Then I don't see "Invalid Email" 26 | Then I don't see "Invalid Credit Card" 27 | Then I press "SUBMIT" 28 | Then I see the text "Success" -------------------------------------------------------------------------------- /calabash-data-binding/features/recycler.feature: -------------------------------------------------------------------------------- 1 | Feature: Recycler feature 2 | 3 | Scenario: As a normal user I can use "Recycler Sample" 4 | When I press "Recycler Sample" 5 | Then I see "Hoang" 6 | Then I see "Petite Note" 7 | Then I see "Cà Phê Quỳ Đà Lạt" 8 | 9 | Scenario: I click sort 10 | When I press "Recycler Sample" 11 | Then I press view with id "action_sort" 12 | Then I press "Store" 13 | Then I see "Co.opMart Nhiêu Lộc" 14 | Then I press view with id "action_sort" 15 | Then I press "Theater" 16 | Then I see "Liều Cafe" -------------------------------------------------------------------------------- /calabash-data-binding/features/step_definitions/calabash_steps.rb: -------------------------------------------------------------------------------- 1 | require 'calabash-android/calabash_steps' -------------------------------------------------------------------------------- /calabash-data-binding/features/support/app_installation_hooks.rb: -------------------------------------------------------------------------------- 1 | require 'calabash-android/management/app_installation' 2 | 3 | AfterConfiguration do |config| 4 | FeatureMemory.feature = nil 5 | end 6 | 7 | Before do |scenario| 8 | scenario = scenario.scenario_outline if scenario.respond_to?(:scenario_outline) 9 | 10 | feature = scenario.feature 11 | if FeatureMemory.feature != feature || ENV['RESET_BETWEEN_SCENARIOS'] == '1' 12 | if ENV['RESET_BETWEEN_SCENARIOS'] == '1' 13 | log 'New scenario - reinstalling apps' 14 | else 15 | log 'First scenario in feature - reinstalling apps' 16 | end 17 | 18 | uninstall_apps 19 | install_app(ENV['TEST_APP_PATH']) 20 | install_app(ENV['APP_PATH']) 21 | FeatureMemory.feature = feature 22 | FeatureMemory.invocation = 1 23 | else 24 | FeatureMemory.invocation += 1 25 | end 26 | end 27 | 28 | FeatureMemory = Struct.new(:feature, :invocation).new 29 | -------------------------------------------------------------------------------- /calabash-data-binding/features/support/app_life_cycle_hooks.rb: -------------------------------------------------------------------------------- 1 | require 'calabash-android/management/adb' 2 | require 'calabash-android/operations' 3 | 4 | Before do |scenario| 5 | start_test_server_in_background 6 | end 7 | 8 | After do |scenario| 9 | if scenario.failed? 10 | screenshot_embed 11 | end 12 | shutdown_test_server 13 | end 14 | -------------------------------------------------------------------------------- /calabash-data-binding/features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'calabash-android/cucumber' 2 | -------------------------------------------------------------------------------- /calabash-data-binding/features/support/hooks.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/calabash-data-binding/features/support/hooks.rb -------------------------------------------------------------------------------- /calabash-data-binding/screenshot_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/calabash-data-binding/screenshot_0.png -------------------------------------------------------------------------------- /calabash-data-binding/screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/calabash-data-binding/screenshot_1.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nongdenchet/android-data-binding/b6b33cf7a9109ca9245facab35a9b43a8500d5b8/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Nov 24 18:02:16 GMT+07:00 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /jacoco.gradle: -------------------------------------------------------------------------------- 1 | // Merge of 2 | // https://github.com/mgouline/android-samples/blob/master/jacoco/app/build.gradle 3 | // and https://github.com/pushtorefresh/storio/blob/master/gradle/jacoco-android.gradle 4 | 5 | jacoco { 6 | // See https://github.com/jacoco/jacoco/releases 7 | toolVersion = '0.7.5.201505241946' 8 | } 9 | 10 | project.afterEvaluate { 11 | // Create coverage task of form 'testFlavorTypeCoverage' depending on 'testFlavorTypeUnitTest' 12 | task "jacocoTestReport"(type: JacocoReport, dependsOn: "testDebugUnitTest") { 13 | group = 'Reporting' 14 | description = "Generate Jacoco coverage reports for the debug build." 15 | 16 | classDirectories = fileTree( 17 | dir: "${project.buildDir}/intermediates/classes/debug", 18 | excludes: ['**/R.class', 19 | '**/R$*.class', 20 | '**/apidez/com/databinding/databinding/*', 21 | '**/android/databinding/*', 22 | '**/*$ViewInjector*.*', 23 | '**/*$ViewBinder*.*', 24 | '**/BuildConfig.*', 25 | '**/*Application*.*', 26 | '**/*Component*.*', 27 | '**/UiUtils.*', 28 | '**/*Binding*.*', 29 | '**/*Retrofit*.*', 30 | '**/*BR*.*', 31 | '**/*Adapter*.*', 32 | '**/*Fragment*.*', 33 | '**/*Activity*.*', 34 | '**/*Rx*.*', 35 | '**/*ViewHolder*.*', 36 | '**/Manifest*.*', 37 | '**/*$Lambda$*.*', 38 | '**/*Module.*', 39 | '**/*Dagger*.*', 40 | '**/*MembersInjector*.*', 41 | '**/*_Provide*Factory*.*'] 42 | ) 43 | 44 | def coverageSourceDirs = [ 45 | 'src/main/java', 46 | ] 47 | additionalSourceDirs = files(coverageSourceDirs) 48 | sourceDirectories = files(coverageSourceDirs) 49 | executionData = files("${project.buildDir}/jacoco/testDebugUnitTest.exec") 50 | 51 | reports { 52 | xml.enabled = true 53 | html.enabled = true 54 | xml.destination = "${buildDir}/reports/jacoco/test/jacocoTestReport.xml" 55 | html.destination = "${buildDir}/reports/jacoco/test/html" 56 | } 57 | } 58 | 59 | build.dependsOn "jacocoTestReport" 60 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------