├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── apidez │ │ └── com │ │ └── week8 │ │ ├── EspressoApplication.java │ │ ├── MyRunner.java │ │ ├── activity │ │ └── RegisterActivityTest.java │ │ └── utils │ │ └── EspressoUtils.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── apidez │ │ │ └── com │ │ │ └── week8 │ │ │ ├── MyApplication.java │ │ │ ├── activity │ │ │ ├── BaseActivity.java │ │ │ └── RegisterActivity.java │ │ │ ├── data │ │ │ ├── api │ │ │ │ └── UserApi.java │ │ │ └── repo │ │ │ │ ├── UserRepo.java │ │ │ │ └── UserRepoImpl.java │ │ │ ├── dependency │ │ │ ├── ActivityScope.java │ │ │ ├── component │ │ │ │ ├── AppComponent.java │ │ │ │ └── UserComponent.java │ │ │ └── module │ │ │ │ ├── AppModule.java │ │ │ │ ├── RepoModule.java │ │ │ │ └── UserModule.java │ │ │ ├── utils │ │ │ ├── BindingUtils.java │ │ │ └── view │ │ │ │ ├── TextChange.java │ │ │ │ └── TextChangeAdapter.java │ │ │ ├── validator │ │ │ └── RegisterValidator.java │ │ │ └── viewmodel │ │ │ └── RegisterViewModel.java │ └── res │ │ ├── layout │ │ └── activity_register.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-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── apidez │ └── com │ └── week8 │ └── viewmodel │ └── RegisterViewModelTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/** 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CoderSchool---Lecture8 2 | - Model - View - ViewModel 3 | - Dependency Injection 4 | - Mockito 5 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.jakewharton.butterknife' 3 | apply plugin: 'com.neenbedankt.android-apt' 4 | apply plugin: 'me.tatarka.retrolambda' 5 | 6 | android { 7 | compileSdkVersion 25 8 | buildToolsVersion "25.0.1" 9 | defaultConfig { 10 | applicationId "apidez.com.week8" 11 | minSdkVersion 16 12 | targetSdkVersion 25 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "apidez.com.week8.MyRunner" 16 | } 17 | dataBinding { 18 | enabled true 19 | } 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | targetCompatibility 1.8 28 | sourceCompatibility 1.8 29 | } 30 | } 31 | 32 | dependencies { 33 | compile fileTree(dir: 'libs', include: ['*.jar']) 34 | compile 'com.android.support:appcompat-v7:25.0.1' 35 | compile 'com.android.support:design:25.0.1' 36 | compile 'io.reactivex:rxandroid:1.2.1' 37 | compile 'io.reactivex:rxjava:1.1.6' 38 | compile 'com.jakewharton:butterknife:8.4.0' 39 | apt 'com.jakewharton:butterknife-compiler:8.4.0' 40 | apt 'com.google.dagger:dagger-compiler:2.8' 41 | compile 'com.google.dagger:dagger:2.8' 42 | provided 'javax.annotation:jsr250-api:1.0' 43 | 44 | // Unit test 45 | testCompile 'junit:junit:4.12' 46 | testCompile 'org.mockito:mockito-core:1.10.19' 47 | 48 | // Android test 49 | androidTestCompile 'com.google.dexmaker:dexmaker:1.2' 50 | androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' 51 | androidTestCompile 'org.mockito:mockito-core:1.10.19' 52 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 53 | exclude group: 'com.android.support', module: 'support-annotations' 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /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/week8/EspressoApplication.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8; 2 | 3 | import android.support.test.rule.ActivityTestRule; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | import apidez.com.week8.data.api.UserApi; 7 | import apidez.com.week8.data.repo.UserRepo; 8 | import apidez.com.week8.dependency.component.AppComponent; 9 | import apidez.com.week8.dependency.component.DaggerAppComponent; 10 | import apidez.com.week8.dependency.module.RepoModule; 11 | 12 | import static org.mockito.Mockito.mock; 13 | 14 | /** 15 | * Created by nongdenchet on 11/26/16. 16 | */ 17 | 18 | public class EspressoApplication extends MyApplication { 19 | public UserRepo userRepo = mock(UserRepo.class); 20 | 21 | public static EspressoApplication get(ActivityTestRule activityTestRule) { 22 | return (EspressoApplication) activityTestRule.getActivity().getApplication(); 23 | } 24 | 25 | @Override 26 | public AppComponent component() { 27 | return DaggerAppComponent.builder() 28 | .repoModule(repoModule()) 29 | .build(); 30 | } 31 | 32 | private RepoModule repoModule() { 33 | return new RepoModule() { 34 | @Override 35 | public UserRepo provideUserApi(UserApi userApi) { 36 | return userRepo; 37 | } 38 | }; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/androidTest/java/apidez/com/week8/MyRunner.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.support.test.runner.AndroidJUnitRunner; 6 | 7 | /** 8 | * Created by nongdenchet on 11/26/16. 9 | */ 10 | 11 | public class MyRunner extends AndroidJUnitRunner { 12 | 13 | @Override 14 | public Application newApplication(ClassLoader cl, String className, Context context) 15 | throws IllegalAccessException, ClassNotFoundException, InstantiationException { 16 | return super.newApplication(cl, EspressoApplication.class.getName(), context); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/androidTest/java/apidez/com/week8/activity/RegisterActivityTest.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.activity; 2 | 3 | 4 | import android.support.test.espresso.ViewInteraction; 5 | import android.support.test.filters.MediumTest; 6 | import android.support.test.rule.ActivityTestRule; 7 | import android.support.test.runner.AndroidJUnit4; 8 | import android.widget.LinearLayout; 9 | 10 | import org.hamcrest.core.IsInstanceOf; 11 | import org.junit.Rule; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | 15 | import apidez.com.week8.EspressoApplication; 16 | import apidez.com.week8.R; 17 | import apidez.com.week8.data.repo.UserRepo; 18 | import rx.Observable; 19 | 20 | import static android.support.test.espresso.Espresso.onView; 21 | import static android.support.test.espresso.action.ViewActions.click; 22 | import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard; 23 | import static android.support.test.espresso.action.ViewActions.replaceText; 24 | import static android.support.test.espresso.action.ViewActions.typeText; 25 | import static android.support.test.espresso.assertion.ViewAssertions.matches; 26 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 27 | import static android.support.test.espresso.matcher.ViewMatchers.withId; 28 | import static android.support.test.espresso.matcher.ViewMatchers.withParent; 29 | import static android.support.test.espresso.matcher.ViewMatchers.withText; 30 | import static apidez.com.week8.utils.EspressoUtils.childAtPosition; 31 | import static org.hamcrest.Matchers.allOf; 32 | import static org.hamcrest.Matchers.not; 33 | import static org.mockito.Mockito.when; 34 | 35 | @MediumTest 36 | @RunWith(AndroidJUnit4.class) 37 | public class RegisterActivityTest { 38 | 39 | @Rule 40 | public ActivityTestRule activityTestRule = 41 | new ActivityTestRule<>(RegisterActivity.class); 42 | 43 | @Test 44 | public void emailError() { 45 | edtEmail().perform(typeText("android"), closeSoftKeyboard()); 46 | checkError("Invalid email"); 47 | edtEmail().perform(replaceText("android@gmail.com"), closeSoftKeyboard()); 48 | checkNoError("Invalid email"); 49 | } 50 | 51 | @Test 52 | public void passwordError() { 53 | edtPassword().perform(typeText("123"), closeSoftKeyboard()); 54 | checkError("Password is too short"); 55 | edtPassword().perform(replaceText("12345678"), closeSoftKeyboard()); 56 | checkNoError("Password is too short"); 57 | } 58 | 59 | @Test 60 | public void confirmError() { 61 | edtPassword().perform(replaceText("12345678"), closeSoftKeyboard()); 62 | edtConfirm().perform(typeText("123"), closeSoftKeyboard()); 63 | checkError("Password and confirm not match"); 64 | edtConfirm().perform(replaceText("12345678"), closeSoftKeyboard()); 65 | checkNoError("Password and confirm not match"); 66 | } 67 | 68 | @Test 69 | public void registerSuccess() { 70 | when(userApi().register("android@gmail.com", "12345678")) 71 | .thenReturn(Observable.just("Success")); 72 | edtEmail().perform(typeText("android@gmail.com"), closeSoftKeyboard()); 73 | edtPassword().perform(replaceText("12345678"), closeSoftKeyboard()); 74 | edtConfirm().perform(replaceText("12345678"), closeSoftKeyboard()); 75 | registerBtn().perform(click()); 76 | checkMessage("Success"); 77 | } 78 | 79 | @Test 80 | public void registerFail() { 81 | when(userApi().register("cs@gmail.com", "12345678")) 82 | .thenReturn(Observable.error(new Throwable("Error"))); 83 | edtEmail().perform(typeText("cs@gmail.com"), closeSoftKeyboard()); 84 | edtPassword().perform(replaceText("12345678"), closeSoftKeyboard()); 85 | edtConfirm().perform(replaceText("12345678"), closeSoftKeyboard()); 86 | registerBtn().perform(click()); 87 | checkMessage("Error"); 88 | } 89 | 90 | private void checkMessage(String message) { 91 | onView(allOf(withId(android.R.id.message), withText(message), 92 | childAtPosition(childAtPosition(withId(R.id.scrollView), 0), 0), isDisplayed())) 93 | .check(matches(withText(message))); 94 | } 95 | 96 | private void checkError(String message) { 97 | onView(allOf(withText(message), childAtPosition(childAtPosition( 98 | IsInstanceOf.instanceOf(LinearLayout.class), 1), 0), isDisplayed())); 99 | } 100 | 101 | private void checkNoError(String message) { 102 | onView(allOf(withText(message), childAtPosition(childAtPosition( 103 | IsInstanceOf.instanceOf(LinearLayout.class), 1), 0), not(isDisplayed()))); 104 | } 105 | 106 | private ViewInteraction registerBtn() { 107 | return onView(allOf(withId(R.id.btnRegister), withText("Register"), 108 | withParent(allOf(withId(R.id.activity_register), 109 | withParent(withId(android.R.id.content)))), 110 | isDisplayed())); 111 | } 112 | 113 | private UserRepo userApi() { 114 | return EspressoApplication.get(activityTestRule).userRepo; 115 | } 116 | 117 | private ViewInteraction edtEmail() { 118 | return onView(allOf(withId(R.id.edtEmail), isDisplayed())); 119 | } 120 | 121 | private ViewInteraction edtPassword() { 122 | return onView(allOf(withId(R.id.edtPassword), isDisplayed())); 123 | } 124 | 125 | private ViewInteraction edtConfirm() { 126 | return onView(allOf(withId(R.id.edtConfirm), isDisplayed())); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/src/androidTest/java/apidez/com/week8/utils/EspressoUtils.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.utils; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | import android.view.ViewParent; 6 | 7 | import org.hamcrest.Description; 8 | import org.hamcrest.Matcher; 9 | import org.hamcrest.TypeSafeMatcher; 10 | 11 | /** 12 | * Created by nongdenchet on 11/26/16. 13 | */ 14 | 15 | public class EspressoUtils { 16 | 17 | public static Matcher childAtPosition(final Matcher parentMatcher, final int position) { 18 | 19 | return new TypeSafeMatcher() { 20 | @Override 21 | public void describeTo(Description description) { 22 | description.appendText("Child at position " + position + " in parent "); 23 | parentMatcher.describeTo(description); 24 | } 25 | 26 | @Override 27 | public boolean matchesSafely(View view) { 28 | ViewParent parent = view.getParent(); 29 | return parent instanceof ViewGroup && parentMatcher.matches(parent) 30 | && view.equals(((ViewGroup) parent).getChildAt(position)); 31 | } 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/MyApplication.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8; 2 | 3 | import android.app.Application; 4 | 5 | import apidez.com.week8.dependency.component.AppComponent; 6 | import apidez.com.week8.dependency.component.DaggerAppComponent; 7 | 8 | /** 9 | * Created by nongdenchet on 11/24/16. 10 | */ 11 | 12 | public class MyApplication extends Application { 13 | private AppComponent appComponent; 14 | 15 | @Override 16 | public void onCreate() { 17 | super.onCreate(); 18 | appComponent = DaggerAppComponent.builder() 19 | .build(); 20 | } 21 | 22 | public AppComponent component() { 23 | return appComponent; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/activity/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | import apidez.com.week8.MyApplication; 8 | import apidez.com.week8.dependency.component.AppComponent; 9 | import rx.Observable; 10 | import rx.subjects.PublishSubject; 11 | 12 | /** 13 | * Created by nongdenchet on 11/26/16. 14 | */ 15 | 16 | public abstract class BaseActivity extends AppCompatActivity { 17 | private AppComponent appComponent; 18 | private static final int START = 0; 19 | private static final int STOP = 1; 20 | private PublishSubject stopEvent = PublishSubject.create(); 21 | private PublishSubject startEvent = PublishSubject.create(); 22 | 23 | public Observable stopEvent() { 24 | return stopEvent.asObservable(); 25 | } 26 | 27 | public Observable startEvent() { 28 | return startEvent.asObservable(); 29 | } 30 | 31 | @Override 32 | protected void onCreate(@Nullable Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | appComponent = ((MyApplication) getApplication()).component(); 35 | } 36 | 37 | protected AppComponent getAppComponent() { 38 | return appComponent; 39 | } 40 | 41 | protected abstract void bindViewModel(); 42 | 43 | @Override 44 | protected void onStart() { 45 | super.onStart(); 46 | startEvent.onNext(START); 47 | bindViewModel(); 48 | } 49 | 50 | @Override 51 | protected void onStop() { 52 | stopEvent.onNext(STOP); 53 | super.onStop(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/activity/RegisterActivity.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.activity; 2 | 3 | import android.databinding.DataBindingUtil; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AlertDialog; 6 | 7 | import javax.inject.Inject; 8 | 9 | import apidez.com.week8.R; 10 | import apidez.com.week8.databinding.ActivityRegisterBinding; 11 | import apidez.com.week8.dependency.component.UserComponent; 12 | import apidez.com.week8.dependency.module.UserModule; 13 | import apidez.com.week8.viewmodel.RegisterViewModel; 14 | import butterknife.ButterKnife; 15 | import butterknife.OnClick; 16 | import rx.android.schedulers.AndroidSchedulers; 17 | import rx.schedulers.Schedulers; 18 | 19 | @SuppressWarnings("FieldCanBeLocal") 20 | public class RegisterActivity extends BaseActivity { 21 | private ActivityRegisterBinding binding; 22 | private UserComponent userComponent; 23 | 24 | @Inject 25 | RegisterViewModel viewModel; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | userComponent = getAppComponent().plus(new UserModule()); 31 | userComponent.inject(this); 32 | binding = DataBindingUtil.setContentView(this, R.layout.activity_register); 33 | binding.setViewModel(viewModel); 34 | ButterKnife.bind(this); 35 | } 36 | 37 | @Override 38 | protected void bindViewModel() { 39 | viewModel.message() 40 | .takeUntil(stopEvent()) 41 | .observeOn(AndroidSchedulers.mainThread()) 42 | .subscribe(this::showMessage); 43 | } 44 | 45 | @OnClick(R.id.btnRegister) 46 | public void onRegisterClick() { 47 | viewModel.register() 48 | .takeUntil(stopEvent()) 49 | .subscribeOn(Schedulers.io()) 50 | .observeOn(AndroidSchedulers.mainThread()) 51 | .subscribe(value -> {}, throwable -> {}); 52 | } 53 | 54 | private void showMessage(String value) { 55 | new AlertDialog.Builder(this) 56 | .setMessage(value) 57 | .setPositiveButton(android.R.string.yes, (dialog, which) -> dialog.dismiss()) 58 | .setIcon(android.R.drawable.ic_dialog_alert) 59 | .show(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/data/api/UserApi.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.data.api; 2 | 3 | import javax.inject.Inject; 4 | 5 | import rx.Observable; 6 | import rx.Subscriber; 7 | 8 | /** 9 | * Created by nongdenchet on 11/25/16. 10 | */ 11 | 12 | public class UserApi { 13 | 14 | @Inject 15 | public UserApi() { 16 | } 17 | 18 | public Observable register(final String email, String password) { 19 | return Observable.create(new Observable.OnSubscribe() { 20 | @Override 21 | public void call(Subscriber subscriber) { 22 | if (email.equals("fpt@gmail.com")) { 23 | subscriber.onError(new Throwable("Email has been used")); 24 | } else { 25 | subscriber.onNext("Success"); 26 | subscriber.onCompleted(); 27 | } 28 | } 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/data/repo/UserRepo.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.data.repo; 2 | 3 | import rx.Observable; 4 | 5 | /** 6 | * Created by nongdenchet on 11/25/16. 7 | */ 8 | 9 | public interface UserRepo { 10 | Observable register(final String email, String password); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/data/repo/UserRepoImpl.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.data.repo; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import apidez.com.week8.data.api.UserApi; 6 | import rx.Observable; 7 | 8 | /** 9 | * Created by nongdenchet on 11/27/16. 10 | */ 11 | 12 | public class UserRepoImpl implements UserRepo { 13 | private UserApi userApi; 14 | 15 | public UserRepoImpl(@NonNull UserApi userApi) { 16 | this.userApi = userApi; 17 | } 18 | 19 | @Override 20 | public Observable register(String email, String password) { 21 | return userApi.register(email, password); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/dependency/ActivityScope.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.dependency; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | /** 9 | * Created by nongdenchet on 11/25/16. 10 | */ 11 | 12 | @Scope 13 | @Retention(RetentionPolicy.RUNTIME) 14 | public @interface ActivityScope { 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/dependency/component/AppComponent.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.dependency.component; 2 | 3 | import javax.inject.Singleton; 4 | 5 | import apidez.com.week8.dependency.module.RepoModule; 6 | import apidez.com.week8.dependency.module.AppModule; 7 | import apidez.com.week8.dependency.module.UserModule; 8 | import dagger.Component; 9 | 10 | /** 11 | * Created by nongdenchet on 11/24/16. 12 | */ 13 | 14 | @Singleton 15 | @Component(modules = {AppModule.class, RepoModule.class}) 16 | public interface AppComponent { 17 | UserComponent plus(UserModule userModule); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/dependency/component/UserComponent.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.dependency.component; 2 | 3 | import apidez.com.week8.activity.RegisterActivity; 4 | import apidez.com.week8.dependency.ActivityScope; 5 | import apidez.com.week8.dependency.module.UserModule; 6 | import dagger.Subcomponent; 7 | 8 | /** 9 | * Created by nongdenchet on 11/25/16. 10 | */ 11 | 12 | @ActivityScope 13 | @Subcomponent(modules = {UserModule.class}) 14 | public interface UserComponent { 15 | void inject(RegisterActivity registerActivity); 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/dependency/module/AppModule.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.dependency.module; 2 | 3 | import android.content.Context; 4 | 5 | import javax.inject.Singleton; 6 | 7 | import dagger.Module; 8 | import dagger.Provides; 9 | 10 | /** 11 | * Created by nongdenchet on 11/24/16. 12 | */ 13 | 14 | @Module 15 | public class AppModule { 16 | private Context context; 17 | 18 | public AppModule(Context context) { 19 | this.context = context; 20 | } 21 | 22 | @Singleton 23 | @Provides 24 | public Context provideContext() { 25 | return context; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/dependency/module/RepoModule.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.dependency.module; 2 | 3 | import apidez.com.week8.data.api.UserApi; 4 | import apidez.com.week8.data.repo.UserRepo; 5 | import apidez.com.week8.data.repo.UserRepoImpl; 6 | import dagger.Module; 7 | import dagger.Provides; 8 | 9 | /** 10 | * Created by nongdenchet on 11/24/16. 11 | */ 12 | 13 | @Module 14 | public class RepoModule { 15 | 16 | @Provides 17 | public UserRepo provideUserApi(UserApi userApi) { 18 | return new UserRepoImpl(userApi); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/dependency/module/UserModule.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.dependency.module; 2 | 3 | import apidez.com.week8.validator.RegisterValidator; 4 | import dagger.Module; 5 | import dagger.Provides; 6 | 7 | /** 8 | * Created by nongdenchet on 11/25/16. 9 | */ 10 | 11 | @Module 12 | public class UserModule { 13 | 14 | @Provides 15 | public RegisterValidator provideRegisterValidator() { 16 | return new RegisterValidator(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/utils/BindingUtils.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.utils; 2 | 3 | import android.databinding.BindingAdapter; 4 | import android.widget.EditText; 5 | 6 | import apidez.com.week8.utils.view.TextChange; 7 | import apidez.com.week8.utils.view.TextChangeAdapter; 8 | 9 | /** 10 | * Created by nongdenchet on 11/22/16. 11 | */ 12 | 13 | public class BindingUtils { 14 | 15 | @BindingAdapter("textChange") 16 | public static void textChange(final EditText editText, final TextChange textChange) { 17 | editText.addTextChangedListener(new TextChangeAdapter() { 18 | @Override 19 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 20 | textChange.onChange(charSequence.toString()); 21 | } 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/utils/view/TextChange.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.utils.view; 2 | 3 | /** 4 | * Created by nongdenchet on 11/22/16. 5 | */ 6 | 7 | public interface TextChange { 8 | void onChange(String value); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/utils/view/TextChangeAdapter.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.utils.view; 2 | 3 | import android.text.Editable; 4 | import android.text.TextWatcher; 5 | 6 | /** 7 | * Created by nongdenchet on 11/22/16. 8 | */ 9 | 10 | public class TextChangeAdapter implements TextWatcher { 11 | @Override 12 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 13 | 14 | } 15 | 16 | @Override 17 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 18 | 19 | } 20 | 21 | @Override 22 | public void afterTextChanged(Editable editable) { 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/validator/RegisterValidator.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.validator; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | /** 7 | * Created by nongdenchet on 11/26/16. 8 | */ 9 | 10 | public class RegisterValidator { 11 | private final Pattern VALID_EMAIL_ADDRESS_REGEX = 12 | Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE); 13 | 14 | public String validateEmail(String email) { 15 | return validEmail(email) ? null : "Invalid email"; 16 | } 17 | 18 | private boolean validEmail(String email) { 19 | Matcher matcher = VALID_EMAIL_ADDRESS_REGEX.matcher(email); 20 | return matcher.find(); 21 | } 22 | 23 | public String validatePassword(String password) { 24 | return validPassword(password) ? null : "Password is too short"; 25 | } 26 | 27 | private boolean validPassword(String password) { 28 | return password != null && password.length() >= 8; 29 | } 30 | 31 | public String validateConfirm(String password, String confirm) { 32 | return validConfirm(password, confirm) ? null : "Password and confirm not match"; 33 | } 34 | 35 | private boolean validConfirm(String password, String confirm) { 36 | return confirm != null && confirm.equals(password); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/apidez/com/week8/viewmodel/RegisterViewModel.java: -------------------------------------------------------------------------------- 1 | package apidez.com.week8.viewmodel; 2 | 3 | import android.databinding.ObservableBoolean; 4 | import android.databinding.ObservableField; 5 | import android.support.annotation.NonNull; 6 | 7 | import javax.inject.Inject; 8 | 9 | import apidez.com.week8.data.repo.UserRepo; 10 | import apidez.com.week8.dependency.ActivityScope; 11 | import apidez.com.week8.utils.view.TextChange; 12 | import apidez.com.week8.validator.RegisterValidator; 13 | import rx.Observable; 14 | import rx.subjects.PublishSubject; 15 | 16 | /** 17 | * Created by nongdenchet on 11/22/16. 18 | */ 19 | 20 | @ActivityScope 21 | public class RegisterViewModel { 22 | private UserRepo userRepo; 23 | private RegisterValidator validator; 24 | private String email = ""; 25 | private String password = ""; 26 | private String confirm = ""; 27 | private PublishSubject message = PublishSubject.create(); 28 | public ObservableField emailError = new ObservableField<>(); 29 | public ObservableField passwordError = new ObservableField<>(); 30 | public ObservableField confirmError = new ObservableField<>(); 31 | public ObservableBoolean isValid = new ObservableBoolean(false); 32 | 33 | @Inject 34 | RegisterViewModel(@NonNull UserRepo userRepo, @NonNull RegisterValidator validator) { 35 | this.userRepo = userRepo; 36 | this.validator = validator; 37 | } 38 | 39 | public Observable message() { 40 | return message.asObservable(); 41 | } 42 | 43 | public TextChange emailChange = value -> { 44 | email = value; 45 | emailError.set(validator.validateEmail(email)); 46 | updateBtnState(); 47 | }; 48 | 49 | public TextChange passwordChange = value -> { 50 | password = value; 51 | passwordError.set(validator.validatePassword(password)); 52 | updateBtnState(); 53 | }; 54 | 55 | public TextChange confirmChange = value -> { 56 | confirm = value; 57 | confirmError.set(validator.validateConfirm(password, confirm)); 58 | updateBtnState(); 59 | }; 60 | 61 | private void updateBtnState() { 62 | isValid.set(!hasEmptyData() && !hasError()); 63 | } 64 | 65 | private boolean hasEmptyData() { 66 | return email.equals("") 67 | || password.equals("") 68 | || confirm.equals(""); 69 | } 70 | 71 | private boolean hasError() { 72 | return emailError.get() != null 73 | || passwordError.get() != null 74 | || confirmError.get() != null; 75 | } 76 | 77 | public Observable register() { 78 | return Observable.just(isValid.get()) 79 | .filter(btnState -> btnState) 80 | .flatMap((value) -> userRepo.register(email, password)) 81 | .doOnNext(value -> message.onNext(value)) 82 | .doOnError(throwable -> message.onNext(throwable.getMessage())); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_register.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 18 | 19 | 25 | 26 | 33 | 34 | 35 | 41 | 42 | 49 | 50 | 51 | 57 | 58 | 65 | 66 | 67 |