├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── kotlinc.xml ├── markdown-navigator.xml ├── markdown-navigator │ └── profiles_settings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── github │ │ └── kobakei │ │ └── kotlinexample │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── kobakei │ │ │ └── kotlinexample │ │ │ ├── App.kt │ │ │ ├── di │ │ │ ├── ActivityComponent.kt │ │ │ ├── ActivityModule.kt │ │ │ ├── AppComponent.kt │ │ │ ├── AppModule.kt │ │ │ └── scope │ │ │ │ └── PerActivity.kt │ │ │ ├── model │ │ │ ├── dao │ │ │ │ ├── OrmaHolder.kt │ │ │ │ └── UserDao.kt │ │ │ ├── entity │ │ │ │ └── User.kt │ │ │ ├── net │ │ │ │ └── GitHubApi.kt │ │ │ └── repository │ │ │ │ └── UserRepository.kt │ │ │ └── ui │ │ │ ├── base │ │ │ ├── BaseActivity.kt │ │ │ └── DaggerFactory.kt │ │ │ ├── detail │ │ │ ├── DetailActivity.kt │ │ │ └── DetailViewModel.kt │ │ │ ├── main │ │ │ ├── MainActivity.kt │ │ │ ├── MainAdapter.kt │ │ │ ├── MainItemViewModel.kt │ │ │ ├── MainViewHolder.kt │ │ │ └── MainViewModel.kt │ │ │ └── util │ │ │ └── BindingAdapterUtil.kt │ └── res │ │ ├── layout │ │ ├── detail_activity.xml │ │ ├── main_activity.xml │ │ └── main_item.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 │ └── test │ ├── java │ └── io │ │ └── github │ │ └── kobakei │ │ └── kotlinexample │ │ ├── ExampleRobolectricTest.kt │ │ ├── ExampleUnitTest.kt │ │ └── ui │ │ └── MainActivityViewModelTest.kt │ └── resources │ └── mockito-extensions │ └── org.mockito.plugins.MockMaker ├── 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/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android Kotlin Example 2 | ======================= 3 | 4 | Kotlinで書いたAndroidのサンプルアプリです。 5 | 6 | ## Spec 7 | 8 | droidkaigi 2017 appのコントリビュータをリスト表示します。各コントリビュータをクリックすると、そのコントリビュータの詳細を表示します。 9 | 10 | |Main|Detail| 11 | |---|---| 12 | | ![1](https://cloud.githubusercontent.com/assets/900756/25039948/6fee9b4c-2141-11e7-913e-e84b884fbb1b.png) | ![2](https://cloud.githubusercontent.com/assets/900756/25039949/72733cce-2141-11e7-9707-14d05bbf3d7c.png) | 13 | 14 | ## Architecture 15 | 16 | MVVM with data binding 17 | 18 | ## Library 19 | 20 | - Architecture components 21 | - Data binding 22 | - Dagger 2 23 | - RxJava / RxAndroid / RxKotlin / AutoDispose 24 | - Retrofit / OkHttp 25 | - Orma 26 | - Glide 27 | 28 | ## Test 29 | 30 | ### 単体テスト 31 | 32 | - Robolectric + Mockito2 33 | - Mockito2で、final classのモック化が可能になったので、PowerMockは使用していません。 34 | 35 | 36 | ### UIテスト 37 | 38 | TODO 39 | 40 | ## License 41 | 42 | ``` 43 | Copyright 2017 Keisuke Kobayashi 44 | 45 | Licensed under the Apache License, Version 2.0 (the "License"); 46 | you may not use this file except in compliance with the License. 47 | You may obtain a copy of the License at 48 | 49 | http://www.apache.org/licenses/LICENSE-2.0 50 | 51 | Unless required by applicable law or agreed to in writing, software 52 | distributed under the License is distributed on an "AS IS" BASIS, 53 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 54 | See the License for the specific language governing permissions and 55 | limitations under the License. 56 | ``` 57 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | 5 | android { 6 | compileSdkVersion 27 7 | buildToolsVersion "27.0.0" 8 | defaultConfig { 9 | applicationId "io.github.kobakei.kotlinexample" 10 | minSdkVersion 16 11 | targetSdkVersion 27 12 | versionCode 1 13 | versionName "1.0" 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | dataBinding { 23 | enabled true 24 | } 25 | } 26 | 27 | dependencies { 28 | compile fileTree(dir: 'libs', include: ['*.jar']) 29 | 30 | // Support lib 31 | compile 'com.android.support:appcompat-v7:27.0.1' 32 | compile 'com.android.support:support-v4:27.0.1' 33 | compile 'com.android.support:recyclerview-v7:27.0.1' 34 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 35 | 36 | // Data binding 37 | kapt 'com.android.databinding:compiler:3.0.0' 38 | 39 | // Architecture component 40 | implementation "android.arch.lifecycle:extensions:1.0.0" 41 | 42 | // kotlin 43 | compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 44 | 45 | // OkHttp & Retrofit 46 | compile 'com.squareup.okhttp3:okhttp:3.9.0' 47 | compile 'com.squareup.retrofit2:retrofit:2.3.0' 48 | compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' 49 | compile 'com.squareup.retrofit2:converter-gson:2.3.0' 50 | 51 | // RxJava 52 | compile 'io.reactivex.rxjava2:rxjava:2.1.6' 53 | compile 'io.reactivex.rxjava2:rxandroid:2.0.1' 54 | compile 'io.reactivex.rxjava2:rxkotlin:2.1.0' 55 | 56 | compile 'com.uber.autodispose:autodispose-kotlin:0.4.0' 57 | compile 'com.uber.autodispose:autodispose-android-kotlin:0.4.0' 58 | compile 'com.uber.autodispose:autodispose-android-archcomponents-kotlin:0.4.0' 59 | 60 | // Gson 61 | compile 'com.google.code.gson:gson:2.8.2' 62 | 63 | // Orma 64 | kapt 'com.github.gfx.android.orma:orma-processor:4.2.3' 65 | compile 'com.github.gfx.android.orma:orma:4.2.3' 66 | 67 | compile 'com.github.bumptech.glide:glide:3.7.0' 68 | 69 | compile 'com.jakewharton.timber:timber:4.5.1' 70 | 71 | // DI 72 | compile 'com.google.dagger:dagger:2.13' 73 | kapt 'com.google.dagger:dagger-compiler:2.13' 74 | 75 | compile 'com.facebook.stetho:stetho:1.5.0' 76 | compile 'com.facebook.stetho:stetho-okhttp3:1.5.0' 77 | 78 | testCompile 'junit:junit:4.12' 79 | testCompile 'org.robolectric:robolectric:3.5' 80 | testCompile 'org.mockito:mockito-core:2.8.9' 81 | 82 | androidTestCompile('com.android.support.test.espresso:espresso-core:3.0.0', { 83 | exclude group: 'com.android.support', module: 'support-annotations' 84 | }) 85 | } 86 | repositories { 87 | mavenCentral() 88 | } 89 | -------------------------------------------------------------------------------- /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/keisukekobayashi/Library/Android/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 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/io/github/kobakei/kotlinexample/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample 2 | 3 | import android.content.Context 4 | import android.support.test.InstrumentationRegistry 5 | import android.support.test.runner.AndroidJUnit4 6 | 7 | import org.junit.Test 8 | import org.junit.runner.RunWith 9 | 10 | import org.junit.Assert.* 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | 15 | * @see [Testing documentation](http://d.android.com/tools/testing) 16 | */ 17 | @RunWith(AndroidJUnit4::class) 18 | class ExampleInstrumentedTest { 19 | @Test 20 | @Throws(Exception::class) 21 | fun useAppContext() { 22 | // Context of the app under test. 23 | val appContext = InstrumentationRegistry.getTargetContext() 24 | 25 | assertEquals("io.github.kobakei.kotlinexample", appContext.packageName) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/App.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample 2 | 3 | import android.app.Application 4 | import com.facebook.stetho.Stetho 5 | import io.github.kobakei.kotlinexample.di.AppComponent 6 | import io.github.kobakei.kotlinexample.di.AppModule 7 | import io.github.kobakei.kotlinexample.di.DaggerAppComponent 8 | import timber.log.Timber 9 | 10 | /** 11 | * Created by keisukekobayashi on 2017/04/14. 12 | */ 13 | class App: Application() { 14 | 15 | lateinit var injector: AppComponent 16 | 17 | override fun onCreate() { 18 | super.onCreate() 19 | 20 | Timber.plant(Timber.DebugTree()) 21 | 22 | Stetho.initializeWithDefaults(this) 23 | 24 | injector = DaggerAppComponent.builder() 25 | .appModule(AppModule(this)) 26 | .build() 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/di/ActivityComponent.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.di 2 | 3 | import dagger.Subcomponent 4 | import io.github.kobakei.kotlinexample.di.scope.PerActivity 5 | import io.github.kobakei.kotlinexample.ui.detail.DetailActivity 6 | import io.github.kobakei.kotlinexample.ui.detail.DetailViewModel 7 | import io.github.kobakei.kotlinexample.ui.main.MainActivity 8 | import io.github.kobakei.kotlinexample.ui.main.MainItemViewModel 9 | import io.github.kobakei.kotlinexample.ui.main.MainViewModel 10 | 11 | /** 12 | * Created by keisukekobayashi on 2017/04/14. 13 | */ 14 | @PerActivity 15 | @Subcomponent(modules = arrayOf(ActivityModule::class)) 16 | interface ActivityComponent { 17 | fun mainViewModel(): MainViewModel 18 | fun detailViewModel(): DetailViewModel 19 | 20 | fun mainItemViewModel(): MainItemViewModel 21 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/di/ActivityModule.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.di 2 | 3 | import android.app.Activity 4 | import android.arch.lifecycle.Lifecycle 5 | import android.support.v4.app.FragmentActivity 6 | import dagger.Module 7 | import dagger.Provides 8 | import io.github.kobakei.kotlinexample.di.scope.PerActivity 9 | 10 | /** 11 | * Created by keisukekobayashi on 2017/04/14. 12 | */ 13 | @Module 14 | class ActivityModule(private val activity: FragmentActivity) { 15 | 16 | @PerActivity 17 | @Provides 18 | fun provideActivity(): Activity { 19 | return activity 20 | } 21 | 22 | @PerActivity 23 | @Provides 24 | fun provideLifecycle(): Lifecycle { 25 | return activity.lifecycle 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/di/AppComponent.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.di 2 | 3 | import dagger.Component 4 | import javax.inject.Singleton 5 | 6 | /** 7 | * Created by keisukekobayashi on 2017/04/14. 8 | */ 9 | @Singleton 10 | @Component(modules = arrayOf(AppModule::class)) 11 | interface AppComponent { 12 | 13 | fun activityComponent(activityModule: ActivityModule): ActivityComponent 14 | 15 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.di 2 | 3 | import android.content.Context 4 | import com.facebook.stetho.okhttp3.StethoInterceptor 5 | import com.google.gson.Gson 6 | import com.google.gson.GsonBuilder 7 | import dagger.Module 8 | import dagger.Provides 9 | import io.github.kobakei.kotlinexample.App 10 | import io.github.kobakei.kotlinexample.model.dao.OrmaHolder 11 | import io.github.kobakei.kotlinexample.model.entity.OrmaDatabase 12 | import io.github.kobakei.kotlinexample.model.net.GitHubApi 13 | import okhttp3.OkHttpClient 14 | import retrofit2.Retrofit 15 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 16 | import retrofit2.converter.gson.GsonConverterFactory 17 | import javax.inject.Singleton 18 | 19 | /** 20 | * Dagger module for app scope 21 | * アプリケーション全体で使いまわすインスタンスを提供する 22 | * 23 | * Created by keisukekobayashi on 2017/04/14. 24 | */ 25 | @Module 26 | class AppModule(private val app: App) { 27 | 28 | @Singleton 29 | @Provides 30 | fun provideContext(): Context { 31 | return app.applicationContext 32 | } 33 | 34 | @Singleton 35 | @Provides 36 | fun provideGson(): Gson { 37 | return GsonBuilder().create() 38 | } 39 | 40 | @Singleton 41 | @Provides 42 | fun provideOkHttpClient(): OkHttpClient { 43 | return OkHttpClient.Builder() 44 | .addNetworkInterceptor(StethoInterceptor()) 45 | .build() 46 | } 47 | 48 | @Singleton 49 | @Provides 50 | fun provideGitHubApi(gson: Gson, client: OkHttpClient): GitHubApi { 51 | val retrofit = Retrofit.Builder() 52 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 53 | .addConverterFactory(GsonConverterFactory.create(gson)) 54 | .client(client) 55 | .baseUrl("https://api.github.com/") 56 | .build() 57 | return retrofit.create(GitHubApi::class.java) 58 | } 59 | 60 | /** 61 | * kaptの制限で、動的に生成されるOrmaDatabaseはprovideできない 62 | * 仕方なので、OrmaDatabaseをラップしたOrmaHolderというシングルトンを返す 63 | */ 64 | @Singleton 65 | @Provides 66 | fun provideOrmaDatabase(context: Context): OrmaHolder { 67 | return OrmaHolder(context) 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/di/scope/PerActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.di.scope 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * Created by keisukekobayashi on 2017/04/14. 7 | */ 8 | @Scope 9 | @Retention(AnnotationRetention.RUNTIME) 10 | annotation class PerActivity -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/model/dao/OrmaHolder.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.model.dao 2 | 3 | import android.content.Context 4 | import io.github.kobakei.kotlinexample.model.entity.OrmaDatabase 5 | 6 | /** 7 | * OrmaDatabaseをラップするだけのクラス 8 | * kaptの制限のために必要 9 | * 10 | * Created by keisukekobayashi on 2017/04/14. 11 | */ 12 | class OrmaHolder(val context: Context) { 13 | 14 | val ormaDatabase: OrmaDatabase = OrmaDatabase.builder(context).build() 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/model/dao/UserDao.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.model.dao 2 | 3 | import io.github.kobakei.kotlinexample.model.dao.OrmaHolder 4 | import io.github.kobakei.kotlinexample.model.entity.OrmaDatabase 5 | import io.github.kobakei.kotlinexample.model.entity.User 6 | import io.github.kobakei.kotlinexample.model.entity.User_Relation 7 | import io.reactivex.Single 8 | import javax.inject.Inject 9 | import javax.inject.Singleton 10 | 11 | /** 12 | * DAO for user 13 | * Ormaをラップしています 14 | * 15 | * Created by keisukekobayashi on 2017/04/14. 16 | */ 17 | @Singleton 18 | class UserDao @Inject constructor(ormaHolder: OrmaHolder) { 19 | 20 | val ormaDatabase: OrmaDatabase = ormaHolder.ormaDatabase 21 | 22 | private fun relation(): User_Relation { 23 | return ormaDatabase.relationOfUser() 24 | } 25 | 26 | fun findById(id: Long): Single { 27 | return relation().selector() 28 | .idEq(id) 29 | .executeAsObservable() 30 | .firstOrError() 31 | } 32 | 33 | fun findAll(): Single> { 34 | return relation().selector() 35 | .executeAsObservable() 36 | .toList() 37 | } 38 | 39 | fun insertAll(users: Iterable): Single> { 40 | return relation().inserter() 41 | .executeAllAsObservable(users) 42 | .toList() 43 | } 44 | 45 | fun deleteAll(): Single { 46 | return relation().deleter() 47 | .executeAsSingle() 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/model/entity/User.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.model.entity 2 | 3 | import com.github.gfx.android.orma.annotation.Column 4 | import com.github.gfx.android.orma.annotation.PrimaryKey 5 | import com.github.gfx.android.orma.annotation.Table 6 | 7 | /** 8 | * Created by keisuke on 2017/11/12. 9 | */ 10 | @Table 11 | data class User(@PrimaryKey(auto = false) var id: Long = 0L, 12 | @Column var login: String = "", 13 | @Column var avatar_url: String = "") -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/model/net/GitHubApi.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.model.net 2 | 3 | import io.github.kobakei.kotlinexample.model.entity.User 4 | import io.reactivex.Single 5 | import retrofit2.http.GET 6 | import retrofit2.http.Path 7 | 8 | /** 9 | * GitHub API 10 | * Created by keisukekobayashi on 2017/04/14. 11 | */ 12 | interface GitHubApi { 13 | 14 | @GET("/repos/{owner}/{repo}/contributors") 15 | fun listContributors(@Path("owner") owner: String, 16 | @Path("repo") repo: String): Single> 17 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/model/repository/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.model.repository 2 | 3 | import io.github.kobakei.kotlinexample.model.dao.UserDao 4 | import io.github.kobakei.kotlinexample.model.entity.User 5 | import io.github.kobakei.kotlinexample.model.net.GitHubApi 6 | import io.reactivex.Single 7 | import javax.inject.Inject 8 | import javax.inject.Singleton 9 | 10 | /** 11 | * user entityに対するリポジトリ 12 | * CRUDを提供します。 13 | * 14 | * このサンプルは機能が少ないので、ビューモデルから直接呼ばれますが 15 | * 規模の大きいアプリではビューモデルとリポジトリの間にユースケース層を挟む事が多いです 16 | * 17 | * Created by keisukekobayashi on 2017/04/14. 18 | */ 19 | @Singleton 20 | class UserRepository 21 | @Inject constructor(private val gitHubApi: GitHubApi, private val userDao: UserDao) { 22 | 23 | var dirty: Boolean = true 24 | 25 | /** 26 | * 指定のユーザー/リポジトリのコントリビュータを取得する 27 | */ 28 | fun findContributorsByRepo(owner: String, repo: String): Single> { 29 | if (dirty) { 30 | return gitHubApi.listContributors(owner, repo) 31 | .flatMap { 32 | repos -> userDao.deleteAll().map { repos } 33 | } 34 | .flatMap { 35 | repos -> userDao.insertAll(repos).map { repos } 36 | } 37 | .doOnSuccess { dirty = false } 38 | } else { 39 | return userDao.findAll() 40 | } 41 | } 42 | 43 | /** 44 | * 指定のIDのユーザーを取得する 45 | */ 46 | fun findUserById(userId: Long): Single { 47 | return userDao.findById(userId) 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/ui/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.ui.base 2 | 3 | import android.annotation.SuppressLint 4 | import android.support.v7.app.AppCompatActivity 5 | import io.github.kobakei.kotlinexample.App 6 | import io.github.kobakei.kotlinexample.di.ActivityComponent 7 | import io.github.kobakei.kotlinexample.di.ActivityModule 8 | import java.util.ArrayList 9 | 10 | /** 11 | * Created by keisukekobayashi on 2017/04/14. 12 | */ 13 | @SuppressLint("Registered") 14 | open class BaseActivity: AppCompatActivity() { 15 | val injector: ActivityComponent 16 | get() { 17 | val app = applicationContext as App 18 | return app.injector.activityComponent(ActivityModule(this)) 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/ui/base/DaggerFactory.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.ui.base 2 | 3 | import android.arch.lifecycle.ViewModel 4 | import android.arch.lifecycle.ViewModelProvider 5 | import io.github.kobakei.kotlinexample.ui.detail.DetailActivity 6 | import io.github.kobakei.kotlinexample.ui.main.MainActivity 7 | 8 | /** 9 | * ビューモデルを作成するファクトリ 10 | * Daggerから生成します 11 | * 12 | * Created by keisuke on 2017/11/12. 13 | */ 14 | class DaggerFactory(private val activity: BaseActivity) : ViewModelProvider.Factory { 15 | override fun create(modelClass: Class): T { 16 | return when (activity.javaClass.simpleName) { 17 | MainActivity::class.java.simpleName -> activity.injector.mainViewModel() as T 18 | DetailActivity::class.java.simpleName -> activity.injector.detailViewModel() as T 19 | else -> throw IllegalArgumentException() 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/ui/detail/DetailActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.ui.detail 2 | 3 | import android.app.Activity 4 | import android.arch.lifecycle.ViewModel 5 | import android.arch.lifecycle.ViewModelProvider 6 | import android.arch.lifecycle.ViewModelProviders 7 | import android.content.Intent 8 | import android.databinding.DataBindingUtil 9 | import android.os.Bundle 10 | import android.support.v4.app.ActivityCompat 11 | import io.github.kobakei.kotlinexample.R 12 | import io.github.kobakei.kotlinexample.databinding.DetailActivityBinding 13 | import io.github.kobakei.kotlinexample.di.ActivityComponent 14 | import io.github.kobakei.kotlinexample.ui.base.BaseActivity 15 | import io.github.kobakei.kotlinexample.ui.base.DaggerFactory 16 | import javax.inject.Inject 17 | 18 | class DetailActivity : BaseActivity() { 19 | 20 | companion object { 21 | 22 | const val KEY_ID = "user_id" 23 | 24 | fun start(activity: Activity, userId: Long) { 25 | val intent = Intent(activity, DetailActivity::class.java) 26 | intent.putExtra(KEY_ID, userId) 27 | activity.startActivity(intent) 28 | } 29 | } 30 | 31 | lateinit var viewModel: DetailViewModel 32 | 33 | override fun onCreate(savedInstanceState: Bundle?) { 34 | super.onCreate(savedInstanceState) 35 | 36 | // ビューモデルを作成 37 | viewModel = ViewModelProviders.of(this, DaggerFactory(this)).get(DetailViewModel::class.java) 38 | viewModel.lifecycle = lifecycle 39 | lifecycle.addObserver(viewModel) 40 | 41 | // 受け取ったパラメータをビューモデルに渡す 42 | val userId = intent.getLongExtra(KEY_ID, 0L) 43 | viewModel.userId = userId 44 | 45 | // レイアウト 46 | val binding: DetailActivityBinding = DataBindingUtil.setContentView(this, R.layout.detail_activity) 47 | binding.viewModel = viewModel 48 | } 49 | 50 | override fun onDestroy() { 51 | super.onDestroy() 52 | lifecycle.removeObserver(viewModel) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/ui/detail/DetailViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.ui.detail 2 | 3 | import android.arch.lifecycle.Lifecycle 4 | import android.arch.lifecycle.LifecycleObserver 5 | import android.arch.lifecycle.OnLifecycleEvent 6 | import android.arch.lifecycle.ViewModel 7 | import android.databinding.ObservableField 8 | import com.uber.autodispose.AutoDispose 9 | import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider 10 | import io.github.kobakei.kotlinexample.di.scope.PerActivity 11 | import io.github.kobakei.kotlinexample.model.entity.User 12 | import io.github.kobakei.kotlinexample.model.repository.UserRepository 13 | import io.reactivex.android.schedulers.AndroidSchedulers 14 | import io.reactivex.schedulers.Schedulers 15 | import timber.log.Timber 16 | import javax.inject.Inject 17 | 18 | /** 19 | * 詳細画面のビューモデル 20 | * Created by keisukekobayashi on 2017/04/14. 21 | */ 22 | @PerActivity 23 | class DetailViewModel 24 | @Inject constructor( 25 | private val userRepository: UserRepository 26 | ) : ViewModel(), LifecycleObserver { 27 | 28 | lateinit var lifecycle: Lifecycle 29 | 30 | val user: ObservableField = ObservableField() 31 | 32 | var userId: Long = 0L 33 | 34 | @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) 35 | fun onResume() { 36 | userRepository.findUserById(userId) 37 | .subscribeOn(Schedulers.io()) 38 | .observeOn(AndroidSchedulers.mainThread()) 39 | .to(AutoDispose.with(AndroidLifecycleScopeProvider.from(lifecycle)).forSingle()) 40 | .subscribe({ 41 | user.set(it) 42 | }, { 43 | Timber.e(it) 44 | }) 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/ui/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.ui.main 2 | 3 | import android.arch.lifecycle.ViewModelProviders 4 | import android.databinding.DataBindingUtil 5 | import android.os.Bundle 6 | import android.support.v7.widget.LinearLayoutManager 7 | import io.github.kobakei.kotlinexample.R 8 | import io.github.kobakei.kotlinexample.databinding.MainActivityBinding 9 | import io.github.kobakei.kotlinexample.ui.base.BaseActivity 10 | import io.github.kobakei.kotlinexample.ui.base.DaggerFactory 11 | 12 | class MainActivity : BaseActivity() { 13 | 14 | lateinit var viewModel: MainViewModel 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | 19 | // ViewModel 20 | viewModel = ViewModelProviders.of(this, DaggerFactory(this)).get(MainViewModel::class.java) 21 | viewModel.lifecycle = lifecycle 22 | lifecycle.addObserver(viewModel) 23 | 24 | // レイアウト 25 | val binding: MainActivityBinding = DataBindingUtil.setContentView(this, R.layout.main_activity) 26 | binding.viewModel = viewModel 27 | 28 | binding.recyclerView.adapter = MainAdapter(this, injector, viewModel.users) 29 | binding.recyclerView.layoutManager = LinearLayoutManager(this) 30 | } 31 | 32 | override fun onDestroy() { 33 | super.onDestroy() 34 | lifecycle.removeObserver(viewModel) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/ui/main/MainAdapter.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.ui.main 2 | 3 | import android.content.Context 4 | import android.databinding.ObservableList 5 | import android.support.v7.widget.RecyclerView 6 | import android.view.LayoutInflater 7 | import android.view.ViewGroup 8 | import io.github.kobakei.kotlinexample.databinding.MainItemBinding 9 | import io.github.kobakei.kotlinexample.di.ActivityComponent 10 | import io.github.kobakei.kotlinexample.model.entity.User 11 | 12 | /** 13 | * Created by keisukekobayashi on 2017/04/14. 14 | */ 15 | class MainAdapter( 16 | val context: Context, 17 | val injector: ActivityComponent, 18 | val items: ObservableList): RecyclerView.Adapter() { 19 | 20 | init { 21 | items.addOnListChangedCallback(object : ObservableList.OnListChangedCallback>() { 22 | override fun onChanged(users: ObservableList) { 23 | notifyDataSetChanged() 24 | } 25 | 26 | override fun onItemRangeChanged(users: ObservableList, i: Int, i1: Int) { 27 | notifyItemRangeChanged(i, i1) 28 | } 29 | 30 | override fun onItemRangeInserted(users: ObservableList, i: Int, i1: Int) { 31 | notifyItemRangeInserted(i, i1) 32 | } 33 | 34 | override fun onItemRangeMoved(users: ObservableList, i: Int, i1: Int, i2: Int) { 35 | notifyItemMoved(i, i1) 36 | } 37 | 38 | override fun onItemRangeRemoved(users: ObservableList, i: Int, i1: Int) { 39 | notifyItemRangeRemoved(i, i1) 40 | } 41 | }) 42 | } 43 | 44 | override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): MainViewHolder { 45 | val inflater = LayoutInflater.from(context) 46 | val binding = MainItemBinding.inflate(inflater, parent, false) 47 | binding.viewModel = injector.mainItemViewModel() 48 | return MainViewHolder(binding) 49 | } 50 | 51 | override fun onBindViewHolder(holder: MainViewHolder?, position: Int) { 52 | val binding = holder!!.binding as MainItemBinding 53 | binding.viewModel?.user?.set(items[position]) 54 | } 55 | 56 | override fun getItemCount(): Int { 57 | return items.size 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/ui/main/MainItemViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.ui.main 2 | 3 | import android.app.Activity 4 | import android.arch.lifecycle.ViewModel 5 | import android.databinding.ObservableField 6 | import android.view.View 7 | import io.github.kobakei.kotlinexample.model.entity.User 8 | import io.github.kobakei.kotlinexample.ui.detail.DetailActivity 9 | import javax.inject.Inject 10 | 11 | /** 12 | * Created by keisukekobayashi on 2017/04/14. 13 | */ 14 | class MainItemViewModel @Inject constructor( 15 | private val activity: Activity 16 | ): ViewModel() { 17 | val user: ObservableField = ObservableField() 18 | 19 | fun onItemClick(view: View) { 20 | DetailActivity.start(activity, user.get().id) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/ui/main/MainViewHolder.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.ui.main 2 | 3 | import android.databinding.ViewDataBinding 4 | import android.support.v7.widget.RecyclerView 5 | 6 | /** 7 | * Created by keisukekobayashi on 2017/04/14. 8 | */ 9 | class MainViewHolder(val binding: ViewDataBinding): RecyclerView.ViewHolder(binding.root) -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/ui/main/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.ui.main 2 | 3 | import android.arch.lifecycle.Lifecycle 4 | import android.arch.lifecycle.LifecycleObserver 5 | import android.arch.lifecycle.OnLifecycleEvent 6 | import android.arch.lifecycle.ViewModel 7 | import android.databinding.ObservableArrayList 8 | import android.databinding.ObservableList 9 | import com.uber.autodispose.AutoDispose 10 | import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider 11 | import io.github.kobakei.kotlinexample.di.scope.PerActivity 12 | import io.github.kobakei.kotlinexample.model.entity.User 13 | import io.github.kobakei.kotlinexample.model.repository.UserRepository 14 | import io.reactivex.android.schedulers.AndroidSchedulers 15 | import io.reactivex.schedulers.Schedulers 16 | import timber.log.Timber 17 | import javax.inject.Inject 18 | 19 | /** 20 | * メイン画面のビューモデル 21 | * Created by keisukekobayashi on 2017/04/14. 22 | */ 23 | @PerActivity 24 | class MainViewModel 25 | @Inject constructor( 26 | private val userRepository: UserRepository 27 | ) : ViewModel(), LifecycleObserver { 28 | 29 | lateinit var lifecycle: Lifecycle 30 | 31 | val users: ObservableList = ObservableArrayList() 32 | 33 | @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) 34 | fun onResume() { 35 | val owner = "DroidKaigi" 36 | val repo = "conference-app-2017" 37 | userRepository.findContributorsByRepo(owner, repo) 38 | .subscribeOn(Schedulers.io()) 39 | .observeOn(AndroidSchedulers.mainThread()) 40 | .to(AutoDispose.with(AndroidLifecycleScopeProvider.from(lifecycle)).forSingle()) 41 | .subscribe({ 42 | Timber.v("Count: ${it.size}") 43 | users.clear() 44 | users.addAll(it) 45 | }, { 46 | Timber.e(it) 47 | }) 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/kotlinexample/ui/util/BindingAdapterUtil.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.ui.util 2 | 3 | import android.databinding.BindingAdapter 4 | import android.widget.ImageView 5 | import com.bumptech.glide.Glide 6 | 7 | /** 8 | * BindingAdapter 9 | * Javaではstaticメソッドで定義していたが、Kotlinでは拡張関数を使うのがスマートぽい 10 | * 11 | * Created by keisukekobayashi on 2017/04/14. 12 | */ 13 | @BindingAdapter(value = "imageUrl") 14 | fun ImageView.setImageUrl(url: String?) { 15 | if (url != null) { 16 | Glide.with(this.context).load(url).into(this) 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/detail_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 12 | 13 | 14 | 18 | 19 | 27 | 28 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 12 | 13 | 14 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 11 | 12 | 13 | 19 | 20 | 28 | 29 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Android-Kotlin-Example/e05db48210e51f55b1488a7aa0e8871624f3488d/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Android-Kotlin-Example/e05db48210e51f55b1488a7aa0e8871624f3488d/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Android-Kotlin-Example/e05db48210e51f55b1488a7aa0e8871624f3488d/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Android-Kotlin-Example/e05db48210e51f55b1488a7aa0e8871624f3488d/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Android-Kotlin-Example/e05db48210e51f55b1488a7aa0e8871624f3488d/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Android-Kotlin-Example/e05db48210e51f55b1488a7aa0e8871624f3488d/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Android-Kotlin-Example/e05db48210e51f55b1488a7aa0e8871624f3488d/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Android-Kotlin-Example/e05db48210e51f55b1488a7aa0e8871624f3488d/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Android-Kotlin-Example/e05db48210e51f55b1488a7aa0e8871624f3488d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Android-Kotlin-Example/e05db48210e51f55b1488a7aa0e8871624f3488d/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Android-Kotlin-Example 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/io/github/kobakei/kotlinexample/ExampleRobolectricTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | import org.junit.runner.RunWith 6 | import org.robolectric.RobolectricTestRunner 7 | import org.robolectric.annotation.Config 8 | 9 | /** 10 | * Created by keisukekobayashi on 2017/04/24. 11 | */ 12 | @RunWith(RobolectricTestRunner::class) 13 | @Config(constants = BuildConfig::class) 14 | class ExampleRobolectricTest { 15 | @Test 16 | @Throws(Exception::class) 17 | fun addition_isCorrect() { 18 | Assert.assertEquals(4, (2 + 2).toLong()) 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/test/java/io/github/kobakei/kotlinexample/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | 10 | * @see [Testing documentation](http://d.android.com/tools/testing) 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | @Throws(Exception::class) 15 | fun addition_isCorrect() { 16 | assertEquals(4, (2 + 2).toLong()) 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/test/java/io/github/kobakei/kotlinexample/ui/MainActivityViewModelTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.kotlinexample.ui 2 | 3 | import io.github.kobakei.kotlinexample.BuildConfig 4 | import io.github.kobakei.kotlinexample.model.entity.User 5 | import io.github.kobakei.kotlinexample.model.repository.UserRepository 6 | import io.github.kobakei.kotlinexample.ui.main.MainViewModel 7 | import io.reactivex.Single 8 | import io.reactivex.plugins.RxJavaPlugins 9 | import io.reactivex.schedulers.Schedulers 10 | import org.junit.* 11 | import org.junit.runner.RunWith 12 | import org.mockito.BDDMockito.given 13 | import org.mockito.Mockito 14 | import org.robolectric.RobolectricTestRunner 15 | import org.robolectric.annotation.Config 16 | 17 | 18 | 19 | /** 20 | * ViewModel test 21 | * Created by keisukekobayashi on 2017/04/24. 22 | */ 23 | @RunWith(RobolectricTestRunner::class) 24 | @Config(constants = BuildConfig::class, 25 | sdk = intArrayOf(21)) 26 | class MainActivityViewModelTest { 27 | 28 | @Before 29 | fun setup() { 30 | RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() } 31 | } 32 | 33 | @After 34 | fun teardown() { 35 | RxJavaPlugins.reset() 36 | } 37 | 38 | @Test 39 | fun onResume_isCorrect() { 40 | val users = listOf( 41 | User(1, "user1", "http://example.com/image"), 42 | User(2, "user2", "http://example.com/image"), 43 | User(3, "user3", "http://example.com/image")) 44 | val userRepository = Mockito.mock(UserRepository::class.java) 45 | given(userRepository.findContributorsByRepo("DroidKaigi", "conference-app-2017")) 46 | .willReturn(Single.just(users)) 47 | 48 | val viewModel = MainViewModel(userRepository) 49 | 50 | viewModel.onResume() 51 | 52 | Assert.assertNotNull(viewModel.users) 53 | Assert.assertEquals(3, viewModel.users.size) 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.1.51' 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.0.0' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /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 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Android-Kotlin-Example/e05db48210e51f55b1488a7aa0e8871624f3488d/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Nov 12 00:27:31 JST 2017 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-4.1-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 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------