├── .gitignore
├── .idea
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── gradle.xml
├── misc.xml
├── runConfigurations.xml
└── vcs.xml
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── clean
│ │ └── project
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── clean
│ │ │ └── project
│ │ │ ├── AppComponent.kt
│ │ │ ├── AppModule.kt
│ │ │ └── NasaApp.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.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
│ └── test
│ └── java
│ └── com
│ └── clean
│ └── project
│ └── ExampleUnitTest.kt
├── build.gradle
├── data
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── clean
│ │ └── data
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── clean
│ │ │ └── data
│ │ │ ├── NasaRepositoryImpl.kt
│ │ │ ├── StringProviderImpl.kt
│ │ │ ├── cache
│ │ │ ├── AsteroidDao.kt
│ │ │ ├── AsteroidEntity.kt
│ │ │ ├── DBConstants.kt
│ │ │ ├── NasaCache.kt
│ │ │ └── NasaDataBase.kt
│ │ │ ├── config
│ │ │ ├── DataComponent.kt
│ │ │ └── DataModule.kt
│ │ │ ├── mapper
│ │ │ └── AsteroidMapper.kt
│ │ │ ├── model
│ │ │ └── Asteroid.kt
│ │ │ └── remote
│ │ │ ├── NasaRemote.kt
│ │ │ └── NasaService.kt
│ └── res
│ │ └── values
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── clean
│ └── data
│ └── ExampleUnitTest.java
├── dependencies.gradle
├── domain
├── .gitignore
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── clean
│ │ └── domain
│ │ └── asteroid
│ │ ├── AsteroidViewEventHandler.kt
│ │ ├── AsteroidViewFlow.kt
│ │ ├── AsteroidViewStateReducer.kt
│ │ ├── NasaRepository.kt
│ │ ├── StringProvider.kt
│ │ ├── model
│ │ ├── Asteroid.kt
│ │ ├── AsteroidViewEvent.kt
│ │ ├── AsteroidViewResult.kt
│ │ └── AsteroidViewState.kt
│ │ └── usecase
│ │ ├── AsteroidUseCase.kt
│ │ ├── GetAsteroidOfTheDay.kt
│ │ └── SaveAsteroid.kt
│ └── test
│ └── kotlin
│ └── com
│ └── clean
│ └── domain
│ └── asteroid
│ ├── AsteroidViewFlowTest.kt
│ ├── NasaRepositoryFake.kt
│ └── StringProviderFake.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── presentation
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── clean
│ │ └── asteroids
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── clean
│ │ │ └── asteroids
│ │ │ ├── AsteroidActivity.kt
│ │ │ ├── AsteroidEffectHandler.kt
│ │ │ ├── AsteroidViewModel.kt
│ │ │ ├── AsteroidViewRenderer.kt
│ │ │ ├── ViewModelFactory.kt
│ │ │ └── config
│ │ │ ├── CoreComponent.kt
│ │ │ ├── CoreComponentProvider.kt
│ │ │ ├── PresentationComponent.kt
│ │ │ ├── PresentationModule.kt
│ │ │ ├── Scopes.kt
│ │ │ └── ViewModelKey.kt
│ └── res
│ │ ├── layout
│ │ └── activity_main.xml
│ │ └── values
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── clean
│ └── asteroids
│ └── ExampleUnitTest.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | xmlns:android
14 |
15 | ^$
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | xmlns:.*
25 |
26 | ^$
27 |
28 |
29 | BY_NAME
30 |
31 |
32 |
33 |
34 |
35 |
36 | .*:id
37 |
38 | http://schemas.android.com/apk/res/android
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | .*:name
48 |
49 | http://schemas.android.com/apk/res/android
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | name
59 |
60 | ^$
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | style
70 |
71 | ^$
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | .*
81 |
82 | ^$
83 |
84 |
85 | BY_NAME
86 |
87 |
88 |
89 |
90 |
91 |
92 | .*
93 |
94 | http://schemas.android.com/apk/res/android
95 |
96 |
97 | ANDROID_ATTRIBUTE_ORDER
98 |
99 |
100 |
101 |
102 |
103 |
104 | .*
105 |
106 | .*
107 |
108 |
109 | BY_NAME
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
20 |
21 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: 'kotlin-android-extensions'
6 |
7 | apply plugin: 'kotlin-kapt'
8 |
9 | android {
10 | compileSdkVersion 28
11 | defaultConfig {
12 | applicationId "com.clean.project"
13 | minSdkVersion 21
14 | targetSdkVersion 28
15 | versionCode 1
16 | versionName "1.0"
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 |
26 | compileOptions {
27 | sourceCompatibility = 1.8
28 | targetCompatibility = 1.8
29 | }
30 | }
31 |
32 | configurations.all {
33 | resolutionStrategy {
34 | force "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
35 | }
36 | }
37 |
38 | dependencies {
39 | implementation 'androidx.appcompat:appcompat:1.0.2'
40 | implementation 'com.facebook.stetho:stetho:1.5.1'
41 | implementation "io.reactivex.rxjava2:rxjava:2.2.9"
42 | implementation 'com.google.dagger:dagger:2.20'
43 | kapt 'com.google.dagger:dagger-compiler:2.20'
44 |
45 | implementation project(path: ':domain')
46 | implementation project(path: ':data')
47 | implementation project(path: ':presentation')
48 | }
49 |
--------------------------------------------------------------------------------
/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/clean/project/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.clean.project
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.clean.project", appContext.packageName)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/clean/project/AppComponent.kt:
--------------------------------------------------------------------------------
1 | package com.clean.project
2 |
3 | import com.clean.asteroids.config.CoreComponent
4 | import com.clean.data.config.DataComponent
5 | import com.clean.domain.asteroid.AsteroidViewFlow
6 | import dagger.Component
7 | import javax.inject.Scope
8 |
9 | @AppSingleton
10 | @Component(
11 | modules = arrayOf(AppModule::class),
12 | dependencies = arrayOf(DataComponent::class)
13 | )
14 | interface AppComponent : CoreComponent {
15 |
16 | override fun provideAsteriodFlow(): AsteroidViewFlow
17 |
18 | @Component.Builder
19 | interface Builder {
20 |
21 | fun dataComponent(dataComponent: DataComponent): Builder
22 | fun build(): AppComponent
23 | }
24 | }
25 |
26 | @Scope
27 | @MustBeDocumented
28 | @Retention
29 | annotation class AppSingleton
--------------------------------------------------------------------------------
/app/src/main/java/com/clean/project/AppModule.kt:
--------------------------------------------------------------------------------
1 | package com.clean.project
2 |
3 | import com.clean.domain.asteroid.model.AsteroidViewEvent
4 | import com.clean.domain.asteroid.usecase.AsteroidUseCase
5 | import com.clean.domain.asteroid.usecase.GetAsteroidOfTheDay
6 | import com.clean.domain.asteroid.usecase.SaveAsteroid
7 | import dagger.Module
8 | import dagger.Provides
9 | import dagger.multibindings.IntoSet
10 |
11 | @Module
12 | object AppModule {
13 |
14 | @Provides
15 | @JvmStatic
16 | @IntoSet
17 | fun provideGetAsteroidOfTheDayUseCase(getAsteroidOfTheDay: GetAsteroidOfTheDay): AsteroidUseCase {
18 | return getAsteroidOfTheDay as AsteroidUseCase
19 | }
20 |
21 | @Provides
22 | @JvmStatic
23 | @IntoSet
24 | fun provideSaveAsteroid(saveAsteroid: SaveAsteroid): AsteroidUseCase {
25 | return saveAsteroid as AsteroidUseCase
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/clean/project/NasaApp.kt:
--------------------------------------------------------------------------------
1 | package com.clean.project
2 |
3 | import android.app.Application
4 | import com.clean.asteroids.config.CoreComponent
5 | import com.clean.asteroids.config.CoreComponentProvider
6 | import com.clean.data.config.DaggerDataComponent
7 | import com.facebook.stetho.Stetho
8 |
9 | class NasaApp : Application(), CoreComponentProvider {
10 |
11 | val appComponent: AppComponent by lazy {
12 | DaggerAppComponent.builder()
13 | .dataComponent(dataComponent())
14 | .build()
15 | }
16 |
17 | override fun onCreate() {
18 | super.onCreate()
19 | Stetho.initializeWithDefaults(this);
20 | }
21 |
22 | override fun provide(): CoreComponent {
23 | return appComponent
24 | }
25 |
26 | private fun dataComponent() = DaggerDataComponent.builder().application(this).build()
27 | }
--------------------------------------------------------------------------------
/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 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/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/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdrianoCelentano/CleanProject/ab415e4d84025175ae578a5c9ac3861cee84bfde/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdrianoCelentano/CleanProject/ab415e4d84025175ae578a5c9ac3861cee84bfde/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdrianoCelentano/CleanProject/ab415e4d84025175ae578a5c9ac3861cee84bfde/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdrianoCelentano/CleanProject/ab415e4d84025175ae578a5c9ac3861cee84bfde/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdrianoCelentano/CleanProject/ab415e4d84025175ae578a5c9ac3861cee84bfde/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdrianoCelentano/CleanProject/ab415e4d84025175ae578a5c9ac3861cee84bfde/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdrianoCelentano/CleanProject/ab415e4d84025175ae578a5c9ac3861cee84bfde/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdrianoCelentano/CleanProject/ab415e4d84025175ae578a5c9ac3861cee84bfde/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdrianoCelentano/CleanProject/ab415e4d84025175ae578a5c9ac3861cee84bfde/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdrianoCelentano/CleanProject/ab415e4d84025175ae578a5c9ac3861cee84bfde/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | CleanProject
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/clean/project/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.clean.project
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 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 |
2 | buildscript {
3 | ext.kotlin_version = '1.3.30'
4 | repositories {
5 | google()
6 | jcenter()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.4.1'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | apply from: 'dependencies.gradle'
15 |
16 | allprojects {
17 | repositories {
18 | google()
19 | jcenter()
20 | }
21 | }
22 |
23 | task clean(type: Delete) {
24 | delete rootProject.buildDir
25 | }
26 |
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/data/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: 'kotlin-android-extensions'
6 |
7 | apply plugin: 'kotlin-kapt'
8 |
9 | android {
10 | compileSdkVersion 28
11 |
12 | defaultConfig {
13 | minSdkVersion 21
14 | targetSdkVersion 28
15 | versionCode 1
16 | versionName "1.0"
17 |
18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
19 |
20 | }
21 |
22 | buildTypes {
23 | release {
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
26 | }
27 | }
28 |
29 | compileOptions {
30 | sourceCompatibility = 1.8
31 | targetCompatibility = 1.8
32 | }
33 |
34 | }
35 |
36 | kapt {
37 | javacOptions {
38 | // Increase the max count of errors from annotation processors.
39 | // Default is 100.
40 | option("-Xmaxerrs", 500)
41 | }
42 | }
43 |
44 | dependencies {
45 | def dataDependencies = rootProject.ext.dataDependencies
46 | def dataTestDependencies = rootProject.ext.dataTestDependencies
47 |
48 | implementation project(":domain")
49 | implementation dataDependencies.javaxAnnotation
50 | implementation dataDependencies.javaxInject
51 | implementation dataDependencies.kotlin
52 | implementation dataDependencies.rxKotlin
53 | implementation dataDependencies.rxJava
54 | implementation dataDependencies.gson
55 | implementation dataDependencies.okHttp
56 | implementation dataDependencies.okHttpLogger
57 | implementation dataDependencies.retrofit
58 | implementation dataDependencies.retrofitConverter
59 | implementation dataDependencies.retrofitAdapter
60 | implementation dataDependencies.roomRuntime
61 | implementation dataDependencies.roomKotlin
62 | implementation dataDependencies.roomRxJava
63 | kapt dataDependencies.roomCompiler
64 | implementation dataDependencies.dagger
65 | kapt dataDependencies.daggerCompiler
66 | testImplementation dataTestDependencies.junit
67 | testImplementation dataTestDependencies.mockito
68 | testImplementation dataTestDependencies.assertj
69 | }
70 |
--------------------------------------------------------------------------------
/data/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 |
--------------------------------------------------------------------------------
/data/src/androidTest/java/com/clean/data/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.clean.data;
2 |
3 | import android.content.Context;
4 | import androidx.test.InstrumentationRegistry;
5 | import androidx.test.runner.AndroidJUnit4;
6 | import org.junit.Test;
7 | import org.junit.runner.RunWith;
8 |
9 | import static org.junit.Assert.assertEquals;
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * @see Testing documentation
15 | */
16 | @RunWith(AndroidJUnit4.class)
17 | public class ExampleInstrumentedTest {
18 | @Test
19 | public void useAppContext() {
20 | // Context of the app under test.
21 | Context appContext = InstrumentationRegistry.getTargetContext();
22 |
23 | assertEquals("com.clean.data.test", appContext.getPackageName());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/data/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/data/src/main/java/com/clean/data/NasaRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.clean.data
2 |
3 | import com.clean.data.cache.NasaCache
4 | import com.clean.data.mapper.AsteroidMapper
5 | import com.clean.data.remote.NasaRemote
6 | import com.clean.domain.asteroid.NasaRepository
7 | import com.clean.domain.asteroid.model.Asteroid
8 | import io.reactivex.Completable
9 | import io.reactivex.Observable
10 | import javax.inject.Inject
11 |
12 | class NasaRepositoryImpl @Inject constructor(
13 | val nasaRemote: NasaRemote,
14 | val nasaCache: NasaCache,
15 | private val asteroidMapper: AsteroidMapper
16 | ) : NasaRepository {
17 |
18 | override fun getAsteroidOfTheDay(): Observable {
19 | return nasaRemote.getAsteroidOfTheDay().map { asteroidMapper.mapDataToDomain(it) }
20 | }
21 |
22 | override fun saveAsteroid(asteroid: Asteroid): Completable {
23 | return Observable.just(asteroid)
24 | .map { asteroidMapper.mapDomaintoEntity(asteroid) }
25 | .flatMapCompletable { nasaCache.saveAsteroid(it) }
26 | }
27 |
28 | override fun getSavedAsteroid(): Observable> {
29 | return nasaCache.getAsteroids()
30 | .map { asteroids -> asteroids.map { asteroidMapper.mapEntitiyToDomain(it) } }
31 | }
32 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/clean/data/StringProviderImpl.kt:
--------------------------------------------------------------------------------
1 | package com.clean.data
2 |
3 | import android.app.Application
4 | import com.clean.domain.asteroid.StringProvider
5 | import javax.inject.Inject
6 |
7 | class StringProviderImpl @Inject constructor(private val application: Application) : StringProvider {
8 |
9 | override val serverError: String
10 | get() = application.getString(R.string.server_errror)
11 | override val generalError: String
12 | get() = application.getString(R.string.general_errror)
13 | override val storeAsteroidSuccess: String
14 | get() = application.getString(R.string.store_asteroid_success)
15 | override val messageInABottle: String
16 | get() = application.getString(R.string.message_in_a_bottle)
17 |
18 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/clean/data/cache/AsteroidDao.kt:
--------------------------------------------------------------------------------
1 | package com.clean.data.cache
2 |
3 | import androidx.room.*
4 | import com.clean.data.cache.DBConstants.ASTEROID_TABLE_NAME
5 | import io.reactivex.Completable
6 | import io.reactivex.Observable
7 |
8 | @Dao
9 | interface AsteroidDao {
10 |
11 | @Query("SELECT * FROM ${ASTEROID_TABLE_NAME}")
12 | fun getAll(): Observable>
13 |
14 | @Insert(onConflict = OnConflictStrategy.REPLACE)
15 | fun insert(asteroidEntity: AsteroidEntity): Completable
16 |
17 | @Delete
18 | fun delete(asteroidEntity: AsteroidEntity): Completable
19 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/clean/data/cache/AsteroidEntity.kt:
--------------------------------------------------------------------------------
1 | package com.clean.data.cache
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 | import com.clean.data.cache.DBConstants.ASTEROID_TABLE_NAME
6 |
7 | @Entity(tableName = ASTEROID_TABLE_NAME)
8 | data class AsteroidEntity(
9 | @PrimaryKey(autoGenerate = true)
10 | val id: Int = 0,
11 | val title: String? = null,
12 | val url: String? = null
13 | )
--------------------------------------------------------------------------------
/data/src/main/java/com/clean/data/cache/DBConstants.kt:
--------------------------------------------------------------------------------
1 | package com.clean.data.cache
2 |
3 | object DBConstants {
4 |
5 | const val ASTEROID_TABLE_NAME = "asteroid_table"
6 | const val DB_NAME = "nasaDB"
7 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/clean/data/cache/NasaCache.kt:
--------------------------------------------------------------------------------
1 | package com.clean.data.cache
2 |
3 | import io.reactivex.Completable
4 | import io.reactivex.Observable
5 | import javax.inject.Inject
6 |
7 | class NasaCache @Inject constructor(
8 | private val nasaDataBase: NasaDataBase
9 | ) {
10 |
11 | fun saveAsteroid(asteroidEntity: AsteroidEntity): Completable {
12 | return nasaDataBase.asteroidDao().insert(asteroidEntity)
13 | }
14 |
15 | fun getAsteroids(): Observable> {
16 | return nasaDataBase.asteroidDao().getAll()
17 | }
18 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/clean/data/cache/NasaDataBase.kt:
--------------------------------------------------------------------------------
1 | package com.clean.data.cache
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 |
6 | @Database(entities = arrayOf(AsteroidEntity::class), version = 1)
7 | abstract class NasaDataBase : RoomDatabase() {
8 | abstract fun asteroidDao(): AsteroidDao
9 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/clean/data/config/DataComponent.kt:
--------------------------------------------------------------------------------
1 | package com.clean.data.config
2 |
3 | import android.app.Application
4 | import com.clean.domain.asteroid.NasaRepository
5 | import com.clean.domain.asteroid.StringProvider
6 | import dagger.BindsInstance
7 | import dagger.Component
8 | import javax.inject.Scope
9 |
10 | @Component(modules = arrayOf(DataModule::class))
11 | @DataSingleton
12 | interface DataComponent {
13 |
14 | fun provideNasaRepository(): NasaRepository
15 |
16 | fun provideStringProvider(): StringProvider
17 |
18 | @Component.Builder
19 | interface Builder {
20 |
21 | @BindsInstance
22 | fun application(application: Application): Builder
23 | fun build(): DataComponent
24 | }
25 | }
26 |
27 | @Scope
28 | @MustBeDocumented
29 | @Retention
30 | annotation class DataSingleton
--------------------------------------------------------------------------------
/data/src/main/java/com/clean/data/config/DataModule.kt:
--------------------------------------------------------------------------------
1 | package com.clean.data.config
2 |
3 | import android.app.Application
4 | import androidx.room.Room
5 | import com.clean.data.BuildConfig
6 | import com.clean.data.NasaRepositoryImpl
7 | import com.clean.data.StringProviderImpl
8 | import com.clean.data.cache.DBConstants.DB_NAME
9 | import com.clean.data.cache.NasaDataBase
10 | import com.clean.data.remote.NasaService
11 | import com.clean.domain.asteroid.NasaRepository
12 | import com.clean.domain.asteroid.StringProvider
13 | import com.google.gson.Gson
14 | import dagger.Binds
15 | import dagger.Module
16 | import dagger.Provides
17 | import okhttp3.OkHttpClient
18 | import okhttp3.logging.HttpLoggingInterceptor
19 | import retrofit2.Retrofit
20 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
21 | import retrofit2.converter.gson.GsonConverterFactory
22 | import java.util.concurrent.TimeUnit
23 |
24 | @Module(includes = [DataBindsModule::class])
25 | object DataModule {
26 |
27 | @Provides
28 | @JvmStatic
29 | fun provideIsDebug() = BuildConfig.DEBUG
30 |
31 | @Provides
32 | @JvmStatic
33 | @DataSingleton
34 | fun provideNasaDB(application: Application): NasaDataBase {
35 | return Room.databaseBuilder(
36 | application,
37 | NasaDataBase::class.java, DB_NAME
38 | ).build()
39 | }
40 |
41 | @Provides
42 | @JvmStatic
43 | fun provideNasaService(okHttpClient: OkHttpClient, gson: Gson): NasaService {
44 | val retrofit = Retrofit.Builder()
45 | .baseUrl("https://api.nasa.gov/planetary/")
46 | .client(okHttpClient)
47 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
48 | .addConverterFactory(GsonConverterFactory.create(gson))
49 | .build()
50 | return retrofit.create(NasaService::class.java)
51 | }
52 |
53 | @Provides
54 | @JvmStatic
55 | fun provideOkHttpClient(httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient {
56 | return OkHttpClient.Builder()
57 | .addInterceptor(httpLoggingInterceptor)
58 | .connectTimeout(5, TimeUnit.SECONDS)
59 | .readTimeout(5, TimeUnit.SECONDS)
60 | .build()
61 | }
62 |
63 | @Provides
64 | @JvmStatic
65 | fun provideGson(): Gson {
66 | return Gson()
67 | }
68 |
69 | @Provides
70 | @JvmStatic
71 | fun provideLoggingInterceptor(isDebug: Boolean): HttpLoggingInterceptor {
72 | val logging = HttpLoggingInterceptor()
73 | logging.level = if (isDebug) {
74 | HttpLoggingInterceptor.Level.BODY
75 | } else {
76 | HttpLoggingInterceptor.Level.NONE
77 | }
78 | return logging
79 | }
80 | }
81 |
82 | @Module
83 | interface DataBindsModule {
84 |
85 | @Binds
86 | fun bindNasaRepository(naseRepositoryImpl: NasaRepositoryImpl): NasaRepository
87 |
88 | @Binds
89 | fun bindStringProvider(stringProviderImpl: StringProviderImpl): StringProvider
90 | }
91 |
--------------------------------------------------------------------------------
/data/src/main/java/com/clean/data/mapper/AsteroidMapper.kt:
--------------------------------------------------------------------------------
1 | package com.clean.data.mapper
2 |
3 | import com.clean.data.cache.AsteroidEntity
4 | import javax.inject.Inject
5 | import com.clean.data.model.Asteroid as DataAsteroid
6 | import com.clean.domain.asteroid.model.Asteroid as DomainAsteroid
7 |
8 | class AsteroidMapper @Inject constructor() {
9 |
10 | fun mapDataToDomain(dataAsteroid: DataAsteroid): DomainAsteroid {
11 | return DomainAsteroid(
12 | title = dataAsteroid.title,
13 | imageUrl = dataAsteroid.url
14 | )
15 | }
16 |
17 | fun mapDomaintoEntity(domainAsteroid: DomainAsteroid): AsteroidEntity {
18 | return AsteroidEntity(
19 | title = domainAsteroid.title,
20 | url = domainAsteroid.imageUrl
21 | )
22 | }
23 |
24 | fun mapEntitiyToDomain(asteroidEntity: AsteroidEntity): DomainAsteroid {
25 | return DomainAsteroid(
26 | title = asteroidEntity.title,
27 | imageUrl = asteroidEntity.url
28 | )
29 | }
30 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/clean/data/model/Asteroid.kt:
--------------------------------------------------------------------------------
1 | package com.clean.data.model
2 |
3 | data class Asteroid(
4 | val copyright: String? = null,
5 | val date: String? = null,
6 | val explanation: String? = null,
7 | val hdurl: String? = null,
8 | val media_type: String? = null,
9 | val service_version: String? = null,
10 | val title: String? = null,
11 | val url: String? = null
12 | )
--------------------------------------------------------------------------------
/data/src/main/java/com/clean/data/remote/NasaRemote.kt:
--------------------------------------------------------------------------------
1 | package com.clean.data.remote
2 |
3 | import com.clean.data.model.Asteroid
4 | import com.clean.domain.asteroid.DataError
5 | import io.reactivex.Observable
6 | import retrofit2.Response
7 | import javax.inject.Inject
8 |
9 | class NasaRemote @Inject constructor(private val nasaService: NasaService) {
10 |
11 | fun getAsteroidOfTheDay(): Observable {
12 | return nasaService.getAsteroidOfTheDay()
13 | .map { response ->
14 | if (isResponseSuccessful(response)) {
15 | return@map response.body()!!
16 | } else {
17 | val message = buildErrorMessage(response)
18 | throw DataError(message)
19 | }
20 | }
21 | }
22 |
23 | private fun buildErrorMessage(response: Response) =
24 | "there was a network error, code: ${response.code()}, message: ${response.message()}"
25 |
26 | private fun isResponseSuccessful(response: Response) =
27 | response.isSuccessful && response.body() != null
28 | }
29 |
--------------------------------------------------------------------------------
/data/src/main/java/com/clean/data/remote/NasaService.kt:
--------------------------------------------------------------------------------
1 | package com.clean.data.remote
2 |
3 | import com.clean.data.model.Asteroid
4 | import io.reactivex.Observable
5 | import io.reactivex.Single
6 | import retrofit2.Response
7 | import retrofit2.http.GET
8 |
9 | interface NasaService {
10 | @GET("apod?api_key=wSNEtcl1BcDve7JggX1VQgScHRoVSmQNcLvM1rI4")
11 | fun getAsteroidOfTheDay(): Observable>
12 |
13 | }
--------------------------------------------------------------------------------
/data/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | data
3 | "Error loading Asteroid from Server"
4 | "Error loading Asteroid"
5 | "Asteroid saved"
6 | "Message in a bottle"
7 |
8 |
--------------------------------------------------------------------------------
/data/src/test/java/com/clean/data/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.clean.data;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.assertEquals;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/dependencies.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | //Android
3 | androidMinSdkVersion = 21
4 | androidTargetSdkVersion = 28
5 | androidCompileSdkVersion = 28
6 |
7 | //Libraries
8 | kotlinVersion = '1.3.31'
9 | rxKotlinVersion = '2.3.0'
10 | rxAndroidVersion = '2.1.1'
11 | rxJavaVersion = '2.2.8'
12 | rxRelayVersion = '2.1.0'
13 | rxBindingsVersion = '2.2.0'
14 | ktxFragmentVersion = '1.1.0-alpha09'
15 | roomVersion = '1.0.0-alpha9-1'
16 | gsonVersion = '2.8.1'
17 | okHttpVersion = '3.8.1'
18 | retrofitVersion = '2.5.0'
19 | roomVersion = '2.1.0-rc01'
20 | appcompatVersion = '1.0.2'
21 | materialVersion = '1.0.0'
22 | livecycleExtensionsVersion = '2.0.0'
23 | constraintlayoutVersion = '1.1.3'
24 | glideVersion = '4.3.1'
25 | timberVersion = '4.5.1'
26 | glideVersion = '4.0.0'
27 | daggerVersion = '2.20'
28 | javaxAnnotationVersion = '1.0'
29 | javaxInjectVersion = '1'
30 |
31 | //Testing
32 | jUnitVersion = '4.12'
33 | assertJVersion = '3.8.0'
34 | espressoVersion = '3.0.0'
35 | mockitoKotlinVersion = '1.5.0'
36 |
37 | dataDependencies = [
38 | javaxAnnotation : "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
39 | javaxInject : "javax.inject:javax.inject:${javaxInjectVersion}",
40 | okHttp : "com.squareup.okhttp3:okhttp:${okHttpVersion}",
41 | okHttpLogger : "com.squareup.okhttp3:logging-interceptor:${okHttpVersion}",
42 | gson : "com.google.code.gson:gson:${gsonVersion}",
43 | roomRuntime : "androidx.room:room-runtime:${roomVersion}",
44 | roomKotlin : "androidx.room:room-ktx:${roomVersion}",
45 | roomCompiler : "androidx.room:room-compiler:${roomVersion}",
46 | roomRxJava : "androidx.room:room-rxjava2:${roomVersion}",
47 | rxKotlin : "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
48 | rxJava : "io.reactivex.rxjava2:rxjava:${rxJavaVersion}",
49 | kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVersion}",
50 | retrofit : "com.squareup.retrofit2:retrofit:${retrofitVersion}",
51 | retrofitConverter: "com.squareup.retrofit2:converter-gson:${retrofitVersion}",
52 | retrofitAdapter : "com.squareup.retrofit2:adapter-rxjava2:${retrofitVersion}",
53 | dagger : "com.google.dagger:dagger:${daggerVersion}",
54 | daggerCompiler : "com.google.dagger:dagger-compiler:${daggerVersion}"
55 | ]
56 |
57 | dataTestDependencies = [
58 | junit : "junit:junit:${jUnitVersion}",
59 | kotlinJUnit: "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}",
60 | assertj : "org.assertj:assertj-core:${assertJVersion}",
61 | mockito : "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}",
62 | ]
63 |
64 | domainDependencies = [
65 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
66 | javaxInject : "javax.inject:javax.inject:${javaxInjectVersion}",
67 | rxKotlin : "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
68 | rxJava : "io.reactivex.rxjava2:rxjava:${rxJavaVersion}",
69 | kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVersion}",
70 | ]
71 |
72 | domainTestDependencies = [
73 | junit : "junit:junit:${jUnitVersion}",
74 | kotlinJUnit: "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}",
75 | assertj : "org.assertj:assertj-core:${assertJVersion}",
76 | mockito : "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}",
77 | ]
78 |
79 | presentationDependencies = [
80 | glide : "com.github.bumptech.glide:glide:${glideVersion}",
81 | glideCompiler : "com.github.bumptech.glide:compiler:${glideVersion}",
82 | constrainLayout : "androidx.constraintlayout:constraintlayout:${constraintlayoutVersion}",
83 | appcompat : "androidx.appcompat:appcompat:${appcompatVersion}",
84 | ktxFragment : "androidx.fragment:fragment-ktx:${ktxFragmentVersion}",
85 | lifecycleExtensions: "androidx.lifecycle:lifecycle-extensions:${livecycleExtensionsVersion}",
86 | javaxAnnotation : "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
87 | javaxInject : "javax.inject:javax.inject:${javaxInjectVersion}",
88 | rxKotlin : "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
89 | rxJava : "io.reactivex.rxjava2:rxjava:${rxJavaVersion}",
90 | rxRelay : "com.jakewharton.rxrelay2:rxrelay:${rxRelayVersion}",
91 | rxAndroid : "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}",
92 | rxBindings : "com.jakewharton.rxbinding2:rxbinding-kotlin:${rxBindingsVersion}",
93 | kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVersion}",
94 | dagger : "com.google.dagger:dagger:${daggerVersion}",
95 | daggerCompiler : "com.google.dagger:dagger-compiler:${daggerVersion}",
96 | material : "com.google.android.material:material:${materialVersion}"
97 | ]
98 |
99 | presentationTestDependencies = [
100 | junit : "junit:junit:${jUnitVersion}",
101 | kotlinJUnit: "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}",
102 | assertj : "org.assertj:assertj-core:${assertJVersion}",
103 | mockito : "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}",
104 | ]
105 | }
--------------------------------------------------------------------------------
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/domain/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'kotlin'
2 |
3 | sourceCompatibility = 1.8
4 | targetCompatibility = 1.8
5 |
6 | configurations.all {
7 | resolutionStrategy {
8 | force "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
9 | }
10 | }
11 |
12 | dependencies {
13 | def domainDependencies = rootProject.ext.domainDependencies
14 | def domainTestDependencies = rootProject.ext.domainTestDependencies
15 |
16 | implementation domainDependencies.javaxAnnotation
17 | implementation domainDependencies.javaxInject
18 | implementation domainDependencies.kotlin
19 | implementation domainDependencies.rxKotlin
20 | implementation domainDependencies.rxJava
21 | testImplementation domainTestDependencies.junit
22 | testImplementation domainTestDependencies.mockito
23 | testImplementation domainTestDependencies.assertj
24 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/clean/domain/asteroid/AsteroidViewEventHandler.kt:
--------------------------------------------------------------------------------
1 | package com.clean.domain.asteroid
2 |
3 | import com.clean.domain.asteroid.model.AsteroidViewEvent
4 | import com.clean.domain.asteroid.model.AsteroidViewResult
5 | import com.clean.domain.asteroid.usecase.AsteroidUseCase
6 | import io.reactivex.Observable
7 | import javax.inject.Inject
8 |
9 | class AsteroidViewEventHandler @Inject constructor(
10 | private val useCases: Set<@JvmSuppressWildcards AsteroidUseCase>
11 | ) {
12 |
13 | fun handleEvent(viewEvent: AsteroidViewEvent): Observable {
14 | return useCases
15 | .find { it.isForEvent(viewEvent) }
16 | .let { requireNotNull(it) { "No UseCase supports the ViewEvent: $viewEvent" } }
17 | .execute(viewEvent)
18 | }
19 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/clean/domain/asteroid/AsteroidViewFlow.kt:
--------------------------------------------------------------------------------
1 | package com.clean.domain.asteroid
2 |
3 | import com.clean.domain.asteroid.model.AsteroidViewEvent
4 | import com.clean.domain.asteroid.model.AsteroidViewResult
5 | import com.clean.domain.asteroid.model.AsteroidViewState
6 | import io.reactivex.Observable
7 | import javax.inject.Inject
8 |
9 | class AsteroidViewFlow @Inject constructor(
10 | private val eventHandler: AsteroidViewEventHandler,
11 | private val viewStateReducer: AsteroidViewStateReducer
12 | ) {
13 |
14 | fun start(
15 | eventEmitter: Observable
16 | ): Pair, Observable> {
17 | val sharedResultEmitter = eventEmitter
18 | .handleEvent(eventHandler)
19 | .share()
20 | val effectEmitter = effectEmitter(sharedResultEmitter)
21 | val viewStateEmitter = viewStateEmitter(sharedResultEmitter, viewStateReducer)
22 | return effectEmitter to viewStateEmitter
23 | }
24 |
25 | private fun Observable.handleEvent(
26 | eventToResultProcessor: AsteroidViewEventHandler
27 | ): Observable {
28 | return doOnNext { println("flow intent: $it") }
29 | .flatMap(eventToResultProcessor::handleEvent)
30 | .doOnNext { println("flow result: $it") }
31 | }
32 |
33 | private fun effectEmitter(
34 | share: Observable
35 | ): Observable {
36 | return share.ofType(AsteroidViewResult.AsteroidViewEffect::class.java)
37 | }
38 |
39 | private fun viewStateEmitter(
40 | share: Observable,
41 | viewStateReducer: AsteroidViewStateReducer
42 | ): Observable {
43 | return share.ofType(AsteroidViewResult.AsteroidPartialState::class.java)
44 | .compose(viewStateReducer.reduce())
45 | .doOnNext { println("flow viewstate: $it") }
46 | .distinctUntilChanged()
47 | }
48 |
49 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/clean/domain/asteroid/AsteroidViewStateReducer.kt:
--------------------------------------------------------------------------------
1 | package com.clean.domain.asteroid
2 |
3 | import com.clean.domain.asteroid.model.AsteroidViewResult
4 | import com.clean.domain.asteroid.model.AsteroidViewState
5 | import com.clean.domain.asteroid.model.ViewData
6 | import io.reactivex.ObservableTransformer
7 | import javax.inject.Inject
8 |
9 | class AsteroidViewStateReducer @Inject constructor() {
10 |
11 | fun reduce(): ObservableTransformer {
12 | return ObservableTransformer { partialStateObservable ->
13 | partialStateObservable.scan(AsteroidViewState.init())
14 | { oldviewstate: AsteroidViewState, result: AsteroidViewResult.AsteroidPartialState ->
15 | when (result) {
16 | is AsteroidViewResult.AsteroidPartialState.NewAsteroid -> {
17 | oldviewstate.copy(data = ViewData(result.asteroid), loading = false, errorMessage = null)
18 | }
19 | is AsteroidViewResult.AsteroidPartialState.Error -> {
20 | oldviewstate.copy(errorMessage = result.message, loading = false, data = null)
21 | }
22 | AsteroidViewResult.AsteroidPartialState.Loading -> {
23 | oldviewstate.copy(loading = true, data = null, errorMessage = null)
24 | }
25 | }
26 | }
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/clean/domain/asteroid/NasaRepository.kt:
--------------------------------------------------------------------------------
1 | package com.clean.domain.asteroid
2 |
3 | import com.clean.domain.asteroid.model.Asteroid
4 | import io.reactivex.Completable
5 | import io.reactivex.Observable
6 |
7 | interface NasaRepository {
8 |
9 | fun getAsteroidOfTheDay(): Observable
10 |
11 | fun getSavedAsteroid(): Observable>
12 |
13 | fun saveAsteroid(asteroid: Asteroid): Completable
14 | }
15 |
16 | class DataError(message: String) : Exception(message)
--------------------------------------------------------------------------------
/domain/src/main/java/com/clean/domain/asteroid/StringProvider.kt:
--------------------------------------------------------------------------------
1 | package com.clean.domain.asteroid
2 |
3 | interface StringProvider {
4 |
5 | val serverError: String
6 | val generalError: String
7 | val storeAsteroidSuccess: String
8 | val messageInABottle: String
9 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/clean/domain/asteroid/model/Asteroid.kt:
--------------------------------------------------------------------------------
1 | package com.clean.domain.asteroid.model
2 |
3 | data class Asteroid(
4 | val title: String? = null,
5 | val imageUrl: String? = null
6 | )
--------------------------------------------------------------------------------
/domain/src/main/java/com/clean/domain/asteroid/model/AsteroidViewEvent.kt:
--------------------------------------------------------------------------------
1 | package com.clean.domain.asteroid.model
2 |
3 | sealed class AsteroidViewEvent {
4 | data class Store(val asteroid: Asteroid) : AsteroidViewEvent()
5 | object Load : AsteroidViewEvent()
6 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/clean/domain/asteroid/model/AsteroidViewResult.kt:
--------------------------------------------------------------------------------
1 | package com.clean.domain.asteroid.model
2 |
3 | sealed class AsteroidViewResult {
4 |
5 | sealed class AsteroidPartialState : AsteroidViewResult() {
6 | data class NewAsteroid(val asteroid: Asteroid) : AsteroidPartialState()
7 | data class Error(val message: String) : AsteroidPartialState()
8 | object Loading : AsteroidPartialState()
9 | }
10 |
11 | sealed class AsteroidViewEffect : AsteroidViewResult() {
12 | data class UserMessage(val message: String) : AsteroidViewEffect()
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/clean/domain/asteroid/model/AsteroidViewState.kt:
--------------------------------------------------------------------------------
1 | package com.clean.domain.asteroid.model
2 |
3 | data class AsteroidViewState(
4 | val loading: Boolean = false,
5 | val errorMessage: String? = null,
6 | val data: ViewData? = null
7 | ) {
8 | companion object {
9 | fun init(): AsteroidViewState {
10 | return AsteroidViewState(false, null, null)
11 | }
12 | }
13 | }
14 |
15 | data class ViewData(val asteroid: Asteroid)
--------------------------------------------------------------------------------
/domain/src/main/java/com/clean/domain/asteroid/usecase/AsteroidUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.clean.domain.asteroid.usecase
2 |
3 | import com.clean.domain.asteroid.model.AsteroidViewEvent
4 | import com.clean.domain.asteroid.model.AsteroidViewResult
5 | import io.reactivex.Observable
6 |
7 | interface AsteroidUseCase {
8 |
9 | fun execute(event: T): Observable
10 |
11 | fun isForEvent(event: AsteroidViewEvent): Boolean
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/clean/domain/asteroid/usecase/GetAsteroidOfTheDay.kt:
--------------------------------------------------------------------------------
1 | package com.clean.domain.asteroid.usecase
2 |
3 | import com.clean.domain.asteroid.NasaRepository
4 | import com.clean.domain.asteroid.StringProvider
5 | import com.clean.domain.asteroid.model.AsteroidViewEvent
6 | import com.clean.domain.asteroid.model.AsteroidViewResult
7 | import io.reactivex.Observable
8 | import javax.inject.Inject
9 |
10 | class GetAsteroidOfTheDay @Inject constructor(
11 | private val nasaRepository: NasaRepository,
12 | private val stringProvider: StringProvider
13 | ) : AsteroidUseCase {
14 |
15 | override fun isForEvent(event: AsteroidViewEvent): Boolean {
16 | return event is AsteroidViewEvent.Load
17 | }
18 |
19 | override fun execute(event: AsteroidViewEvent.Load): Observable {
20 | return Observable.concat(emitLoading(), emitAsteroid())
21 | .onErrorReturnItem(AsteroidViewResult.AsteroidPartialState.Error(stringProvider.generalError))
22 | .doOnError { println(it.message) }
23 | }
24 |
25 | private fun emitLoading(): Observable {
26 | return Observable.just(AsteroidViewResult.AsteroidPartialState.Loading)
27 | }
28 |
29 | private fun emitAsteroid(): Observable {
30 | return nasaRepository.getAsteroidOfTheDay()
31 | .map { AsteroidViewResult.AsteroidPartialState.NewAsteroid(it) }
32 | }
33 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/clean/domain/asteroid/usecase/SaveAsteroid.kt:
--------------------------------------------------------------------------------
1 | package com.clean.domain.asteroid.usecase
2 |
3 | import com.clean.domain.asteroid.NasaRepository
4 | import com.clean.domain.asteroid.StringProvider
5 | import com.clean.domain.asteroid.model.AsteroidViewEvent
6 | import com.clean.domain.asteroid.model.AsteroidViewResult
7 | import io.reactivex.Observable
8 | import javax.inject.Inject
9 |
10 | class SaveAsteroid @Inject constructor(
11 | private val nasaRepository: NasaRepository,
12 | private val stringProvider: StringProvider
13 | ) : AsteroidUseCase {
14 |
15 | override fun isForEvent(event: AsteroidViewEvent): Boolean {
16 | return event is AsteroidViewEvent.Store
17 | }
18 |
19 | override fun execute(event: AsteroidViewEvent.Store): Observable {
20 | return Observable.concat(saveAsteroid(event), emitUserMessageEffect())
21 | .onErrorReturnItem(AsteroidViewResult.AsteroidViewEffect.UserMessage(stringProvider.generalError))
22 | .doOnError { println(it.message) }
23 | }
24 |
25 | private fun saveAsteroid(event: AsteroidViewEvent.Store): Observable {
26 | return nasaRepository.saveAsteroid(event.asteroid).toObservable()
27 | }
28 |
29 | private fun emitUserMessageEffect(): Observable {
30 | return Observable.just(AsteroidViewResult.AsteroidViewEffect.UserMessage(stringProvider.storeAsteroidSuccess))
31 | }
32 | }
--------------------------------------------------------------------------------
/domain/src/test/kotlin/com/clean/domain/asteroid/AsteroidViewFlowTest.kt:
--------------------------------------------------------------------------------
1 | package com.clean.domain.asteroid
2 |
3 | import com.clean.domain.asteroid.model.Asteroid
4 | import com.clean.domain.asteroid.model.AsteroidViewEvent
5 | import com.clean.domain.asteroid.model.AsteroidViewResult
6 | import com.clean.domain.asteroid.model.AsteroidViewState
7 | import com.clean.domain.asteroid.usecase.AsteroidUseCase
8 | import com.clean.domain.asteroid.usecase.GetAsteroidOfTheDay
9 | import com.clean.domain.asteroid.usecase.SaveAsteroid
10 | import io.reactivex.Observable
11 | import io.reactivex.subjects.PublishSubject
12 | import org.assertj.core.api.Assertions
13 | import org.junit.Test
14 |
15 | class AsteroidViewFlowTest {
16 |
17 | private val repository: NasaRepository = NasaRepositoryFake()
18 |
19 | private val stringProvider: StringProvider = StringProviderFake()
20 |
21 | private val asteroidViewFlow: AsteroidViewFlow = asteroidViewFlow()
22 |
23 | @Test
24 | fun `when event is store then return effect user message`() {
25 | val (effectsEmiter, viewstateEmiter) =
26 | asteroidViewFlow.start(Observable.just(AsteroidViewEvent.Store(asteroid())))
27 |
28 | emitsUserMessageEffect(effectsEmiter)
29 | }
30 |
31 | @Test
32 | fun `when event is store then save Asteroid`() {
33 | val (effectsEmiter, viewstateEmiter) =
34 | asteroidViewFlow.start(Observable.just(AsteroidViewEvent.Store(asteroid())))
35 |
36 | val viewStateSubscriber = viewstateEmiter.test()
37 |
38 | asteroidIsStored(asteroid())
39 | }
40 |
41 | @Test
42 | fun `when event is store then viewState is not changing`() {
43 | val viewEventEmiter = PublishSubject.create()
44 | val (effectsEmiter, viewstateEmiter) =
45 | asteroidViewFlow.start(viewEventEmiter)
46 |
47 | val viewStateTestSubscriber = viewstateEmiter.test()
48 | viewEventEmiter.onNext(AsteroidViewEvent.Load)
49 | val oldItemCount = viewStateTestSubscriber.valueCount()
50 | viewEventEmiter.onNext(AsteroidViewEvent.Store(asteroid()))
51 | val newItemCount = viewStateTestSubscriber.valueCount()
52 | Assertions.assertThat(oldItemCount).isEqualTo(newItemCount)
53 | }
54 |
55 | @Test
56 | fun `when event is load then return effect user message`() {
57 | val (effectsEmiter, viewstateEmiter) =
58 | asteroidViewFlow.start(Observable.just(AsteroidViewEvent.Load))
59 |
60 | emitsNoEffects(effectsEmiter)
61 | }
62 |
63 | @Test
64 | fun `when event is load then return partial states init, loading, data`() {
65 | val (effectsEmiter, viewstate) =
66 | asteroidViewFlow.start(Observable.just(AsteroidViewEvent.Load))
67 |
68 | val viewStateSubscriber = viewstate.test()
69 | viewStateSubscriber.assertValueCount(3)
70 | viewStateSubscriber.assertValueAt(0, AsteroidViewState.init())
71 | viewStateSubscriber.assertValueAt(1, { asteroidViewState -> isLoading(asteroidViewState) })
72 | viewStateSubscriber.assertValueAt(2, { asteroidViewState -> hasData(asteroidViewState) })
73 | }
74 |
75 | @Test
76 | fun `when event is second load then return partial states loading, data`() {
77 | val (effectsEmiter, viewstate) =
78 | asteroidViewFlow.start(Observable.just(AsteroidViewEvent.Load, AsteroidViewEvent.Load))
79 |
80 | val viewStateSubscriber = viewstate.test()
81 | viewStateSubscriber.assertValueCount(5)
82 | viewStateSubscriber.assertValueAt(3, { asteroidViewState -> isLoading(asteroidViewState) })
83 | viewStateSubscriber.assertValueAt(4, { asteroidViewState -> hasData(asteroidViewState) })
84 | }
85 |
86 | private fun asteroid() = Asteroid("title", "url")
87 |
88 | private fun asteroidIsStored(asteroid: Asteroid) {
89 | val savedAsteroids = repository.getSavedAsteroid().test()
90 | savedAsteroids.assertValueAt(0, { asteroids -> asteroids.contains(asteroid) })
91 | }
92 |
93 | private fun emitsUserMessageEffect(effects: Observable) {
94 | val effectsSubscriber = effects.test()
95 | effectsSubscriber.assertValueCount(1)
96 | .assertValue(AsteroidViewResult.AsteroidViewEffect.UserMessage(stringProvider.storeAsteroidSuccess))
97 | }
98 |
99 | private fun emitsNoEffects(effects: Observable) {
100 | val effectsSubscriber = effects.test()
101 | effectsSubscriber.assertValueCount(0)
102 | }
103 |
104 | private fun isLoading(asteroidViewState: AsteroidViewState): Boolean =
105 | asteroidViewState.loading == true && asteroidViewState.errorMessage == null && asteroidViewState.data == null
106 |
107 | private fun hasData(asteroidViewState: AsteroidViewState) =
108 | asteroidViewState.loading == false && asteroidViewState.errorMessage == null && asteroidViewState.data != null
109 |
110 | private fun asteroidViewFlow(): AsteroidViewFlow {
111 |
112 | return AsteroidViewFlow(
113 | AsteroidViewEventHandler(
114 | setOf(
115 | GetAsteroidOfTheDay(
116 | nasaRepository = repository,
117 | stringProvider = stringProvider
118 | ) as AsteroidUseCase,
119 | SaveAsteroid(
120 | nasaRepository = repository,
121 | stringProvider = stringProvider
122 | ) as AsteroidUseCase
123 | )
124 | ),
125 | AsteroidViewStateReducer()
126 | )
127 | }
128 | }
--------------------------------------------------------------------------------
/domain/src/test/kotlin/com/clean/domain/asteroid/NasaRepositoryFake.kt:
--------------------------------------------------------------------------------
1 | package com.clean.domain.asteroid
2 |
3 | import com.clean.domain.asteroid.model.Asteroid
4 | import io.reactivex.Completable
5 | import io.reactivex.Observable
6 |
7 | class NasaRepositoryFake : NasaRepository {
8 |
9 | val cache: MutableList = mutableListOf()
10 |
11 | override fun saveAsteroid(asteroid: Asteroid): Completable {
12 | cache.add(asteroid)
13 | return Completable.complete()
14 | }
15 |
16 | override fun getAsteroidOfTheDay(): Observable {
17 | return Observable.just(Asteroid(title = "title", imageUrl = "imageUrl"))
18 | }
19 |
20 | override fun getSavedAsteroid(): Observable> {
21 | return Observable.just(cache)
22 | }
23 | }
--------------------------------------------------------------------------------
/domain/src/test/kotlin/com/clean/domain/asteroid/StringProviderFake.kt:
--------------------------------------------------------------------------------
1 | package com.clean.domain.asteroid
2 |
3 | class StringProviderFake() : StringProvider {
4 |
5 | override val serverError: String
6 | get() = "serverError"
7 | override val generalError: String
8 | get() = "generalError"
9 | override val storeAsteroidSuccess: String
10 | get() = "storeAsteroidSuccess"
11 | override val messageInABottle: String
12 | get() = "messageInABottle"
13 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
10 | org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m
11 | # When configured, Gradle will run in incubating parallel mode.
12 | # This option should only be used with decoupled projects. More details, visit
13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
14 | # org.gradle.parallel=true
15 | # Kotlin code style for this project: "official" or "obsolete":
16 | kotlin.code.style=official
17 | android.useAndroidX=true
18 | android.enableJetifier=true
19 | # When configured, Gradle will run in incubating parallel mode.
20 | # This option should only be used with decoupled projects. More details, visit
21 | org.gradle.parallel=true
22 | # When set to true the Gradle daemon is used to run the build. For local developer builds this is our favorite property.
23 | # The developer environment is optimized for speed and feedback so we nearly always run Gradle jobs with the daemon.
24 | org.gradle.daemon=true
25 | kapt.incremental.apt=true
26 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdrianoCelentano/CleanProject/ab415e4d84025175ae578a5c9ac3861cee84bfde/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri May 03 13:22:22 CEST 2019
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-5.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/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 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
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 Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/presentation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/presentation/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: 'kotlin-android-extensions'
6 |
7 | apply plugin: 'kotlin-kapt'
8 |
9 | android {
10 | compileSdkVersion 28
11 |
12 |
13 | defaultConfig {
14 | minSdkVersion 21
15 | targetSdkVersion 28
16 | versionCode 1
17 | versionName "1.0"
18 |
19 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
20 |
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 |
30 | compileOptions {
31 | sourceCompatibility = 1.8
32 | targetCompatibility = 1.8
33 | }
34 |
35 | }
36 |
37 | kapt {
38 | javacOptions {
39 | // Increase the max count of errors from annotation processors.
40 | // Default is 100.
41 | option("-Xmaxerrs", 500)
42 | }
43 | }
44 |
45 | configurations.all {
46 | resolutionStrategy {
47 | force "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
48 | }
49 | }
50 |
51 | dependencies {
52 | def presentationDependencies = rootProject.ext.presentationDependencies
53 | def domainTestDependencies = rootProject.ext.presentationTestDependencies
54 |
55 | implementation project(path: ':domain')
56 | implementation presentationDependencies.material
57 | implementation presentationDependencies.appcompat
58 | implementation presentationDependencies.lifecycleExtensions
59 | implementation presentationDependencies.glide
60 | implementation presentationDependencies.ktxFragment
61 | kapt presentationDependencies.glideCompiler
62 | implementation presentationDependencies.constrainLayout
63 | implementation presentationDependencies.javaxAnnotation
64 | implementation presentationDependencies.javaxInject
65 | implementation presentationDependencies.kotlin
66 | implementation presentationDependencies.rxKotlin
67 | implementation presentationDependencies.rxJava
68 | implementation presentationDependencies.rxRelay
69 | implementation presentationDependencies.rxAndroid
70 | implementation presentationDependencies.rxBindings
71 | implementation presentationDependencies.dagger
72 | kapt presentationDependencies.daggerCompiler
73 |
74 | testImplementation domainTestDependencies.junit
75 | testImplementation domainTestDependencies.mockito
76 | testImplementation domainTestDependencies.assertj
77 | }
78 |
--------------------------------------------------------------------------------
/presentation/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 |
--------------------------------------------------------------------------------
/presentation/src/androidTest/java/com/clean/asteroids/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.clean.asteroids;
2 |
3 | import android.content.Context;
4 | import androidx.test.InstrumentationRegistry;
5 | import androidx.test.runner.AndroidJUnit4;
6 | import org.junit.Test;
7 | import org.junit.runner.RunWith;
8 |
9 | import static org.junit.Assert.assertEquals;
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * @see Testing documentation
15 | */
16 | @RunWith(AndroidJUnit4.class)
17 | public class ExampleInstrumentedTest {
18 | @Test
19 | public void useAppContext() {
20 | // Context of the app under test.
21 | Context appContext = InstrumentationRegistry.getTargetContext();
22 |
23 | assertEquals("com.clean.asteroids.test", appContext.getPackageName());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/presentation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/clean/asteroids/AsteroidActivity.kt:
--------------------------------------------------------------------------------
1 | package com.clean.asteroids
2 |
3 | import android.os.Bundle
4 | import androidx.activity.viewModels
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.lifecycle.ViewModelProvider
7 | import com.clean.asteroids.config.CoreComponentProvider
8 | import com.clean.asteroids.config.DaggerPresentationComponent
9 | import com.clean.asteroids.config.PresentationComponent
10 | import com.clean.domain.asteroid.model.AsteroidViewEvent
11 | import com.clean.domain.asteroid.model.AsteroidViewState
12 | import com.jakewharton.rxbinding2.view.clicks
13 | import io.reactivex.Observable
14 | import io.reactivex.android.schedulers.AndroidSchedulers
15 | import io.reactivex.disposables.CompositeDisposable
16 | import io.reactivex.rxkotlin.addTo
17 | import io.reactivex.rxkotlin.subscribeBy
18 | import io.reactivex.schedulers.Schedulers
19 | import kotlinx.android.synthetic.main.activity_main.*
20 | import javax.inject.Inject
21 | import androidx.lifecycle.Observer as LifecycleObserver
22 |
23 | class AsteroidActivity : AppCompatActivity() {
24 |
25 | @Inject
26 | lateinit var viewModelFactory: ViewModelProvider.Factory
27 |
28 | @Inject
29 | lateinit var asteroidViewRenderer: AsteroidViewRenderer
30 |
31 | @Inject
32 | lateinit var asteroidEffectHandler: AsteroidEffectHandler
33 |
34 | private val asteroidViewModel: AsteroidViewModel by viewModels(::viewModelFactory)
35 |
36 | private var component: PresentationComponent? = null
37 |
38 | private val disposables = CompositeDisposable()
39 |
40 | override fun onCreate(savedInstanceState: Bundle?) {
41 | injectMembers()
42 | super.onCreate(savedInstanceState)
43 | setContentView(R.layout.activity_main)
44 | observeModel()
45 | }
46 |
47 | override fun onResume() {
48 | super.onResume()
49 | observeEvents()
50 | observeEffects()
51 | }
52 |
53 | override fun onPause() {
54 | super.onPause()
55 | disposables.clear()
56 | }
57 |
58 | override fun onDestroy() {
59 | super.onDestroy()
60 | component = null
61 | }
62 |
63 | private fun observeEvents() {
64 | Observable.merge(RefreshButtonObservable(), StoreButtonObservable())
65 | .observeOn(Schedulers.io())
66 | .subscribeBy(
67 | onNext = { asteroidViewModel.processEvent(it) }
68 | ).addTo(disposables)
69 | }
70 |
71 | private fun observeModel() {
72 | asteroidViewModel.viewStateLive.observe(this, object : LifecycleObserver {
73 | override fun onChanged(asteroidViewState: AsteroidViewState) {
74 | asteroidViewRenderer.render(asteroidViewState)
75 | }
76 | })
77 | }
78 |
79 | private fun observeEffects() {
80 | asteroidViewModel.viewEffectEmitter
81 | .subscribeOn(Schedulers.io())
82 | .observeOn(AndroidSchedulers.mainThread())
83 | .subscribeBy(
84 | onNext = { effect ->
85 | asteroidEffectHandler.handleEffect(effect)
86 | }
87 | ).addTo(disposables)
88 | }
89 |
90 | private fun StoreButtonObservable(): Observable {
91 | return StoreButton.clicks()
92 | .filter { asteroidViewModel.asteroidOfTheDay != null }
93 | .map { AsteroidViewEvent.Store(asteroidViewModel.asteroidOfTheDay!!) }
94 | }
95 |
96 | private fun RefreshButtonObservable(): Observable {
97 | return RefreshButton.clicks()
98 | .map { AsteroidViewEvent.Load }
99 | }
100 |
101 | private fun injectMembers() {
102 | component = DaggerPresentationComponent.builder()
103 | .activity(this)
104 | .coreComponent((application as CoreComponentProvider).provide())
105 | .build()
106 | .also { it.inject(this) }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/clean/asteroids/AsteroidEffectHandler.kt:
--------------------------------------------------------------------------------
1 | package com.clean.asteroids
2 |
3 | import android.app.Activity
4 | import android.widget.Toast
5 | import com.clean.asteroids.config.ActivityScope
6 | import com.clean.domain.asteroid.model.AsteroidViewResult
7 | import javax.inject.Inject
8 |
9 | @ActivityScope
10 | class AsteroidEffectHandler @Inject constructor(private val activity: Activity) {
11 |
12 | fun handleEffect(effect: AsteroidViewResult.AsteroidViewEffect?) {
13 | when (effect) {
14 | is AsteroidViewResult.AsteroidViewEffect.UserMessage -> showToast(effect)
15 | }
16 | }
17 |
18 | private fun showToast(effect: AsteroidViewResult.AsteroidViewEffect.UserMessage) {
19 | Toast.makeText(activity, effect.message, Toast.LENGTH_SHORT).show()
20 | }
21 |
22 |
23 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/com/clean/asteroids/AsteroidViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.clean.asteroids
2 |
3 | import android.util.Log
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.MutableLiveData
6 | import androidx.lifecycle.ViewModel
7 | import com.clean.domain.asteroid.AsteroidViewFlow
8 | import com.clean.domain.asteroid.model.Asteroid
9 | import com.clean.domain.asteroid.model.AsteroidViewEvent
10 | import com.clean.domain.asteroid.model.AsteroidViewResult
11 | import com.clean.domain.asteroid.model.AsteroidViewState
12 | import com.jakewharton.rxrelay2.PublishRelay
13 | import io.reactivex.Observable
14 | import io.reactivex.disposables.CompositeDisposable
15 | import io.reactivex.rxkotlin.addTo
16 | import io.reactivex.rxkotlin.subscribeBy
17 | import io.reactivex.schedulers.Schedulers
18 | import javax.inject.Inject
19 |
20 | class AsteroidViewModel @Inject constructor(
21 | asteroidViewFlow: AsteroidViewFlow
22 | ) : ViewModel() {
23 |
24 | val viewStateLive: LiveData
25 | get() = viewStateMutableLive
26 |
27 | private val viewStateMutableLive by lazy { MutableLiveData() }
28 |
29 | val viewEffectEmitter: Observable
30 |
31 | val asteroidOfTheDay: Asteroid? get() = viewStateLive.value?.data?.asteroid
32 |
33 | private val eventRelay: PublishRelay = PublishRelay.create()
34 |
35 | private val eventEmitter get() = eventRelay.hide().startWith(AsteroidViewEvent.Load)
36 |
37 | private val disposables: CompositeDisposable = CompositeDisposable()
38 |
39 | init {
40 | val (effectEmitter, viewStateEmitter) =
41 | asteroidViewFlow.start(eventEmitter)
42 |
43 | observeViewState(viewStateEmitter)
44 |
45 | this.viewEffectEmitter = effectEmitter
46 | }
47 |
48 | fun processEvent(viewEvent: AsteroidViewEvent) {
49 | eventRelay.accept(viewEvent)
50 | }
51 |
52 | private fun observeViewState(viewStateEmitter: Observable) {
53 | viewStateEmitter
54 | .subscribeOn(Schedulers.io())
55 | .subscribeBy(
56 | onNext = { viewState -> viewStateMutableLive.postValue(viewState) },
57 | onError = { error -> Log.e("qwer", "error viewstate", error) }
58 | ).addTo(disposables)
59 | }
60 |
61 | override fun onCleared() {
62 | super.onCleared()
63 | disposables.clear()
64 | }
65 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/com/clean/asteroids/AsteroidViewRenderer.kt:
--------------------------------------------------------------------------------
1 | package com.clean.asteroids
2 |
3 | import android.app.Activity
4 | import android.view.View
5 | import android.widget.ImageView
6 | import android.widget.ProgressBar
7 | import android.widget.TextView
8 | import androidx.constraintlayout.widget.ConstraintLayout
9 | import com.bumptech.glide.Glide
10 | import com.clean.asteroids.config.ActivityScope
11 | import com.clean.domain.asteroid.model.AsteroidViewState
12 | import com.clean.domain.asteroid.model.ViewData
13 | import com.google.android.material.snackbar.Snackbar
14 | import javax.inject.Inject
15 |
16 | @ActivityScope
17 | class AsteroidViewRenderer @Inject constructor(private val activity: Activity) {
18 |
19 | private val loadingIndicator: ProgressBar by lazy(LazyThreadSafetyMode.NONE)
20 | { activity.findViewById(R.id.loadingIndicator) }
21 |
22 | private val asteroidTitleTextView: TextView by lazy(LazyThreadSafetyMode.NONE)
23 | { activity.findViewById(R.id.asteroidTitle) }
24 |
25 | private val asteroidImage: ImageView by lazy(LazyThreadSafetyMode.NONE)
26 | { activity.findViewById(R.id.asteroidImage) }
27 |
28 | private val rootLayout: ConstraintLayout by lazy(LazyThreadSafetyMode.NONE)
29 | { activity.findViewById(R.id.rootLayout) }
30 |
31 | private var errorSnackBar: Snackbar? = null
32 |
33 | fun render(asteroidViewState: AsteroidViewState) {
34 | renderLoading(asteroidViewState.loading)
35 | renderData(asteroidViewState.data)
36 | renderError(asteroidViewState.errorMessage)
37 | }
38 |
39 | private fun renderData(viewData: ViewData?) {
40 | if (viewData != null) {
41 | showData(viewData)
42 | } else {
43 | hideData()
44 | }
45 | }
46 |
47 | private fun renderError(errorMessage: String?) {
48 | if (errorMessage.isNullOrBlank()) {
49 | hideErrorMessage()
50 | } else {
51 | showErrorMessage(errorMessage)
52 | }
53 | }
54 |
55 | private fun renderLoading(loading: Boolean) {
56 | if (loading) {
57 | showLoading()
58 | } else {
59 | hideLoading()
60 | }
61 | }
62 |
63 | private fun hideData() {
64 | asteroidTitleTextView.visibility = View.INVISIBLE
65 | asteroidImage.visibility = View.INVISIBLE
66 | }
67 |
68 | private fun showData(viewData: ViewData) {
69 | val asteroid = viewData.asteroid
70 | asteroidTitleTextView.text = asteroid.title
71 | Glide.with(activity).load(asteroid.imageUrl).into(asteroidImage);
72 | asteroidTitleTextView.visibility = View.VISIBLE
73 | asteroidImage.visibility = View.VISIBLE
74 | }
75 |
76 | private fun showErrorMessage(errorMessage: String) {
77 | hideLoading()
78 | hideData()
79 | showSnackBar(errorMessage)
80 | }
81 |
82 | private fun hideErrorMessage() {
83 | errorSnackBar?.dismiss()
84 | }
85 |
86 | private fun showLoading() {
87 | loadingIndicator.visibility = View.VISIBLE
88 | }
89 |
90 | private fun hideLoading() {
91 | loadingIndicator.visibility = View.INVISIBLE
92 | }
93 |
94 | private fun showSnackBar(errorMessage: String) {
95 | errorSnackBar = Snackbar.make(rootLayout, errorMessage, Snackbar.LENGTH_INDEFINITE)
96 | .also { it.show() };
97 | }
98 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/com/clean/asteroids/ViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | package com.clean.asteroids
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import javax.inject.Inject
6 | import javax.inject.Provider
7 |
8 | @Suppress("UNCHECKED_CAST")
9 | class ViewModelFactory
10 | @Inject constructor(
11 | private val creators: Map, @JvmSuppressWildcards Provider>
12 | ) : ViewModelProvider.Factory {
13 |
14 | override fun create(modelClass: Class): T {
15 | var creator: Provider? = creators[modelClass]
16 | if (creator == null) {
17 | creator = creators.entries
18 | .find { modelClass.isAssignableFrom(it.key) }?.value
19 | }
20 | if (creator == null) {
21 | throw IllegalArgumentException("unknown model class " + modelClass)
22 | }
23 | try {
24 | return creator.get() as T
25 | } catch (e: Exception) {
26 | throw RuntimeException(e)
27 | }
28 |
29 | }
30 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/com/clean/asteroids/config/CoreComponent.kt:
--------------------------------------------------------------------------------
1 | package com.clean.asteroids.config
2 |
3 | import com.clean.domain.asteroid.AsteroidViewFlow
4 |
5 | interface CoreComponent {
6 |
7 | fun provideAsteriodFlow(): AsteroidViewFlow
8 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/com/clean/asteroids/config/CoreComponentProvider.kt:
--------------------------------------------------------------------------------
1 | package com.clean.asteroids.config
2 |
3 | interface CoreComponentProvider {
4 |
5 | fun provide(): CoreComponent
6 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/com/clean/asteroids/config/PresentationComponent.kt:
--------------------------------------------------------------------------------
1 | package com.clean.asteroids.config
2 |
3 | import com.clean.asteroids.AsteroidActivity
4 | import dagger.BindsInstance
5 | import dagger.Component
6 |
7 | @Component(
8 | modules = arrayOf(PresentationModule::class),
9 | dependencies = arrayOf(CoreComponent::class)
10 | )
11 | @ActivityScope
12 | interface PresentationComponent {
13 |
14 | fun inject(asteroidActivity: AsteroidActivity)
15 |
16 | @Component.Builder
17 | interface Builder {
18 |
19 | fun coreComponent(coreComponent: CoreComponent): Builder
20 |
21 | @BindsInstance
22 | fun activity(asteroidActivity: AsteroidActivity): Builder
23 |
24 | fun build(): PresentationComponent
25 | }
26 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/com/clean/asteroids/config/PresentationModule.kt:
--------------------------------------------------------------------------------
1 | package com.clean.asteroids.config
2 |
3 | import android.app.Activity
4 | import androidx.lifecycle.ViewModel
5 | import androidx.lifecycle.ViewModelProvider
6 | import com.clean.asteroids.AsteroidActivity
7 | import com.clean.asteroids.AsteroidViewModel
8 | import com.clean.asteroids.ViewModelFactory
9 | import dagger.Binds
10 | import dagger.Module
11 | import dagger.multibindings.ClassKey
12 | import dagger.multibindings.IntoMap
13 |
14 | @Module(includes = [PresentationBindsModule::class])
15 | object PresentationModule {
16 |
17 |
18 | }
19 |
20 | @Module
21 | interface PresentationBindsModule {
22 |
23 | @Binds
24 | @IntoMap
25 | @ClassKey(AsteroidViewModel::class)
26 | fun bindMainViewModel(asteroidViewModel: AsteroidViewModel): ViewModel
27 |
28 | @Binds
29 | fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory
30 |
31 | @Binds
32 | fun bindActivity(asteroidActivity: AsteroidActivity): Activity
33 | }
34 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/clean/asteroids/config/Scopes.kt:
--------------------------------------------------------------------------------
1 | package com.clean.asteroids.config
2 |
3 | import javax.inject.Scope
4 |
5 | @Scope
6 | @MustBeDocumented
7 | @Retention
8 | annotation class ActivityScope
9 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/clean/asteroids/config/ViewModelKey.kt:
--------------------------------------------------------------------------------
1 | package com.clean.asteroids.config
2 |
3 | import androidx.lifecycle.ViewModel
4 | import dagger.MapKey
5 | import kotlin.reflect.KClass
6 |
7 | @MustBeDocumented
8 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
9 | @kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
10 | @MapKey
11 | annotation class ViewModelKey(val value: KClass)
--------------------------------------------------------------------------------
/presentation/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
18 |
19 |
26 |
36 |
46 |
47 |
56 |
57 |
--------------------------------------------------------------------------------
/presentation/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | asteroids
3 |
4 |
--------------------------------------------------------------------------------
/presentation/src/test/java/com/clean/asteroids/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.clean.asteroids;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.assertEquals;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':domain', ':presentation', ':data'
2 |
--------------------------------------------------------------------------------