├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── tomoima │ │ └── daggertestexample │ │ ├── TestAppComponent.kt │ │ ├── api │ │ └── TestApiModule.kt │ │ ├── data │ │ └── TestPrefModule.kt │ │ └── signup │ │ └── SignupActivityTest.kt │ ├── debug │ └── java │ │ └── com │ │ └── tomoima │ │ └── daggertestexample │ │ └── annotations │ │ └── DebugOpenClass.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── tomoima │ │ │ └── daggertestexample │ │ │ ├── AppComponent.kt │ │ │ ├── AppModule.kt │ │ │ ├── MyApplication.kt │ │ │ ├── annotations │ │ │ └── OpenClass.kt │ │ │ ├── api │ │ │ ├── ApiModule.kt │ │ │ └── RegisterApi.kt │ │ │ ├── data │ │ │ ├── BasePreferences.kt │ │ │ ├── PrefModule.kt │ │ │ └── UserPrefs.kt │ │ │ ├── di │ │ │ └── ActivityScope.kt │ │ │ ├── model │ │ │ └── User.kt │ │ │ ├── repository │ │ │ ├── Repository.kt │ │ │ ├── RepositoryModule.kt │ │ │ └── UserRepository.kt │ │ │ └── signup │ │ │ ├── SignupActivity.kt │ │ │ ├── SignupComponent.kt │ │ │ └── SignupViewModel.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── release │ └── java │ │ └── com │ │ └── tomoima │ │ └── daggertestexample │ │ └── annotations │ │ └── DebugOpenClass.kt │ └── test │ └── java │ └── com │ └── tomoima │ └── daggertestexample │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img └── image.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Writing Espresso Instrumental test with Dagger2 + Kotlin 2 | 3 | This is a sample project that explains how to run instrumentation tests using Dagger2 and Kotlin 4 | 5 | ![img](img/image.png) 6 | 7 | This app has a simple registration screen. The project follows MVVM architecture and RxJava2 is used to implement Observer Pattern. 8 | Instrumentation tests run with this sample project. 9 | 10 | - Displays “No info” by default 11 | - Displays Name and Age when the input is legit 12 | - Displays an error message(“Name invalid”) when Name is not set -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | dependencies { 6 | classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version" 7 | } 8 | } 9 | 10 | apply plugin: 'com.android.application' 11 | 12 | apply plugin: 'kotlin-android' 13 | 14 | apply plugin: 'kotlin-android-extensions' 15 | 16 | apply plugin: 'kotlin-kapt' 17 | 18 | apply plugin: 'kotlin-allopen' 19 | 20 | kapt { 21 | correctErrorTypes = true 22 | } 23 | 24 | allOpen { 25 | annotation("com.tomoima.daggertestexample.annotations.OpenClass") 26 | } 27 | 28 | android { 29 | compileSdkVersion 26 30 | defaultConfig { 31 | applicationId "com.tomoima.daggertestexample" 32 | minSdkVersion 21 33 | targetSdkVersion 26 34 | versionCode 1 35 | versionName "1.0" 36 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 37 | } 38 | buildTypes { 39 | release { 40 | minifyEnabled false 41 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 42 | } 43 | } 44 | 45 | compileOptions { 46 | sourceCompatibility JavaVersion.VERSION_1_8 47 | targetCompatibility JavaVersion.VERSION_1_8 48 | } 49 | packagingOptions { 50 | exclude 'META-INF/rxjava.properties' 51 | } 52 | lintOptions { 53 | abortOnError false 54 | } 55 | } 56 | 57 | dependencies { 58 | implementation fileTree(dir: 'libs', include: ['*.jar']) 59 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 60 | implementation 'com.android.support:appcompat-v7:26.1.0' 61 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 62 | implementation 'com.google.dagger:dagger:2.11' 63 | kapt 'com.google.dagger:dagger-compiler:2.9' 64 | implementation 'io.reactivex.rxjava2:rxjava:2.1.5' 65 | implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' 66 | implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0' 67 | implementation 'com.jakewharton:butterknife:8.8.1' 68 | kapt 'com.jakewharton:butterknife-compiler:8.8.1' 69 | 70 | // Test 71 | testImplementation 'junit:junit:4.12' 72 | androidTestCompile 'com.android.support:support-annotations:26.1.0' 73 | androidTestCompile 'com.android.support.test:rules:1.0.1' 74 | androidTestImplementation "org.mockito:mockito-core:$mockito_version" 75 | androidTestImplementation "org.mockito:mockito-android:$mockito_version" 76 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 77 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 78 | androidTestImplementation 'com.nhaarman:mockito-kotlin:1.5.0' 79 | androidTestImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" 80 | kaptAndroidTest 'com.google.dagger:dagger-compiler:2.9' 81 | 82 | } 83 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/tomoima/daggertestexample/TestAppComponent.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample 2 | 3 | import com.tomoima.daggertestexample.api.ApiModule 4 | import com.tomoima.daggertestexample.data.PrefModule 5 | import com.tomoima.daggertestexample.repository.RepositoryModule 6 | import com.tomoima.daggertestexample.signup.SignupActivityTest 7 | import dagger.Component 8 | import javax.inject.Singleton 9 | 10 | @Singleton 11 | @Component(modules = arrayOf( 12 | AppModule::class, 13 | PrefModule::class, 14 | ApiModule::class, 15 | RepositoryModule::class) 16 | ) 17 | interface TestAppComponent : AppComponent { 18 | fun inject(test: SignupActivityTest) 19 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/tomoima/daggertestexample/api/TestApiModule.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.api 2 | 3 | import org.mockito.Mockito 4 | 5 | 6 | class TestApiModule : ApiModule() { 7 | 8 | override fun provideRegisterApi(): RegisterApi = Mockito.mock(RegisterApi::class.java) 9 | 10 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/tomoima/daggertestexample/data/TestPrefModule.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.data 2 | 3 | import com.tomoima.daggertestexample.MyApplication 4 | import org.mockito.Mockito 5 | 6 | 7 | class TestPrefModule: PrefModule() { 8 | 9 | override fun provideUserPref(application: MyApplication): UserPrefs { 10 | return Mockito.mock(UserPrefs::class.java) 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/tomoima/daggertestexample/signup/SignupActivityTest.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.signup 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.espresso.Espresso.onView 5 | import android.support.test.espresso.action.ViewActions.* 6 | import android.support.test.espresso.assertion.ViewAssertions.matches 7 | import android.support.test.espresso.matcher.ViewMatchers.withId 8 | import android.support.test.espresso.matcher.ViewMatchers.withText 9 | import android.support.test.rule.ActivityTestRule 10 | import android.support.test.runner.AndroidJUnit4 11 | import com.nhaarman.mockito_kotlin.any 12 | import com.nhaarman.mockito_kotlin.whenever 13 | import com.tomoima.daggertestexample.* 14 | import com.tomoima.daggertestexample.api.RegisterApi 15 | import com.tomoima.daggertestexample.api.TestApiModule 16 | import com.tomoima.daggertestexample.data.TestPrefModule 17 | import com.tomoima.daggertestexample.data.UserPrefs 18 | import com.tomoima.daggertestexample.model.User 19 | import io.reactivex.Maybe 20 | import org.junit.Before 21 | import org.junit.Rule 22 | import org.junit.Test 23 | import org.junit.runner.RunWith 24 | import org.mockito.MockitoAnnotations 25 | import javax.inject.Inject 26 | 27 | 28 | @RunWith(AndroidJUnit4::class) 29 | class SignupActivityTest { 30 | 31 | @get:Rule 32 | val testRule: ActivityTestRule 33 | = ActivityTestRule(SignupActivity::class.java, false, false) 34 | 35 | @Inject 36 | lateinit var registerApi: RegisterApi 37 | 38 | @Inject 39 | lateinit var userPref: UserPrefs 40 | 41 | private lateinit var testAppComponent: TestAppComponent 42 | 43 | @Before 44 | fun setup() { 45 | MockitoAnnotations.initMocks(this) 46 | val app = InstrumentationRegistry.getTargetContext().applicationContext as MyApplication 47 | testAppComponent = DaggerTestAppComponent.builder() 48 | .appModule(AppModule(app)) 49 | .apiModule(TestApiModule()) 50 | .prefModule(TestPrefModule()) 51 | .build() 52 | app.appComponent = testAppComponent 53 | testAppComponent.inject(this) 54 | System.out.println("==== TestAppComponent injected") 55 | } 56 | 57 | @Test 58 | fun userInfo_returns_no_info_by_default() { 59 | // given 60 | // nothing is stored 61 | whenever(userPref.hasAge()).thenReturn(false) 62 | whenever(userPref.hasName()).thenReturn(false) 63 | 64 | // when 65 | testRule.launchActivity(null) 66 | 67 | // then 68 | onView(withId(R.id.user_info)).check(matches(withText("No info"))) 69 | } 70 | 71 | @Test 72 | fun userInfo_returns_stored_userInfo() { 73 | // given 74 | // "Mike" and 20 is stored 75 | whenever(userPref.hasAge()).thenReturn(true) 76 | whenever(userPref.hasName()).thenReturn(true) 77 | whenever(userPref.name).thenReturn("Mike") 78 | whenever(userPref.age).thenReturn(20) 79 | 80 | // when 81 | testRule.launchActivity(null) 82 | 83 | // then 84 | onView(withId(R.id.user_info)).check(matches(withText("Current info: Name Mike Age 20"))) 85 | } 86 | 87 | @Test 88 | fun userInfo_updates_with_input() { 89 | // given 90 | // api returns posted user 91 | val user = User("John", 10) 92 | whenever(registerApi.post(user)).thenReturn(Maybe.just(user)) 93 | 94 | // nothing is stored 95 | whenever(userPref.hasAge()).thenReturn(false) 96 | whenever(userPref.hasName()).thenReturn(false) 97 | 98 | // when 99 | testRule.launchActivity(null) 100 | 101 | onView(withId(R.id.name)).perform(typeText("John"), closeSoftKeyboard()) 102 | onView(withId(R.id.age)).perform(typeText("10"), closeSoftKeyboard()) 103 | onView(withId(R.id.register)).perform(click()) 104 | 105 | // then 106 | onView(withId(R.id.user_info)).check(matches(withText("Current info: Name John Age 10"))) 107 | } 108 | 109 | @Test 110 | fun errorMessage_shows_with_illegal_input() { 111 | // given 112 | // api returns an error 113 | whenever(registerApi.post(any())).thenReturn(Maybe.error(IllegalArgumentException("error"))) 114 | 115 | // nothing is stored 116 | whenever(userPref.hasAge()).thenReturn(false) 117 | whenever(userPref.hasName()).thenReturn(false) 118 | 119 | // when 120 | testRule.launchActivity(null) 121 | 122 | onView(withId(R.id.name)).perform(typeText(""), closeSoftKeyboard()) 123 | onView(withId(R.id.age)).perform(typeText("10"), closeSoftKeyboard()) 124 | onView(withId(R.id.register)).perform(click()) 125 | 126 | // then 127 | onView(withId(R.id.warning)).check(matches(withText("error"))) 128 | onView(withId(R.id.user_info)).check(matches(withText("No info"))) 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /app/src/debug/java/com/tomoima/daggertestexample/annotations/DebugOpenClass.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.annotations 2 | 3 | @OpenClass 4 | @Target(AnnotationTarget.CLASS) 5 | annotation class DebugOpenClass -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/AppComponent.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample 2 | 3 | import com.tomoima.daggertestexample.api.ApiModule 4 | import com.tomoima.daggertestexample.data.PrefModule 5 | import com.tomoima.daggertestexample.repository.RepositoryModule 6 | import com.tomoima.daggertestexample.signup.SignupComponent 7 | import dagger.Component 8 | import javax.inject.Singleton 9 | 10 | 11 | @Singleton 12 | @Component(modules = arrayOf( 13 | AppModule::class, 14 | PrefModule::class, 15 | ApiModule::class, 16 | RepositoryModule::class)) 17 | interface AppComponent { 18 | fun inject(app: MyApplication) 19 | 20 | fun plus(module: SignupComponent.Module): SignupComponent 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import javax.inject.Singleton 6 | 7 | @Module 8 | class AppModule(private val app: MyApplication) { 9 | @Provides 10 | @Singleton 11 | fun provideApp(): MyApplication = app 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/MyApplication.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample 2 | 3 | import android.app.Application 4 | 5 | 6 | class MyApplication: Application() { 7 | 8 | lateinit var appComponent: AppComponent 9 | 10 | override fun onCreate() { 11 | super.onCreate() 12 | appComponent = DaggerAppComponent.builder() 13 | .appModule(AppModule(this)) 14 | .build() 15 | 16 | appComponent.inject(this) 17 | } 18 | 19 | 20 | 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/annotations/OpenClass.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.annotations 2 | 3 | 4 | @Target(AnnotationTarget.ANNOTATION_CLASS) 5 | annotation class OpenClass -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/api/ApiModule.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.api 2 | 3 | import com.tomoima.daggertestexample.annotations.DebugOpenClass 4 | import dagger.Module 5 | import dagger.Provides 6 | import javax.inject.Singleton 7 | 8 | @DebugOpenClass 9 | @Module 10 | class ApiModule { 11 | @Provides 12 | @Singleton 13 | fun provideRegisterApi() = RegisterApi() 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/api/RegisterApi.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.api 2 | 3 | import com.tomoima.daggertestexample.annotations.DebugOpenClass 4 | import com.tomoima.daggertestexample.model.User 5 | import io.reactivex.Maybe 6 | 7 | @DebugOpenClass 8 | class RegisterApi { 9 | 10 | fun post(user: User): Maybe = Maybe.create { 11 | if (user.name.isEmpty()) { 12 | it.onError(IllegalArgumentException("Name invalid")) 13 | } 14 | it.onSuccess(user) 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/data/BasePreferences.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.data 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | 6 | 7 | abstract class BasePreferences { 8 | private lateinit var prefs: SharedPreferences 9 | 10 | protected fun init(context: Context, tableName: String) { 11 | this.prefs = context.getSharedPreferences(tableName, Context.MODE_PRIVATE) 12 | } 13 | 14 | protected fun getString(key: String, value: String): String = prefs.getString(key, value) 15 | 16 | protected fun putString(key: String, value: String) { 17 | prefs.edit().putString(key, value).apply() 18 | } 19 | 20 | protected fun has(key: String): Boolean { 21 | return prefs.contains(key) 22 | } 23 | 24 | protected fun getInt(key: String, defValue: Int): Int = prefs.getInt(key, defValue) 25 | 26 | protected fun putInt(key: String, value: Int) { 27 | prefs.edit().putInt(key, value).apply() 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/data/PrefModule.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.data 2 | 3 | import com.tomoima.daggertestexample.MyApplication 4 | import com.tomoima.daggertestexample.annotations.DebugOpenClass 5 | import dagger.Module 6 | import dagger.Provides 7 | import javax.inject.Singleton 8 | 9 | @DebugOpenClass 10 | @Module 11 | class PrefModule { 12 | @Provides 13 | @Singleton 14 | fun provideUserPref(application: MyApplication): UserPrefs = UserPrefs(application) 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/data/UserPrefs.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.data 2 | 3 | import android.app.Application 4 | import com.tomoima.daggertestexample.annotations.DebugOpenClass 5 | 6 | @DebugOpenClass 7 | class UserPrefs(application: Application) : BasePreferences() { 8 | init { 9 | super.init(application, "User") 10 | } 11 | 12 | var name: String 13 | get() = getString("name", "") 14 | set(name) = putString("name", name) 15 | 16 | fun hasName(): Boolean { 17 | return has("name") 18 | } 19 | 20 | var age: Int 21 | get() = getInt("age", 0) 22 | set(age) = putInt("age", age) 23 | 24 | fun hasAge(): Boolean { 25 | return has("age") 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/di/ActivityScope.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.di 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | @MustBeDocumented 7 | @kotlin.annotation.Retention(value = AnnotationRetention.RUNTIME) 8 | annotation class ActivityScope 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/model/User.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.model 2 | 3 | 4 | data class User(val name: String, val age: Int) -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/repository/Repository.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.repository 2 | 3 | import io.reactivex.Completable 4 | import io.reactivex.Flowable 5 | 6 | 7 | abstract class Repository { 8 | abstract fun get(): T 9 | abstract fun observe(): Flowable 10 | abstract fun set(value :T): Completable 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/repository/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.repository 2 | 3 | import com.tomoima.daggertestexample.api.RegisterApi 4 | import com.tomoima.daggertestexample.data.UserPrefs 5 | import dagger.Module 6 | import dagger.Provides 7 | import javax.inject.Singleton 8 | 9 | @Module 10 | class RepositoryModule { 11 | 12 | @Provides 13 | @Singleton 14 | fun provideUserRepository(api: RegisterApi, userPrefs: UserPrefs): UserRepository 15 | = UserRepository(api, userPrefs) 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/repository/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.repository 2 | 3 | import com.tomoima.daggertestexample.annotations.DebugOpenClass 4 | import com.tomoima.daggertestexample.api.RegisterApi 5 | import com.tomoima.daggertestexample.data.UserPrefs 6 | import com.tomoima.daggertestexample.model.User 7 | import io.reactivex.Completable 8 | import io.reactivex.Flowable 9 | import io.reactivex.processors.BehaviorProcessor 10 | 11 | @DebugOpenClass 12 | class UserRepository( 13 | private val api: RegisterApi, 14 | private val userPref: UserPrefs) : Repository() { 15 | private val processor = BehaviorProcessor.createDefault(User("", 0)) 16 | 17 | init { 18 | initialization() 19 | } 20 | 21 | private fun initialization() { 22 | if (userPref.hasAge() && userPref.hasName()) { 23 | processor.onNext(User(userPref.name, userPref.age)) 24 | } 25 | } 26 | 27 | override fun get(): User = processor.value 28 | 29 | override fun observe(): Flowable { 30 | return processor 31 | } 32 | 33 | override fun set(value: User): Completable = 34 | api.post(value) 35 | .doOnSuccess { 36 | userPref.name = it.name 37 | userPref.age = it.age 38 | processor.onNext(it) 39 | } 40 | .ignoreElement() 41 | 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/signup/SignupActivity.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.signup 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.widget.Button 6 | import android.widget.TextView 7 | import butterknife.BindView 8 | import butterknife.ButterKnife 9 | import com.jakewharton.rxbinding2.view.RxView 10 | import com.tomoima.daggertestexample.MyApplication 11 | import com.tomoima.daggertestexample.R 12 | import io.reactivex.BackpressureStrategy 13 | import io.reactivex.android.schedulers.AndroidSchedulers 14 | import io.reactivex.disposables.CompositeDisposable 15 | import io.reactivex.schedulers.Schedulers 16 | import javax.inject.Inject 17 | 18 | class SignupActivity : AppCompatActivity() { 19 | 20 | @Inject 21 | lateinit var signupViewModel: SignupViewModel 22 | 23 | @BindView(R.id.name) 24 | lateinit var name: TextView 25 | 26 | @BindView(R.id.age) 27 | lateinit var age: TextView 28 | 29 | @BindView(R.id.user_info) 30 | lateinit var userInfo: TextView 31 | 32 | @BindView(R.id.warning) 33 | lateinit var warning: TextView 34 | 35 | @BindView(R.id.register) 36 | lateinit var registerBtn: Button 37 | 38 | private val disposables: CompositeDisposable = CompositeDisposable() 39 | 40 | override fun onCreate(savedInstanceState: Bundle?) { 41 | super.onCreate(savedInstanceState) 42 | setContentView(R.layout.activity_main) 43 | (application as MyApplication).appComponent 44 | .plus(SignupComponent.Module()) 45 | .inject(this) 46 | System.out.println("==== SignupActivity injected") 47 | ButterKnife.bind(this) 48 | 49 | val registerInput = RxView.clicks(registerBtn).toFlowable(BackpressureStrategy.LATEST) 50 | 51 | disposables.addAll( 52 | registerInput 53 | .observeOn(Schedulers.io()) 54 | .flatMapCompletable { _ -> 55 | signupViewModel.update(name.text.toString(), age.text.toString()) 56 | .onErrorComplete() 57 | } 58 | .subscribe({ }, { println(it) }), 59 | signupViewModel.observeUserInfo() 60 | .subscribeOn(Schedulers.io()) 61 | .observeOn(AndroidSchedulers.mainThread()) 62 | .subscribe( 63 | { 64 | if (it.name.isEmpty()) { 65 | userInfo.text = "No info" 66 | } else { 67 | userInfo.text = 68 | "Current info: Name ${it.name} Age ${it.age}" 69 | } 70 | }, 71 | { } 72 | ), 73 | signupViewModel.errorMessage 74 | .subscribeOn(Schedulers.io()) 75 | .observeOn(AndroidSchedulers.mainThread()) 76 | .subscribe( 77 | { warning.text = it }, 78 | { } 79 | ) 80 | ) 81 | 82 | } 83 | 84 | override fun onDestroy() { 85 | super.onDestroy() 86 | disposables.clear() 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/signup/SignupComponent.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.signup 2 | 3 | import com.tomoima.daggertestexample.di.ActivityScope 4 | import com.tomoima.daggertestexample.repository.UserRepository 5 | import dagger.Provides 6 | import dagger.Subcomponent 7 | 8 | 9 | @ActivityScope 10 | @Subcomponent(modules = arrayOf(SignupComponent.Module::class)) 11 | interface SignupComponent { 12 | 13 | fun inject(activity: SignupActivity) 14 | 15 | @dagger.Module 16 | class Module { 17 | @Provides 18 | fun provideSignupViewModel(userRepository: UserRepository): SignupViewModel { 19 | return SignupViewModel(userRepository) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/tomoima/daggertestexample/signup/SignupViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.tomoima.daggertestexample.signup 2 | 3 | import com.tomoima.daggertestexample.model.User 4 | import com.tomoima.daggertestexample.repository.UserRepository 5 | import io.reactivex.Completable 6 | import io.reactivex.Flowable 7 | import io.reactivex.processors.PublishProcessor 8 | 9 | 10 | class SignupViewModel(private val userRepository: UserRepository) { 11 | 12 | val errorMessage = PublishProcessor.create() 13 | 14 | /** 15 | * Action 16 | */ 17 | fun update(name: String, age: String): Completable = 18 | userRepository.set(User(name, age.toInt())) 19 | .doOnError{ errorMessage.onNext(it.message) } 20 | .doOnComplete{ errorMessage.onNext("") } 21 | 22 | /** 23 | * Observable 24 | */ 25 | fun observeUserInfo(): Flowable = userRepository.observe() 26 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 14 | 18 | 22 | 26 | 30 | 34 | 38 | 42 | 46 | 50 | 54 | 58 | 62 | 66 | 70 | 74 | 78 | 82 | 86 | 90 | 94 | 98 | 102 | 106 | 110 | 114 | 118 | 122 | 126 | 130 | 134 | 138 | 139 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 23 | 36 | 49 |