├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── app ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── kotlin │ │ └── com │ │ └── rantolin │ │ └── cleanarchitecture │ │ ├── AcceptanceTest.kt │ │ ├── Events.kt │ │ └── Matchers.kt │ ├── main │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── com │ │ │ └── rantolin │ │ │ └── cleanarchitecture │ │ │ ├── presentation │ │ │ ├── AndroidApplication.kt │ │ │ ├── UIThread.kt │ │ │ ├── internal │ │ │ │ └── di │ │ │ │ │ ├── components │ │ │ │ │ ├── ActivityComponent.kt │ │ │ │ │ ├── ApplicationComponent.kt │ │ │ │ │ └── FragmentComponent.kt │ │ │ │ │ ├── modules │ │ │ │ │ ├── ActivityModule.kt │ │ │ │ │ ├── ApplicationModule.kt │ │ │ │ │ └── FragmentModule.kt │ │ │ │ │ └── scope │ │ │ │ │ ├── PerActivity.kt │ │ │ │ │ └── PerFragment.kt │ │ │ ├── navigation │ │ │ │ └── NavigationExtensions.kt │ │ │ └── ui │ │ │ │ ├── activities │ │ │ │ └── MainActivity.kt │ │ │ │ ├── adapters │ │ │ │ └── MainFragmentViewAdapter.kt │ │ │ │ ├── fragments │ │ │ │ ├── BaseFragment.kt │ │ │ │ └── MainFragment.kt │ │ │ │ ├── presenters │ │ │ │ └── MainPresenter.kt │ │ │ │ └── views │ │ │ │ ├── BaseView.kt │ │ │ │ └── MainView.kt │ │ │ └── utils │ │ │ └── Extensions.kt │ └── res │ │ ├── drawable-v21 │ │ ├── ic_menu_camera.xml │ │ ├── ic_menu_gallery.xml │ │ ├── ic_menu_manage.xml │ │ ├── ic_menu_send.xml │ │ ├── ic_menu_share.xml │ │ └── ic_menu_slideshow.xml │ │ ├── drawable │ │ └── side_nav_bar.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── app_bar_main.xml │ │ ├── content_main.xml │ │ ├── fragment_main.xml │ │ ├── item_home.xml │ │ └── nav_header_main.xml │ │ ├── menu │ │ ├── activity_main_drawer.xml │ │ └── main.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 │ │ ├── dimens.xml │ │ ├── drawables.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── kotlin │ └── com │ └── rantolin │ └── cleanarchitecture │ └── presentation │ └── ExampleUnitTest.kt ├── build.gradle ├── buildsystem ├── ci.gradle ├── debug.keystore └── dependencies.gradle ├── data ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── com │ │ │ └── rantolin │ │ │ └── cleanarchitecture │ │ │ └── data │ │ │ ├── bbdd │ │ │ └── DBHelper.kt │ │ │ ├── cloud │ │ │ ├── ApiService.kt │ │ │ ├── RestApi.kt │ │ │ └── responsemodels │ │ │ │ └── UserResponseModel.kt │ │ │ ├── datasource │ │ │ ├── CloudDataStore.kt │ │ │ ├── DBDataStore.kt │ │ │ ├── DataFactory.kt │ │ │ └── DataStore.kt │ │ │ ├── entity │ │ │ ├── BaseEntity.kt │ │ │ ├── UserEntity.kt │ │ │ └── mapper │ │ │ │ ├── EntityMapper.kt │ │ │ │ └── UserEntityMapper.kt │ │ │ ├── executor │ │ │ └── JobExecutor.kt │ │ │ └── repository │ │ │ └── DataRepository.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── kotlin │ └── com │ └── rantolin │ └── cleanarchitecture │ └── data │ └── ExampleUnitTest.kt ├── domain ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── kotlin │ │ └── com │ │ │ └── rantolin │ │ │ └── cleanarchitecture │ │ │ └── domain │ │ │ ├── exception │ │ │ ├── DefaultErrorBundle.kt │ │ │ └── ErrorBundle.kt │ │ │ ├── executor │ │ │ ├── PostExecutionThread.kt │ │ │ └── ThreadExecutor.kt │ │ │ ├── model │ │ │ ├── BaseModel.kt │ │ │ └── UserModel.kt │ │ │ ├── repository │ │ │ └── Repository.kt │ │ │ └── usecases │ │ │ ├── DefaultObserver.kt │ │ │ ├── GetUserListUseCase.kt │ │ │ └── UseCase.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── kotlin │ └── com │ └── rantolin │ └── cleanarchitecture │ └── domain │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/android,intellij,osx 3 | 4 | ### Android ### 5 | # Built application files 6 | *.apk 7 | *.ap_ 8 | 9 | # Files for the ART/Dalvik VM 10 | *.dex 11 | 12 | # Java class files 13 | *.class 14 | 15 | # Generated files 16 | bin/ 17 | gen/ 18 | out/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # Intellij 40 | *.iml 41 | .idea 42 | 43 | # Keystore files 44 | *.jks 45 | 46 | # External native build folder generated in Android Studio 2.2 and later 47 | .externalNativeBuild 48 | 49 | # Google Services (e.g. APIs or Firebase) 50 | google-services.json 51 | 52 | # Freeline 53 | freeline.py 54 | freeline/ 55 | freeline_project_description.json 56 | 57 | ### Android Patch ### 58 | gen-external-apklibs 59 | 60 | ### Intellij ### 61 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 62 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 63 | 64 | # User-specific stuff: 65 | .idea/**/workspace.xml 66 | .idea/**/tasks.xml 67 | 68 | # Sensitive or high-churn files: 69 | .idea/**/dataSources/ 70 | .idea/**/dataSources.ids 71 | .idea/**/dataSources.xml 72 | .idea/**/dataSources.local.xml 73 | .idea/**/sqlDataSources.xml 74 | .idea/**/dynamic.xml 75 | .idea/**/uiDesigner.xml 76 | 77 | # Gradle: 78 | .idea/**/gradle.xml 79 | .idea/**/libraries 80 | 81 | # Mongo Explorer plugin: 82 | .idea/**/mongoSettings.xml 83 | 84 | ## File-based project format: 85 | *.iws 86 | 87 | ## Plugin-specific files: 88 | 89 | # IntelliJ 90 | /out/ 91 | 92 | # mpeltonen/sbt-idea plugin 93 | .idea_modules/ 94 | 95 | # JIRA plugin 96 | atlassian-ide-plugin.xml 97 | 98 | # Cursive Clojure plugin 99 | .idea/replstate.xml 100 | 101 | # Crashlytics plugin (for Android Studio and IntelliJ) 102 | com_crashlytics_export_strings.xml 103 | crashlytics.properties 104 | crashlytics-build.properties 105 | fabric.properties 106 | 107 | ### Intellij Patch ### 108 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 109 | 110 | # *.iml 111 | # modules.xml 112 | # .idea/misc.xml 113 | # *.ipr 114 | 115 | ### OSX ### 116 | *.DS_Store 117 | .AppleDouble 118 | .LSOverride 119 | 120 | # Icon must end with two \r 121 | Icon 122 | 123 | 124 | # Thumbnails 125 | ._* 126 | 127 | # Files that might appear in the root of a volume 128 | .DocumentRevisions-V100 129 | .fseventsd 130 | .Spotlight-V100 131 | .TemporaryItems 132 | .Trashes 133 | .VolumeIcon.icns 134 | .com.apple.timemachine.donotpresent 135 | 136 | # Directories potentially created on remote AFP share 137 | .AppleDB 138 | .AppleDesktop 139 | Network Trash Folder 140 | Temporary Items 141 | .apdisk 142 | 143 | # End of https://www.gitignore.io/api/android,intellij,osx -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: android 3 | jdk: 4 | - oraclejdk8 5 | env: 6 | global: 7 | - ANDROID_ABI=armeabi-v7a 8 | - API_LEVEL=25 9 | - ANDROID_TARGET=android-$API_LEVEL 10 | - ANDROID_TAG=google_apis 11 | 12 | android: 13 | components: 14 | - build-tools-25.0.2 15 | - $ANDROID_TARGET 16 | - extra-google-m2repository 17 | 18 | before_install: 19 | - mkdir "$ANDROID_HOME/licenses" || true 20 | - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license" 21 | - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license" 22 | 23 | script: 24 | - ./gradlew clean runUnitTest 25 | 26 | before_cache: 27 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 28 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 29 | 30 | cache: 31 | directories: 32 | - $HOME/.gradle/caches/ 33 | - $HOME/.gradle/wrapper/ 34 | - $HOME/.android/build-cache 35 | - $ANDROID_HOME/ 36 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android Kotlin Clean Architecture [![Build Status](https://travis-ci.org/ricardoAntolin/androidKotlinCleanArchitecture.svg?branch=master)](https://travis-ci.org/ricardoAntolin/androidKotlinCleanArchitecture) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Android--Kotlin--CleanArchitecture-green.svg?style=true)](https://android-arsenal.com/details/3/5501) 2 | ================================= 3 | 4 | Android architecture based on: 5 | ------------------------------ 6 | 7 | [Fernando Cejas Android-CleanArchitecture](https://github.com/android10/Android-CleanArchitecture) 8 | [Daniel Juarez Kotlin-CleanArchitecture](https://github.com/djuarez/Kotlin-CleanArchitecture) 9 | 10 | 11 | Library used: 12 | ------------- 13 | 14 | [Realm](https://github.com/realm/realm-java), 15 | [rxJava2](https://github.com/ReactiveX/RxJava), 16 | [rxAndroid](https://github.com/ReactiveX/RxAndroid), 17 | [Dagger2](https://github.com/google/dagger), 18 | [Glide](https://github.com/bumptech/glide), 19 | [Retrofit](https://github.com/square/retrofit) 20 | 21 | 22 | 23 | License 24 | ======= 25 | 26 | Licensed under the Apache License, Version 2.0 (the "License"); 27 | you may not use this file except in compliance with the License. 28 | You may obtain a copy of the License at 29 | 30 | http://www.apache.org/licenses/LICENSE-2.0 31 | 32 | Unless required by applicable law or agreed to in writing, software 33 | distributed under the License is distributed on an "AS IS" BASIS, 34 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 35 | See the License for the specific language governing permissions and 36 | limitations under the License. 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'realm-android' 4 | apply plugin: 'kotlin-android-extensions' 5 | 6 | android { 7 | def globalConfiguration = rootProject.ext 8 | 9 | compileSdkVersion globalConfiguration.androidCompileSdkVersion 10 | buildToolsVersion globalConfiguration.androidBuildToolsVersion 11 | 12 | defaultConfig { 13 | minSdkVersion globalConfiguration.androidMinSdkVersion 14 | targetSdkVersion globalConfiguration.androidTargetSdkVersion 15 | 16 | applicationId globalConfiguration.androidApplicationId 17 | 18 | versionCode globalConfiguration.androidVersionCode 19 | versionName globalConfiguration.androidVersionName 20 | 21 | testInstrumentationRunner globalConfiguration.testInstrumentationRunner 22 | testApplicationId globalConfiguration.testApplicationId 23 | 24 | } 25 | 26 | buildTypes { 27 | debug { 28 | minifyEnabled false 29 | signingConfig signingConfigs.debug 30 | } 31 | release { 32 | minifyEnabled false 33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 34 | } 35 | } 36 | 37 | signingConfigs { 38 | debug { 39 | storeFile file('../buildsystem/debug.keystore') 40 | storePassword 'android' 41 | keyAlias 'androiddebugkey' 42 | keyPassword 'android' 43 | } 44 | } 45 | 46 | packagingOptions { 47 | exclude 'LICENSE.txt' 48 | exclude 'META-INF/DEPENDENCIES' 49 | exclude 'META-INF/ASL2.0' 50 | exclude 'META-INF/NOTICE' 51 | exclude 'META-INF/LICENSE' 52 | } 53 | 54 | sourceSets { 55 | main.java.srcDirs += 'src/main/kotlin' 56 | test.java.srcDirs += 'src/test/kotlin' 57 | androidTest.java.srcDirs += 'src/androidTest/kotlin' 58 | } 59 | } 60 | 61 | // Required for annotation processing plugins like Dagger 62 | kapt { 63 | generateStubs = true 64 | } 65 | 66 | dependencies { 67 | def unitTestDependencies = rootProject.ext.unitTesting 68 | def acceptanceTestDependencies = rootProject.ext.acceptanceTesting 69 | def developmentDependencies = rootProject.ext.development 70 | def commonDependencies = rootProject.ext.commonDependencies 71 | def presentationDependencies = rootProject.ext.presentationDependencies 72 | def compileTimeDependencies = rootProject.ext.compileTimeDependencies 73 | 74 | for (dependency in commonDependencies) { 75 | compile dependency.value 76 | } 77 | for (dependency in presentationDependencies) { 78 | compile dependency.value 79 | } 80 | for (dependency in unitTestDependencies) { 81 | testCompile dependency.value 82 | } 83 | for (dependency in acceptanceTestDependencies) { 84 | androidTestCompile dependency.value 85 | } 86 | 87 | //Development dependencies 88 | debugCompile developmentDependencies.leakCanary 89 | releaseCompile developmentDependencies.leakCanaryNoop 90 | testCompile developmentDependencies.leakCanaryNoop 91 | 92 | //Compile Dependencies 93 | kapt compileTimeDependencies.daggerCompiler 94 | provided compileTimeDependencies.javaxAnnotation 95 | provided compileTimeDependencies.javaxInject 96 | 97 | //Module Dependencies 98 | compile project(':domain') 99 | compile project(':data') 100 | } 101 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/ricar/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/kotlin/com/rantolin/cleanarchitecture/AcceptanceTest.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture 2 | 3 | import android.app.Activity 4 | import android.support.test.espresso.intent.rule.IntentsTestRule 5 | import android.support.test.filters.LargeTest 6 | import android.support.test.rule.ActivityTestRule 7 | import android.support.test.runner.AndroidJUnit4 8 | import org.junit.Rule 9 | import org.junit.runner.RunWith 10 | 11 | 12 | @LargeTest 13 | @RunWith(AndroidJUnit4::class) 14 | abstract class AcceptanceTest(clazz: Class) { 15 | 16 | @Rule @JvmField 17 | val testRule: ActivityTestRule = IntentsTestRule(clazz) 18 | 19 | val checkThat: Matchers = Matchers() 20 | val events: Events = Events() 21 | } -------------------------------------------------------------------------------- /app/src/androidTest/kotlin/com/rantolin/cleanarchitecture/Events.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture 2 | 3 | import android.support.annotation.IdRes 4 | import android.support.test.espresso.Espresso.onView 5 | import android.support.test.espresso.action.ViewActions.click 6 | import android.support.test.espresso.matcher.ViewMatchers.withId 7 | 8 | class Events { 9 | fun clickOnView(@IdRes viewId: Int) { 10 | onView(withId(viewId)).perform(click()) 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/androidTest/kotlin/com/rantolin/cleanarchitecture/Matchers.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture 2 | 3 | import android.app.Activity 4 | import android.support.annotation.IdRes 5 | import android.support.annotation.StringRes 6 | import android.support.test.espresso.Espresso.onView 7 | import android.support.test.espresso.assertion.ViewAssertions.matches 8 | import android.support.test.espresso.intent.Intents.intended 9 | import android.support.test.espresso.intent.matcher.IntentMatchers 10 | import android.support.test.espresso.matcher.ViewMatchers.* 11 | 12 | class Matchers { 13 | fun nextOpenActivityIs(clazz: Class) { 14 | intended(IntentMatchers.hasComponent(clazz.name)) 15 | } 16 | 17 | fun viewIsVisibleAndContainsText(@StringRes stringResource: Int) { 18 | onView(withText(stringResource)).check(matches(withEffectiveVisibility(Visibility.VISIBLE))) 19 | } 20 | 21 | fun viewContainsText(@IdRes viewId: Int, @StringRes stringResource: Int) { 22 | onView(withId(viewId)).check(matches(withText(stringResource))) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/AndroidApplication.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.presentation 2 | 3 | import android.app.Application 4 | import com.rantolin.cleanarchitecture.presentation.internal.di.components.ApplicationComponent 5 | import com.rantolin.cleanarchitecture.presentation.internal.di.components.DaggerApplicationComponent 6 | import com.rantolin.cleanarchitecture.presentation.internal.di.modules.ApplicationModule 7 | import com.squareup.leakcanary.LeakCanary 8 | import io.realm.Realm 9 | import io.realm.RealmConfiguration 10 | 11 | 12 | class AndroidApplication : Application() { 13 | 14 | val component: ApplicationComponent 15 | get() = DaggerApplicationComponent.builder() 16 | .applicationModule(ApplicationModule(this)) 17 | .build() 18 | 19 | override fun onCreate() { 20 | super.onCreate() 21 | this.initializeLeakDetection() 22 | component.inject(this) 23 | initRealm() 24 | } 25 | 26 | private fun initializeLeakDetection() { 27 | if (BuildConfig.DEBUG) { 28 | LeakCanary.install(this) 29 | } 30 | } 31 | 32 | private fun initRealm() { 33 | Realm.init(this) 34 | val realmConfiguration: RealmConfiguration = RealmConfiguration.Builder() 35 | .deleteRealmIfMigrationNeeded() 36 | .build() 37 | Realm.setDefaultConfiguration(realmConfiguration) 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/UIThread.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.presentation 2 | 3 | import com.rantolin.cleanarchitecture.domain.executor.PostExecutionThread 4 | import io.reactivex.Scheduler 5 | import io.reactivex.android.schedulers.AndroidSchedulers 6 | import javax.inject.Inject 7 | import javax.inject.Singleton 8 | 9 | 10 | /** 11 | * MainThread (UI Thread) implementation based on a [Scheduler] 12 | * which will execute actions on the Android UI thread 13 | */ 14 | @Singleton 15 | class UIThread 16 | @Inject 17 | constructor() : PostExecutionThread { 18 | override fun getScheduler(): Scheduler { 19 | return AndroidSchedulers.mainThread() 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/internal/di/components/ActivityComponent.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.presentation.internal.di.components 2 | 3 | 4 | import com.rantolin.cleanarchitecture.presentation.internal.di.modules.ActivityModule 5 | import com.rantolin.cleanarchitecture.presentation.internal.di.scope.PerActivity 6 | import com.rantolin.cleanarchitecture.presentation.ui.activities.MainActivity 7 | import dagger.Component 8 | 9 | /** 10 | * A base component upon which fragment's components may depend. 11 | * Activity-level components should extend this component. 12 | * 13 | * 14 | * Subtypes of ActivityComponent should be decorated with annotation: 15 | * [PerActivity] 16 | */ 17 | @PerActivity 18 | @Component(dependencies = arrayOf(ApplicationComponent::class), modules = arrayOf(ActivityModule::class)) 19 | interface ActivityComponent { 20 | 21 | fun inject(mainActivity: MainActivity) 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/internal/di/components/ApplicationComponent.kt: -------------------------------------------------------------------------------- 1 | 2 | package com.rantolin.cleanarchitecture.presentation.internal.di.components 3 | 4 | import android.content.Context 5 | import com.rantolin.cleanarchitecture.domain.executor.PostExecutionThread 6 | import com.rantolin.cleanarchitecture.domain.executor.ThreadExecutor 7 | import com.rantolin.cleanarchitecture.domain.repository.Repository 8 | import com.rantolin.cleanarchitecture.presentation.AndroidApplication 9 | import com.rantolin.cleanarchitecture.presentation.internal.di.modules.ApplicationModule 10 | import dagger.Component 11 | import javax.inject.Singleton 12 | 13 | /** 14 | * A component whose lifetime is the life of the application. 15 | */ 16 | @Singleton // Constraints this component to one-per-application or unscoped bindings. 17 | @Component(modules = arrayOf(ApplicationModule::class)) 18 | interface ApplicationComponent { 19 | 20 | fun inject(androidApplication: AndroidApplication) 21 | 22 | val androidApplication: AndroidApplication 23 | 24 | fun context(): Context 25 | 26 | fun threadExecutor(): ThreadExecutor 27 | 28 | fun postExecutionThread(): PostExecutionThread 29 | 30 | fun repository(): Repository 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/internal/di/components/FragmentComponent.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.presentation.internal.di.components 2 | 3 | import com.rantolin.cleanarchitecture.presentation.internal.di.modules.FragmentModule 4 | import com.rantolin.cleanarchitecture.presentation.internal.di.scope.PerFragment 5 | import com.rantolin.cleanarchitecture.presentation.ui.fragments.MainFragment 6 | import dagger.Component 7 | 8 | @PerFragment 9 | @Component(dependencies = arrayOf(ApplicationComponent::class), modules = arrayOf(FragmentModule::class)) 10 | interface FragmentComponent { 11 | fun inject(mainFragment: MainFragment) 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/internal/di/modules/ActivityModule.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.presentation.internal.di.modules 2 | 3 | import android.app.Activity 4 | import com.rantolin.cleanarchitecture.presentation.internal.di.scope.PerActivity 5 | import dagger.Module 6 | import dagger.Provides 7 | 8 | /** 9 | * A module to wrap the Activity state and expose it to the graph. 10 | */ 11 | @Module 12 | class ActivityModule(private val baseActivity: Activity) { 13 | 14 | /** 15 | * Expose the activity to dependents in the graph. 16 | */ 17 | @Provides 18 | @PerActivity 19 | internal fun provideActivity(): Activity { 20 | return this.baseActivity 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/internal/di/modules/ApplicationModule.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.presentation.internal.di.modules 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import com.rantolin.cleanarchitecture.data.executor.JobExecutor 6 | import com.rantolin.cleanarchitecture.data.repository.DataRepository 7 | import com.rantolin.cleanarchitecture.domain.executor.PostExecutionThread 8 | import com.rantolin.cleanarchitecture.domain.executor.ThreadExecutor 9 | import com.rantolin.cleanarchitecture.domain.repository.Repository 10 | import com.rantolin.cleanarchitecture.presentation.AndroidApplication 11 | import com.rantolin.cleanarchitecture.presentation.UIThread 12 | import dagger.Module 13 | import dagger.Provides 14 | import javax.inject.Singleton 15 | 16 | 17 | /** 18 | * Dagger module that provides objects which will live during the application lifecycle. 19 | */ 20 | @Module 21 | class ApplicationModule(private val androidApplication: AndroidApplication) { 22 | 23 | @Provides 24 | @Singleton 25 | fun application(): AndroidApplication { 26 | return androidApplication 27 | } 28 | 29 | @Provides 30 | @Singleton 31 | fun provideApplicationContext(): Context { 32 | return androidApplication 33 | } 34 | 35 | @Provides 36 | @Singleton 37 | fun provideThreadExecutor(jobExecutor: JobExecutor): ThreadExecutor { 38 | return jobExecutor 39 | } 40 | 41 | @Provides 42 | @Singleton 43 | fun providePostExecutionThread(uiThread: UIThread): PostExecutionThread { 44 | return uiThread 45 | } 46 | 47 | @Provides 48 | @Singleton 49 | fun provideSharedPreferences(): SharedPreferences { 50 | return androidApplication.getSharedPreferences("app", Context.MODE_APPEND) 51 | } 52 | 53 | @Provides 54 | @Singleton 55 | fun provideRepository(dataRepository: DataRepository): Repository { 56 | return dataRepository 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/internal/di/modules/FragmentModule.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.presentation.internal.di.modules 2 | 3 | import android.support.v4.app.Fragment 4 | import com.rantolin.cleanarchitecture.presentation.internal.di.scope.PerFragment 5 | import dagger.Module 6 | import dagger.Provides 7 | 8 | @Module 9 | class FragmentModule(private val baseFragment: Fragment) { 10 | 11 | @Provides 12 | @PerFragment 13 | fun provideFragment(): Fragment { 14 | return this.baseFragment 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/internal/di/scope/PerActivity.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.presentation.internal.di.scope 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * A scoping annotation to permit objects whose lifetime should 7 | * conform to the life of the activity to be memorized in the 8 | * correct component. 9 | */ 10 | @Scope 11 | @Retention(AnnotationRetention.RUNTIME) 12 | annotation class PerActivity 13 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/internal/di/scope/PerFragment.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.presentation.internal.di.scope 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class PerFragment 8 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/navigation/NavigationExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.presentation.navigation 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.support.v4.app.Fragment 7 | import android.support.v4.app.FragmentActivity 8 | import android.support.v4.app.FragmentManager 9 | import com.rantolin.cleanarchitecture.presentation.ui.fragments.BaseFragment 10 | 11 | 12 | fun Activity.navigateToActivityRemovingPrevious(classToStartIntent: Class<*>) { 13 | this.navigateToActivityRemovingPrevious(classToStartIntent, null) 14 | } 15 | 16 | fun Activity.navigateToActivityRemovingPrevious(classToStartIntent: Class<*>, extras: Bundle?) { 17 | val intent = Intent(this.applicationContext, classToStartIntent) 18 | if (extras != null) { 19 | intent.putExtras(extras) 20 | } 21 | this.startActivity(intent) 22 | this.finish() 23 | } 24 | 25 | fun Activity.navigateToActivityRemovingPreviousWithExtrasClearTop( 26 | classToStartIntent: Class<*>, extras: Bundle) { 27 | this.navigateToActivityWithExtrasClearTop(classToStartIntent, extras, false, 0, 0) 28 | this.finish() 29 | } 30 | 31 | fun Activity.navigateToActivityRemovingPreviousWithExtrasClearTopAndAnimation( 32 | classToStartIntent: Class<*>, extras: Bundle, inTransition: Int, outTransition: Int) { 33 | this.navigateToActivityWithExtrasClearTop(classToStartIntent, extras, true, inTransition, outTransition) 34 | this.finish() 35 | } 36 | 37 | fun Activity.navigateToActivity(classToStartIntent: Class<*>) { 38 | this.navigateToActivity(classToStartIntent, null, false, 0, 0) 39 | } 40 | 41 | fun Activity.navigateToActivityWithAnimation(classToStartIntent: Class<*>, 42 | inTransition: Int, outTransition: Int) { 43 | this.navigateToActivity(classToStartIntent, null, true, inTransition, outTransition) 44 | } 45 | 46 | fun Activity.navigateToActivity(classToStartIntent: Class<*>, 47 | extras: Bundle?, haveTransition: Boolean, 48 | inTransition: Int, outTransition: Int) { 49 | val intent = Intent(this.applicationContext, classToStartIntent) 50 | 51 | if (extras != null) { 52 | intent.putExtras(extras) 53 | } 54 | this.startActivity(intent) 55 | if (haveTransition) { 56 | this.setActivitityTransitionAnimation(inTransition, outTransition) 57 | } 58 | } 59 | 60 | fun Activity.navigateToActivityWithExtrasClearTop(classToStartIntent: Class<*>, extras: Bundle?, 61 | haveTransition: Boolean, 62 | inTransition: Int, outTransition: Int) { 63 | val intent = Intent(this.applicationContext, classToStartIntent) 64 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) 65 | 66 | if (extras != null) { 67 | intent.putExtras(extras) 68 | } 69 | this.startActivity(intent) 70 | if (haveTransition) { 71 | this.setActivitityTransitionAnimation(inTransition, outTransition) 72 | } 73 | 74 | } 75 | 76 | fun Activity.navigateToActivityRemovingPreviousClearTop(classToStartIntent: Class<*>, 77 | haveTransition: Boolean, inTransition: Int, outTransition: Int) { 78 | val intent = Intent(this.applicationContext, classToStartIntent) 79 | intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP 80 | this.startActivity(intent) 81 | if (haveTransition) { 82 | this.setActivitityTransitionAnimation(inTransition, outTransition) 83 | } 84 | this.finish() 85 | } 86 | 87 | fun Activity.setActivitityTransitionAnimation(inTransition: Int, outTransition: Int) { 88 | this.overridePendingTransition(inTransition, outTransition) 89 | } 90 | 91 | fun FragmentActivity.isFragmentAlreadyInForeground(fragmentTag: String): Boolean { 92 | val foundFragment = this.supportFragmentManager 93 | .findFragmentByTag(fragmentTag) 94 | 95 | return foundFragment != null && foundFragment.isVisible 96 | } 97 | 98 | fun FragmentActivity.getBackStackSize(): Int { 99 | return this.supportFragmentManager.backStackEntryCount 100 | } 101 | 102 | fun Activity.navigateToActivityForResult(classToStartIntent: Class<*>, extras: Bundle?, requestCode: Int) { 103 | this.navigateToActivityForResult(classToStartIntent,extras,requestCode,false,0,0) 104 | } 105 | 106 | fun Activity.navigateToActivityForResult(classToStartIntent: Class<*>, extras: Bundle?, 107 | requestCode: Int, haveTransition: Boolean, 108 | inTransition: Int, outTransition: Int) { 109 | val intent = Intent(this.applicationContext, classToStartIntent) 110 | if (extras != null) { 111 | intent.putExtras(extras) 112 | } 113 | this.startActivityForResult(intent, requestCode) 114 | if (haveTransition) { 115 | this.setActivitityTransitionAnimation(inTransition,outTransition) 116 | } 117 | } 118 | 119 | fun FragmentActivity.navigateToFragment(fragment: Fragment, contentFrame: Int, addToBackStack: Boolean) { 120 | this.pushFragment(fragment, contentFrame, addToBackStack, 0, 0, 0, 0) 121 | } 122 | 123 | fun FragmentActivity.navigateToFragment(fragment: Fragment, contentFrame: Int, addToBackStack: Boolean, 124 | anim: Int, anim2: Int, animOut: Int, animOut2: Int) { 125 | this.pushFragment(fragment, contentFrame, addToBackStack, anim, anim2, animOut, animOut2) 126 | } 127 | 128 | fun FragmentActivity.navigateToFragment(fragment: Fragment, contentFrame: Int, addToBackStack: Boolean, anim: Int, anim2: Int) { 129 | this.pushFragment(fragment, contentFrame, addToBackStack, anim, anim2, 0, 0) 130 | } 131 | 132 | fun FragmentActivity.navigateToFragmentCleanStack(fragment: Fragment, contentFrame: Int, addToBackStack: Boolean) { 133 | this.cleanBackStack(fragment) 134 | this.pushFragment(fragment, contentFrame, addToBackStack, 0, 0, 0, 0) 135 | } 136 | 137 | fun FragmentActivity.navigateToFragmentCleanStack(fragment: Fragment, contentFrame: Int, addToBackStack: Boolean, 138 | anim: Int, anim2: Int, animOut: Int, animOut2: Int) { 139 | this.cleanBackStack(fragment) 140 | this.pushFragment(fragment, contentFrame, addToBackStack, anim, anim2, animOut, animOut2) 141 | } 142 | 143 | fun FragmentActivity.navigateToFragmentCleanStack(fragment: Fragment, contentFrame: Int,addToBackStack: Boolean, 144 | anim: Int, anim2: Int) { 145 | this.cleanBackStack(fragment) 146 | this.pushFragment(fragment, contentFrame, addToBackStack, anim, anim2, 0, 0) 147 | } 148 | 149 | fun FragmentActivity.cleanBackStack(viewStringId: String) { 150 | this.supportFragmentManager.popBackStackImmediate(viewStringId, 151 | FragmentManager.POP_BACK_STACK_INCLUSIVE) 152 | } 153 | 154 | 155 | fun FragmentActivity.cleanBackStack(fragment: Fragment) { 156 | removeCurrentFragment(fragment) 157 | this.supportFragmentManager.popBackStackImmediate(null, 158 | FragmentManager.POP_BACK_STACK_INCLUSIVE) 159 | } 160 | 161 | fun FragmentActivity.removeCurrentFragment(fragment: Fragment) { 162 | val supportFragmentManager = this.supportFragmentManager 163 | val transaction = supportFragmentManager.beginTransaction() 164 | transaction.remove(fragment) 165 | transaction.commit() 166 | } 167 | 168 | fun FragmentActivity.pushFragment(fragment: Fragment ,contentFrame: Int, addToBackStack: Boolean, 169 | anim: Int, anim2: Int, animOut: Int, animOut2: Int) { 170 | val fragmentTransaction = this.supportFragmentManager 171 | .beginTransaction() 172 | if (anim > 0 && anim2 > 0) { 173 | if (animOut > 0 && animOut2 > 0) { 174 | fragmentTransaction.setCustomAnimations(anim, anim2, animOut, animOut2) 175 | } else { 176 | fragmentTransaction.setCustomAnimations(anim, anim2) 177 | } 178 | } 179 | 180 | 181 | val tag: String? 182 | if (fragment is BaseFragment) { 183 | tag = fragment.fragmentId 184 | } else { 185 | tag = (fragment as Any).javaClass.name 186 | } 187 | 188 | fragmentTransaction.replace(contentFrame, fragment, tag) 189 | 190 | if (addToBackStack) { 191 | fragmentTransaction.addToBackStack(tag) 192 | } 193 | 194 | fragmentTransaction.commit() 195 | if (contentFrame <= 0) { 196 | this.supportFragmentManager.executePendingTransactions() 197 | } 198 | 199 | 200 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/ui/activities/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.presentation.ui.activities 2 | 3 | import android.os.Bundle 4 | import android.support.design.widget.NavigationView 5 | import android.support.design.widget.Snackbar 6 | import android.support.v4.view.GravityCompat 7 | import android.support.v7.app.ActionBarDrawerToggle 8 | import android.support.v7.app.AppCompatActivity 9 | import android.view.Menu 10 | import android.view.MenuItem 11 | import com.rantolin.cleanarchitecture.presentation.AndroidApplication 12 | import com.rantolin.cleanarchitecture.presentation.R 13 | import com.rantolin.cleanarchitecture.presentation.internal.di.components.ActivityComponent 14 | import com.rantolin.cleanarchitecture.presentation.internal.di.components.DaggerActivityComponent 15 | import com.rantolin.cleanarchitecture.presentation.internal.di.modules.ActivityModule 16 | import com.rantolin.cleanarchitecture.presentation.navigation.navigateToFragment 17 | import com.rantolin.cleanarchitecture.presentation.ui.fragments.MainFragment 18 | import kotlinx.android.synthetic.main.activity_main.* 19 | import kotlinx.android.synthetic.main.app_bar_main.* 20 | 21 | class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { 22 | 23 | private val component: ActivityComponent 24 | get() = DaggerActivityComponent.builder() 25 | .applicationComponent((application as AndroidApplication).component) 26 | .activityModule(ActivityModule(this)) 27 | .build() 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | component.inject(this) 32 | setContentView(R.layout.activity_main) 33 | setSupportActionBar(toolbar) 34 | 35 | fab.setOnClickListener { view -> 36 | Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) 37 | .setAction("Action", null).show() 38 | } 39 | 40 | val toggle = ActionBarDrawerToggle( 41 | this, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close) 42 | drawerLayout.setDrawerListener(toggle) 43 | toggle.syncState() 44 | 45 | navView.setNavigationItemSelectedListener(this) 46 | inflateMainFragment() 47 | } 48 | 49 | override fun onBackPressed() { 50 | if (drawerLayout.isDrawerOpen(GravityCompat.START)) { 51 | drawerLayout.closeDrawer(GravityCompat.START) 52 | } else { 53 | super.onBackPressed() 54 | } 55 | } 56 | 57 | fun inflateMainFragment(){ 58 | navigateToFragment(MainFragment.newInstance(null),R.id.fragmentContainer,false) 59 | } 60 | 61 | override fun onCreateOptionsMenu(menu: Menu): Boolean { 62 | // Inflate the menu; this adds items to the action bar if it is present. 63 | menuInflater.inflate(R.menu.main, menu) 64 | return true 65 | } 66 | 67 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 68 | // Handle action bar item clicks here. The action bar will 69 | // automatically handle clicks on the Home/Up button, so long 70 | // as you specify a parent activity in AndroidManifest.xml. 71 | val id = item.itemId 72 | 73 | 74 | if (id == R.id.action_settings) { 75 | return true 76 | } 77 | 78 | return super.onOptionsItemSelected(item) 79 | } 80 | 81 | override fun onNavigationItemSelected(item: MenuItem): Boolean { 82 | // Handle navigation view item clicks here. 83 | val id = item.itemId 84 | 85 | if (id == R.id.nav_camera) { 86 | // Handle the camera action 87 | } else if (id == R.id.nav_gallery) { 88 | 89 | } else if (id == R.id.nav_slideshow) { 90 | 91 | } else if (id == R.id.nav_manage) { 92 | 93 | } else if (id == R.id.nav_share) { 94 | 95 | } else if (id == R.id.nav_send) { 96 | 97 | } 98 | 99 | drawerLayout.closeDrawer(GravityCompat.START) 100 | return true 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/ui/adapters/MainFragmentViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.presentation.ui.adapters 2 | 3 | import android.support.v7.widget.RecyclerView 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import com.rantolin.cleanarchitecture.domain.model.UserModel 7 | import com.rantolin.cleanarchitecture.presentation.R 8 | import com.rantolin.cleanarchitecture.utils.inflate 9 | import com.rantolin.cleanarchitecture.utils.loadUrl 10 | import kotlinx.android.synthetic.main.item_home.view.* 11 | 12 | class MainFragmentViewAdapter(val objects: List, val listener: (UserModel) -> Unit) : 13 | RecyclerView.Adapter() { 14 | override fun getItemCount(): Int = objects.size 15 | 16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): 17 | HomeViewHolder = HomeViewHolder(parent.inflate(R.layout.item_home)) 18 | 19 | override fun onBindViewHolder(vh: HomeViewHolder, i: Int) = vh.bind(objects[i], listener) 20 | 21 | class HomeViewHolder(var itemview: View) : RecyclerView.ViewHolder(itemview) { 22 | fun bind(userModel: UserModel, listener: (UserModel) -> Unit) = with(itemview) { 23 | if (userModel.userImage != null) { 24 | cardImage.layout(0, 0, 0, 0) 25 | cardImage.loadUrl(userModel.userImage!!) 26 | } 27 | cardTitle.text = userModel.username 28 | setOnClickListener { listener(userModel) } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/ui/fragments/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.presentation.ui.fragments 2 | 3 | import android.support.v4.app.Fragment 4 | import java.math.BigInteger 5 | import java.security.SecureRandom 6 | 7 | open class BaseFragment : Fragment { 8 | var fragmentId:String? = null 9 | 10 | constructor(){ 11 | generateId() 12 | } 13 | 14 | fun generateId(){ 15 | var hash = "" 16 | this.javaClass.simpleName.map { hash = hash + BigInteger(1, SecureRandom()).toString() + it.toInt() + it} 17 | fragmentId = hash 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/ui/fragments/MainFragment.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.presentation.ui.fragments 2 | 3 | import android.animation.Animator 4 | import android.animation.AnimatorListenerAdapter 5 | import android.os.Bundle 6 | import android.support.v7.widget.RecyclerView 7 | import android.support.v7.widget.StaggeredGridLayoutManager 8 | import android.util.Log 9 | import android.view.LayoutInflater 10 | import android.view.View 11 | import android.view.ViewGroup 12 | import android.widget.Toast 13 | import com.rantolin.cleanarchitecture.domain.model.UserModel 14 | import com.rantolin.cleanarchitecture.presentation.AndroidApplication 15 | import com.rantolin.cleanarchitecture.presentation.R 16 | import com.rantolin.cleanarchitecture.presentation.internal.di.components.DaggerFragmentComponent 17 | import com.rantolin.cleanarchitecture.presentation.internal.di.components.FragmentComponent 18 | import com.rantolin.cleanarchitecture.presentation.internal.di.modules.FragmentModule 19 | import com.rantolin.cleanarchitecture.presentation.ui.adapters.MainFragmentViewAdapter 20 | import com.rantolin.cleanarchitecture.presentation.ui.presenters.MainPresenter 21 | import com.rantolin.cleanarchitecture.presentation.ui.views.MainView 22 | import kotlinx.android.synthetic.main.fragment_main.* 23 | import java.util.ArrayList 24 | import javax.inject.Inject 25 | 26 | class MainFragment: BaseFragment(), MainView { 27 | private var entryList:List = ArrayList() 28 | private val spanCount = 2 29 | private val orientation = StaggeredGridLayoutManager.VERTICAL 30 | 31 | @Inject 32 | lateinit var presenter:MainPresenter 33 | 34 | private val component: FragmentComponent 35 | get() = DaggerFragmentComponent.builder() 36 | .applicationComponent((activity.application as AndroidApplication).component) 37 | .fragmentModule(FragmentModule(this)) 38 | .build() 39 | 40 | 41 | companion object{ 42 | fun newInstance (bundle: Bundle?): MainFragment { 43 | val fragment = MainFragment() 44 | fragment.arguments = bundle 45 | return fragment 46 | } 47 | } 48 | 49 | override fun showError(throwable: Throwable) { 50 | Toast.makeText(context, "Something was wrong: ${throwable.message}", Toast.LENGTH_LONG).show() 51 | Log.d("Error", throwable.message) 52 | } 53 | 54 | override fun searchResult(entryList: List) { 55 | itemsList.adapter = MainFragmentViewAdapter(entryList) { 56 | goToUserDetails(it) 57 | } 58 | } 59 | 60 | override fun onCreate(savedInstanceState: Bundle?) { 61 | super.onCreate(savedInstanceState) 62 | 63 | } 64 | 65 | override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { 66 | val view: View? = inflater?.inflate(R.layout.fragment_main,container,false) 67 | component.inject(this) 68 | return view 69 | } 70 | 71 | override fun onViewCreated(view: View?, savedInstanceState: Bundle?) { 72 | super.onViewCreated(view, savedInstanceState) 73 | presenter.onCreate(this) 74 | val staggeredLM = StaggeredGridLayoutManager(spanCount,orientation) 75 | staggeredLM.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_NONE 76 | itemsList.setHasFixedSize(true) 77 | itemsList.isDrawingCacheEnabled = true 78 | itemsList.drawingCacheQuality = View.DRAWING_CACHE_QUALITY_HIGH; 79 | itemsList.layoutManager = staggeredLM 80 | itemsList.addOnScrollListener(object : RecyclerView.OnScrollListener() { 81 | override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { 82 | super.onScrollStateChanged(recyclerView, newState) 83 | (recyclerView.layoutManager as StaggeredGridLayoutManager).invalidateSpanAssignments() 84 | } 85 | }) 86 | 87 | } 88 | 89 | fun goToUserDetails(userModel:UserModel){ 90 | 91 | } 92 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/ui/presenters/MainPresenter.kt: -------------------------------------------------------------------------------- 1 | 2 | package com.rantolin.cleanarchitecture.presentation.ui.presenters 3 | 4 | import com.rantolin.cleanarchitecture.domain.model.UserModel 5 | import com.rantolin.cleanarchitecture.domain.usecases.DefaultObserver 6 | import com.rantolin.cleanarchitecture.domain.usecases.GetUserListUseCase 7 | 8 | import com.rantolin.cleanarchitecture.presentation.internal.di.scope.PerFragment 9 | import com.rantolin.cleanarchitecture.presentation.ui.views.MainView 10 | import javax.inject.Inject 11 | 12 | @PerFragment 13 | class MainPresenter @Inject 14 | constructor(val searchUseCase: GetUserListUseCase) { 15 | 16 | var view: MainView? = null 17 | 18 | fun onCreate(view: MainView) { 19 | this.view = view 20 | searchUseCase.execute(UserListObserver(), GetUserListUseCase.Params.createQuery("Android")) 21 | } 22 | 23 | inner class UserListObserver: DefaultObserver>() { 24 | override fun onComplete() { } 25 | 26 | override fun onNext(t: List) { this@MainPresenter.view?.searchResult(t) } 27 | 28 | override fun onError(exception: Throwable) { this@MainPresenter.view?.showError(exception) } 29 | 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/ui/views/BaseView.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.presentation.ui.views 2 | 3 | interface BaseView { 4 | fun showError(throwable: Throwable) 5 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/presentation/ui/views/MainView.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.presentation.ui.views 2 | 3 | import com.rantolin.cleanarchitecture.domain.model.UserModel 4 | 5 | 6 | interface MainView: BaseView{ 7 | fun searchResult(entryList:List) 8 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/rantolin/cleanarchitecture/utils/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.rantolin.cleanarchitecture.utils 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.ImageView 7 | import com.bumptech.glide.Glide 8 | 9 | 10 | fun ImageView.loadUrl(url:String){ 11 | Glide.with(context) 12 | .load(url) 13 | .into(this) 14 | } 15 | 16 | fun ViewGroup.inflate(layoutRes: Int): View { 17 | return LayoutInflater.from(context).inflate(layoutRes, this, false) 18 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_camera.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_gallery.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_manage.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_send.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/ic_menu_slideshow.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/app_bar_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 18 | 19 | 24 | 25 | 32 | 33 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/res/layout/nav_header_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 21 | 22 | 28 | 29 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/menu/activity_main_drawer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 13 | 17 | 21 | 22 | 23 | 24 | 25 | 29 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardoAntolin/androidKotlinCleanArchitecture/98b076b044017c4c68917b02bfff704dfba4a39c/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardoAntolin/androidKotlinCleanArchitecture/98b076b044017c4c68917b02bfff704dfba4a39c/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardoAntolin/androidKotlinCleanArchitecture/98b076b044017c4c68917b02bfff704dfba4a39c/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardoAntolin/androidKotlinCleanArchitecture/98b076b044017c4c68917b02bfff704dfba4a39c/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardoAntolin/androidKotlinCleanArchitecture/98b076b044017c4c68917b02bfff704dfba4a39c/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardoAntolin/androidKotlinCleanArchitecture/98b076b044017c4c68917b02bfff704dfba4a39c/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardoAntolin/androidKotlinCleanArchitecture/98b076b044017c4c68917b02bfff704dfba4a39c/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardoAntolin/androidKotlinCleanArchitecture/98b076b044017c4c68917b02bfff704dfba4a39c/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardoAntolin/androidKotlinCleanArchitecture/98b076b044017c4c68917b02bfff704dfba4a39c/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricardoAntolin/androidKotlinCleanArchitecture/98b076b044017c4c68917b02bfff704dfba4a39c/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 160dp 7 | 16dp 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/drawables.xml: -------------------------------------------------------------------------------- 1 | 2 | @android:drawable/ic_menu_camera 3 | @android:drawable/ic_menu_gallery 4 | @android:drawable/ic_menu_slideshow 5 | @android:drawable/ic_menu_manage 6 | @android:drawable/ic_menu_share 7 | @android:drawable/ic_menu_send 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Clean Architecture 3 | MainActivity 4 | 5 | Open navigation drawer 6 | Close navigation drawer 7 | 8 | Settings 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 |