├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── 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 │ │ │ │ ├── styles.xml │ │ │ │ └── strings.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── activity_repo.xml │ │ │ │ ├── progress.xml │ │ │ │ └── activity_main.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── azabost │ │ │ │ └── simplemvvm │ │ │ │ ├── net │ │ │ │ ├── connectivity │ │ │ │ │ ├── Connectivity.kt │ │ │ │ │ ├── ConnectivityException.kt │ │ │ │ │ ├── AndroidConnectivity.kt │ │ │ │ │ └── ConnectivityModule.kt │ │ │ │ ├── response │ │ │ │ │ └── RepoResponse.kt │ │ │ │ ├── GitHubService.kt │ │ │ │ ├── ApiClient.kt │ │ │ │ └── NetworkModule.kt │ │ │ │ ├── utils │ │ │ │ ├── View.kt │ │ │ │ ├── logger.kt │ │ │ │ ├── HasLifecycleScopeProvider.kt │ │ │ │ ├── HttpErrors.kt │ │ │ │ └── Observable.kt │ │ │ │ ├── di │ │ │ │ ├── AppModule.kt │ │ │ │ ├── ViewModelFactory.kt │ │ │ │ ├── AndroidInjectorsModule.kt │ │ │ │ └── AppComponent.kt │ │ │ │ ├── ui │ │ │ │ ├── repo │ │ │ │ │ ├── RepoViewModel.kt │ │ │ │ │ ├── RepoActivityIntentModule.kt │ │ │ │ │ └── RepoActivity.kt │ │ │ │ ├── BaseActivity.kt │ │ │ │ ├── BaseFragment.kt │ │ │ │ └── main │ │ │ │ │ ├── MainActivity.kt │ │ │ │ │ └── MainViewModel.kt │ │ │ │ └── App.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── azabost │ │ │ └── simplemvvm │ │ │ ├── net │ │ │ └── connectivity │ │ │ │ └── FakeConnectivity.kt │ │ │ ├── utils │ │ │ ├── HttpErrors.kt │ │ │ └── MockitoUtils.kt │ │ │ └── ui │ │ │ └── main │ │ │ └── MainViewModelTests.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── azabost │ │ └── simplemvvm │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea/ 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azabost/simple-mvvm-example/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azabost/simple-mvvm-example/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azabost/simple-mvvm-example/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azabost/simple-mvvm-example/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azabost/simple-mvvm-example/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azabost/simple-mvvm-example/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azabost/simple-mvvm-example/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azabost/simple-mvvm-example/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azabost/simple-mvvm-example/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azabost/simple-mvvm-example/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azabost/simple-mvvm-example/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/net/connectivity/Connectivity.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.net.connectivity 2 | 3 | interface Connectivity { 4 | fun hasInternetAccess(): Boolean 5 | } -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/net/response/RepoResponse.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.net.response 2 | 3 | import java.io.Serializable 4 | 5 | data class RepoResponse(val id: Long) : Serializable -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/utils/View.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.utils 2 | 3 | import android.view.View 4 | 5 | fun View.show() { 6 | this.visibility = View.VISIBLE 7 | } 8 | 9 | fun View.hide() { 10 | this.visibility = View.INVISIBLE 11 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jun 14 14:32:08 CEST 2018 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.4-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/test/java/com/azabost/simplemvvm/net/connectivity/FakeConnectivity.kt: -------------------------------------------------------------------------------- 1 | package pl.brightinventions.myparkinson.net.connectivity 2 | 3 | import com.azabost.simplemvvm.net.connectivity.Connectivity 4 | 5 | class FakeConnectivity(var isConnected: Boolean = true) : Connectivity { 6 | override fun hasInternetAccess(): Boolean = isConnected 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/utils/logger.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.utils 2 | 3 | import org.slf4j.Logger 4 | import org.slf4j.LoggerFactory 5 | import kotlin.reflect.KClass 6 | 7 | val Any.logger: Logger get() = javaClass.logger 8 | val KClass<*>.logger: Logger get() = java.logger 9 | val Class<*>.logger: Logger get() = LoggerFactory.getLogger(this) -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/net/connectivity/ConnectivityException.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.net.connectivity 2 | 3 | import android.support.annotation.StringRes 4 | import com.azabost.simplemvvm.R 5 | 6 | class ConnectivityException( 7 | @StringRes val reason: Int = R.string.error_no_internet_connection 8 | ) : Exception("No internet connection") 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.di 2 | 3 | import com.azabost.simplemvvm.net.NetworkModule 4 | import dagger.Module 5 | 6 | @Module(includes = [ 7 | NetworkModule::class 8 | ]) 9 | class AppModule { 10 | /* You can place something useful here, e.g.: 11 | @Provides 12 | fun providesResources(context: Context): Resources = context.resources 13 | */ 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/ui/repo/RepoViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.ui.repo 2 | 3 | import android.arch.lifecycle.ViewModel 4 | import com.azabost.simplemvvm.net.response.RepoResponse 5 | import javax.inject.Inject 6 | 7 | interface RepoVM { 8 | val repoResponse: RepoResponse 9 | } 10 | 11 | class RepoViewModel @Inject constructor( 12 | override val repoResponse: RepoResponse 13 | ) : ViewModel(), RepoVM -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/ui/repo/RepoActivityIntentModule.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.ui.repo 2 | 3 | import com.azabost.simplemvvm.net.response.RepoResponse 4 | import dagger.Module 5 | import dagger.Provides 6 | 7 | @Module 8 | class RepoActivityIntentModule { 9 | @Provides 10 | fun providesRepoResponse(activity: RepoActivity): RepoResponse { 11 | return activity.intent.getSerializableExtra(RepoActivity.REPO_RESPONSE_EXTRA) as RepoResponse 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/test/java/com/azabost/simplemvvm/utils/HttpErrors.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.utils 2 | 3 | import okhttp3.MediaType 4 | import okhttp3.ResponseBody 5 | import retrofit2.HttpException 6 | import retrofit2.Response 7 | 8 | fun HttpErrors.getHttpException( 9 | code: Int = 404, 10 | contentType: MediaType? = null, 11 | content: String = ""): HttpException { 12 | return HttpException(Response.error(code, ResponseBody.create(contentType, content))) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/utils/HasLifecycleScopeProvider.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.utils 2 | 3 | import com.uber.autodispose.LifecycleScopeProvider 4 | import com.uber.autodispose.kotlin.autoDisposable 5 | import io.reactivex.Observable 6 | 7 | interface HasLifecycleScopeProvider { 8 | val scopeProvider: LifecycleScopeProvider<*> 9 | 10 | fun Observable.observeOnMainThreadAndAutoDispose() = 11 | this.observeOnMainThread().autoDisposable(scopeProvider) 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/net/connectivity/AndroidConnectivity.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.net.connectivity 2 | 3 | import android.net.ConnectivityManager 4 | import android.net.NetworkInfo 5 | 6 | class AndroidConnectivity(private val connectivityManager: ConnectivityManager) : Connectivity { 7 | 8 | override fun hasInternetAccess(): Boolean { 9 | val activeNetwork: NetworkInfo? = connectivityManager.activeNetworkInfo 10 | return activeNetwork?.isConnected ?: false 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/di/ViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.di 2 | 3 | 4 | import android.arch.lifecycle.ViewModel 5 | import android.arch.lifecycle.ViewModelProvider 6 | import dagger.Lazy 7 | import javax.inject.Inject 8 | 9 | class ViewModelFactory @Inject constructor( 10 | private val viewModel: Lazy 11 | ) : ViewModelProvider.Factory { 12 | 13 | @Suppress("UNCHECKED_CAST") 14 | override fun create(modelClass: Class): T { 15 | return viewModel.get() as T 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/net/GitHubService.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.net 2 | 3 | import com.azabost.simplemvvm.net.response.RepoResponse 4 | import io.reactivex.Observable 5 | import retrofit2.http.GET 6 | import retrofit2.http.Path 7 | 8 | interface GitHubService { 9 | 10 | @GET("repos/{owner}/{repo}") 11 | fun getRepo( 12 | @Path("owner") owner: String, 13 | @Path("repo") repo: String 14 | ): Observable 15 | 16 | companion object { 17 | val BASE_URL = "https://api.github.com/" 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_repo.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/net/connectivity/ConnectivityModule.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.net.connectivity 2 | 3 | import android.content.Context 4 | import android.net.ConnectivityManager 5 | import dagger.Module 6 | import dagger.Provides 7 | import javax.inject.Singleton 8 | 9 | @Module 10 | class ConnectivityModule { 11 | 12 | @Provides 13 | @Singleton 14 | fun providesConnectivity(context: Context): Connectivity { 15 | val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 16 | return AndroidConnectivity(cm) 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SimpleMvvm 3 | Your favorite meaningless error message 4 | No internet connection 5 | Repository name should be formatted like this: <owner>/<repo> 6 | <owner>/<repo> 7 | Get data 8 | There is no such repository. Check the provided name and try again. 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/di/AndroidInjectorsModule.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.di 2 | 3 | import com.azabost.simplemvvm.ui.main.MainActivity 4 | import com.azabost.simplemvvm.ui.repo.RepoActivity 5 | import com.azabost.simplemvvm.ui.repo.RepoActivityIntentModule 6 | import dagger.Module 7 | import dagger.android.ContributesAndroidInjector 8 | 9 | @Module 10 | abstract class AndroidInjectorsModule { 11 | @ContributesAndroidInjector 12 | abstract fun contributeMainActivity(): MainActivity 13 | 14 | @ContributesAndroidInjector(modules = [RepoActivityIntentModule::class]) 15 | abstract fun contributeRepoActivity(): RepoActivity 16 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/utils/HttpErrors.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.utils 2 | 3 | import com.azabost.simplemvvm.R 4 | import retrofit2.HttpException 5 | 6 | typealias HttpErrorsMapper = (HttpException) -> Int? 7 | 8 | object HttpErrors { 9 | const val DEFAULT_HTTP_ERROR_MESSAGE = R.string.default_error_message 10 | 11 | fun httpExceptionToErrorMessage( 12 | exception: HttpException, 13 | httpErrorsMapper: HttpErrorsMapper? = this::defaultHttpErrorsMapper 14 | ): Int { 15 | return httpErrorsMapper?.invoke(exception) ?: defaultHttpErrorsMapper(exception) 16 | } 17 | 18 | private fun defaultHttpErrorsMapper(exception: HttpException): Int { 19 | return DEFAULT_HTTP_ERROR_MESSAGE 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/di/AppComponent.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.di 2 | 3 | import android.content.Context 4 | import com.azabost.simplemvvm.App 5 | import dagger.BindsInstance 6 | import dagger.Component 7 | import dagger.android.AndroidInjectionModule 8 | import dagger.android.AndroidInjector 9 | import javax.inject.Singleton 10 | 11 | 12 | @Singleton 13 | @Component(modules = arrayOf( 14 | AndroidInjectionModule::class, 15 | AppModule::class, 16 | AndroidInjectorsModule::class 17 | )) 18 | interface AppComponent : AndroidInjector { 19 | 20 | @Component.Builder 21 | abstract class Builder : AndroidInjector.Builder() { 22 | @BindsInstance abstract fun appContext(appContext: Context): Builder 23 | } 24 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/azabost/simplemvvm/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import android.support.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.azabost.simplemvvm", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /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/main/java/com/azabost/simplemvvm/ui/repo/RepoActivity.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.ui.repo 2 | 3 | import android.os.Bundle 4 | import com.azabost.simplemvvm.R 5 | import com.azabost.simplemvvm.di.ViewModelFactory 6 | import com.azabost.simplemvvm.ui.BaseActivity 7 | import kotlinx.android.synthetic.main.activity_repo.* 8 | import javax.inject.Inject 9 | 10 | class RepoActivity : BaseActivity() { 11 | 12 | @Inject 13 | lateinit var vmFactory: ViewModelFactory 14 | 15 | lateinit var vm: RepoVM 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setContentView(R.layout.activity_repo) 20 | 21 | vm = vmFactory.get() 22 | 23 | repoData.text = vm.repoResponse.id.toString() 24 | } 25 | 26 | companion object { 27 | const val REPO_RESPONSE_EXTRA = "REPO_RESPONSE_EXTRA" 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/net/ApiClient.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.net 2 | 3 | import com.azabost.simplemvvm.net.connectivity.Connectivity 4 | import com.azabost.simplemvvm.net.connectivity.ConnectivityException 5 | import com.azabost.simplemvvm.net.response.RepoResponse 6 | import io.reactivex.Observable 7 | import javax.inject.Inject 8 | 9 | class ApiClient @Inject constructor( 10 | private val gitHubService: GitHubService, 11 | private val connectivity: Connectivity 12 | ) { 13 | fun getRepo(owner: String, repo: String): Observable { 14 | return gitHubService.getRepo(owner, repo).checkConnectivity() 15 | } 16 | 17 | private fun Observable.checkConnectivity(): Observable { 18 | return if (connectivity.hasInternetAccess()) { 19 | this 20 | } else { 21 | Observable.error(ConnectivityException()) 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/test/java/com/azabost/simplemvvm/utils/MockitoUtils.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.utils 2 | 3 | import io.reactivex.Observable 4 | import io.reactivex.schedulers.TestScheduler 5 | import org.mockito.Mockito 6 | import org.mockito.stubbing.OngoingStubbing 7 | 8 | object MockitoUtils { 9 | 10 | // Workaround for Mockito's any() in Kotlin 11 | // https://medium.com/@elye.project/befriending-kotlin-and-mockito-1c2e7b0ef791 12 | fun any(): T { 13 | Mockito.any() 14 | return null as T 15 | } 16 | 17 | fun mockApiResponse( 18 | stubbing: OngoingStubbing>, 19 | testScheduler: TestScheduler, 20 | response: T? = null, 21 | error: Throwable? = null 22 | ) { 23 | val r = if (response != null) Observable.just(response) else Observable.error(error) 24 | stubbing.thenReturn(r.subscribeOn(testScheduler).observeOn(testScheduler)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/ui/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.ui 2 | 3 | import android.arch.lifecycle.ViewModel 4 | import android.arch.lifecycle.ViewModelProviders 5 | import android.os.Bundle 6 | import android.support.v7.app.AppCompatActivity 7 | import com.azabost.simplemvvm.di.ViewModelFactory 8 | import com.azabost.simplemvvm.utils.HasLifecycleScopeProvider 9 | import com.uber.autodispose.LifecycleScopeProvider 10 | import com.uber.autodispose.android.lifecycle.scope 11 | import dagger.android.AndroidInjection 12 | 13 | 14 | abstract class BaseActivity : AppCompatActivity(), HasLifecycleScopeProvider { 15 | 16 | override val scopeProvider: LifecycleScopeProvider<*> by lazy { scope() } 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | AndroidInjection.inject(this) 19 | super.onCreate(savedInstanceState) 20 | } 21 | 22 | inline fun ViewModelFactory.get(): T = 23 | ViewModelProviders.of(this@BaseActivity, this)[T::class.java] 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/App.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.content.Context 6 | import android.support.multidex.MultiDex 7 | import com.azabost.simplemvvm.di.DaggerAppComponent 8 | import dagger.android.AndroidInjector 9 | import dagger.android.DispatchingAndroidInjector 10 | import dagger.android.HasActivityInjector 11 | import javax.inject.Inject 12 | 13 | class App : Application(), HasActivityInjector { 14 | 15 | @Inject 16 | lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector 17 | 18 | override fun activityInjector(): AndroidInjector = dispatchingAndroidInjector 19 | 20 | override fun onCreate() { 21 | super.onCreate() 22 | 23 | DaggerAppComponent 24 | .builder() 25 | .appContext(this) 26 | .create(this) 27 | .inject(this) 28 | } 29 | 30 | override fun attachBaseContext(base: Context) { 31 | super.attachBaseContext(base) 32 | MultiDex.install(this) 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/azabost/simplemvvm/net/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.azabost.simplemvvm.net 2 | 3 | import com.azabost.simplemvvm.net.connectivity.Connectivity 4 | import com.azabost.simplemvvm.net.connectivity.ConnectivityModule 5 | import com.google.gson.Gson 6 | import com.google.gson.GsonBuilder 7 | import dagger.Module 8 | import dagger.Provides 9 | import retrofit2.Retrofit 10 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 11 | import retrofit2.converter.gson.GsonConverterFactory 12 | import javax.inject.Singleton 13 | 14 | @Module( 15 | includes = [ 16 | ConnectivityModule::class 17 | ] 18 | ) 19 | class NetworkModule { 20 | @Provides 21 | @Singleton 22 | fun providesGitHubClient(gson: Gson, connectivity: Connectivity): ApiClient { 23 | val retrofit = Retrofit.Builder() 24 | .baseUrl(GitHubService.BASE_URL) 25 | .addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync()) 26 | .addConverterFactory(GsonConverterFactory.create(gson)) 27 | .build() 28 | 29 | val gitHubService = retrofit.create(GitHubService::class.java) 30 | return ApiClient(gitHubService, connectivity) 31 | } 32 | 33 | @Singleton 34 | @Provides 35 | fun getGsonInstance(): Gson { 36 | val gsonBuilder = GsonBuilder() 37 | return gsonBuilder.create() 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 |