├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── buildsystem ├── ci.gradle ├── debug.keystore └── dependencies.gradle ├── data ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── fernandocejas │ │ └── android10 │ │ └── sample │ │ └── data │ │ ├── cache │ │ ├── FileManager.java │ │ ├── UserCache.java │ │ ├── UserCacheImpl.java │ │ └── serializer │ │ │ └── Serializer.java │ │ ├── entity │ │ ├── UserEntity.java │ │ └── mapper │ │ │ ├── UserEntityDataMapper.java │ │ │ └── UserEntityJsonMapper.java │ │ ├── exception │ │ ├── NetworkConnectionException.java │ │ ├── RepositoryErrorBundle.java │ │ └── UserNotFoundException.java │ │ ├── executor │ │ └── JobExecutor.java │ │ ├── net │ │ ├── ApiConnection.java │ │ ├── RestApi.java │ │ └── RestApiImpl.java │ │ └── repository │ │ ├── UserDataRepository.java │ │ └── datasource │ │ ├── CloudUserDataStore.java │ │ ├── DiskUserDataStore.java │ │ ├── UserDataStore.java │ │ └── UserDataStoreFactory.java │ └── test │ └── java │ └── com │ └── fernandocejas │ └── android10 │ └── sample │ └── data │ ├── ApplicationStub.java │ ├── ApplicationTestCase.java │ ├── cache │ ├── FileManagerTest.java │ └── serializer │ │ └── SerializerTest.java │ ├── entity │ └── mapper │ │ ├── UserEntityDataMapperTest.java │ │ └── UserEntityJsonMapperTest.java │ ├── exception │ └── RepositoryErrorBundleTest.java │ └── repository │ ├── UserDataRepositoryTest.java │ └── datasource │ ├── CloudUserDataStoreTest.java │ ├── DiskUserDataStoreTest.java │ └── UserDataStoreFactoryTest.java ├── domain ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── fernandocejas │ │ └── android10 │ │ └── sample │ │ └── domain │ │ ├── User.java │ │ ├── exception │ │ ├── DefaultErrorBundle.java │ │ └── ErrorBundle.java │ │ ├── executor │ │ ├── PostExecutionThread.java │ │ └── ThreadExecutor.java │ │ ├── interactor │ │ ├── DefaultObserver.java │ │ ├── GetUserDetails.java │ │ ├── GetUserList.java │ │ └── UseCase.java │ │ └── repository │ │ └── UserRepository.java │ └── test │ └── java │ └── com │ └── fernandocejas │ └── android10 │ └── sample │ └── domain │ ├── UserTest.java │ ├── exception │ └── DefaultErrorBundleTest.java │ └── interactor │ ├── GetUserDetailsTest.java │ ├── GetUserListTest.java │ └── UseCaseTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── presentation ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── fernandocejas │ │ └── android10 │ │ └── sample │ │ └── test │ │ ├── exception │ │ └── ErrorMessageFactoryTest.java │ │ ├── mapper │ │ └── UserModelDataMapperTest.java │ │ ├── presenter │ │ ├── UserDetailsPresenterTest.java │ │ └── UserListPresenterTest.java │ │ └── view │ │ └── activity │ │ ├── UserDetailsActivityTest.java │ │ └── UserListActivityTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── fernandocejas │ │ └── android10 │ │ └── sample │ │ └── presentation │ │ ├── AndroidApplication.java │ │ ├── UIThread.java │ │ ├── exception │ │ └── ErrorMessageFactory.java │ │ ├── internal │ │ └── di │ │ │ ├── HasComponent.java │ │ │ ├── PerActivity.java │ │ │ ├── components │ │ │ ├── ActivityComponent.java │ │ │ ├── ApplicationComponent.java │ │ │ └── UserComponent.java │ │ │ └── modules │ │ │ ├── ActivityModule.java │ │ │ ├── ApplicationModule.java │ │ │ └── UserModule.java │ │ ├── mapper │ │ └── UserModelDataMapper.java │ │ ├── model │ │ └── UserModel.java │ │ ├── navigation │ │ └── Navigator.java │ │ ├── presenter │ │ ├── Presenter.java │ │ ├── UserDetailsPresenter.java │ │ └── UserListPresenter.java │ │ └── view │ │ ├── LoadDataView.java │ │ ├── UserDetailsView.java │ │ ├── UserListView.java │ │ ├── activity │ │ ├── BaseActivity.java │ │ ├── MainActivity.java │ │ ├── UserDetailsActivity.java │ │ └── UserListActivity.java │ │ ├── adapter │ │ ├── UsersAdapter.java │ │ └── UsersLayoutManager.java │ │ ├── component │ │ └── AutoLoadImageView.java │ │ └── fragment │ │ ├── BaseFragment.java │ │ ├── UserDetailsFragment.java │ │ └── UserListFragment.java │ └── res │ ├── drawable-hdpi │ ├── ic_launcher.png │ └── logo.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── drawable │ └── selector_item_user.xml │ ├── layout │ ├── activity_layout.xml │ ├── activity_main.xml │ ├── fragment_user_details.xml │ ├── fragment_user_list.xml │ ├── row_user.xml │ ├── view_progress.xml │ ├── view_retry.xml │ └── view_user_details.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows thumbnail db 2 | Thumbs.db 3 | 4 | # OSX files 5 | .DS_Store 6 | 7 | # built application files 8 | *.apk 9 | *.ap_ 10 | 11 | # files for the dex VM 12 | *.dex 13 | 14 | # Java class files 15 | *.class 16 | 17 | # generated files 18 | bin/ 19 | gen/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Eclipse project files 26 | .classpath 27 | .project 28 | 29 | # Android Studio 30 | .idea 31 | .gradle 32 | /*/local.properties 33 | /*/out 34 | /*/*/build 35 | build 36 | /*/*/production 37 | *.iml 38 | *.iws 39 | *.ipr 40 | *~ 41 | *.swp 42 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: oraclejdk8 3 | 4 | android: 5 | components: 6 | - tools 7 | - platform-tools 8 | - tools 9 | - build-tools-27.0.1 10 | - android-26 11 | - extra-google-m2repository 12 | - extra-android-m2repository 13 | 14 | licenses: 15 | - 'android-sdk-preview-license-.+' 16 | - 'android-sdk-license-.+' 17 | - 'google-gdk-license-.+' 18 | 19 | script: 20 | ./gradlew build 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android-CleanArchitecture 2 | ========================= 3 | 4 | ## New version available written in Kotlin: 5 | [Architecting Android… Reloaded](https://fernandocejas.com/2018/05/07/architecting-android-reloaded/) 6 | 7 | Introduction 8 | ----------------- 9 | This is a sample app that is part of a blog post I have written about how to architect android application using the Uncle Bob's clean architecture approach. 10 | 11 | [Architecting Android…The clean way?](http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/) 12 | 13 | [Architecting Android…The evolution](http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/) 14 | 15 | [Tasting Dagger 2 on Android](http://fernandocejas.com/2015/04/11/tasting-dagger-2-on-android/) 16 | 17 | [Clean Architecture…Dynamic Parameters in Use Cases](http://fernandocejas.com/2016/12/24/clean-architecture-dynamic-parameters-in-use-cases/) 18 | 19 | [Demo video of this sample](http://youtu.be/XSjV4sG3ni0) 20 | 21 | Clean architecture 22 | ----------------- 23 | ![http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/](https://github.com/android10/Sample-Data/blob/master/Android-CleanArchitecture/clean_architecture.png) 24 | 25 | Architectural approach 26 | ----------------- 27 | ![http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/](https://github.com/android10/Sample-Data/blob/master/Android-CleanArchitecture/clean_architecture_layers.png) 28 | 29 | Architectural reactive approach 30 | ----------------- 31 | ![http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/](https://github.com/android10/Sample-Data/blob/master/Android-CleanArchitecture/clean_architecture_layers_details.png) 32 | 33 | Local Development 34 | ----------------- 35 | 36 | Here are some useful Gradle/adb commands for executing this example: 37 | 38 | * `./gradlew clean build` - Build the entire example and execute unit and integration tests plus lint check. 39 | * `./gradlew installDebug` - Install the debug apk on the current connected device. 40 | * `./gradlew runUnitTests` - Execute domain and data layer tests (both unit and integration). 41 | * `./gradlew runAcceptanceTests` - Execute espresso and instrumentation acceptance tests. 42 | 43 | Discussions 44 | ----------------- 45 | 46 | Refer to the issues section: https://github.com/android10/Android-CleanArchitecture/issues 47 | 48 | 49 | Code style 50 | ----------- 51 | 52 | Here you can download and install the java codestyle. 53 | https://github.com/android10/java-code-styles 54 | 55 | 56 | License 57 | -------- 58 | 59 | Copyright 2018 Fernando Cejas 60 | 61 | Licensed under the Apache License, Version 2.0 (the "License"); 62 | you may not use this file except in compliance with the License. 63 | You may obtain a copy of the License at 64 | 65 | http://www.apache.org/licenses/LICENSE-2.0 66 | 67 | Unless required by applicable law or agreed to in writing, software 68 | distributed under the License is distributed on an "AS IS" BASIS, 69 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 70 | See the License for the specific language governing permissions and 71 | limitations under the License. 72 | 73 | 74 | ![http://www.fernandocejas.com](https://github.com/android10/Sample-Data/blob/master/android10/android10_logo_big.png) 75 | 76 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Android--CleanArchitecture-brightgreen.svg?style=flat)](https://android-arsenal.com/details/3/909) 77 | 78 | Buy Me A Coffee 79 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply from: 'buildsystem/ci.gradle' 2 | apply from: 'buildsystem/dependencies.gradle' 3 | 4 | buildscript { 5 | repositories { 6 | jcenter() 7 | mavenCentral() 8 | google() 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.0.1' 12 | // classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4' 13 | } 14 | } 15 | 16 | allprojects { 17 | ext { 18 | androidApplicationId = 'com.fernanependocejas.android10.sample.presentation' 19 | androidVersionCode = 1 20 | androidVersionName = "1.0" 21 | testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" 22 | testApplicationId = 'com.fernandocejas.android10.sample.presentation.test' 23 | } 24 | } 25 | 26 | task runDomainUnitTests(dependsOn: [':domain:test']) { 27 | description 'Run unit tests for the domain layer.' 28 | } 29 | 30 | task runDataUnitTests(dependsOn: [':data:cleanTestDebugUnitTest', ':data:testDebugUnitTest']) { 31 | description 'Run unit tests for the data layer.' 32 | } 33 | 34 | task runUnitTests(dependsOn: ['runDomainUnitTests', 'runDataUnitTests']) { 35 | description 'Run unit tests for both domain and data layers.' 36 | } 37 | 38 | task runAcceptanceTests(dependsOn: [':presentation:connectedAndroidTest']) { 39 | description 'Run application acceptance tests.' 40 | } 41 | 42 | -------------------------------------------------------------------------------- /buildsystem/ci.gradle: -------------------------------------------------------------------------------- 1 | def ciServer = 'TRAVIS' 2 | def executingOnCI = "true".equals(System.getenv(ciServer)) 3 | 4 | // Since for CI we always do full clean builds, we don't want to pre-dex 5 | // See http://tools.android.com/tech-docs/new-build-system/tips 6 | subprojects { 7 | project.plugins.whenPluginAdded { plugin -> 8 | if ('com.android.build.gradle.AppPlugin'.equals(plugin.class.name) || 9 | 'com.android.build.gradle.LibraryPlugin'.equals(plugin.class.name)) { 10 | project.android.dexOptions.preDexLibraries = !executingOnCI 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /buildsystem/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/android10/Android-CleanArchitecture/8ed4222c537e40db05e9e685bbc253fafb6b8e1f/buildsystem/debug.keystore -------------------------------------------------------------------------------- /buildsystem/dependencies.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | jcenter() 4 | } 5 | } 6 | 7 | ext { 8 | //Android 9 | androidBuildToolsVersion = "27.0.1" 10 | androidMinSdkVersion = 15 11 | androidTargetSdkVersion = 26 12 | androidCompileSdkVersion = 26 13 | 14 | //Libraries 15 | daggerVersion = '2.8' 16 | butterKnifeVersion = '7.0.1' 17 | recyclerViewVersion = '25.4.0' 18 | rxJavaVersion = '2.0.2' 19 | rxAndroidVersion = '2.0.1' 20 | javaxAnnotationVersion = '1.0' 21 | javaxInjectVersion = '1' 22 | gsonVersion = '2.3' 23 | okHttpVersion = '2.5.0' 24 | androidAnnotationsVersion = '25.4.0' 25 | arrowVersion = '1.0.0' 26 | 27 | //Testing 28 | robolectricVersion = '3.1.1' 29 | jUnitVersion = '4.12' 30 | assertJVersion = '1.7.1' 31 | mockitoVersion = '1.9.5' 32 | dexmakerVersion = '1.0' 33 | espressoVersion = '3.0.1' 34 | testingSupportLibVersion = '0.1' 35 | 36 | //Development 37 | leakCanaryVersion = '1.3.1' 38 | 39 | presentationDependencies = [ 40 | daggerCompiler: "com.google.dagger:dagger-compiler:${daggerVersion}", 41 | dagger: "com.google.dagger:dagger:${daggerVersion}", 42 | butterKnife: "com.jakewharton:butterknife:${butterKnifeVersion}", 43 | recyclerView: "com.android.support:recyclerview-v7:${recyclerViewVersion}", 44 | rxJava: "io.reactivex.rxjava2:rxjava:${rxJavaVersion}", 45 | rxAndroid: "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}", 46 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}" 47 | ] 48 | 49 | presentationTestDependencies = [ 50 | mockito: "org.mockito:mockito-core:${mockitoVersion}", 51 | dexmaker: "com.google.dexmaker:dexmaker:${dexmakerVersion}", 52 | dexmakerMockito: "com.google.dexmaker:dexmaker-mockito:${dexmakerVersion}", 53 | espresso: "com.android.support.test.espresso:espresso-core:${espressoVersion}", 54 | testingSupportLib: "com.android.support.test:testing-support-lib:${testingSupportLibVersion}", 55 | ] 56 | 57 | domainDependencies = [ 58 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}", 59 | javaxInject: "javax.inject:javax.inject:${javaxInjectVersion}", 60 | rxJava: "io.reactivex.rxjava2:rxjava:${rxJavaVersion}", 61 | arrow: "com.fernandocejas:arrow:${arrowVersion}" 62 | ] 63 | 64 | domainTestDependencies = [ 65 | junit: "junit:junit:${jUnitVersion}", 66 | mockito: "org.mockito:mockito-core:${mockitoVersion}", 67 | assertj: "org.assertj:assertj-core:${assertJVersion}" 68 | ] 69 | 70 | dataDependencies = [ 71 | daggerCompiler: "com.google.dagger:dagger-compiler:${daggerVersion}", 72 | dagger: "com.google.dagger:dagger:${daggerVersion}", 73 | okHttp: "com.squareup.okhttp:okhttp:${okHttpVersion}", 74 | gson: "com.google.code.gson:gson:${gsonVersion}", 75 | rxJava: "io.reactivex.rxjava2:rxjava:${rxJavaVersion}", 76 | rxAndroid: "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}", 77 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}", 78 | javaxInject: "javax.inject:javax.inject:${javaxInjectVersion}", 79 | androidAnnotations: "com.android.support:support-annotations:${androidAnnotationsVersion}" 80 | ] 81 | 82 | dataTestDependencies = [ 83 | junit: "junit:junit:${jUnitVersion}", 84 | assertj: "org.assertj:assertj-core:${assertJVersion}", 85 | mockito: "org.mockito:mockito-core:${mockitoVersion}", 86 | robolectric: "org.robolectric:robolectric:${robolectricVersion}", 87 | ] 88 | 89 | developmentDependencies = [ 90 | leakCanary: "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}", 91 | ] 92 | } 93 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /data/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | dependencies { 6 | classpath 'me.tatarka:gradle-retrolambda:3.7.0' 7 | } 8 | } 9 | 10 | apply plugin: 'com.android.library' 11 | //apply plugin: 'com.neenbedankt.android-apt' 12 | apply plugin: 'me.tatarka.retrolambda' 13 | 14 | android { 15 | defaultPublishConfig "debug" 16 | 17 | def globalConfiguration = rootProject.extensions.getByName("ext") 18 | 19 | compileSdkVersion globalConfiguration.getAt("androidCompileSdkVersion") 20 | buildToolsVersion globalConfiguration.getAt("androidBuildToolsVersion") 21 | 22 | defaultConfig { 23 | minSdkVersion globalConfiguration.getAt("androidMinSdkVersion") 24 | targetSdkVersion globalConfiguration.getAt("androidTargetSdkVersion") 25 | versionCode globalConfiguration.getAt("androidVersionCode") 26 | } 27 | 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | 33 | packagingOptions { 34 | exclude 'LICENSE.txt' 35 | exclude 'META-INF/DEPENDENCIES' 36 | exclude 'META-INF/ASL2.0' 37 | exclude 'META-INF/NOTICE' 38 | exclude 'META-INF/LICENSE' 39 | } 40 | 41 | lintOptions { 42 | quiet true 43 | abortOnError false 44 | ignoreWarnings true 45 | disable 'InvalidPackage' // Some libraries have issues with this 46 | disable 'OldTargetApi' // Due to Robolectric that modifies the manifest when running tests 47 | } 48 | } 49 | 50 | dependencies { 51 | def dataDependencies = rootProject.ext.dataDependencies 52 | def testDependencies = rootProject.ext.dataTestDependencies 53 | 54 | implementation project(':domain') 55 | compileOnly dataDependencies.javaxAnnotation 56 | implementation dataDependencies.javaxInject 57 | implementation dataDependencies.okHttp 58 | implementation dataDependencies.gson 59 | implementation dataDependencies.rxJava 60 | implementation dataDependencies.rxAndroid 61 | implementation dataDependencies.androidAnnotations 62 | 63 | testImplementation testDependencies.junit 64 | testImplementation testDependencies.assertj 65 | testImplementation testDependencies.mockito 66 | testImplementation testDependencies.robolectric 67 | } 68 | 69 | repositories { 70 | google() 71 | } -------------------------------------------------------------------------------- /data/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/fcejas/Software/SDKs/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 | -------------------------------------------------------------------------------- /data/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/cache/FileManager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.cache; 17 | 18 | import android.content.Context; 19 | import android.content.SharedPreferences; 20 | import java.io.BufferedReader; 21 | import java.io.File; 22 | import java.io.FileReader; 23 | import java.io.FileWriter; 24 | import java.io.IOException; 25 | import javax.inject.Inject; 26 | import javax.inject.Singleton; 27 | 28 | /** 29 | * Helper class to do operations on regular files/directories. 30 | */ 31 | @Singleton 32 | public class FileManager { 33 | 34 | @Inject 35 | FileManager() {} 36 | 37 | /** 38 | * Writes a file to Disk. 39 | * This is an I/O operation and this method executes in the main thread, so it is recommended to 40 | * perform this operation using another thread. 41 | * 42 | * @param file The file to write to Disk. 43 | */ 44 | void writeToFile(File file, String fileContent) { 45 | if (!file.exists()) { 46 | try { 47 | final FileWriter writer = new FileWriter(file); 48 | writer.write(fileContent); 49 | writer.close(); 50 | } catch (IOException e) { 51 | e.printStackTrace(); 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * Reads a content from a file. 58 | * This is an I/O operation and this method executes in the main thread, so it is recommended to 59 | * perform the operation using another thread. 60 | * 61 | * @param file The file to read from. 62 | * @return A string with the content of the file. 63 | */ 64 | String readFileContent(File file) { 65 | final StringBuilder fileContentBuilder = new StringBuilder(); 66 | if (file.exists()) { 67 | String stringLine; 68 | try { 69 | final FileReader fileReader = new FileReader(file); 70 | final BufferedReader bufferedReader = new BufferedReader(fileReader); 71 | while ((stringLine = bufferedReader.readLine()) != null) { 72 | fileContentBuilder.append(stringLine).append("\n"); 73 | } 74 | bufferedReader.close(); 75 | fileReader.close(); 76 | } catch (IOException e) { 77 | e.printStackTrace(); 78 | } 79 | } 80 | return fileContentBuilder.toString(); 81 | } 82 | 83 | /** 84 | * Returns a boolean indicating whether this file can be found on the underlying file system. 85 | * 86 | * @param file The file to check existence. 87 | * @return true if this file exists, false otherwise. 88 | */ 89 | boolean exists(File file) { 90 | return file.exists(); 91 | } 92 | 93 | /** 94 | * Warning: Deletes the content of a directory. 95 | * This is an I/O operation and this method executes in the main thread, so it is recommended to 96 | * perform the operation using another thread. 97 | * 98 | * @param directory The directory which its content will be deleted. 99 | */ 100 | boolean clearDirectory(File directory) { 101 | boolean result = false; 102 | if (directory.exists()) { 103 | for (File file : directory.listFiles()) { 104 | result = file.delete(); 105 | } 106 | } 107 | return result; 108 | } 109 | 110 | /** 111 | * Write a value to a user preferences file. 112 | * 113 | * @param context {@link android.content.Context} to retrieve android user preferences. 114 | * @param preferenceFileName A file name reprensenting where data will be written to. 115 | * @param key A string for the key that will be used to retrieve the value in the future. 116 | * @param value A long representing the value to be inserted. 117 | */ 118 | void writeToPreferences(Context context, String preferenceFileName, String key, 119 | long value) { 120 | 121 | final SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceFileName, 122 | Context.MODE_PRIVATE); 123 | final SharedPreferences.Editor editor = sharedPreferences.edit(); 124 | editor.putLong(key, value); 125 | editor.apply(); 126 | } 127 | 128 | /** 129 | * Get a value from a user preferences file. 130 | * 131 | * @param context {@link android.content.Context} to retrieve android user preferences. 132 | * @param preferenceFileName A file name representing where data will be get from. 133 | * @param key A key that will be used to retrieve the value from the preference file. 134 | * @return A long representing the value retrieved from the preferences file. 135 | */ 136 | long getFromPreferences(Context context, String preferenceFileName, String key) { 137 | final SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceFileName, 138 | Context.MODE_PRIVATE); 139 | return sharedPreferences.getLong(key, 0); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/cache/UserCache.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.cache; 17 | 18 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 19 | import io.reactivex.Observable; 20 | 21 | /** 22 | * An interface representing a user Cache. 23 | */ 24 | public interface UserCache { 25 | /** 26 | * Gets an {@link Observable} which will emit a {@link UserEntity}. 27 | * 28 | * @param userId The user id to retrieve data. 29 | */ 30 | Observable get(final int userId); 31 | 32 | /** 33 | * Puts and element into the cache. 34 | * 35 | * @param userEntity Element to insert in the cache. 36 | */ 37 | void put(UserEntity userEntity); 38 | 39 | /** 40 | * Checks if an element (User) exists in the cache. 41 | * 42 | * @param userId The id used to look for inside the cache. 43 | * @return true if the element is cached, otherwise false. 44 | */ 45 | boolean isCached(final int userId); 46 | 47 | /** 48 | * Checks if the cache is expired. 49 | * 50 | * @return true, the cache is expired, otherwise false. 51 | */ 52 | boolean isExpired(); 53 | 54 | /** 55 | * Evict all elements of the cache. 56 | */ 57 | void evictAll(); 58 | } 59 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/cache/serializer/Serializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.cache.serializer; 17 | 18 | import com.google.gson.Gson; 19 | import javax.inject.Inject; 20 | import javax.inject.Singleton; 21 | 22 | /** 23 | * Json Serializer/Deserializer. 24 | */ 25 | @Singleton 26 | public class Serializer { 27 | 28 | private final Gson gson = new Gson(); 29 | 30 | @Inject Serializer() {} 31 | 32 | /** 33 | * Serialize an object to Json. 34 | * 35 | * @param object to serialize. 36 | */ 37 | public String serialize(Object object, Class clazz) { 38 | return gson.toJson(object, clazz); 39 | } 40 | 41 | /** 42 | * Deserialize a json representation of an object. 43 | * 44 | * @param string A json string to deserialize. 45 | */ 46 | public T deserialize(String string, Class clazz) { 47 | return gson.fromJson(string, clazz); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/entity/UserEntity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.entity; 17 | 18 | import com.google.gson.annotations.SerializedName; 19 | 20 | /** 21 | * User Entity used in the data layer. 22 | */ 23 | public class UserEntity { 24 | 25 | @SerializedName("id") 26 | private int userId; 27 | 28 | @SerializedName("cover_url") 29 | private String coverUrl; 30 | 31 | @SerializedName("full_name") 32 | private String fullname; 33 | 34 | @SerializedName("description") 35 | private String description; 36 | 37 | @SerializedName("followers") 38 | private int followers; 39 | 40 | @SerializedName("email") 41 | private String email; 42 | 43 | public UserEntity() { 44 | //empty 45 | } 46 | 47 | public int getUserId() { 48 | return userId; 49 | } 50 | 51 | public void setUserId(int userId) { 52 | this.userId = userId; 53 | } 54 | 55 | public String getCoverUrl() { 56 | return coverUrl; 57 | } 58 | 59 | public String getFullname() { 60 | return fullname; 61 | } 62 | 63 | public void setFullname(String fullname) { 64 | this.fullname = fullname; 65 | } 66 | 67 | public String getDescription() { 68 | return description; 69 | } 70 | 71 | public int getFollowers() { 72 | return followers; 73 | } 74 | 75 | public String getEmail() { 76 | return email; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityDataMapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.entity.mapper; 17 | 18 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 19 | import com.fernandocejas.android10.sample.domain.User; 20 | import java.util.ArrayList; 21 | import java.util.Collection; 22 | import java.util.List; 23 | import javax.inject.Inject; 24 | import javax.inject.Singleton; 25 | 26 | /** 27 | * Mapper class used to transform {@link UserEntity} (in the data layer) to {@link User} in the 28 | * domain layer. 29 | */ 30 | @Singleton 31 | public class UserEntityDataMapper { 32 | 33 | @Inject 34 | UserEntityDataMapper() {} 35 | 36 | /** 37 | * Transform a {@link UserEntity} into an {@link User}. 38 | * 39 | * @param userEntity Object to be transformed. 40 | * @return {@link User} if valid {@link UserEntity} otherwise null. 41 | */ 42 | public User transform(UserEntity userEntity) { 43 | User user = null; 44 | if (userEntity != null) { 45 | user = new User(userEntity.getUserId()); 46 | user.setCoverUrl(userEntity.getCoverUrl()); 47 | user.setFullName(userEntity.getFullname()); 48 | user.setDescription(userEntity.getDescription()); 49 | user.setFollowers(userEntity.getFollowers()); 50 | user.setEmail(userEntity.getEmail()); 51 | } 52 | return user; 53 | } 54 | 55 | /** 56 | * Transform a List of {@link UserEntity} into a Collection of {@link User}. 57 | * 58 | * @param userEntityCollection Object Collection to be transformed. 59 | * @return {@link User} if valid {@link UserEntity} otherwise null. 60 | */ 61 | public List transform(Collection userEntityCollection) { 62 | final List userList = new ArrayList<>(20); 63 | for (UserEntity userEntity : userEntityCollection) { 64 | final User user = transform(userEntity); 65 | if (user != null) { 66 | userList.add(user); 67 | } 68 | } 69 | return userList; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityJsonMapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.entity.mapper; 17 | 18 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 19 | import com.google.gson.Gson; 20 | import com.google.gson.JsonSyntaxException; 21 | import com.google.gson.reflect.TypeToken; 22 | import java.lang.reflect.Type; 23 | import java.util.List; 24 | import javax.inject.Inject; 25 | 26 | /** 27 | * Class used to transform from Strings representing json to valid objects. 28 | */ 29 | public class UserEntityJsonMapper { 30 | 31 | private final Gson gson; 32 | 33 | @Inject 34 | public UserEntityJsonMapper() { 35 | this.gson = new Gson(); 36 | } 37 | 38 | /** 39 | * Transform from valid json string to {@link UserEntity}. 40 | * 41 | * @param userJsonResponse A json representing a user profile. 42 | * @return {@link UserEntity}. 43 | * @throws com.google.gson.JsonSyntaxException if the json string is not a valid json structure. 44 | */ 45 | public UserEntity transformUserEntity(String userJsonResponse) throws JsonSyntaxException { 46 | final Type userEntityType = new TypeToken() {}.getType(); 47 | return this.gson.fromJson(userJsonResponse, userEntityType); 48 | } 49 | 50 | /** 51 | * Transform from valid json string to List of {@link UserEntity}. 52 | * 53 | * @param userListJsonResponse A json representing a collection of users. 54 | * @return List of {@link UserEntity}. 55 | * @throws com.google.gson.JsonSyntaxException if the json string is not a valid json structure. 56 | */ 57 | public List transformUserEntityCollection(String userListJsonResponse) 58 | throws JsonSyntaxException { 59 | final Type listOfUserEntityType = new TypeToken>() {}.getType(); 60 | return this.gson.fromJson(userListJsonResponse, listOfUserEntityType); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/exception/NetworkConnectionException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.exception; 17 | 18 | /** 19 | * Exception throw by the application when a there is a network connection exception. 20 | */ 21 | public class NetworkConnectionException extends Exception { 22 | 23 | public NetworkConnectionException() { 24 | super(); 25 | } 26 | 27 | public NetworkConnectionException(final Throwable cause) { 28 | super(cause); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/exception/RepositoryErrorBundle.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.exception; 17 | 18 | import com.fernandocejas.android10.sample.domain.exception.ErrorBundle; 19 | 20 | /** 21 | * Wrapper around Exceptions used to manage errors in the repository. 22 | */ 23 | class RepositoryErrorBundle implements ErrorBundle { 24 | 25 | private final Exception exception; 26 | 27 | RepositoryErrorBundle(Exception exception) { 28 | this.exception = exception; 29 | } 30 | 31 | @Override 32 | public Exception getException() { 33 | return exception; 34 | } 35 | 36 | @Override 37 | public String getErrorMessage() { 38 | String message = ""; 39 | if (this.exception != null) { 40 | message = this.exception.getMessage(); 41 | } 42 | return message; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/exception/UserNotFoundException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.exception; 17 | 18 | /** 19 | * Exception throw by the application when a User search can't return a valid result. 20 | */ 21 | public class UserNotFoundException extends Exception { 22 | public UserNotFoundException() { 23 | super(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/executor/JobExecutor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.executor; 17 | 18 | import android.support.annotation.NonNull; 19 | import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; 20 | import java.util.concurrent.LinkedBlockingQueue; 21 | import java.util.concurrent.ThreadFactory; 22 | import java.util.concurrent.ThreadPoolExecutor; 23 | import java.util.concurrent.TimeUnit; 24 | import javax.inject.Inject; 25 | import javax.inject.Singleton; 26 | 27 | /** 28 | * Decorated {@link java.util.concurrent.ThreadPoolExecutor} 29 | */ 30 | @Singleton 31 | public class JobExecutor implements ThreadExecutor { 32 | private final ThreadPoolExecutor threadPoolExecutor; 33 | 34 | @Inject 35 | JobExecutor() { 36 | this.threadPoolExecutor = new ThreadPoolExecutor(3, 5, 10, TimeUnit.SECONDS, 37 | new LinkedBlockingQueue<>(), new JobThreadFactory()); 38 | } 39 | 40 | @Override public void execute(@NonNull Runnable runnable) { 41 | this.threadPoolExecutor.execute(runnable); 42 | } 43 | 44 | private static class JobThreadFactory implements ThreadFactory { 45 | private int counter = 0; 46 | 47 | @Override public Thread newThread(@NonNull Runnable runnable) { 48 | return new Thread(runnable, "android_" + counter++); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/net/ApiConnection.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.net; 17 | 18 | import android.support.annotation.Nullable; 19 | import com.squareup.okhttp.OkHttpClient; 20 | import com.squareup.okhttp.Request; 21 | import java.io.IOException; 22 | import java.net.MalformedURLException; 23 | import java.net.URL; 24 | import java.util.concurrent.Callable; 25 | import java.util.concurrent.TimeUnit; 26 | 27 | /** 28 | * Api Connection class used to retrieve data from the cloud. 29 | * Implements {@link java.util.concurrent.Callable} so when executed asynchronously can 30 | * return a value. 31 | */ 32 | class ApiConnection implements Callable { 33 | 34 | private static final String CONTENT_TYPE_LABEL = "Content-Type"; 35 | private static final String CONTENT_TYPE_VALUE_JSON = "application/json; charset=utf-8"; 36 | 37 | private URL url; 38 | private String response; 39 | 40 | private ApiConnection(String url) throws MalformedURLException { 41 | this.url = new URL(url); 42 | } 43 | 44 | static ApiConnection createGET(String url) throws MalformedURLException { 45 | return new ApiConnection(url); 46 | } 47 | 48 | /** 49 | * Do a request to an api synchronously. 50 | * It should not be executed in the main thread of the application. 51 | * 52 | * @return A string response 53 | */ 54 | @Nullable 55 | String requestSyncCall() { 56 | connectToApi(); 57 | return response; 58 | } 59 | 60 | private void connectToApi() { 61 | OkHttpClient okHttpClient = this.createClient(); 62 | final Request request = new Request.Builder() 63 | .url(this.url) 64 | .addHeader(CONTENT_TYPE_LABEL, CONTENT_TYPE_VALUE_JSON) 65 | .get() 66 | .build(); 67 | 68 | try { 69 | this.response = okHttpClient.newCall(request).execute().body().string(); 70 | } catch (IOException e) { 71 | e.printStackTrace(); 72 | } 73 | } 74 | 75 | private OkHttpClient createClient() { 76 | final OkHttpClient okHttpClient = new OkHttpClient(); 77 | okHttpClient.setReadTimeout(10000, TimeUnit.MILLISECONDS); 78 | okHttpClient.setConnectTimeout(15000, TimeUnit.MILLISECONDS); 79 | 80 | return okHttpClient; 81 | } 82 | 83 | @Override public String call() throws Exception { 84 | return requestSyncCall(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/net/RestApi.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.net; 17 | 18 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 19 | import io.reactivex.Observable; 20 | import java.util.List; 21 | 22 | /** 23 | * RestApi for retrieving data from the network. 24 | */ 25 | public interface RestApi { 26 | String API_BASE_URL = 27 | "https://raw.githubusercontent.com/android10/Sample-Data/master/Android-CleanArchitecture/"; 28 | 29 | /** Api url for getting all users */ 30 | String API_URL_GET_USER_LIST = API_BASE_URL + "users.json"; 31 | /** Api url for getting a user profile: Remember to concatenate id + 'json' */ 32 | String API_URL_GET_USER_DETAILS = API_BASE_URL + "user_"; 33 | 34 | /** 35 | * Retrieves an {@link Observable} which will emit a List of {@link UserEntity}. 36 | */ 37 | Observable> userEntityList(); 38 | 39 | /** 40 | * Retrieves an {@link Observable} which will emit a {@link UserEntity}. 41 | * 42 | * @param userId The user id used to get user data. 43 | */ 44 | Observable userEntityById(final int userId); 45 | } 46 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/net/RestApiImpl.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.net; 17 | 18 | import android.content.Context; 19 | import android.net.ConnectivityManager; 20 | import android.net.NetworkInfo; 21 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 22 | import com.fernandocejas.android10.sample.data.entity.mapper.UserEntityJsonMapper; 23 | import com.fernandocejas.android10.sample.data.exception.NetworkConnectionException; 24 | import io.reactivex.Observable; 25 | import java.net.MalformedURLException; 26 | import java.util.List; 27 | 28 | /** 29 | * {@link RestApi} implementation for retrieving data from the network. 30 | */ 31 | public class RestApiImpl implements RestApi { 32 | 33 | private final Context context; 34 | private final UserEntityJsonMapper userEntityJsonMapper; 35 | 36 | /** 37 | * Constructor of the class 38 | * 39 | * @param context {@link android.content.Context}. 40 | * @param userEntityJsonMapper {@link UserEntityJsonMapper}. 41 | */ 42 | public RestApiImpl(Context context, UserEntityJsonMapper userEntityJsonMapper) { 43 | if (context == null || userEntityJsonMapper == null) { 44 | throw new IllegalArgumentException("The constructor parameters cannot be null!!!"); 45 | } 46 | this.context = context.getApplicationContext(); 47 | this.userEntityJsonMapper = userEntityJsonMapper; 48 | } 49 | 50 | @Override public Observable> userEntityList() { 51 | return Observable.create(emitter -> { 52 | if (isThereInternetConnection()) { 53 | try { 54 | String responseUserEntities = getUserEntitiesFromApi(); 55 | if (responseUserEntities != null) { 56 | emitter.onNext(userEntityJsonMapper.transformUserEntityCollection( 57 | responseUserEntities)); 58 | emitter.onComplete(); 59 | } else { 60 | emitter.onError(new NetworkConnectionException()); 61 | } 62 | } catch (Exception e) { 63 | emitter.onError(new NetworkConnectionException(e.getCause())); 64 | } 65 | } else { 66 | emitter.onError(new NetworkConnectionException()); 67 | } 68 | }); 69 | } 70 | 71 | @Override public Observable userEntityById(final int userId) { 72 | return Observable.create(emitter -> { 73 | if (isThereInternetConnection()) { 74 | try { 75 | String responseUserDetails = getUserDetailsFromApi(userId); 76 | if (responseUserDetails != null) { 77 | emitter.onNext(userEntityJsonMapper.transformUserEntity(responseUserDetails)); 78 | emitter.onComplete(); 79 | } else { 80 | emitter.onError(new NetworkConnectionException()); 81 | } 82 | } catch (Exception e) { 83 | emitter.onError(new NetworkConnectionException(e.getCause())); 84 | } 85 | } else { 86 | emitter.onError(new NetworkConnectionException()); 87 | } 88 | }); 89 | } 90 | 91 | private String getUserEntitiesFromApi() throws MalformedURLException { 92 | return ApiConnection.createGET(API_URL_GET_USER_LIST).requestSyncCall(); 93 | } 94 | 95 | private String getUserDetailsFromApi(int userId) throws MalformedURLException { 96 | String apiUrl = API_URL_GET_USER_DETAILS + userId + ".json"; 97 | return ApiConnection.createGET(apiUrl).requestSyncCall(); 98 | } 99 | 100 | /** 101 | * Checks if the device has any active internet connection. 102 | * 103 | * @return true device with internet connection, otherwise false. 104 | */ 105 | private boolean isThereInternetConnection() { 106 | boolean isConnected; 107 | 108 | ConnectivityManager connectivityManager = 109 | (ConnectivityManager) this.context.getSystemService(Context.CONNECTIVITY_SERVICE); 110 | NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); 111 | isConnected = (networkInfo != null && networkInfo.isConnectedOrConnecting()); 112 | 113 | return isConnected; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/repository/UserDataRepository.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.repository; 17 | 18 | import com.fernandocejas.android10.sample.data.entity.mapper.UserEntityDataMapper; 19 | import com.fernandocejas.android10.sample.data.repository.datasource.UserDataStore; 20 | import com.fernandocejas.android10.sample.data.repository.datasource.UserDataStoreFactory; 21 | import com.fernandocejas.android10.sample.domain.User; 22 | import com.fernandocejas.android10.sample.domain.repository.UserRepository; 23 | import io.reactivex.Observable; 24 | import java.util.List; 25 | import javax.inject.Inject; 26 | import javax.inject.Singleton; 27 | 28 | /** 29 | * {@link UserRepository} for retrieving user data. 30 | */ 31 | @Singleton 32 | public class UserDataRepository implements UserRepository { 33 | 34 | private final UserDataStoreFactory userDataStoreFactory; 35 | private final UserEntityDataMapper userEntityDataMapper; 36 | 37 | /** 38 | * Constructs a {@link UserRepository}. 39 | * 40 | * @param dataStoreFactory A factory to construct different data source implementations. 41 | * @param userEntityDataMapper {@link UserEntityDataMapper}. 42 | */ 43 | @Inject 44 | UserDataRepository(UserDataStoreFactory dataStoreFactory, 45 | UserEntityDataMapper userEntityDataMapper) { 46 | this.userDataStoreFactory = dataStoreFactory; 47 | this.userEntityDataMapper = userEntityDataMapper; 48 | } 49 | 50 | @Override public Observable> users() { 51 | //we always get all users from the cloud 52 | final UserDataStore userDataStore = this.userDataStoreFactory.createCloudDataStore(); 53 | return userDataStore.userEntityList().map(this.userEntityDataMapper::transform); 54 | } 55 | 56 | @Override public Observable user(int userId) { 57 | final UserDataStore userDataStore = this.userDataStoreFactory.create(userId); 58 | return userDataStore.userEntityDetails(userId).map(this.userEntityDataMapper::transform); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/CloudUserDataStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.repository.datasource; 17 | 18 | import com.fernandocejas.android10.sample.data.cache.UserCache; 19 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 20 | import com.fernandocejas.android10.sample.data.net.RestApi; 21 | import io.reactivex.Observable; 22 | import java.util.List; 23 | 24 | /** 25 | * {@link UserDataStore} implementation based on connections to the api (Cloud). 26 | */ 27 | class CloudUserDataStore implements UserDataStore { 28 | 29 | private final RestApi restApi; 30 | private final UserCache userCache; 31 | 32 | /** 33 | * Construct a {@link UserDataStore} based on connections to the api (Cloud). 34 | * 35 | * @param restApi The {@link RestApi} implementation to use. 36 | * @param userCache A {@link UserCache} to cache data retrieved from the api. 37 | */ 38 | CloudUserDataStore(RestApi restApi, UserCache userCache) { 39 | this.restApi = restApi; 40 | this.userCache = userCache; 41 | } 42 | 43 | @Override public Observable> userEntityList() { 44 | return this.restApi.userEntityList(); 45 | } 46 | 47 | @Override public Observable userEntityDetails(final int userId) { 48 | return this.restApi.userEntityById(userId).doOnNext(CloudUserDataStore.this.userCache::put); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/DiskUserDataStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.repository.datasource; 17 | 18 | import com.fernandocejas.android10.sample.data.cache.UserCache; 19 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 20 | import io.reactivex.Observable; 21 | import java.util.List; 22 | 23 | /** 24 | * {@link UserDataStore} implementation based on file system data store. 25 | */ 26 | class DiskUserDataStore implements UserDataStore { 27 | 28 | private final UserCache userCache; 29 | 30 | /** 31 | * Construct a {@link UserDataStore} based file system data store. 32 | * 33 | * @param userCache A {@link UserCache} to cache data retrieved from the api. 34 | */ 35 | DiskUserDataStore(UserCache userCache) { 36 | this.userCache = userCache; 37 | } 38 | 39 | @Override public Observable> userEntityList() { 40 | //TODO: implement simple cache for storing/retrieving collections of users. 41 | throw new UnsupportedOperationException("Operation is not available!!!"); 42 | } 43 | 44 | @Override public Observable userEntityDetails(final int userId) { 45 | return this.userCache.get(userId); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.repository.datasource; 17 | 18 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 19 | import io.reactivex.Observable; 20 | import java.util.List; 21 | 22 | /** 23 | * Interface that represents a data store from where data is retrieved. 24 | */ 25 | public interface UserDataStore { 26 | /** 27 | * Get an {@link Observable} which will emit a List of {@link UserEntity}. 28 | */ 29 | Observable> userEntityList(); 30 | 31 | /** 32 | * Get an {@link Observable} which will emit a {@link UserEntity} by its id. 33 | * 34 | * @param userId The id to retrieve user data. 35 | */ 36 | Observable userEntityDetails(final int userId); 37 | } 38 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStoreFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.repository.datasource; 17 | 18 | import android.content.Context; 19 | import android.support.annotation.NonNull; 20 | import com.fernandocejas.android10.sample.data.cache.UserCache; 21 | import com.fernandocejas.android10.sample.data.entity.mapper.UserEntityJsonMapper; 22 | import com.fernandocejas.android10.sample.data.net.RestApi; 23 | import com.fernandocejas.android10.sample.data.net.RestApiImpl; 24 | import javax.inject.Inject; 25 | import javax.inject.Singleton; 26 | 27 | /** 28 | * Factory that creates different implementations of {@link UserDataStore}. 29 | */ 30 | @Singleton 31 | public class UserDataStoreFactory { 32 | 33 | private final Context context; 34 | private final UserCache userCache; 35 | 36 | @Inject 37 | UserDataStoreFactory(@NonNull Context context, @NonNull UserCache userCache) { 38 | this.context = context.getApplicationContext(); 39 | this.userCache = userCache; 40 | } 41 | 42 | /** 43 | * Create {@link UserDataStore} from a user id. 44 | */ 45 | public UserDataStore create(int userId) { 46 | UserDataStore userDataStore; 47 | 48 | if (!this.userCache.isExpired() && this.userCache.isCached(userId)) { 49 | userDataStore = new DiskUserDataStore(this.userCache); 50 | } else { 51 | userDataStore = createCloudDataStore(); 52 | } 53 | 54 | return userDataStore; 55 | } 56 | 57 | /** 58 | * Create {@link UserDataStore} to retrieve data from the Cloud. 59 | */ 60 | public UserDataStore createCloudDataStore() { 61 | final UserEntityJsonMapper userEntityJsonMapper = new UserEntityJsonMapper(); 62 | final RestApi restApi = new RestApiImpl(this.context, userEntityJsonMapper); 63 | 64 | return new CloudUserDataStore(restApi, this.userCache); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /data/src/test/java/com/fernandocejas/android10/sample/data/ApplicationStub.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 android10.org Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data; 17 | 18 | import android.app.Application; 19 | 20 | public class ApplicationStub extends Application {} 21 | -------------------------------------------------------------------------------- /data/src/test/java/com/fernandocejas/android10/sample/data/ApplicationTestCase.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data; 17 | 18 | import android.content.Context; 19 | import java.io.File; 20 | import org.junit.Rule; 21 | import org.junit.rules.TestRule; 22 | import org.junit.runner.RunWith; 23 | import org.mockito.MockitoAnnotations; 24 | import org.robolectric.RobolectricTestRunner; 25 | import org.robolectric.RuntimeEnvironment; 26 | import org.robolectric.annotation.Config; 27 | 28 | /** 29 | * Base class for Robolectric data layer tests. 30 | * Inherit from this class to create a test. 31 | */ 32 | @RunWith(RobolectricTestRunner.class) 33 | @Config(constants = BuildConfig.class, application = ApplicationStub.class, sdk = 21) 34 | public abstract class ApplicationTestCase { 35 | 36 | @Rule public TestRule injectMocksRule = (base, description) -> { 37 | MockitoAnnotations.initMocks(ApplicationTestCase.this); 38 | return base; 39 | }; 40 | 41 | public static Context context() { 42 | return RuntimeEnvironment.application; 43 | } 44 | 45 | public static File cacheDir() { 46 | return RuntimeEnvironment.application.getCacheDir(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /data/src/test/java/com/fernandocejas/android10/sample/data/cache/FileManagerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.cache; 17 | 18 | import com.fernandocejas.android10.sample.data.ApplicationTestCase; 19 | import java.io.File; 20 | import org.junit.After; 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | 24 | import static org.hamcrest.CoreMatchers.equalTo; 25 | import static org.hamcrest.CoreMatchers.is; 26 | import static org.hamcrest.MatcherAssert.assertThat; 27 | 28 | public class FileManagerTest extends ApplicationTestCase { 29 | 30 | private FileManager fileManager; 31 | 32 | @Before 33 | public void setUp() { 34 | fileManager = new FileManager(); 35 | } 36 | 37 | @After 38 | public void tearDown() { 39 | if (cacheDir() != null) { 40 | fileManager.clearDirectory(cacheDir()); 41 | } 42 | } 43 | 44 | @Test 45 | public void testWriteToFile() { 46 | File fileToWrite = createDummyFile(); 47 | String fileContent = "content"; 48 | 49 | fileManager.writeToFile(fileToWrite, fileContent); 50 | 51 | assertThat(fileToWrite.exists(), is(true)); 52 | } 53 | 54 | @Test 55 | public void testFileContent() { 56 | File fileToWrite = createDummyFile(); 57 | String fileContent = "content\n"; 58 | 59 | fileManager.writeToFile(fileToWrite, fileContent); 60 | String expectedContent = fileManager.readFileContent(fileToWrite); 61 | 62 | assertThat(expectedContent, is(equalTo(fileContent))); 63 | } 64 | 65 | private File createDummyFile() { 66 | String dummyFilePath = cacheDir().getPath() + File.separator + "dummyFile"; 67 | return new File(dummyFilePath); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /data/src/test/java/com/fernandocejas/android10/sample/data/cache/serializer/SerializerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.cache.serializer; 17 | 18 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 19 | import org.junit.Before; 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.mockito.runners.MockitoJUnitRunner; 23 | 24 | import static org.hamcrest.CoreMatchers.equalTo; 25 | import static org.hamcrest.CoreMatchers.is; 26 | import static org.junit.Assert.assertThat; 27 | 28 | @RunWith(MockitoJUnitRunner.class) 29 | public class SerializerTest { 30 | 31 | private static final String JSON_RESPONSE = "{\n" 32 | + " \"id\": 1,\n" 33 | + " \"cover_url\": \"http://www.android10.org/myapi/cover_1.jpg\",\n" 34 | + " \"full_name\": \"Simon Hill\",\n" 35 | + " \"description\": \"Curabitur gravida nisi at nibh. In hac habitasse platea dictumst. Aliquam augue quam, sollicitudin vitae, consectetuer eget, rutrum at, lorem.\\n\\nInteger tincidunt ante vel ipsum. Praesent blandit lacinia erat. Vestibulum sed magna at nunc commodo placerat.\\n\\nPraesent blandit. Nam nulla. Integer pede justo, lacinia eget, tincidunt eget, tempus vel, pede.\",\n" 36 | + " \"followers\": 7484,\n" 37 | + " \"email\": \"jcooper@babbleset.edu\"\n" 38 | + "}"; 39 | 40 | private Serializer serializer; 41 | 42 | @Before 43 | public void setUp() { 44 | serializer = new Serializer(); 45 | } 46 | 47 | @Test 48 | public void testSerializeHappyCase() { 49 | final UserEntity userEntityOne = serializer.deserialize(JSON_RESPONSE, UserEntity.class); 50 | final String jsonString = serializer.serialize(userEntityOne, UserEntity.class); 51 | final UserEntity userEntityTwo = serializer.deserialize(jsonString, UserEntity.class); 52 | 53 | assertThat(userEntityOne.getUserId(), is(userEntityTwo.getUserId())); 54 | assertThat(userEntityOne.getFullname(), is(equalTo(userEntityTwo.getFullname()))); 55 | assertThat(userEntityOne.getFollowers(), is(userEntityTwo.getFollowers())); 56 | } 57 | 58 | @Test 59 | public void testDesearializeHappyCase() { 60 | final UserEntity userEntity = serializer.deserialize(JSON_RESPONSE, UserEntity.class); 61 | 62 | assertThat(userEntity.getUserId(), is(1)); 63 | assertThat(userEntity.getFullname(), is("Simon Hill")); 64 | assertThat(userEntity.getFollowers(), is(7484)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /data/src/test/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityDataMapperTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.entity.mapper; 17 | 18 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 19 | import com.fernandocejas.android10.sample.domain.User; 20 | import java.util.ArrayList; 21 | import java.util.Collection; 22 | import java.util.List; 23 | import org.junit.Before; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.mockito.runners.MockitoJUnitRunner; 27 | 28 | import static org.hamcrest.CoreMatchers.instanceOf; 29 | import static org.hamcrest.CoreMatchers.is; 30 | import static org.hamcrest.MatcherAssert.assertThat; 31 | import static org.mockito.Mockito.mock; 32 | 33 | @RunWith(MockitoJUnitRunner.class) 34 | public class UserEntityDataMapperTest { 35 | 36 | private static final int FAKE_USER_ID = 123; 37 | private static final String FAKE_FULLNAME = "Tony Stark"; 38 | 39 | private UserEntityDataMapper userEntityDataMapper; 40 | 41 | @Before 42 | public void setUp() throws Exception { 43 | userEntityDataMapper = new UserEntityDataMapper(); 44 | } 45 | 46 | @Test 47 | public void testTransformUserEntity() { 48 | UserEntity userEntity = createFakeUserEntity(); 49 | User user = userEntityDataMapper.transform(userEntity); 50 | 51 | assertThat(user, is(instanceOf(User.class))); 52 | assertThat(user.getUserId(), is(FAKE_USER_ID)); 53 | assertThat(user.getFullName(), is(FAKE_FULLNAME)); 54 | } 55 | 56 | @Test 57 | public void testTransformUserEntityCollection() { 58 | UserEntity mockUserEntityOne = mock(UserEntity.class); 59 | UserEntity mockUserEntityTwo = mock(UserEntity.class); 60 | 61 | List userEntityList = new ArrayList(5); 62 | userEntityList.add(mockUserEntityOne); 63 | userEntityList.add(mockUserEntityTwo); 64 | 65 | Collection userCollection = userEntityDataMapper.transform(userEntityList); 66 | 67 | assertThat(userCollection.toArray()[0], is(instanceOf(User.class))); 68 | assertThat(userCollection.toArray()[1], is(instanceOf(User.class))); 69 | assertThat(userCollection.size(), is(2)); 70 | } 71 | 72 | private UserEntity createFakeUserEntity() { 73 | UserEntity userEntity = new UserEntity(); 74 | userEntity.setUserId(FAKE_USER_ID); 75 | userEntity.setFullname(FAKE_FULLNAME); 76 | 77 | return userEntity; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /data/src/test/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityJsonMapperTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.entity.mapper; 17 | 18 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 19 | import com.google.gson.JsonSyntaxException; 20 | import java.util.Collection; 21 | import org.junit.Before; 22 | import org.junit.Rule; 23 | import org.junit.Test; 24 | import org.junit.rules.ExpectedException; 25 | import org.junit.runner.RunWith; 26 | import org.mockito.runners.MockitoJUnitRunner; 27 | 28 | import static org.hamcrest.CoreMatchers.equalTo; 29 | import static org.hamcrest.CoreMatchers.is; 30 | import static org.hamcrest.MatcherAssert.assertThat; 31 | 32 | @RunWith(MockitoJUnitRunner.class) 33 | public class UserEntityJsonMapperTest { 34 | 35 | private static final String JSON_RESPONSE_USER_DETAILS = "{\n" 36 | + " \"id\": 1,\n" 37 | + " \"cover_url\": \"http://www.android10.org/myapi/cover_1.jpg\",\n" 38 | + " \"full_name\": \"Simon Hill\",\n" 39 | + " \"description\": \"Curabitur gravida nisi at nibh. In hac habitasse platea dictumst. Aliquam augue quam, sollicitudin vitae, consectetuer eget, rutrum at, lorem.\\n\\nInteger tincidunt ante vel ipsum. Praesent blandit lacinia erat. Vestibulum sed magna at nunc commodo placerat.\\n\\nPraesent blandit. Nam nulla. Integer pede justo, lacinia eget, tincidunt eget, tempus vel, pede.\",\n" 40 | + " \"followers\": 7484,\n" 41 | + " \"email\": \"jcooper@babbleset.edu\"\n" 42 | + "}"; 43 | 44 | private static final String JSON_RESPONSE_USER_COLLECTION = "[{\n" 45 | + " \"id\": 1,\n" 46 | + " \"full_name\": \"Simon Hill\",\n" 47 | + " \"followers\": 7484\n" 48 | + "}, {\n" 49 | + " \"id\": 12,\n" 50 | + " \"full_name\": \"Pedro Garcia\",\n" 51 | + " \"followers\": 1381\n" 52 | + "}]"; 53 | 54 | private UserEntityJsonMapper userEntityJsonMapper; 55 | 56 | @Rule 57 | public ExpectedException expectedException = ExpectedException.none(); 58 | 59 | @Before 60 | public void setUp() { 61 | userEntityJsonMapper = new UserEntityJsonMapper(); 62 | } 63 | 64 | @Test 65 | public void testTransformUserEntityHappyCase() { 66 | UserEntity userEntity = userEntityJsonMapper.transformUserEntity(JSON_RESPONSE_USER_DETAILS); 67 | 68 | assertThat(userEntity.getUserId(), is(1)); 69 | assertThat(userEntity.getFullname(), is(equalTo("Simon Hill"))); 70 | assertThat(userEntity.getEmail(), is(equalTo("jcooper@babbleset.edu"))); 71 | } 72 | 73 | @Test 74 | public void testTransformUserEntityCollectionHappyCase() { 75 | Collection userEntityCollection = 76 | userEntityJsonMapper.transformUserEntityCollection( 77 | JSON_RESPONSE_USER_COLLECTION); 78 | 79 | assertThat(((UserEntity) userEntityCollection.toArray()[0]).getUserId(), is(1)); 80 | assertThat(((UserEntity) userEntityCollection.toArray()[1]).getUserId(), is(12)); 81 | assertThat(userEntityCollection.size(), is(2)); 82 | } 83 | 84 | @Test 85 | public void testTransformUserEntityNotValidResponse() { 86 | expectedException.expect(JsonSyntaxException.class); 87 | userEntityJsonMapper.transformUserEntity("ironman"); 88 | } 89 | 90 | @Test 91 | public void testTransformUserEntityCollectionNotValidResponse() { 92 | expectedException.expect(JsonSyntaxException.class); 93 | userEntityJsonMapper.transformUserEntityCollection("Tony Stark"); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /data/src/test/java/com/fernandocejas/android10/sample/data/exception/RepositoryErrorBundleTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.exception; 17 | 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | import org.junit.runner.RunWith; 21 | import org.mockito.Mock; 22 | import org.mockito.runners.MockitoJUnitRunner; 23 | 24 | import static org.mockito.Mockito.verify; 25 | 26 | @RunWith(MockitoJUnitRunner.class) 27 | public class RepositoryErrorBundleTest { 28 | 29 | private RepositoryErrorBundle repositoryErrorBundle; 30 | 31 | @Mock private Exception mockException; 32 | 33 | @Before 34 | public void setUp() { 35 | repositoryErrorBundle = new RepositoryErrorBundle(mockException); 36 | } 37 | 38 | @Test 39 | @SuppressWarnings("ThrowableResultOfMethodCallIgnored") 40 | public void testGetErrorMessageInteraction() { 41 | repositoryErrorBundle.getErrorMessage(); 42 | 43 | verify(mockException).getMessage(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /data/src/test/java/com/fernandocejas/android10/sample/data/repository/UserDataRepositoryTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.repository; 17 | 18 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 19 | import com.fernandocejas.android10.sample.data.entity.mapper.UserEntityDataMapper; 20 | import com.fernandocejas.android10.sample.data.repository.datasource.UserDataStore; 21 | import com.fernandocejas.android10.sample.data.repository.datasource.UserDataStoreFactory; 22 | import com.fernandocejas.android10.sample.domain.User; 23 | import io.reactivex.Observable; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | import org.junit.Before; 27 | import org.junit.Test; 28 | import org.junit.runner.RunWith; 29 | import org.mockito.Mock; 30 | import org.mockito.runners.MockitoJUnitRunner; 31 | 32 | import static org.mockito.BDDMockito.given; 33 | import static org.mockito.Matchers.anyInt; 34 | import static org.mockito.Mockito.verify; 35 | 36 | @RunWith(MockitoJUnitRunner.class) 37 | public class UserDataRepositoryTest { 38 | 39 | private static final int FAKE_USER_ID = 123; 40 | 41 | private UserDataRepository userDataRepository; 42 | 43 | @Mock private UserDataStoreFactory mockUserDataStoreFactory; 44 | @Mock private UserEntityDataMapper mockUserEntityDataMapper; 45 | @Mock private UserDataStore mockUserDataStore; 46 | @Mock private UserEntity mockUserEntity; 47 | @Mock private User mockUser; 48 | 49 | @Before 50 | public void setUp() { 51 | userDataRepository = new UserDataRepository(mockUserDataStoreFactory, mockUserEntityDataMapper); 52 | given(mockUserDataStoreFactory.create(anyInt())).willReturn(mockUserDataStore); 53 | given(mockUserDataStoreFactory.createCloudDataStore()).willReturn(mockUserDataStore); 54 | } 55 | 56 | @Test 57 | public void testGetUsersHappyCase() { 58 | List usersList = new ArrayList<>(); 59 | usersList.add(new UserEntity()); 60 | given(mockUserDataStore.userEntityList()).willReturn(Observable.just(usersList)); 61 | 62 | userDataRepository.users(); 63 | 64 | verify(mockUserDataStoreFactory).createCloudDataStore(); 65 | verify(mockUserDataStore).userEntityList(); 66 | } 67 | 68 | @Test 69 | public void testGetUserHappyCase() { 70 | UserEntity userEntity = new UserEntity(); 71 | given(mockUserDataStore.userEntityDetails(FAKE_USER_ID)).willReturn(Observable.just(userEntity)); 72 | userDataRepository.user(FAKE_USER_ID); 73 | 74 | verify(mockUserDataStoreFactory).create(FAKE_USER_ID); 75 | verify(mockUserDataStore).userEntityDetails(FAKE_USER_ID); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /data/src/test/java/com/fernandocejas/android10/sample/data/repository/datasource/CloudUserDataStoreTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.repository.datasource; 17 | 18 | import com.fernandocejas.android10.sample.data.cache.UserCache; 19 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 20 | import com.fernandocejas.android10.sample.data.net.RestApi; 21 | import io.reactivex.Observable; 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | import org.junit.runner.RunWith; 25 | import org.mockito.Mock; 26 | import org.mockito.runners.MockitoJUnitRunner; 27 | 28 | import static org.mockito.BDDMockito.given; 29 | import static org.mockito.Mockito.verify; 30 | 31 | @RunWith(MockitoJUnitRunner.class) 32 | public class CloudUserDataStoreTest { 33 | 34 | private static final int FAKE_USER_ID = 765; 35 | 36 | private CloudUserDataStore cloudUserDataStore; 37 | 38 | @Mock private RestApi mockRestApi; 39 | @Mock private UserCache mockUserCache; 40 | 41 | @Before 42 | public void setUp() { 43 | cloudUserDataStore = new CloudUserDataStore(mockRestApi, mockUserCache); 44 | } 45 | 46 | @Test 47 | public void testGetUserEntityListFromApi() { 48 | cloudUserDataStore.userEntityList(); 49 | verify(mockRestApi).userEntityList(); 50 | } 51 | 52 | @Test 53 | public void testGetUserEntityDetailsFromApi() { 54 | UserEntity fakeUserEntity = new UserEntity(); 55 | Observable fakeObservable = Observable.just(fakeUserEntity); 56 | given(mockRestApi.userEntityById(FAKE_USER_ID)).willReturn(fakeObservable); 57 | 58 | cloudUserDataStore.userEntityDetails(FAKE_USER_ID); 59 | 60 | verify(mockRestApi).userEntityById(FAKE_USER_ID); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /data/src/test/java/com/fernandocejas/android10/sample/data/repository/datasource/DiskUserDataStoreTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.repository.datasource; 17 | 18 | import com.fernandocejas.android10.sample.data.cache.UserCache; 19 | import org.junit.Before; 20 | import org.junit.Rule; 21 | import org.junit.Test; 22 | import org.junit.rules.ExpectedException; 23 | import org.junit.runner.RunWith; 24 | import org.mockito.Mock; 25 | import org.mockito.runners.MockitoJUnitRunner; 26 | 27 | import static org.mockito.Mockito.verify; 28 | 29 | @RunWith(MockitoJUnitRunner.class) 30 | public class DiskUserDataStoreTest { 31 | 32 | private static final int FAKE_USER_ID = 11; 33 | 34 | private DiskUserDataStore diskUserDataStore; 35 | 36 | @Mock private UserCache mockUserCache; 37 | 38 | @Rule public ExpectedException expectedException = ExpectedException.none(); 39 | 40 | @Before 41 | public void setUp() { 42 | diskUserDataStore = new DiskUserDataStore(mockUserCache); 43 | } 44 | 45 | @Test 46 | public void testGetUserEntityListUnsupported() { 47 | expectedException.expect(UnsupportedOperationException.class); 48 | diskUserDataStore.userEntityList(); 49 | } 50 | 51 | @Test 52 | public void testGetUserEntityDetailesFromCache() { 53 | diskUserDataStore.userEntityDetails(FAKE_USER_ID); 54 | verify(mockUserCache).get(FAKE_USER_ID); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /data/src/test/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStoreFactoryTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.data.repository.datasource; 17 | 18 | import com.fernandocejas.android10.sample.data.ApplicationTestCase; 19 | import com.fernandocejas.android10.sample.data.cache.UserCache; 20 | import org.junit.Before; 21 | import org.junit.Test; 22 | import org.mockito.Mock; 23 | import org.robolectric.RuntimeEnvironment; 24 | 25 | import static org.hamcrest.CoreMatchers.instanceOf; 26 | import static org.hamcrest.CoreMatchers.is; 27 | import static org.hamcrest.CoreMatchers.notNullValue; 28 | import static org.hamcrest.MatcherAssert.assertThat; 29 | import static org.mockito.BDDMockito.given; 30 | import static org.mockito.Mockito.verify; 31 | 32 | public class UserDataStoreFactoryTest extends ApplicationTestCase { 33 | 34 | private static final int FAKE_USER_ID = 11; 35 | 36 | private UserDataStoreFactory userDataStoreFactory; 37 | 38 | @Mock private UserCache mockUserCache; 39 | 40 | @Before 41 | public void setUp() { 42 | userDataStoreFactory = new UserDataStoreFactory(RuntimeEnvironment.application, mockUserCache); 43 | } 44 | 45 | @Test 46 | public void testCreateDiskDataStore() { 47 | given(mockUserCache.isCached(FAKE_USER_ID)).willReturn(true); 48 | given(mockUserCache.isExpired()).willReturn(false); 49 | 50 | UserDataStore userDataStore = userDataStoreFactory.create(FAKE_USER_ID); 51 | 52 | assertThat(userDataStore, is(notNullValue())); 53 | assertThat(userDataStore, is(instanceOf(DiskUserDataStore.class))); 54 | 55 | verify(mockUserCache).isCached(FAKE_USER_ID); 56 | verify(mockUserCache).isExpired(); 57 | } 58 | 59 | @Test 60 | public void testCreateCloudDataStore() { 61 | given(mockUserCache.isExpired()).willReturn(true); 62 | given(mockUserCache.isCached(FAKE_USER_ID)).willReturn(false); 63 | 64 | UserDataStore userDataStore = userDataStoreFactory.create(FAKE_USER_ID); 65 | 66 | assertThat(userDataStore, is(notNullValue())); 67 | assertThat(userDataStore, is(instanceOf(CloudUserDataStore.class))); 68 | 69 | verify(mockUserCache).isExpired(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /domain/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | //noinspection GroovyUnusedAssignment 4 | sourceCompatibility = 1.7 5 | //noinspection GroovyUnusedAssignment 6 | targetCompatibility = 1.7 7 | 8 | configurations { 9 | provided 10 | } 11 | 12 | sourceSets { 13 | main { 14 | compileClasspath += configurations.provided 15 | } 16 | } 17 | 18 | dependencies { 19 | def domainDependencies = rootProject.ext.domainDependencies 20 | def domainTestDependencies = rootProject.ext.domainTestDependencies 21 | 22 | compileOnly domainDependencies.javaxAnnotation 23 | 24 | implementation domainDependencies.javaxInject 25 | implementation domainDependencies.rxJava 26 | compile domainDependencies.arrow 27 | 28 | testImplementation domainTestDependencies.junit 29 | testImplementation domainTestDependencies.mockito 30 | testImplementation domainTestDependencies.assertj 31 | } 32 | -------------------------------------------------------------------------------- /domain/src/main/java/com/fernandocejas/android10/sample/domain/User.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.domain; 17 | 18 | /** 19 | * Class that represents a User in the domain layer. 20 | */ 21 | public class User { 22 | 23 | private final int userId; 24 | 25 | public User(int userId) { 26 | this.userId = userId; 27 | } 28 | 29 | private String coverUrl; 30 | private String fullName; 31 | private String email; 32 | private String description; 33 | private int followers; 34 | 35 | public int getUserId() { 36 | return userId; 37 | } 38 | 39 | public String getCoverUrl() { 40 | return coverUrl; 41 | } 42 | 43 | public void setCoverUrl(String coverUrl) { 44 | this.coverUrl = coverUrl; 45 | } 46 | 47 | public String getFullName() { 48 | return fullName; 49 | } 50 | 51 | public void setFullName(String fullName) { 52 | this.fullName = fullName; 53 | } 54 | 55 | public String getEmail() { 56 | return email; 57 | } 58 | 59 | public void setEmail(String email) { 60 | this.email = email; 61 | } 62 | 63 | public String getDescription() { 64 | return description; 65 | } 66 | 67 | public void setDescription(String description) { 68 | this.description = description; 69 | } 70 | 71 | public int getFollowers() { 72 | return followers; 73 | } 74 | 75 | public void setFollowers(int followers) { 76 | this.followers = followers; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /domain/src/main/java/com/fernandocejas/android10/sample/domain/exception/DefaultErrorBundle.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.domain.exception; 17 | 18 | /** 19 | * Wrapper around Exceptions used to manage default errors. 20 | */ 21 | public class DefaultErrorBundle implements ErrorBundle { 22 | 23 | private static final String DEFAULT_ERROR_MSG = "Unknown error"; 24 | 25 | private final Exception exception; 26 | 27 | public DefaultErrorBundle(Exception exception) { 28 | this.exception = exception; 29 | } 30 | 31 | @Override 32 | public Exception getException() { 33 | return exception; 34 | } 35 | 36 | @Override 37 | public String getErrorMessage() { 38 | return (exception != null) ? this.exception.getMessage() : DEFAULT_ERROR_MSG; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /domain/src/main/java/com/fernandocejas/android10/sample/domain/exception/ErrorBundle.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.domain.exception; 17 | 18 | /** 19 | * Interface to represent a wrapper around an {@link java.lang.Exception} to manage errors. 20 | */ 21 | public interface ErrorBundle { 22 | Exception getException(); 23 | 24 | String getErrorMessage(); 25 | } 26 | -------------------------------------------------------------------------------- /domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/PostExecutionThread.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.domain.executor; 17 | 18 | import io.reactivex.Scheduler; 19 | 20 | /** 21 | * Thread abstraction created to change the execution context from any thread to any other thread. 22 | * Useful to encapsulate a UI Thread for example, since some job will be done in background, an 23 | * implementation of this interface will change context and update the UI. 24 | */ 25 | public interface PostExecutionThread { 26 | Scheduler getScheduler(); 27 | } 28 | -------------------------------------------------------------------------------- /domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/ThreadExecutor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.domain.executor; 17 | 18 | import java.util.concurrent.Executor; 19 | 20 | /** 21 | * Executor implementation can be based on different frameworks or techniques of asynchronous 22 | * execution, but every implementation will execute the 23 | * {@link com.fernandocejas.android10.sample.domain.interactor.UseCase} out of the UI thread. 24 | */ 25 | public interface ThreadExecutor extends Executor {} 26 | -------------------------------------------------------------------------------- /domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/DefaultObserver.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.domain.interactor; 17 | 18 | import io.reactivex.observers.DisposableObserver; 19 | 20 | /** 21 | * Default {@link DisposableObserver} base class to be used whenever you want default error handling. 22 | */ 23 | public class DefaultObserver extends DisposableObserver { 24 | @Override public void onNext(T t) { 25 | // no-op by default. 26 | } 27 | 28 | @Override public void onComplete() { 29 | // no-op by default. 30 | } 31 | 32 | @Override public void onError(Throwable exception) { 33 | // no-op by default. 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserDetails.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.domain.interactor; 17 | 18 | import com.fernandocejas.android10.sample.domain.User; 19 | import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread; 20 | import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; 21 | import com.fernandocejas.android10.sample.domain.repository.UserRepository; 22 | import com.fernandocejas.arrow.checks.Preconditions; 23 | import io.reactivex.Observable; 24 | import javax.inject.Inject; 25 | 26 | /** 27 | * This class is an implementation of {@link UseCase} that represents a use case for 28 | * retrieving data related to an specific {@link User}. 29 | */ 30 | public class GetUserDetails extends UseCase { 31 | 32 | private final UserRepository userRepository; 33 | 34 | @Inject 35 | GetUserDetails(UserRepository userRepository, ThreadExecutor threadExecutor, 36 | PostExecutionThread postExecutionThread) { 37 | super(threadExecutor, postExecutionThread); 38 | this.userRepository = userRepository; 39 | } 40 | 41 | @Override Observable buildUseCaseObservable(Params params) { 42 | Preconditions.checkNotNull(params); 43 | return this.userRepository.user(params.userId); 44 | } 45 | 46 | public static final class Params { 47 | 48 | private final int userId; 49 | 50 | private Params(int userId) { 51 | this.userId = userId; 52 | } 53 | 54 | public static Params forUser(int userId) { 55 | return new Params(userId); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserList.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.domain.interactor; 17 | 18 | import com.fernandocejas.android10.sample.domain.User; 19 | import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread; 20 | import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; 21 | import com.fernandocejas.android10.sample.domain.repository.UserRepository; 22 | import io.reactivex.Observable; 23 | import java.util.List; 24 | import javax.inject.Inject; 25 | 26 | /** 27 | * This class is an implementation of {@link UseCase} that represents a use case for 28 | * retrieving a collection of all {@link User}. 29 | */ 30 | public class GetUserList extends UseCase, Void> { 31 | 32 | private final UserRepository userRepository; 33 | 34 | @Inject 35 | GetUserList(UserRepository userRepository, ThreadExecutor threadExecutor, 36 | PostExecutionThread postExecutionThread) { 37 | super(threadExecutor, postExecutionThread); 38 | this.userRepository = userRepository; 39 | } 40 | 41 | @Override Observable> buildUseCaseObservable(Void unused) { 42 | return this.userRepository.users(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/UseCase.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.domain.interactor; 17 | 18 | import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread; 19 | import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; 20 | import com.fernandocejas.arrow.checks.Preconditions; 21 | import io.reactivex.Observable; 22 | import io.reactivex.disposables.CompositeDisposable; 23 | import io.reactivex.disposables.Disposable; 24 | import io.reactivex.observers.DisposableObserver; 25 | import io.reactivex.schedulers.Schedulers; 26 | 27 | /** 28 | * Abstract class for a Use Case (Interactor in terms of Clean Architecture). 29 | * This interface represents a execution unit for different use cases (this means any use case 30 | * in the application should implement this contract). 31 | * 32 | * By convention each UseCase implementation will return the result using a {@link DisposableObserver} 33 | * that will execute its job in a background thread and will post the result in the UI thread. 34 | */ 35 | public abstract class UseCase { 36 | 37 | private final ThreadExecutor threadExecutor; 38 | private final PostExecutionThread postExecutionThread; 39 | private final CompositeDisposable disposables; 40 | 41 | UseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) { 42 | this.threadExecutor = threadExecutor; 43 | this.postExecutionThread = postExecutionThread; 44 | this.disposables = new CompositeDisposable(); 45 | } 46 | 47 | /** 48 | * Builds an {@link Observable} which will be used when executing the current {@link UseCase}. 49 | */ 50 | abstract Observable buildUseCaseObservable(Params params); 51 | 52 | /** 53 | * Executes the current use case. 54 | * 55 | * @param observer {@link DisposableObserver} which will be listening to the observable build 56 | * by {@link #buildUseCaseObservable(Params)} ()} method. 57 | * @param params Parameters (Optional) used to build/execute this use case. 58 | */ 59 | public void execute(DisposableObserver observer, Params params) { 60 | Preconditions.checkNotNull(observer); 61 | final Observable observable = this.buildUseCaseObservable(params) 62 | .subscribeOn(Schedulers.from(threadExecutor)) 63 | .observeOn(postExecutionThread.getScheduler()); 64 | addDisposable(observable.subscribeWith(observer)); 65 | } 66 | 67 | /** 68 | * Dispose from current {@link CompositeDisposable}. 69 | */ 70 | public void dispose() { 71 | if (!disposables.isDisposed()) { 72 | disposables.dispose(); 73 | } 74 | } 75 | 76 | /** 77 | * Dispose from current {@link CompositeDisposable}. 78 | */ 79 | private void addDisposable(Disposable disposable) { 80 | Preconditions.checkNotNull(disposable); 81 | Preconditions.checkNotNull(disposables); 82 | disposables.add(disposable); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /domain/src/main/java/com/fernandocejas/android10/sample/domain/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.domain.repository; 17 | 18 | import com.fernandocejas.android10.sample.domain.User; 19 | import io.reactivex.Observable; 20 | import java.util.List; 21 | 22 | /** 23 | * Interface that represents a Repository for getting {@link User} related data. 24 | */ 25 | public interface UserRepository { 26 | /** 27 | * Get an {@link Observable} which will emit a List of {@link User}. 28 | */ 29 | Observable> users(); 30 | 31 | /** 32 | * Get an {@link Observable} which will emit a {@link User}. 33 | * 34 | * @param userId The user id used to retrieve user data. 35 | */ 36 | Observable user(final int userId); 37 | } 38 | -------------------------------------------------------------------------------- /domain/src/test/java/com/fernandocejas/android10/sample/domain/UserTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.domain; 17 | 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | 23 | public class UserTest { 24 | 25 | private static final int FAKE_USER_ID = 8; 26 | 27 | private User user; 28 | 29 | @Before 30 | public void setUp() { 31 | user = new User(FAKE_USER_ID); 32 | } 33 | 34 | @Test 35 | public void testUserConstructorHappyCase() { 36 | final int userId = user.getUserId(); 37 | 38 | assertThat(userId).isEqualTo(FAKE_USER_ID); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /domain/src/test/java/com/fernandocejas/android10/sample/domain/exception/DefaultErrorBundleTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.domain.exception; 17 | 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | import org.junit.runner.RunWith; 21 | import org.mockito.Mock; 22 | import org.mockito.runners.MockitoJUnitRunner; 23 | 24 | import static org.mockito.Mockito.verify; 25 | 26 | @RunWith(MockitoJUnitRunner.class) 27 | public class DefaultErrorBundleTest { 28 | private DefaultErrorBundle defaultErrorBundle; 29 | 30 | @Mock private Exception mockException; 31 | 32 | @Before 33 | public void setUp() { 34 | defaultErrorBundle = new DefaultErrorBundle(mockException); 35 | } 36 | 37 | @Test 38 | public void testGetErrorMessageInteraction() { 39 | defaultErrorBundle.getErrorMessage(); 40 | 41 | verify(mockException).getMessage(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /domain/src/test/java/com/fernandocejas/android10/sample/domain/interactor/GetUserDetailsTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.domain.interactor; 17 | 18 | import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread; 19 | import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; 20 | import com.fernandocejas.android10.sample.domain.interactor.GetUserDetails.Params; 21 | import com.fernandocejas.android10.sample.domain.repository.UserRepository; 22 | import org.junit.Before; 23 | import org.junit.Rule; 24 | import org.junit.Test; 25 | import org.junit.rules.ExpectedException; 26 | import org.junit.runner.RunWith; 27 | import org.mockito.Mock; 28 | import org.mockito.runners.MockitoJUnitRunner; 29 | 30 | import static org.mockito.Mockito.verify; 31 | import static org.mockito.Mockito.verifyNoMoreInteractions; 32 | import static org.mockito.Mockito.verifyZeroInteractions; 33 | 34 | @RunWith(MockitoJUnitRunner.class) 35 | public class GetUserDetailsTest { 36 | 37 | private static final int USER_ID = 123; 38 | 39 | private GetUserDetails getUserDetails; 40 | 41 | @Mock private UserRepository mockUserRepository; 42 | @Mock private ThreadExecutor mockThreadExecutor; 43 | @Mock private PostExecutionThread mockPostExecutionThread; 44 | 45 | @Rule public ExpectedException expectedException = ExpectedException.none(); 46 | 47 | @Before 48 | public void setUp() { 49 | getUserDetails = new GetUserDetails(mockUserRepository, mockThreadExecutor, 50 | mockPostExecutionThread); 51 | } 52 | 53 | @Test 54 | public void testGetUserDetailsUseCaseObservableHappyCase() { 55 | getUserDetails.buildUseCaseObservable(Params.forUser(USER_ID)); 56 | 57 | verify(mockUserRepository).user(USER_ID); 58 | verifyNoMoreInteractions(mockUserRepository); 59 | verifyZeroInteractions(mockPostExecutionThread); 60 | verifyZeroInteractions(mockThreadExecutor); 61 | } 62 | 63 | @Test 64 | public void testShouldFailWhenNoOrEmptyParameters() { 65 | expectedException.expect(NullPointerException.class); 66 | getUserDetails.buildUseCaseObservable(null); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /domain/src/test/java/com/fernandocejas/android10/sample/domain/interactor/GetUserListTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.domain.interactor; 17 | 18 | import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread; 19 | import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; 20 | import com.fernandocejas.android10.sample.domain.repository.UserRepository; 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | import org.mockito.Mock; 25 | import org.mockito.runners.MockitoJUnitRunner; 26 | 27 | import static org.mockito.Mockito.verify; 28 | import static org.mockito.Mockito.verifyNoMoreInteractions; 29 | import static org.mockito.Mockito.verifyZeroInteractions; 30 | 31 | @RunWith(MockitoJUnitRunner.class) 32 | public class GetUserListTest { 33 | 34 | private GetUserList getUserList; 35 | 36 | @Mock private ThreadExecutor mockThreadExecutor; 37 | @Mock private PostExecutionThread mockPostExecutionThread; 38 | @Mock private UserRepository mockUserRepository; 39 | 40 | @Before 41 | public void setUp() { 42 | getUserList = new GetUserList(mockUserRepository, mockThreadExecutor, 43 | mockPostExecutionThread); 44 | } 45 | 46 | @Test 47 | public void testGetUserListUseCaseObservableHappyCase() { 48 | getUserList.buildUseCaseObservable(null); 49 | 50 | verify(mockUserRepository).users(); 51 | verifyNoMoreInteractions(mockUserRepository); 52 | verifyZeroInteractions(mockThreadExecutor); 53 | verifyZeroInteractions(mockPostExecutionThread); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /domain/src/test/java/com/fernandocejas/android10/sample/domain/interactor/UseCaseTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.domain.interactor; 17 | 18 | import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread; 19 | import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; 20 | import io.reactivex.Observable; 21 | import io.reactivex.observers.DisposableObserver; 22 | import io.reactivex.schedulers.TestScheduler; 23 | import org.junit.Before; 24 | import org.junit.Rule; 25 | import org.junit.Test; 26 | import org.junit.rules.ExpectedException; 27 | import org.junit.runner.RunWith; 28 | import org.mockito.Mock; 29 | import org.mockito.runners.MockitoJUnitRunner; 30 | 31 | import static org.assertj.core.api.Assertions.assertThat; 32 | import static org.mockito.BDDMockito.given; 33 | 34 | @RunWith(MockitoJUnitRunner.class) 35 | public class UseCaseTest { 36 | 37 | private UseCaseTestClass useCase; 38 | 39 | private TestDisposableObserver testObserver; 40 | 41 | @Mock private ThreadExecutor mockThreadExecutor; 42 | @Mock private PostExecutionThread mockPostExecutionThread; 43 | 44 | @Rule public ExpectedException expectedException = ExpectedException.none(); 45 | 46 | @Before 47 | public void setUp() { 48 | this.useCase = new UseCaseTestClass(mockThreadExecutor, mockPostExecutionThread); 49 | this.testObserver = new TestDisposableObserver<>(); 50 | given(mockPostExecutionThread.getScheduler()).willReturn(new TestScheduler()); 51 | } 52 | 53 | @Test 54 | public void testBuildUseCaseObservableReturnCorrectResult() { 55 | useCase.execute(testObserver, Params.EMPTY); 56 | 57 | assertThat(testObserver.valuesCount).isZero(); 58 | } 59 | 60 | @Test 61 | public void testSubscriptionWhenExecutingUseCase() { 62 | useCase.execute(testObserver, Params.EMPTY); 63 | useCase.dispose(); 64 | 65 | assertThat(testObserver.isDisposed()).isTrue(); 66 | } 67 | 68 | @Test 69 | public void testShouldFailWhenExecuteWithNullObserver() { 70 | expectedException.expect(NullPointerException.class); 71 | useCase.execute(null, Params.EMPTY); 72 | } 73 | 74 | private static class UseCaseTestClass extends UseCase { 75 | 76 | UseCaseTestClass(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) { 77 | super(threadExecutor, postExecutionThread); 78 | } 79 | 80 | @Override Observable buildUseCaseObservable(Params params) { 81 | return Observable.empty(); 82 | } 83 | 84 | @Override 85 | public void execute(DisposableObserver observer, Params params) { 86 | super.execute(observer, params); 87 | } 88 | } 89 | 90 | private static class TestDisposableObserver extends DisposableObserver { 91 | private int valuesCount = 0; 92 | 93 | @Override public void onNext(T value) { 94 | valuesCount++; 95 | } 96 | 97 | @Override public void onError(Throwable e) { 98 | // no-op by default. 99 | } 100 | 101 | @Override public void onComplete() { 102 | // no-op by default. 103 | } 104 | } 105 | 106 | private static class Params { 107 | private static Params EMPTY = new Params(); 108 | private Params() {} 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #Gradle configuration 2 | org.gradle.daemon=true 3 | org.gradle.jvmargs=-Dfile.encoding=UTF-8 4 | org.gradle.parallel=true 5 | org.gradle.configureondemand=true 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/android10/Android-CleanArchitecture/8ed4222c537e40db05e9e685bbc253fafb6b8e1f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Dec 21 17:11:04 ART 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew.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.application' 2 | //apply plugin: 'com.neenbedankt.android-apt' 3 | 4 | android { 5 | def globalConfiguration = rootProject.extensions.getByName("ext") 6 | 7 | compileSdkVersion globalConfiguration.getAt("androidCompileSdkVersion") 8 | buildToolsVersion globalConfiguration.getAt("androidBuildToolsVersion") 9 | 10 | defaultConfig { 11 | minSdkVersion globalConfiguration.getAt("androidMinSdkVersion") 12 | targetSdkVersion globalConfiguration.getAt("androidTargetSdkVersion") 13 | 14 | applicationId globalConfiguration.getAt("androidApplicationId") 15 | versionCode globalConfiguration.getAt("androidVersionCode") 16 | versionName globalConfiguration.getAt("androidVersionName") 17 | testInstrumentationRunner globalConfiguration.getAt("testInstrumentationRunner") 18 | testApplicationId globalConfiguration.getAt("testApplicationId") 19 | } 20 | 21 | compileOptions { 22 | sourceCompatibility JavaVersion.VERSION_1_7 23 | targetCompatibility JavaVersion.VERSION_1_7 24 | } 25 | 26 | packagingOptions { 27 | exclude 'LICENSE.txt' 28 | exclude 'META-INF/DEPENDENCIES' 29 | exclude 'META-INF/ASL2.0' 30 | exclude 'META-INF/NOTICE' 31 | exclude 'META-INF/LICENSE' 32 | } 33 | 34 | lintOptions { 35 | quiet true 36 | abortOnError false 37 | ignoreWarnings true 38 | disable 'InvalidPackage' //Some libraries have issues with this. 39 | disable 'OldTargetApi' //Lint gives this warning but SDK 20 would be Android L Beta. 40 | disable 'IconDensities' //For testing purpose. This is safe to remove. 41 | disable 'IconMissingDensityFolder' //For testing purpose. This is safe to remove. 42 | } 43 | 44 | signingConfigs { 45 | debug { 46 | storeFile file('../buildsystem/debug.keystore') 47 | storePassword 'android' 48 | keyAlias 'androiddebugkey' 49 | keyPassword 'android' 50 | } 51 | } 52 | 53 | buildTypes { 54 | debug { 55 | signingConfig signingConfigs.debug 56 | } 57 | } 58 | } 59 | 60 | dependencies { 61 | def presentationDependencies = rootProject.ext.presentationDependencies 62 | def presentationTestDependencies = rootProject.ext.presentationTestDependencies 63 | def developmentDependencies = rootProject.ext.developmentDependencies 64 | 65 | implementation project(':domain') 66 | implementation project(':data') 67 | 68 | annotationProcessor presentationDependencies.daggerCompiler 69 | implementation presentationDependencies.dagger 70 | implementation presentationDependencies.butterKnife 71 | annotationProcessor presentationDependencies.butterKnife 72 | implementation presentationDependencies.recyclerView 73 | implementation presentationDependencies.rxJava 74 | implementation presentationDependencies.rxAndroid 75 | compileOnly presentationDependencies.javaxAnnotation 76 | 77 | androidTestImplementation presentationTestDependencies.mockito 78 | androidTestImplementation presentationTestDependencies.dexmaker 79 | androidTestImplementation presentationTestDependencies.dexmakerMockito 80 | androidTestImplementation presentationTestDependencies.espresso 81 | androidTestImplementation presentationTestDependencies.testingSupportLib 82 | 83 | //Development 84 | implementation developmentDependencies.leakCanary 85 | } 86 | 87 | repositories { 88 | google() 89 | } -------------------------------------------------------------------------------- /presentation/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/fcejas/Software/SDKs/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 | -------------------------------------------------------------------------------- /presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/exception/ErrorMessageFactoryTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.test.exception; 17 | 18 | import android.test.AndroidTestCase; 19 | import com.fernandocejas.android10.sample.data.exception.NetworkConnectionException; 20 | import com.fernandocejas.android10.sample.data.exception.UserNotFoundException; 21 | import com.fernandocejas.android10.sample.presentation.R; 22 | import com.fernandocejas.android10.sample.presentation.exception.ErrorMessageFactory; 23 | 24 | import static org.hamcrest.CoreMatchers.equalTo; 25 | import static org.hamcrest.CoreMatchers.is; 26 | import static org.hamcrest.MatcherAssert.assertThat; 27 | 28 | public class ErrorMessageFactoryTest extends AndroidTestCase { 29 | 30 | @Override protected void setUp() throws Exception { 31 | super.setUp(); 32 | } 33 | 34 | public void testNetworkConnectionErrorMessage() { 35 | String expectedMessage = getContext().getString(R.string.exception_message_no_connection); 36 | String actualMessage = ErrorMessageFactory.create(getContext(), 37 | new NetworkConnectionException()); 38 | 39 | assertThat(actualMessage, is(equalTo(expectedMessage))); 40 | } 41 | 42 | public void testUserNotFoundErrorMessage() { 43 | String expectedMessage = getContext().getString(R.string.exception_message_user_not_found); 44 | String actualMessage = ErrorMessageFactory.create(getContext(), new UserNotFoundException()); 45 | 46 | assertThat(actualMessage, is(equalTo(expectedMessage))); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/mapper/UserModelDataMapperTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.test.mapper; 17 | 18 | import com.fernandocejas.android10.sample.domain.User; 19 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper; 20 | import com.fernandocejas.android10.sample.presentation.model.UserModel; 21 | import java.util.ArrayList; 22 | import java.util.Collection; 23 | import java.util.List; 24 | import junit.framework.TestCase; 25 | 26 | import static org.hamcrest.CoreMatchers.instanceOf; 27 | import static org.hamcrest.CoreMatchers.is; 28 | import static org.hamcrest.MatcherAssert.assertThat; 29 | import static org.mockito.Mockito.mock; 30 | 31 | public class UserModelDataMapperTest extends TestCase { 32 | 33 | private static final int FAKE_USER_ID = 123; 34 | private static final String FAKE_FULL_NAME = "Tony Stark"; 35 | 36 | private UserModelDataMapper userModelDataMapper; 37 | 38 | @Override protected void setUp() throws Exception { 39 | super.setUp(); 40 | userModelDataMapper = new UserModelDataMapper(); 41 | } 42 | 43 | public void testTransformUser() { 44 | User user = createFakeUser(); 45 | UserModel userModel = userModelDataMapper.transform(user); 46 | 47 | assertThat(userModel, is(instanceOf(UserModel.class))); 48 | assertThat(userModel.getUserId(), is(FAKE_USER_ID)); 49 | assertThat(userModel.getFullName(), is(FAKE_FULL_NAME)); 50 | } 51 | 52 | public void testTransformUserCollection() { 53 | User mockUserOne = mock(User.class); 54 | User mockUserTwo = mock(User.class); 55 | 56 | List userList = new ArrayList(5); 57 | userList.add(mockUserOne); 58 | userList.add(mockUserTwo); 59 | 60 | Collection userModelList = userModelDataMapper.transform(userList); 61 | 62 | assertThat(userModelList.toArray()[0], is(instanceOf(UserModel.class))); 63 | assertThat(userModelList.toArray()[1], is(instanceOf(UserModel.class))); 64 | assertThat(userModelList.size(), is(2)); 65 | } 66 | 67 | private User createFakeUser() { 68 | User user = new User(FAKE_USER_ID); 69 | user.setFullName(FAKE_FULL_NAME); 70 | 71 | return user; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/presenter/UserDetailsPresenterTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.test.presenter; 17 | 18 | import android.content.Context; 19 | import com.fernandocejas.android10.sample.domain.interactor.GetUserDetails; 20 | import com.fernandocejas.android10.sample.domain.interactor.GetUserDetails.Params; 21 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper; 22 | import com.fernandocejas.android10.sample.presentation.presenter.UserDetailsPresenter; 23 | import com.fernandocejas.android10.sample.presentation.view.UserDetailsView; 24 | import io.reactivex.observers.DisposableObserver; 25 | import org.junit.Before; 26 | import org.junit.Test; 27 | import org.junit.runner.RunWith; 28 | import org.mockito.Mock; 29 | import org.mockito.runners.MockitoJUnitRunner; 30 | 31 | import static org.mockito.BDDMockito.given; 32 | import static org.mockito.Matchers.any; 33 | import static org.mockito.Mockito.verify; 34 | 35 | @RunWith(MockitoJUnitRunner.class) 36 | public class UserDetailsPresenterTest { 37 | 38 | private static final int USER_ID = 1; 39 | 40 | private UserDetailsPresenter userDetailsPresenter; 41 | 42 | @Mock private Context mockContext; 43 | @Mock private UserDetailsView mockUserDetailsView; 44 | @Mock private GetUserDetails mockGetUserDetails; 45 | @Mock private UserModelDataMapper mockUserModelDataMapper; 46 | 47 | @Before 48 | public void setUp() { 49 | userDetailsPresenter = new UserDetailsPresenter(mockGetUserDetails, mockUserModelDataMapper); 50 | userDetailsPresenter.setView(mockUserDetailsView); 51 | } 52 | 53 | @Test 54 | @SuppressWarnings("unchecked") 55 | public void testUserDetailsPresenterInitialize() { 56 | given(mockUserDetailsView.context()).willReturn(mockContext); 57 | 58 | userDetailsPresenter.initialize(USER_ID); 59 | 60 | verify(mockUserDetailsView).hideRetry(); 61 | verify(mockUserDetailsView).showLoading(); 62 | verify(mockGetUserDetails).execute(any(DisposableObserver.class), any(Params.class)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/presenter/UserListPresenterTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.test.presenter; 17 | 18 | import android.content.Context; 19 | import com.fernandocejas.android10.sample.domain.interactor.GetUserList; 20 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper; 21 | import com.fernandocejas.android10.sample.presentation.presenter.UserListPresenter; 22 | import com.fernandocejas.android10.sample.presentation.view.UserListView; 23 | import io.reactivex.observers.DisposableObserver; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | import org.mockito.Mock; 28 | import org.mockito.runners.MockitoJUnitRunner; 29 | 30 | import static org.mockito.BDDMockito.given; 31 | import static org.mockito.Matchers.any; 32 | import static org.mockito.Mockito.verify; 33 | 34 | @RunWith(MockitoJUnitRunner.class) 35 | public class UserListPresenterTest { 36 | 37 | private UserListPresenter userListPresenter; 38 | 39 | @Mock private Context mockContext; 40 | @Mock private UserListView mockUserListView; 41 | @Mock private GetUserList mockGetUserList; 42 | @Mock private UserModelDataMapper mockUserModelDataMapper; 43 | 44 | @Before 45 | public void setUp() { 46 | userListPresenter = new UserListPresenter(mockGetUserList, mockUserModelDataMapper); 47 | userListPresenter.setView(mockUserListView); 48 | } 49 | 50 | @Test 51 | @SuppressWarnings("unchecked") 52 | public void testUserListPresenterInitialize() { 53 | given(mockUserListView.context()).willReturn(mockContext); 54 | 55 | userListPresenter.initialize(); 56 | 57 | verify(mockUserListView).hideRetry(); 58 | verify(mockUserListView).showLoading(); 59 | verify(mockGetUserList).execute(any(DisposableObserver.class), any(Void.class)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/view/activity/UserDetailsActivityTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.test.view.activity; 17 | 18 | import android.app.Fragment; 19 | import android.content.Intent; 20 | import android.test.ActivityInstrumentationTestCase2; 21 | import com.fernandocejas.android10.sample.presentation.R; 22 | import com.fernandocejas.android10.sample.presentation.view.activity.UserDetailsActivity; 23 | 24 | import static android.support.test.espresso.Espresso.onView; 25 | import static android.support.test.espresso.assertion.ViewAssertions.matches; 26 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 27 | import static android.support.test.espresso.matcher.ViewMatchers.withId; 28 | import static android.support.test.espresso.matcher.ViewMatchers.withText; 29 | import static org.hamcrest.CoreMatchers.is; 30 | import static org.hamcrest.CoreMatchers.notNullValue; 31 | import static org.hamcrest.MatcherAssert.assertThat; 32 | import static org.hamcrest.Matchers.not; 33 | 34 | public class UserDetailsActivityTest extends ActivityInstrumentationTestCase2 { 35 | 36 | private static final int FAKE_USER_ID = 10; 37 | 38 | private UserDetailsActivity userDetailsActivity; 39 | 40 | public UserDetailsActivityTest() { 41 | super(UserDetailsActivity.class); 42 | } 43 | 44 | @Override protected void setUp() throws Exception { 45 | super.setUp(); 46 | this.setActivityIntent(createTargetIntent()); 47 | this.userDetailsActivity = getActivity(); 48 | } 49 | 50 | @Override protected void tearDown() throws Exception { 51 | super.tearDown(); 52 | } 53 | 54 | public void testContainsUserDetailsFragment() { 55 | Fragment userDetailsFragment = 56 | userDetailsActivity.getFragmentManager().findFragmentById(R.id.fragmentContainer); 57 | assertThat(userDetailsFragment, is(notNullValue())); 58 | } 59 | 60 | public void testContainsProperTitle() { 61 | String actualTitle = this.userDetailsActivity.getTitle().toString().trim(); 62 | 63 | assertThat(actualTitle, is("User Details")); 64 | } 65 | 66 | public void testLoadUserHappyCaseViews() { 67 | onView(withId(R.id.rl_retry)).check(matches(not(isDisplayed()))); 68 | onView(withId(R.id.rl_progress)).check(matches(not(isDisplayed()))); 69 | 70 | onView(withId(R.id.tv_fullname)).check(matches(isDisplayed())); 71 | onView(withId(R.id.tv_email)).check(matches(isDisplayed())); 72 | onView(withId(R.id.tv_description)).check(matches(isDisplayed())); 73 | } 74 | 75 | public void testLoadUserHappyCaseData() { 76 | onView(withId(R.id.tv_fullname)).check(matches(withText("John Sanchez"))); 77 | onView(withId(R.id.tv_email)).check(matches(withText("dmedina@katz.edu"))); 78 | onView(withId(R.id.tv_followers)).check(matches(withText("4523"))); 79 | } 80 | 81 | private Intent createTargetIntent() { 82 | Intent intentLaunchActivity = 83 | UserDetailsActivity.getCallingIntent(getInstrumentation().getTargetContext(), FAKE_USER_ID); 84 | 85 | return intentLaunchActivity; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/view/activity/UserListActivityTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.test.view.activity; 17 | 18 | import android.app.Fragment; 19 | import android.content.Intent; 20 | import android.test.ActivityInstrumentationTestCase2; 21 | import com.fernandocejas.android10.sample.presentation.R; 22 | import com.fernandocejas.android10.sample.presentation.view.activity.UserListActivity; 23 | 24 | import static org.hamcrest.CoreMatchers.is; 25 | import static org.hamcrest.CoreMatchers.notNullValue; 26 | import static org.hamcrest.MatcherAssert.assertThat; 27 | 28 | public class UserListActivityTest extends ActivityInstrumentationTestCase2 { 29 | 30 | private UserListActivity userListActivity; 31 | 32 | public UserListActivityTest() { 33 | super(UserListActivity.class); 34 | } 35 | 36 | @Override protected void setUp() throws Exception { 37 | super.setUp(); 38 | this.setActivityIntent(createTargetIntent()); 39 | userListActivity = getActivity(); 40 | } 41 | 42 | @Override protected void tearDown() throws Exception { 43 | super.tearDown(); 44 | } 45 | 46 | public void testContainsUserListFragment() { 47 | Fragment userListFragment = 48 | userListActivity.getFragmentManager().findFragmentById(R.id.fragmentContainer); 49 | assertThat(userListFragment, is(notNullValue())); 50 | } 51 | 52 | public void testContainsProperTitle() { 53 | String actualTitle = this.userListActivity.getTitle().toString().trim(); 54 | 55 | assertThat(actualTitle, is("Users List")); 56 | } 57 | 58 | private Intent createTargetIntent() { 59 | Intent intentLaunchActivity = 60 | UserListActivity.getCallingIntent(getInstrumentation().getTargetContext()); 61 | 62 | return intentLaunchActivity; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /presentation/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/AndroidApplication.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation; 17 | 18 | import android.app.Application; 19 | import com.fernandocejas.android10.sample.presentation.internal.di.components.ApplicationComponent; 20 | import com.fernandocejas.android10.sample.presentation.internal.di.components.DaggerApplicationComponent; 21 | import com.fernandocejas.android10.sample.presentation.internal.di.modules.ApplicationModule; 22 | import com.squareup.leakcanary.LeakCanary; 23 | 24 | /** 25 | * Android Main Application 26 | */ 27 | public class AndroidApplication extends Application { 28 | 29 | private ApplicationComponent applicationComponent; 30 | 31 | @Override public void onCreate() { 32 | super.onCreate(); 33 | this.initializeInjector(); 34 | this.initializeLeakDetection(); 35 | } 36 | 37 | private void initializeInjector() { 38 | this.applicationComponent = DaggerApplicationComponent.builder() 39 | .applicationModule(new ApplicationModule(this)) 40 | .build(); 41 | } 42 | 43 | public ApplicationComponent getApplicationComponent() { 44 | return this.applicationComponent; 45 | } 46 | 47 | private void initializeLeakDetection() { 48 | if (BuildConfig.DEBUG) { 49 | LeakCanary.install(this); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/UIThread.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation; 17 | 18 | import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread; 19 | import io.reactivex.Scheduler; 20 | import io.reactivex.android.schedulers.AndroidSchedulers; 21 | import javax.inject.Inject; 22 | import javax.inject.Singleton; 23 | 24 | /** 25 | * MainThread (UI Thread) implementation based on a {@link Scheduler} 26 | * which will execute actions on the Android UI thread 27 | */ 28 | @Singleton 29 | public class UIThread implements PostExecutionThread { 30 | 31 | @Inject 32 | UIThread() {} 33 | 34 | @Override public Scheduler getScheduler() { 35 | return AndroidSchedulers.mainThread(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/exception/ErrorMessageFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation.exception; 17 | 18 | import android.content.Context; 19 | import com.fernandocejas.android10.sample.data.exception.NetworkConnectionException; 20 | import com.fernandocejas.android10.sample.data.exception.UserNotFoundException; 21 | import com.fernandocejas.android10.sample.presentation.R; 22 | 23 | /** 24 | * Factory used to create error messages from an Exception as a condition. 25 | */ 26 | public class ErrorMessageFactory { 27 | 28 | private ErrorMessageFactory() { 29 | //empty 30 | } 31 | 32 | /** 33 | * Creates a String representing an error message. 34 | * 35 | * @param context Context needed to retrieve string resources. 36 | * @param exception An exception used as a condition to retrieve the correct error message. 37 | * @return {@link String} an error message. 38 | */ 39 | public static String create(Context context, Exception exception) { 40 | String message = context.getString(R.string.exception_message_generic); 41 | 42 | if (exception instanceof NetworkConnectionException) { 43 | message = context.getString(R.string.exception_message_no_connection); 44 | } else if (exception instanceof UserNotFoundException) { 45 | message = context.getString(R.string.exception_message_user_not_found); 46 | } 47 | 48 | return message; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/internal/di/HasComponent.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation.internal.di; 17 | 18 | /** 19 | * Interface representing a contract for clients that contains a component for dependency injection. 20 | */ 21 | public interface HasComponent { 22 | C getComponent(); 23 | } 24 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/internal/di/PerActivity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation.internal.di; 17 | 18 | import java.lang.annotation.Retention; 19 | import javax.inject.Scope; 20 | 21 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 22 | 23 | /** 24 | * A scoping annotation to permit objects whose lifetime should 25 | * conform to the life of the activity to be memorized in the 26 | * correct component. 27 | */ 28 | @Scope 29 | @Retention(RUNTIME) 30 | public @interface PerActivity {} 31 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/internal/di/components/ActivityComponent.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation.internal.di.components; 17 | 18 | import android.app.Activity; 19 | import com.fernandocejas.android10.sample.presentation.internal.di.PerActivity; 20 | import com.fernandocejas.android10.sample.presentation.internal.di.modules.ActivityModule; 21 | import dagger.Component; 22 | 23 | /** 24 | * A base component upon which fragment's components may depend. 25 | * Activity-level components should extend this component. 26 | * 27 | * Subtypes of ActivityComponent should be decorated with annotation: 28 | * {@link com.fernandocejas.android10.sample.presentation.internal.di.PerActivity} 29 | */ 30 | @PerActivity 31 | @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) 32 | interface ActivityComponent { 33 | //Exposed to sub-graphs. 34 | Activity activity(); 35 | } 36 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/internal/di/components/ApplicationComponent.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation.internal.di.components; 17 | 18 | import android.content.Context; 19 | import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread; 20 | import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; 21 | import com.fernandocejas.android10.sample.domain.repository.UserRepository; 22 | import com.fernandocejas.android10.sample.presentation.internal.di.modules.ApplicationModule; 23 | import com.fernandocejas.android10.sample.presentation.view.activity.BaseActivity; 24 | import dagger.Component; 25 | import javax.inject.Singleton; 26 | 27 | /** 28 | * A component whose lifetime is the life of the application. 29 | */ 30 | @Singleton // Constraints this component to one-per-application or unscoped bindings. 31 | @Component(modules = ApplicationModule.class) 32 | public interface ApplicationComponent { 33 | void inject(BaseActivity baseActivity); 34 | 35 | //Exposed to sub-graphs. 36 | Context context(); 37 | ThreadExecutor threadExecutor(); 38 | PostExecutionThread postExecutionThread(); 39 | UserRepository userRepository(); 40 | } 41 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/internal/di/components/UserComponent.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation.internal.di.components; 17 | 18 | import com.fernandocejas.android10.sample.presentation.internal.di.PerActivity; 19 | import com.fernandocejas.android10.sample.presentation.internal.di.modules.ActivityModule; 20 | import com.fernandocejas.android10.sample.presentation.internal.di.modules.UserModule; 21 | import com.fernandocejas.android10.sample.presentation.view.fragment.UserDetailsFragment; 22 | import com.fernandocejas.android10.sample.presentation.view.fragment.UserListFragment; 23 | import dagger.Component; 24 | 25 | /** 26 | * A scope {@link com.fernandocejas.android10.sample.presentation.internal.di.PerActivity} component. 27 | * Injects user specific Fragments. 28 | */ 29 | @PerActivity 30 | @Component(dependencies = ApplicationComponent.class, modules = {ActivityModule.class, UserModule.class}) 31 | public interface UserComponent extends ActivityComponent { 32 | void inject(UserListFragment userListFragment); 33 | void inject(UserDetailsFragment userDetailsFragment); 34 | } 35 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/internal/di/modules/ActivityModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation.internal.di.modules; 17 | 18 | import android.app.Activity; 19 | import com.fernandocejas.android10.sample.presentation.internal.di.PerActivity; 20 | import dagger.Module; 21 | import dagger.Provides; 22 | 23 | /** 24 | * A module to wrap the Activity state and expose it to the graph. 25 | */ 26 | @Module 27 | public class ActivityModule { 28 | private final Activity activity; 29 | 30 | public ActivityModule(Activity activity) { 31 | this.activity = activity; 32 | } 33 | 34 | /** 35 | * Expose the activity to dependents in the graph. 36 | */ 37 | @Provides @PerActivity Activity activity() { 38 | return this.activity; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/internal/di/modules/ApplicationModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation.internal.di.modules; 17 | 18 | import android.content.Context; 19 | import com.fernandocejas.android10.sample.data.cache.UserCache; 20 | import com.fernandocejas.android10.sample.data.cache.UserCacheImpl; 21 | import com.fernandocejas.android10.sample.data.executor.JobExecutor; 22 | import com.fernandocejas.android10.sample.data.repository.UserDataRepository; 23 | import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread; 24 | import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; 25 | import com.fernandocejas.android10.sample.domain.repository.UserRepository; 26 | import com.fernandocejas.android10.sample.presentation.AndroidApplication; 27 | import com.fernandocejas.android10.sample.presentation.UIThread; 28 | import com.fernandocejas.android10.sample.presentation.navigation.Navigator; 29 | import dagger.Module; 30 | import dagger.Provides; 31 | import javax.inject.Singleton; 32 | 33 | /** 34 | * Dagger module that provides objects which will live during the application lifecycle. 35 | */ 36 | @Module 37 | public class ApplicationModule { 38 | private final AndroidApplication application; 39 | 40 | public ApplicationModule(AndroidApplication application) { 41 | this.application = application; 42 | } 43 | 44 | @Provides @Singleton Context provideApplicationContext() { 45 | return this.application; 46 | } 47 | 48 | @Provides @Singleton ThreadExecutor provideThreadExecutor(JobExecutor jobExecutor) { 49 | return jobExecutor; 50 | } 51 | 52 | @Provides @Singleton PostExecutionThread providePostExecutionThread(UIThread uiThread) { 53 | return uiThread; 54 | } 55 | 56 | @Provides @Singleton UserCache provideUserCache(UserCacheImpl userCache) { 57 | return userCache; 58 | } 59 | 60 | @Provides @Singleton UserRepository provideUserRepository(UserDataRepository userDataRepository) { 61 | return userDataRepository; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/internal/di/modules/UserModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation.internal.di.modules; 17 | 18 | import dagger.Module; 19 | 20 | /** 21 | * Dagger module that provides user related collaborators. 22 | */ 23 | @Module 24 | public class UserModule { 25 | 26 | public UserModule() {} 27 | } 28 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/mapper/UserModelDataMapper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation.mapper; 17 | 18 | import com.fernandocejas.android10.sample.domain.User; 19 | import com.fernandocejas.android10.sample.presentation.internal.di.PerActivity; 20 | import com.fernandocejas.android10.sample.presentation.model.UserModel; 21 | import java.util.ArrayList; 22 | import java.util.Collection; 23 | import java.util.Collections; 24 | import javax.inject.Inject; 25 | 26 | /** 27 | * Mapper class used to transform {@link User} (in the domain layer) to {@link UserModel} in the 28 | * presentation layer. 29 | */ 30 | @PerActivity 31 | public class UserModelDataMapper { 32 | 33 | @Inject 34 | public UserModelDataMapper() {} 35 | 36 | /** 37 | * Transform a {@link User} into an {@link UserModel}. 38 | * 39 | * @param user Object to be transformed. 40 | * @return {@link UserModel}. 41 | */ 42 | public UserModel transform(User user) { 43 | if (user == null) { 44 | throw new IllegalArgumentException("Cannot transform a null value"); 45 | } 46 | final UserModel userModel = new UserModel(user.getUserId()); 47 | userModel.setCoverUrl(user.getCoverUrl()); 48 | userModel.setFullName(user.getFullName()); 49 | userModel.setEmail(user.getEmail()); 50 | userModel.setDescription(user.getDescription()); 51 | userModel.setFollowers(user.getFollowers()); 52 | 53 | return userModel; 54 | } 55 | 56 | /** 57 | * Transform a Collection of {@link User} into a Collection of {@link UserModel}. 58 | * 59 | * @param usersCollection Objects to be transformed. 60 | * @return List of {@link UserModel}. 61 | */ 62 | public Collection transform(Collection usersCollection) { 63 | Collection userModelsCollection; 64 | 65 | if (usersCollection != null && !usersCollection.isEmpty()) { 66 | userModelsCollection = new ArrayList<>(); 67 | for (User user : usersCollection) { 68 | userModelsCollection.add(transform(user)); 69 | } 70 | } else { 71 | userModelsCollection = Collections.emptyList(); 72 | } 73 | 74 | return userModelsCollection; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/model/UserModel.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation.model; 17 | 18 | /** 19 | * Class that represents a user in the presentation layer. 20 | */ 21 | public class UserModel { 22 | 23 | private final int userId; 24 | 25 | public UserModel(int userId) { 26 | this.userId = userId; 27 | } 28 | 29 | private String coverUrl; 30 | private String fullName; 31 | private String email; 32 | private String description; 33 | private int followers; 34 | 35 | public int getUserId() { 36 | return userId; 37 | } 38 | 39 | public String getCoverUrl() { 40 | return coverUrl; 41 | } 42 | 43 | public void setCoverUrl(String coverUrl) { 44 | this.coverUrl = coverUrl; 45 | } 46 | 47 | public String getFullName() { 48 | return fullName; 49 | } 50 | 51 | public void setFullName(String fullName) { 52 | this.fullName = fullName; 53 | } 54 | 55 | public String getEmail() { 56 | return email; 57 | } 58 | 59 | public void setEmail(String email) { 60 | this.email = email; 61 | } 62 | 63 | public String getDescription() { 64 | return description; 65 | } 66 | 67 | public void setDescription(String description) { 68 | this.description = description; 69 | } 70 | 71 | public int getFollowers() { 72 | return followers; 73 | } 74 | 75 | public void setFollowers(int followers) { 76 | this.followers = followers; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/navigation/Navigator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation.navigation; 17 | 18 | import android.content.Context; 19 | import android.content.Intent; 20 | import com.fernandocejas.android10.sample.presentation.view.activity.UserDetailsActivity; 21 | import com.fernandocejas.android10.sample.presentation.view.activity.UserListActivity; 22 | import javax.inject.Inject; 23 | import javax.inject.Singleton; 24 | 25 | /** 26 | * Class used to navigate through the application. 27 | */ 28 | @Singleton 29 | public class Navigator { 30 | 31 | @Inject 32 | public Navigator() { 33 | //empty 34 | } 35 | 36 | /** 37 | * Goes to the user list screen. 38 | * 39 | * @param context A Context needed to open the destiny activity. 40 | */ 41 | public void navigateToUserList(Context context) { 42 | if (context != null) { 43 | Intent intentToLaunch = UserListActivity.getCallingIntent(context); 44 | context.startActivity(intentToLaunch); 45 | } 46 | } 47 | 48 | /** 49 | * Goes to the user details screen. 50 | * 51 | * @param context A Context needed to open the destiny activity. 52 | */ 53 | public void navigateToUserDetails(Context context, int userId) { 54 | if (context != null) { 55 | Intent intentToLaunch = UserDetailsActivity.getCallingIntent(context, userId); 56 | context.startActivity(intentToLaunch); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/Presenter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation.presenter; 17 | 18 | /** 19 | * Interface representing a Presenter in a model view presenter (MVP) pattern. 20 | */ 21 | public interface Presenter { 22 | /** 23 | * Method that control the lifecycle of the view. It should be called in the view's 24 | * (Activity or Fragment) onResume() method. 25 | */ 26 | void resume(); 27 | 28 | /** 29 | * Method that control the lifecycle of the view. It should be called in the view's 30 | * (Activity or Fragment) onPause() method. 31 | */ 32 | void pause(); 33 | 34 | /** 35 | * Method that control the lifecycle of the view. It should be called in the view's 36 | * (Activity or Fragment) onDestroy() method. 37 | */ 38 | void destroy(); 39 | } 40 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserDetailsPresenter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation.presenter; 17 | 18 | import android.support.annotation.NonNull; 19 | import com.fernandocejas.android10.sample.domain.User; 20 | import com.fernandocejas.android10.sample.domain.exception.DefaultErrorBundle; 21 | import com.fernandocejas.android10.sample.domain.exception.ErrorBundle; 22 | import com.fernandocejas.android10.sample.domain.interactor.DefaultObserver; 23 | import com.fernandocejas.android10.sample.domain.interactor.GetUserDetails; 24 | import com.fernandocejas.android10.sample.domain.interactor.GetUserDetails.Params; 25 | import com.fernandocejas.android10.sample.presentation.exception.ErrorMessageFactory; 26 | import com.fernandocejas.android10.sample.presentation.internal.di.PerActivity; 27 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper; 28 | import com.fernandocejas.android10.sample.presentation.model.UserModel; 29 | import com.fernandocejas.android10.sample.presentation.view.UserDetailsView; 30 | import javax.inject.Inject; 31 | 32 | /** 33 | * {@link Presenter} that controls communication between views and models of the presentation 34 | * layer. 35 | */ 36 | @PerActivity 37 | public class UserDetailsPresenter implements Presenter { 38 | 39 | private UserDetailsView viewDetailsView; 40 | 41 | private final GetUserDetails getUserDetailsUseCase; 42 | private final UserModelDataMapper userModelDataMapper; 43 | 44 | @Inject 45 | public UserDetailsPresenter(GetUserDetails getUserDetailsUseCase, 46 | UserModelDataMapper userModelDataMapper) { 47 | this.getUserDetailsUseCase = getUserDetailsUseCase; 48 | this.userModelDataMapper = userModelDataMapper; 49 | } 50 | 51 | public void setView(@NonNull UserDetailsView view) { 52 | this.viewDetailsView = view; 53 | } 54 | 55 | @Override public void resume() {} 56 | 57 | @Override public void pause() {} 58 | 59 | @Override public void destroy() { 60 | this.getUserDetailsUseCase.dispose(); 61 | this.viewDetailsView = null; 62 | } 63 | 64 | /** 65 | * Initializes the presenter by showing/hiding proper views 66 | * and retrieving user details. 67 | */ 68 | public void initialize(int userId) { 69 | this.hideViewRetry(); 70 | this.showViewLoading(); 71 | this.getUserDetails(userId); 72 | } 73 | 74 | private void getUserDetails(int userId) { 75 | this.getUserDetailsUseCase.execute(new UserDetailsObserver(), Params.forUser(userId)); 76 | } 77 | 78 | private void showViewLoading() { 79 | this.viewDetailsView.showLoading(); 80 | } 81 | 82 | private void hideViewLoading() { 83 | this.viewDetailsView.hideLoading(); 84 | } 85 | 86 | private void showViewRetry() { 87 | this.viewDetailsView.showRetry(); 88 | } 89 | 90 | private void hideViewRetry() { 91 | this.viewDetailsView.hideRetry(); 92 | } 93 | 94 | private void showErrorMessage(ErrorBundle errorBundle) { 95 | String errorMessage = ErrorMessageFactory.create(this.viewDetailsView.context(), 96 | errorBundle.getException()); 97 | this.viewDetailsView.showError(errorMessage); 98 | } 99 | 100 | private void showUserDetailsInView(User user) { 101 | final UserModel userModel = this.userModelDataMapper.transform(user); 102 | this.viewDetailsView.renderUser(userModel); 103 | } 104 | 105 | private final class UserDetailsObserver extends DefaultObserver { 106 | 107 | @Override public void onComplete() { 108 | UserDetailsPresenter.this.hideViewLoading(); 109 | } 110 | 111 | @Override public void onError(Throwable e) { 112 | UserDetailsPresenter.this.hideViewLoading(); 113 | UserDetailsPresenter.this.showErrorMessage(new DefaultErrorBundle((Exception) e)); 114 | UserDetailsPresenter.this.showViewRetry(); 115 | } 116 | 117 | @Override public void onNext(User user) { 118 | UserDetailsPresenter.this.showUserDetailsInView(user); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserListPresenter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Fernando Cejas Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.fernandocejas.android10.sample.presentation.presenter; 17 | 18 | import android.support.annotation.NonNull; 19 | import com.fernandocejas.android10.sample.domain.User; 20 | import com.fernandocejas.android10.sample.domain.exception.DefaultErrorBundle; 21 | import com.fernandocejas.android10.sample.domain.exception.ErrorBundle; 22 | import com.fernandocejas.android10.sample.domain.interactor.DefaultObserver; 23 | import com.fernandocejas.android10.sample.domain.interactor.GetUserList; 24 | import com.fernandocejas.android10.sample.presentation.exception.ErrorMessageFactory; 25 | import com.fernandocejas.android10.sample.presentation.internal.di.PerActivity; 26 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper; 27 | import com.fernandocejas.android10.sample.presentation.model.UserModel; 28 | import com.fernandocejas.android10.sample.presentation.view.UserListView; 29 | import java.util.Collection; 30 | import java.util.List; 31 | import javax.inject.Inject; 32 | 33 | /** 34 | * {@link Presenter} that controls communication between views and models of the presentation 35 | * layer. 36 | */ 37 | @PerActivity 38 | public class UserListPresenter implements Presenter { 39 | 40 | private UserListView viewListView; 41 | 42 | private final GetUserList getUserListUseCase; 43 | private final UserModelDataMapper userModelDataMapper; 44 | 45 | @Inject 46 | public UserListPresenter(GetUserList getUserListUserCase, 47 | UserModelDataMapper userModelDataMapper) { 48 | this.getUserListUseCase = getUserListUserCase; 49 | this.userModelDataMapper = userModelDataMapper; 50 | } 51 | 52 | public void setView(@NonNull UserListView view) { 53 | this.viewListView = view; 54 | } 55 | 56 | @Override public void resume() {} 57 | 58 | @Override public void pause() {} 59 | 60 | @Override public void destroy() { 61 | this.getUserListUseCase.dispose(); 62 | this.viewListView = null; 63 | } 64 | 65 | /** 66 | * Initializes the presenter by start retrieving the user list. 67 | */ 68 | public void initialize() { 69 | this.loadUserList(); 70 | } 71 | 72 | /** 73 | * Loads all users. 74 | */ 75 | private void loadUserList() { 76 | this.hideViewRetry(); 77 | this.showViewLoading(); 78 | this.getUserList(); 79 | } 80 | 81 | public void onUserClicked(UserModel userModel) { 82 | this.viewListView.viewUser(userModel); 83 | } 84 | 85 | private void showViewLoading() { 86 | this.viewListView.showLoading(); 87 | } 88 | 89 | private void hideViewLoading() { 90 | this.viewListView.hideLoading(); 91 | } 92 | 93 | private void showViewRetry() { 94 | this.viewListView.showRetry(); 95 | } 96 | 97 | private void hideViewRetry() { 98 | this.viewListView.hideRetry(); 99 | } 100 | 101 | private void showErrorMessage(ErrorBundle errorBundle) { 102 | String errorMessage = ErrorMessageFactory.create(this.viewListView.context(), 103 | errorBundle.getException()); 104 | this.viewListView.showError(errorMessage); 105 | } 106 | 107 | private void showUsersCollectionInView(Collection usersCollection) { 108 | final Collection userModelsCollection = 109 | this.userModelDataMapper.transform(usersCollection); 110 | this.viewListView.renderUserList(userModelsCollection); 111 | } 112 | 113 | private void getUserList() { 114 | this.getUserListUseCase.execute(new UserListObserver(), null); 115 | } 116 | 117 | private final class UserListObserver extends DefaultObserver> { 118 | 119 | @Override public void onComplete() { 120 | UserListPresenter.this.hideViewLoading(); 121 | } 122 | 123 | @Override public void onError(Throwable e) { 124 | UserListPresenter.this.hideViewLoading(); 125 | UserListPresenter.this.showErrorMessage(new DefaultErrorBundle((Exception) e)); 126 | UserListPresenter.this.showViewRetry(); 127 | } 128 | 129 | @Override public void onNext(List users) { 130 | UserListPresenter.this.showUsersCollectionInView(users); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/LoadDataView.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 android10.org. All rights reserved. 3 | * @author Fernando Cejas (the android10 coder) 4 | */ 5 | package com.fernandocejas.android10.sample.presentation.view; 6 | 7 | import android.content.Context; 8 | 9 | /** 10 | * Interface representing a View that will use to load data. 11 | */ 12 | public interface LoadDataView { 13 | /** 14 | * Show a view with a progress bar indicating a loading process. 15 | */ 16 | void showLoading(); 17 | 18 | /** 19 | * Hide a loading view. 20 | */ 21 | void hideLoading(); 22 | 23 | /** 24 | * Show a retry view in case of an error when retrieving data. 25 | */ 26 | void showRetry(); 27 | 28 | /** 29 | * Hide a retry view shown if there was an error when retrieving data. 30 | */ 31 | void hideRetry(); 32 | 33 | /** 34 | * Show an error message 35 | * 36 | * @param message A string representing an error. 37 | */ 38 | void showError(String message); 39 | 40 | /** 41 | * Get a {@link android.content.Context}. 42 | */ 43 | Context context(); 44 | } 45 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/UserDetailsView.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 android10.org. All rights reserved. 3 | * @author Fernando Cejas (the android10 coder) 4 | */ 5 | package com.fernandocejas.android10.sample.presentation.view; 6 | 7 | import com.fernandocejas.android10.sample.presentation.model.UserModel; 8 | 9 | /** 10 | * Interface representing a View in a model view presenter (MVP) pattern. 11 | * In this case is used as a view representing a user profile. 12 | */ 13 | public interface UserDetailsView extends LoadDataView { 14 | /** 15 | * Render a user in the UI. 16 | * 17 | * @param user The {@link UserModel} that will be shown. 18 | */ 19 | void renderUser(UserModel user); 20 | } 21 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/UserListView.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 android10.org. All rights reserved. 3 | * @author Fernando Cejas (the android10 coder) 4 | */ 5 | package com.fernandocejas.android10.sample.presentation.view; 6 | 7 | import com.fernandocejas.android10.sample.presentation.model.UserModel; 8 | import java.util.Collection; 9 | 10 | /** 11 | * Interface representing a View in a model view presenter (MVP) pattern. 12 | * In this case is used as a view representing a list of {@link UserModel}. 13 | */ 14 | public interface UserListView extends LoadDataView { 15 | /** 16 | * Render a user list in the UI. 17 | * 18 | * @param userModelCollection The collection of {@link UserModel} that will be shown. 19 | */ 20 | void renderUserList(Collection userModelCollection); 21 | 22 | /** 23 | * View a {@link UserModel} profile/details. 24 | * 25 | * @param userModel The user that will be shown. 26 | */ 27 | void viewUser(UserModel userModel); 28 | } 29 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/activity/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.fernandocejas.android10.sample.presentation.view.activity; 2 | 3 | import android.app.Activity; 4 | import android.app.Fragment; 5 | import android.app.FragmentTransaction; 6 | import android.os.Bundle; 7 | import com.fernandocejas.android10.sample.presentation.AndroidApplication; 8 | import com.fernandocejas.android10.sample.presentation.internal.di.components.ApplicationComponent; 9 | import com.fernandocejas.android10.sample.presentation.internal.di.modules.ActivityModule; 10 | import com.fernandocejas.android10.sample.presentation.navigation.Navigator; 11 | import javax.inject.Inject; 12 | 13 | /** 14 | * Base {@link android.app.Activity} class for every Activity in this application. 15 | */ 16 | public abstract class BaseActivity extends Activity { 17 | 18 | @Inject Navigator navigator; 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | this.getApplicationComponent().inject(this); 24 | } 25 | 26 | /** 27 | * Adds a {@link Fragment} to this activity's layout. 28 | * 29 | * @param containerViewId The container view to where add the fragment. 30 | * @param fragment The fragment to be added. 31 | */ 32 | protected void addFragment(int containerViewId, Fragment fragment) { 33 | final FragmentTransaction fragmentTransaction = this.getFragmentManager().beginTransaction(); 34 | fragmentTransaction.add(containerViewId, fragment); 35 | fragmentTransaction.commit(); 36 | } 37 | 38 | /** 39 | * Get the Main Application component for dependency injection. 40 | * 41 | * @return {@link com.fernandocejas.android10.sample.presentation.internal.di.components.ApplicationComponent} 42 | */ 43 | protected ApplicationComponent getApplicationComponent() { 44 | return ((AndroidApplication) getApplication()).getApplicationComponent(); 45 | } 46 | 47 | /** 48 | * Get an Activity module for dependency injection. 49 | * 50 | * @return {@link com.fernandocejas.android10.sample.presentation.internal.di.modules.ActivityModule} 51 | */ 52 | protected ActivityModule getActivityModule() { 53 | return new ActivityModule(this); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.fernandocejas.android10.sample.presentation.view.activity; 2 | 3 | import android.os.Bundle; 4 | import android.widget.Button; 5 | import butterknife.Bind; 6 | import butterknife.ButterKnife; 7 | import butterknife.OnClick; 8 | import com.fernandocejas.android10.sample.presentation.R; 9 | 10 | /** 11 | * Main application screen. This is the app entry point. 12 | */ 13 | public class MainActivity extends BaseActivity { 14 | 15 | @Bind(R.id.btn_LoadData) Button btn_LoadData; 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_main); 21 | ButterKnife.bind(this); 22 | } 23 | 24 | /** 25 | * Goes to the user list screen. 26 | */ 27 | @OnClick(R.id.btn_LoadData) 28 | void navigateToUserList() { 29 | this.navigator.navigateToUserList(this); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/activity/UserDetailsActivity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 android10.org. All rights reserved. 3 | * 4 | * @author Fernando Cejas (the android10 coder) 5 | */ 6 | package com.fernandocejas.android10.sample.presentation.view.activity; 7 | 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.os.Bundle; 11 | import android.view.Window; 12 | import com.fernandocejas.android10.sample.presentation.R; 13 | import com.fernandocejas.android10.sample.presentation.internal.di.HasComponent; 14 | import com.fernandocejas.android10.sample.presentation.internal.di.components.DaggerUserComponent; 15 | import com.fernandocejas.android10.sample.presentation.internal.di.components.UserComponent; 16 | import com.fernandocejas.android10.sample.presentation.view.fragment.UserDetailsFragment; 17 | 18 | /** 19 | * Activity that shows details of a certain user. 20 | */ 21 | public class UserDetailsActivity extends BaseActivity implements HasComponent { 22 | 23 | private static final String INTENT_EXTRA_PARAM_USER_ID = "org.android10.INTENT_PARAM_USER_ID"; 24 | private static final String INSTANCE_STATE_PARAM_USER_ID = "org.android10.STATE_PARAM_USER_ID"; 25 | 26 | public static Intent getCallingIntent(Context context, int userId) { 27 | Intent callingIntent = new Intent(context, UserDetailsActivity.class); 28 | callingIntent.putExtra(INTENT_EXTRA_PARAM_USER_ID, userId); 29 | return callingIntent; 30 | } 31 | 32 | private int userId; 33 | private UserComponent userComponent; 34 | 35 | @Override protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 38 | setContentView(R.layout.activity_layout); 39 | 40 | this.initializeActivity(savedInstanceState); 41 | this.initializeInjector(); 42 | } 43 | 44 | @Override protected void onSaveInstanceState(Bundle outState) { 45 | if (outState != null) { 46 | outState.putInt(INSTANCE_STATE_PARAM_USER_ID, this.userId); 47 | } 48 | super.onSaveInstanceState(outState); 49 | } 50 | 51 | /** 52 | * Initializes this activity. 53 | */ 54 | private void initializeActivity(Bundle savedInstanceState) { 55 | if (savedInstanceState == null) { 56 | this.userId = getIntent().getIntExtra(INTENT_EXTRA_PARAM_USER_ID, -1); 57 | addFragment(R.id.fragmentContainer, UserDetailsFragment.forUser(userId)); 58 | } else { 59 | this.userId = savedInstanceState.getInt(INSTANCE_STATE_PARAM_USER_ID); 60 | } 61 | } 62 | 63 | private void initializeInjector() { 64 | this.userComponent = DaggerUserComponent.builder() 65 | .applicationComponent(getApplicationComponent()) 66 | .activityModule(getActivityModule()) 67 | .build(); 68 | } 69 | 70 | @Override public UserComponent getComponent() { 71 | return userComponent; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/activity/UserListActivity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 android10.org. All rights reserved. 3 | * 4 | * @author Fernando Cejas (the android10 coder) 5 | */ 6 | package com.fernandocejas.android10.sample.presentation.view.activity; 7 | 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.os.Bundle; 11 | import android.view.Window; 12 | import com.fernandocejas.android10.sample.presentation.R; 13 | import com.fernandocejas.android10.sample.presentation.internal.di.HasComponent; 14 | import com.fernandocejas.android10.sample.presentation.internal.di.components.DaggerUserComponent; 15 | import com.fernandocejas.android10.sample.presentation.internal.di.components.UserComponent; 16 | import com.fernandocejas.android10.sample.presentation.model.UserModel; 17 | import com.fernandocejas.android10.sample.presentation.view.fragment.UserListFragment; 18 | 19 | /** 20 | * Activity that shows a list of Users. 21 | */ 22 | public class UserListActivity extends BaseActivity implements HasComponent, 23 | UserListFragment.UserListListener { 24 | 25 | public static Intent getCallingIntent(Context context) { 26 | return new Intent(context, UserListActivity.class); 27 | } 28 | 29 | private UserComponent userComponent; 30 | 31 | @Override protected void onCreate(Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 34 | setContentView(R.layout.activity_layout); 35 | 36 | this.initializeInjector(); 37 | if (savedInstanceState == null) { 38 | addFragment(R.id.fragmentContainer, new UserListFragment()); 39 | } 40 | } 41 | 42 | private void initializeInjector() { 43 | this.userComponent = DaggerUserComponent.builder() 44 | .applicationComponent(getApplicationComponent()) 45 | .activityModule(getActivityModule()) 46 | .build(); 47 | } 48 | 49 | @Override public UserComponent getComponent() { 50 | return userComponent; 51 | } 52 | 53 | @Override public void onUserClicked(UserModel userModel) { 54 | this.navigator.navigateToUserDetails(this, userModel.getUserId()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/adapter/UsersAdapter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 android10.org. All rights reserved. 3 | * @author Fernando Cejas (the android10 coder) 4 | */ 5 | package com.fernandocejas.android10.sample.presentation.view.adapter; 6 | 7 | import android.content.Context; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.TextView; 13 | import butterknife.Bind; 14 | import butterknife.ButterKnife; 15 | import com.fernandocejas.android10.sample.presentation.R; 16 | import com.fernandocejas.android10.sample.presentation.model.UserModel; 17 | import java.util.Collection; 18 | import java.util.Collections; 19 | import java.util.List; 20 | import javax.inject.Inject; 21 | 22 | /** 23 | * Adaptar that manages a collection of {@link UserModel}. 24 | */ 25 | public class UsersAdapter extends RecyclerView.Adapter { 26 | 27 | public interface OnItemClickListener { 28 | void onUserItemClicked(UserModel userModel); 29 | } 30 | 31 | private List usersCollection; 32 | private final LayoutInflater layoutInflater; 33 | 34 | private OnItemClickListener onItemClickListener; 35 | 36 | @Inject 37 | UsersAdapter(Context context) { 38 | this.layoutInflater = 39 | (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 40 | this.usersCollection = Collections.emptyList(); 41 | } 42 | 43 | @Override public int getItemCount() { 44 | return (this.usersCollection != null) ? this.usersCollection.size() : 0; 45 | } 46 | 47 | @Override public UserViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 48 | final View view = this.layoutInflater.inflate(R.layout.row_user, parent, false); 49 | return new UserViewHolder(view); 50 | } 51 | 52 | @Override public void onBindViewHolder(UserViewHolder holder, final int position) { 53 | final UserModel userModel = this.usersCollection.get(position); 54 | holder.textViewTitle.setText(userModel.getFullName()); 55 | holder.itemView.setOnClickListener(new View.OnClickListener() { 56 | @Override public void onClick(View v) { 57 | if (UsersAdapter.this.onItemClickListener != null) { 58 | UsersAdapter.this.onItemClickListener.onUserItemClicked(userModel); 59 | } 60 | } 61 | }); 62 | } 63 | 64 | @Override public long getItemId(int position) { 65 | return position; 66 | } 67 | 68 | public void setUsersCollection(Collection usersCollection) { 69 | this.validateUsersCollection(usersCollection); 70 | this.usersCollection = (List) usersCollection; 71 | this.notifyDataSetChanged(); 72 | } 73 | 74 | public void setOnItemClickListener (OnItemClickListener onItemClickListener) { 75 | this.onItemClickListener = onItemClickListener; 76 | } 77 | 78 | private void validateUsersCollection(Collection usersCollection) { 79 | if (usersCollection == null) { 80 | throw new IllegalArgumentException("The list cannot be null"); 81 | } 82 | } 83 | 84 | static class UserViewHolder extends RecyclerView.ViewHolder { 85 | @Bind(R.id.title) TextView textViewTitle; 86 | 87 | UserViewHolder(View itemView) { 88 | super(itemView); 89 | ButterKnife.bind(this, itemView); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/adapter/UsersLayoutManager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 android10.org. All rights reserved. 3 | * @author Fernando Cejas (the android10 coder) 4 | */ 5 | package com.fernandocejas.android10.sample.presentation.view.adapter; 6 | 7 | import android.content.Context; 8 | import android.support.v7.widget.LinearLayoutManager; 9 | 10 | /** 11 | * Layout manager to position items inside a {@link android.support.v7.widget.RecyclerView}. 12 | */ 13 | public class UsersLayoutManager extends LinearLayoutManager { 14 | public UsersLayoutManager(Context context) { 15 | super(context); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/BaseFragment.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 android10.org. All rights reserved. 3 | * 4 | * @author Fernando Cejas (the android10 coder) 5 | */ 6 | package com.fernandocejas.android10.sample.presentation.view.fragment; 7 | 8 | import android.app.Fragment; 9 | import android.widget.Toast; 10 | import com.fernandocejas.android10.sample.presentation.internal.di.HasComponent; 11 | 12 | /** 13 | * Base {@link android.app.Fragment} class for every fragment in this application. 14 | */ 15 | public abstract class BaseFragment extends Fragment { 16 | /** 17 | * Shows a {@link android.widget.Toast} message. 18 | * 19 | * @param message An string representing a message to be shown. 20 | */ 21 | protected void showToastMessage(String message) { 22 | Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show(); 23 | } 24 | 25 | /** 26 | * Gets a component for dependency injection by its type. 27 | */ 28 | @SuppressWarnings("unchecked") 29 | protected C getComponent(Class componentType) { 30 | return componentType.cast(((HasComponent) getActivity()).getComponent()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/UserDetailsFragment.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 android10.org. All rights reserved. 3 | * @author Fernando Cejas (the android10 coder) 4 | */ 5 | package com.fernandocejas.android10.sample.presentation.view.fragment; 6 | 7 | import android.content.Context; 8 | import android.os.Bundle; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.Button; 13 | import android.widget.RelativeLayout; 14 | import android.widget.TextView; 15 | import butterknife.Bind; 16 | import butterknife.ButterKnife; 17 | import butterknife.OnClick; 18 | import com.fernandocejas.android10.sample.presentation.R; 19 | import com.fernandocejas.android10.sample.presentation.internal.di.components.UserComponent; 20 | import com.fernandocejas.android10.sample.presentation.model.UserModel; 21 | import com.fernandocejas.android10.sample.presentation.presenter.UserDetailsPresenter; 22 | import com.fernandocejas.android10.sample.presentation.view.UserDetailsView; 23 | import com.fernandocejas.android10.sample.presentation.view.component.AutoLoadImageView; 24 | import com.fernandocejas.arrow.checks.Preconditions; 25 | import javax.inject.Inject; 26 | 27 | /** 28 | * Fragment that shows details of a certain user. 29 | */ 30 | public class UserDetailsFragment extends BaseFragment implements UserDetailsView { 31 | private static final String PARAM_USER_ID = "param_user_id"; 32 | 33 | @Inject UserDetailsPresenter userDetailsPresenter; 34 | 35 | @Bind(R.id.iv_cover) AutoLoadImageView iv_cover; 36 | @Bind(R.id.tv_fullname) TextView tv_fullname; 37 | @Bind(R.id.tv_email) TextView tv_email; 38 | @Bind(R.id.tv_followers) TextView tv_followers; 39 | @Bind(R.id.tv_description) TextView tv_description; 40 | @Bind(R.id.rl_progress) RelativeLayout rl_progress; 41 | @Bind(R.id.rl_retry) RelativeLayout rl_retry; 42 | @Bind(R.id.bt_retry) Button bt_retry; 43 | 44 | public static UserDetailsFragment forUser(int userId) { 45 | final UserDetailsFragment userDetailsFragment = new UserDetailsFragment(); 46 | final Bundle arguments = new Bundle(); 47 | arguments.putInt(PARAM_USER_ID, userId); 48 | userDetailsFragment.setArguments(arguments); 49 | return userDetailsFragment; 50 | } 51 | 52 | public UserDetailsFragment() { 53 | setRetainInstance(true); 54 | } 55 | 56 | @Override public void onCreate(Bundle savedInstanceState) { 57 | super.onCreate(savedInstanceState); 58 | this.getComponent(UserComponent.class).inject(this); 59 | } 60 | 61 | @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, 62 | Bundle savedInstanceState) { 63 | final View fragmentView = inflater.inflate(R.layout.fragment_user_details, container, false); 64 | ButterKnife.bind(this, fragmentView); 65 | return fragmentView; 66 | } 67 | 68 | @Override public void onViewCreated(View view, Bundle savedInstanceState) { 69 | super.onViewCreated(view, savedInstanceState); 70 | this.userDetailsPresenter.setView(this); 71 | if (savedInstanceState == null) { 72 | this.loadUserDetails(); 73 | } 74 | } 75 | 76 | @Override public void onResume() { 77 | super.onResume(); 78 | this.userDetailsPresenter.resume(); 79 | } 80 | 81 | @Override public void onPause() { 82 | super.onPause(); 83 | this.userDetailsPresenter.pause(); 84 | } 85 | 86 | @Override public void onDestroyView() { 87 | super.onDestroyView(); 88 | ButterKnife.unbind(this); 89 | } 90 | 91 | @Override public void onDestroy() { 92 | super.onDestroy(); 93 | this.userDetailsPresenter.destroy(); 94 | } 95 | 96 | @Override public void renderUser(UserModel user) { 97 | if (user != null) { 98 | this.iv_cover.setImageUrl(user.getCoverUrl()); 99 | this.tv_fullname.setText(user.getFullName()); 100 | this.tv_email.setText(user.getEmail()); 101 | this.tv_followers.setText(String.valueOf(user.getFollowers())); 102 | this.tv_description.setText(user.getDescription()); 103 | } 104 | } 105 | 106 | @Override public void showLoading() { 107 | this.rl_progress.setVisibility(View.VISIBLE); 108 | this.getActivity().setProgressBarIndeterminateVisibility(true); 109 | } 110 | 111 | @Override public void hideLoading() { 112 | this.rl_progress.setVisibility(View.GONE); 113 | this.getActivity().setProgressBarIndeterminateVisibility(false); 114 | } 115 | 116 | @Override public void showRetry() { 117 | this.rl_retry.setVisibility(View.VISIBLE); 118 | } 119 | 120 | @Override public void hideRetry() { 121 | this.rl_retry.setVisibility(View.GONE); 122 | } 123 | 124 | @Override public void showError(String message) { 125 | this.showToastMessage(message); 126 | } 127 | 128 | @Override public Context context() { 129 | return getActivity().getApplicationContext(); 130 | } 131 | 132 | /** 133 | * Load user details. 134 | */ 135 | private void loadUserDetails() { 136 | if (this.userDetailsPresenter != null) { 137 | this.userDetailsPresenter.initialize(currentUserId()); 138 | } 139 | } 140 | 141 | /** 142 | * Get current user id from fragments arguments. 143 | */ 144 | private int currentUserId() { 145 | final Bundle arguments = getArguments(); 146 | Preconditions.checkNotNull(arguments, "Fragment arguments cannot be null"); 147 | return arguments.getInt(PARAM_USER_ID); 148 | } 149 | 150 | @OnClick(R.id.bt_retry) 151 | void onButtonRetryClick() { 152 | UserDetailsFragment.this.loadUserDetails(); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/UserListFragment.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2014 android10.org. All rights reserved. 3 | * 4 | * @author Fernando Cejas (the android10 coder) 5 | */ 6 | package com.fernandocejas.android10.sample.presentation.view.fragment; 7 | 8 | import android.app.Activity; 9 | import android.content.Context; 10 | import android.os.Bundle; 11 | import android.support.v7.widget.RecyclerView; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.Button; 16 | import android.widget.RelativeLayout; 17 | import butterknife.Bind; 18 | import butterknife.ButterKnife; 19 | import butterknife.OnClick; 20 | import com.fernandocejas.android10.sample.presentation.R; 21 | import com.fernandocejas.android10.sample.presentation.internal.di.components.UserComponent; 22 | import com.fernandocejas.android10.sample.presentation.model.UserModel; 23 | import com.fernandocejas.android10.sample.presentation.presenter.UserListPresenter; 24 | import com.fernandocejas.android10.sample.presentation.view.UserListView; 25 | import com.fernandocejas.android10.sample.presentation.view.adapter.UsersAdapter; 26 | import com.fernandocejas.android10.sample.presentation.view.adapter.UsersLayoutManager; 27 | import java.util.Collection; 28 | import javax.inject.Inject; 29 | 30 | /** 31 | * Fragment that shows a list of Users. 32 | */ 33 | public class UserListFragment extends BaseFragment implements UserListView { 34 | 35 | /** 36 | * Interface for listening user list events. 37 | */ 38 | public interface UserListListener { 39 | void onUserClicked(final UserModel userModel); 40 | } 41 | 42 | @Inject UserListPresenter userListPresenter; 43 | @Inject UsersAdapter usersAdapter; 44 | 45 | @Bind(R.id.rv_users) RecyclerView rv_users; 46 | @Bind(R.id.rl_progress) RelativeLayout rl_progress; 47 | @Bind(R.id.rl_retry) RelativeLayout rl_retry; 48 | @Bind(R.id.bt_retry) Button bt_retry; 49 | 50 | private UserListListener userListListener; 51 | 52 | public UserListFragment() { 53 | setRetainInstance(true); 54 | } 55 | 56 | @Override public void onAttach(Activity activity) { 57 | super.onAttach(activity); 58 | if (activity instanceof UserListListener) { 59 | this.userListListener = (UserListListener) activity; 60 | } 61 | } 62 | 63 | @Override public void onCreate(Bundle savedInstanceState) { 64 | super.onCreate(savedInstanceState); 65 | this.getComponent(UserComponent.class).inject(this); 66 | } 67 | 68 | @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, 69 | Bundle savedInstanceState) { 70 | final View fragmentView = inflater.inflate(R.layout.fragment_user_list, container, false); 71 | ButterKnife.bind(this, fragmentView); 72 | setupRecyclerView(); 73 | return fragmentView; 74 | } 75 | 76 | @Override public void onViewCreated(View view, Bundle savedInstanceState) { 77 | super.onViewCreated(view, savedInstanceState); 78 | this.userListPresenter.setView(this); 79 | if (savedInstanceState == null) { 80 | this.loadUserList(); 81 | } 82 | } 83 | 84 | @Override public void onResume() { 85 | super.onResume(); 86 | this.userListPresenter.resume(); 87 | } 88 | 89 | @Override public void onPause() { 90 | super.onPause(); 91 | this.userListPresenter.pause(); 92 | } 93 | 94 | @Override public void onDestroyView() { 95 | super.onDestroyView(); 96 | rv_users.setAdapter(null); 97 | ButterKnife.unbind(this); 98 | } 99 | 100 | @Override public void onDestroy() { 101 | super.onDestroy(); 102 | this.userListPresenter.destroy(); 103 | } 104 | 105 | @Override public void onDetach() { 106 | super.onDetach(); 107 | this.userListListener = null; 108 | } 109 | 110 | @Override public void showLoading() { 111 | this.rl_progress.setVisibility(View.VISIBLE); 112 | this.getActivity().setProgressBarIndeterminateVisibility(true); 113 | } 114 | 115 | @Override public void hideLoading() { 116 | this.rl_progress.setVisibility(View.GONE); 117 | this.getActivity().setProgressBarIndeterminateVisibility(false); 118 | } 119 | 120 | @Override public void showRetry() { 121 | this.rl_retry.setVisibility(View.VISIBLE); 122 | } 123 | 124 | @Override public void hideRetry() { 125 | this.rl_retry.setVisibility(View.GONE); 126 | } 127 | 128 | @Override public void renderUserList(Collection userModelCollection) { 129 | if (userModelCollection != null) { 130 | this.usersAdapter.setUsersCollection(userModelCollection); 131 | } 132 | } 133 | 134 | @Override public void viewUser(UserModel userModel) { 135 | if (this.userListListener != null) { 136 | this.userListListener.onUserClicked(userModel); 137 | } 138 | } 139 | 140 | @Override public void showError(String message) { 141 | this.showToastMessage(message); 142 | } 143 | 144 | @Override public Context context() { 145 | return this.getActivity().getApplicationContext(); 146 | } 147 | 148 | private void setupRecyclerView() { 149 | this.usersAdapter.setOnItemClickListener(onItemClickListener); 150 | this.rv_users.setLayoutManager(new UsersLayoutManager(context())); 151 | this.rv_users.setAdapter(usersAdapter); 152 | } 153 | 154 | /** 155 | * Loads all users. 156 | */ 157 | private void loadUserList() { 158 | this.userListPresenter.initialize(); 159 | } 160 | 161 | @OnClick(R.id.bt_retry) void onButtonRetryClick() { 162 | UserListFragment.this.loadUserList(); 163 | } 164 | 165 | private UsersAdapter.OnItemClickListener onItemClickListener = 166 | new UsersAdapter.OnItemClickListener() { 167 | @Override public void onUserItemClicked(UserModel userModel) { 168 | if (UserListFragment.this.userListPresenter != null && userModel != null) { 169 | UserListFragment.this.userListPresenter.onUserClicked(userModel); 170 | } 171 | } 172 | }; 173 | } 174 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/android10/Android-CleanArchitecture/8ed4222c537e40db05e9e685bbc253fafb6b8e1f/presentation/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /presentation/src/main/res/drawable-hdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/android10/Android-CleanArchitecture/8ed4222c537e40db05e9e685bbc253fafb6b8e1f/presentation/src/main/res/drawable-hdpi/logo.png -------------------------------------------------------------------------------- /presentation/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/android10/Android-CleanArchitecture/8ed4222c537e40db05e9e685bbc253fafb6b8e1f/presentation/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /presentation/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/android10/Android-CleanArchitecture/8ed4222c537e40db05e9e685bbc253fafb6b8e1f/presentation/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /presentation/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/android10/Android-CleanArchitecture/8ed4222c537e40db05e9e685bbc253fafb6b8e1f/presentation/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /presentation/src/main/res/drawable/selector_item_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/activity_layout.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 19 | 20 | 26 | 27 |