├── .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 │ │ │ └── JsonSerializer.java │ │ ├── datasource │ │ ├── CloudUserDataStore.java │ │ ├── DiskUserDataStore.java │ │ ├── UserDataStore.java │ │ └── UserDataStoreFactory.java │ │ ├── dto │ │ └── User.java │ │ ├── entity │ │ ├── UserEntity.java │ │ └── mapper │ │ │ ├── UserEntityDataMapper.java │ │ │ └── UserEntityJsonMapper.java │ │ ├── exception │ │ ├── DefaultErrorBundle.java │ │ ├── ErrorBundle.java │ │ ├── NetworkConnectionException.java │ │ ├── RepositoryErrorBundle.java │ │ └── UserNotFoundException.java │ │ ├── executor │ │ ├── JobExecutor.java │ │ ├── PostExecutionThread.java │ │ ├── ThreadExecutor.java │ │ └── UIThread.java │ │ └── net │ │ ├── ApiConnection.java │ │ ├── RestApi.java │ │ └── RestApiImpl.java │ └── test │ └── java │ └── com │ └── fernandocejas │ └── android10 │ └── sample │ └── data │ ├── ApplicationStub.java │ ├── ApplicationTestCase.java │ ├── cache │ ├── FileManagerTest.java │ └── serializer │ │ └── JsonSerializerTest.java │ ├── datasource │ ├── CloudUserDataStoreTest.java │ ├── DiskUserDataStoreTest.java │ └── UserDataStoreFactoryTest.java │ ├── dto │ └── UserTest.java │ ├── entity │ └── mapper │ │ ├── UserEntityDataMapperTest.java │ │ └── UserEntityJsonMapperTest.java │ └── exception │ ├── DefaultErrorBundleTest.java │ └── RepositoryErrorBundleTest.java ├── domain ├── build.gradle └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── fernandocejas │ │ └── android10 │ │ └── sample │ │ └── domain │ │ └── interactor │ │ ├── DefaultSubscriber.java │ │ ├── GetUserDetails.java │ │ ├── GetUserList.java │ │ ├── UseCase.java │ │ └── repository │ │ ├── UserDataRepository.java │ │ └── UserRepository.java │ └── test │ └── java │ └── com │ └── fernandocejas │ └── android10 │ └── sample │ └── domain │ └── interactor │ ├── GetUserDetailsTest.java │ ├── GetUserListTest.java │ ├── UseCaseTest.java │ └── repository │ └── UserDataRepositoryTest.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 │ │ ├── view │ │ └── activity │ │ │ ├── UserDetailsActivityTest.java │ │ │ └── UserListActivityTest.java │ │ └── viewmodel │ │ ├── UserDetailsViewModelTest.java │ │ └── UserListViewModelTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── fernandocejas │ │ └── android10 │ │ └── sample │ │ └── presentation │ │ ├── AndroidApplication.java │ │ ├── exception │ │ └── ErrorMessageFactory.java │ │ ├── mapper │ │ └── UserModelDataMapper.java │ │ ├── model │ │ └── UserModel.java │ │ ├── navigation │ │ └── ActivityNavigator.java │ │ ├── view │ │ ├── activity │ │ │ ├── BaseActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── UserDetailsActivity.java │ │ │ └── UserListActivity.java │ │ ├── adapter │ │ │ ├── UsersAdapter.java │ │ │ └── UsersLayoutManager.java │ │ ├── fragment │ │ │ ├── BaseFragment.java │ │ │ ├── UserDetailsFragment.java │ │ │ └── UserListFragment.java │ │ └── widget │ │ │ └── AutoLoadImageView.java │ │ └── viewmodel │ │ ├── HomeViewModel.java │ │ ├── LoadingViewModel.java │ │ ├── UserDetailsViewModel.java │ │ ├── UserListViewModel.java │ │ └── ViewModel.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_user_details.xml │ ├── fragment_user_details.xml │ ├── fragment_user_list.xml │ ├── home_activity.xml │ ├── row_user.xml │ ├── user_list_activity.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 | /npm-debug.log 42 | *.swp 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: oraclejdk7 3 | 4 | android: 5 | components: 6 | - build-tools-21.1.2 7 | - android-19 8 | 9 | script: 10 | ./gradlew build 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MVVM_Android-CleanArchitecture 2 | ========================= 3 | 4 | 此项目由 [Android-CleanArchitecture](https://github.com/android10/Android-CleanArchitecture) fork 而来, 展现层(Presentation tier)重构成由 MVVM 模式实现,并去除 di(Dagger) 和 lambda。 5 | 6 | 项目对应博客文章:[MVVM_Android-CleanArchitecture](http://rocko.xyz/2015/11/07/MVVM_Android-CleanArchitecture/) 7 | 8 | 9 | Clean architecture 10 | ----------------- 11 | ![Clean Architecture](http://rocko-blog.qiniudn.com/MVVM_Android-CleanArchitecture-2.png) 12 | 13 | Architectural approach 14 | ----------------- 15 | ![MVVM_Clean-Architecture 分层结构](http://rocko-blog.qiniudn.com/MVVM_Android-CleanArchitecture-3.png) 16 | 17 | Architectural reactive approach 18 | ----------------- 19 | ![MVVM_Clean-Architecture put all 应用在一起](http://rocko-blog.qiniudn.com/MVVM_Android-CleanArchitecture-4.png) 20 | 21 | Local Development 22 | ----------------- 23 | 24 | Here are some useful Gradle/adb commands for executing this example: 25 | 26 | * `./gradlew clean build` - Build the entire example and execute unit and integration tests plus lint check. 27 | * `./gradlew installDebug` - Install the debug apk on the current connected device. 28 | * `./gradlew runUnitTests` - Execute domain and data layer tests (both unit and integration). 29 | * `./gradlew runAcceptanceTests` - Execute espresso and instrumentation acceptance tests. 30 | 31 | 32 | License 33 | -------- 34 | ``` 35 | Copyright 2014 Fernando Cejas 36 | 37 | Licensed under the Apache License, Version 2.0 (the "License"); 38 | you may not use this file except in compliance with the License. 39 | You may obtain a copy of the License at 40 | 41 | http://www.apache.org/licenses/LICENSE-2.0 42 | 43 | Unless required by applicable law or agreed to in writing, software 44 | distributed under the License is distributed on an "AS IS" BASIS, 45 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 46 | See the License for the specific language governing permissions and 47 | limitations under the License. 48 | ``` -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply from: 'buildsystem/ci.gradle' 2 | apply from: 'buildsystem/dependencies.gradle' 3 | 4 | buildscript { 5 | repositories { 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:1.3.0' 10 | classpath 'com.android.databinding:dataBinder:1.0-rc1' 11 | } 12 | } 13 | 14 | allprojects { 15 | ext { 16 | androidApplicationId = 'com.fernanependocejas.android10.sample.presentation' 17 | androidVersionCode = 1 18 | androidVersionName = "1.0" 19 | 20 | testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" 21 | testApplicationId = 'com.fernandocejas.android10.sample.presentation.test' 22 | } 23 | } 24 | 25 | task wrapper(type: Wrapper) { 26 | description 'Creates the gradle wrapper.' 27 | gradleVersion '2.9' 28 | } 29 | 30 | task runDomainUnitTests(dependsOn: [':domain:test']) { 31 | description 'Run unit tests for the domain layer.' 32 | } 33 | 34 | task runDataUnitTests(dependsOn: [':data:cleanTestDebug', ':data:testDebug']) { 35 | description 'Run unit tests for the data layer.' 36 | } 37 | 38 | task runUnitTests(dependsOn: ['runDomainUnitTests', 'runDataUnitTests']) { 39 | description 'Run unit tests for both domain and data layers.' 40 | } 41 | 42 | task runAcceptanceTests(dependsOn: [':presentation:connectedAndroidTest']) { 43 | description 'Run application acceptance tests.' 44 | } 45 | 46 | -------------------------------------------------------------------------------- /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/zhengxiaopeng/MVVM_Android-CleanArchitecture/ab6b934fdb93bf47daa37c7c6d541497f6d4d951/buildsystem/debug.keystore -------------------------------------------------------------------------------- /buildsystem/dependencies.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | jcenter() 4 | } 5 | } 6 | 7 | ext { 8 | //Android 9 | androidBuildToolsVersion = "23.0.1" 10 | androidMinSdkVersion = 15 11 | androidTargetSdkVersion = 23 12 | androidCompileSdkVersion = 23 13 | 14 | //Libraries 15 | recyclerViewVersion = '23.0.1' 16 | rxJavaVersion = '1.0.14' 17 | rxAndroidVersion = '1.0.1' 18 | gsonVersion = '2.3' 19 | okHttpVersion = '2.5.0' 20 | androidAnnotationsVersion = '23.0.1' 21 | 22 | //Testing 23 | robolectricVersion = '3.0' 24 | jUnitVersion = '4.12' 25 | assertJVersion = '1.7.1' 26 | mockitoVersion = '1.9.5' 27 | dexmakerVersion = '1.0' 28 | runnerVersion = "0.4.1" 29 | rulesVersion = "0.4.1" 30 | espressoVersion = "2.2.1" 31 | espressoIntentVersion = espressoVersion 32 | espressoContrib = espressoVersion 33 | 34 | presentationDependencies = [ 35 | recyclerView: "com.android.support:recyclerview-v7:${recyclerViewVersion}", 36 | rxJava: "io.reactivex:rxjava:${rxJavaVersion}", 37 | rxAndroid: "io.reactivex:rxandroid:${rxAndroidVersion}", 38 | ] 39 | 40 | presentationTestDependencies = [ 41 | mockito: "org.mockito:mockito-core:${mockitoVersion}", 42 | dexmaker: "com.google.dexmaker:dexmaker:${dexmakerVersion}", 43 | dexmakerMockito: "com.google.dexmaker:dexmaker-mockito:${dexmakerVersion}", 44 | espresso: "com.android.support.test.espresso:espresso-core:${espressoVersion}", 45 | espressoIntent: "com.android.support.test.espresso:espresso-intents:${espressoIntentVersion}", 46 | espressoContrib: "com.android.support.test.espresso:espresso-contrib:${espressoContrib}", 47 | runner: "com.android.support.test:runner:${runnerVersion}", 48 | rules: "com.android.support.test:rules:${rulesVersion}", 49 | // Force usage of support annotations in the test app, since it is internally used by the runner module. 50 | androidAnnotations: "com.android.support:support-annotations:${androidAnnotationsVersion}", 51 | 52 | ] 53 | 54 | domainDependencies = [ 55 | rxJava: "io.reactivex:rxjava:${rxJavaVersion}", 56 | ] 57 | 58 | domainTestDependencies = [ 59 | junit: "junit:junit:${jUnitVersion}", 60 | mockito: "org.mockito:mockito-core:${mockitoVersion}", 61 | ] 62 | 63 | dataDependencies = [ 64 | okHttp: "com.squareup.okhttp:okhttp:${okHttpVersion}", 65 | gson: "com.google.code.gson:gson:${gsonVersion}", 66 | rxJava: "io.reactivex:rxjava:${rxJavaVersion}", 67 | rxAndroid: "io.reactivex:rxandroid:${rxAndroidVersion}", 68 | androidAnnotations: "com.android.support:support-annotations:${androidAnnotationsVersion}" 69 | ] 70 | 71 | dataTestDependencies = [ 72 | junit: "junit:junit:${jUnitVersion}", 73 | assertj: "org.assertj:assertj-core:${assertJVersion}", 74 | mockito: "org.mockito:mockito-core:${mockitoVersion}", 75 | robolectric: "org.robolectric:robolectric:${robolectricVersion}", 76 | ] 77 | } -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /data/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | def globalConfiguration = rootProject.extensions.getByName("ext") 5 | 6 | compileSdkVersion globalConfiguration.getAt("androidCompileSdkVersion") 7 | buildToolsVersion globalConfiguration.getAt("androidBuildToolsVersion") 8 | 9 | defaultConfig { 10 | minSdkVersion globalConfiguration.getAt("androidMinSdkVersion") 11 | targetSdkVersion globalConfiguration.getAt("androidTargetSdkVersion") 12 | versionCode globalConfiguration.getAt("androidVersionCode") 13 | } 14 | 15 | compileOptions { 16 | sourceCompatibility JavaVersion.VERSION_1_7 17 | targetCompatibility JavaVersion.VERSION_1_7 18 | } 19 | 20 | packagingOptions { 21 | exclude 'LICENSE.txt' 22 | exclude 'META-INF/DEPENDENCIES' 23 | exclude 'META-INF/ASL2.0' 24 | exclude 'META-INF/NOTICE' 25 | exclude 'META-INF/LICENSE' 26 | } 27 | 28 | lintOptions { 29 | quiet true 30 | abortOnError false 31 | ignoreWarnings true 32 | disable 'InvalidPackage' // Some libraries have issues with this 33 | disable 'OldTargetApi' // Due to Robolectric that modifies the manifest when running tests 34 | } 35 | } 36 | 37 | dependencies { 38 | def dataDependencies = rootProject.ext.dataDependencies 39 | def testDependencies = rootProject.ext.dataTestDependencies 40 | 41 | compile dataDependencies.okHttp 42 | compile dataDependencies.gson 43 | compile dataDependencies.rxJava 44 | compile dataDependencies.rxAndroid 45 | compile dataDependencies.androidAnnotations 46 | 47 | testCompile testDependencies.junit 48 | testCompile testDependencies.assertj 49 | testCompile testDependencies.mockito 50 | testCompile testDependencies.robolectric 51 | } 52 | -------------------------------------------------------------------------------- /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 | 21 | import java.io.BufferedReader; 22 | import java.io.File; 23 | import java.io.FileNotFoundException; 24 | import java.io.FileReader; 25 | import java.io.FileWriter; 26 | import java.io.IOException; 27 | 28 | /** 29 | * Helper class to do operations on regular files/directories. 30 | */ 31 | public class FileManager { 32 | 33 | public FileManager() { 34 | } 35 | 36 | /** 37 | * Writes a file to Disk. 38 | * This is an I/O operation and this method executes in the main thread, so it is recommended to 39 | * perform this operation using another thread. 40 | * 41 | * @param file The file to write to Disk. 42 | */ 43 | public void writeToFile(File file, String fileContent) { 44 | if (!file.exists()) { 45 | try { 46 | FileWriter writer = new FileWriter(file); 47 | writer.write(fileContent); 48 | writer.close(); 49 | } catch (FileNotFoundException e) { 50 | e.printStackTrace(); 51 | } catch (IOException e) { 52 | e.printStackTrace(); 53 | } finally { 54 | 55 | } 56 | } 57 | } 58 | 59 | /** 60 | * Reads a content from a file. 61 | * This is an I/O operation and this method executes in the main thread, so it is recommended to 62 | * perform the operation using another thread. 63 | * 64 | * @param file The file to read from. 65 | * @return A string with the content of the file. 66 | */ 67 | public String readFileContent(File file) { 68 | StringBuilder fileContentBuilder = new StringBuilder(); 69 | if (file.exists()) { 70 | String stringLine; 71 | try { 72 | FileReader fileReader = new FileReader(file); 73 | BufferedReader bufferedReader = new BufferedReader(fileReader); 74 | while ((stringLine = bufferedReader.readLine()) != null) { 75 | fileContentBuilder.append(stringLine + "\n"); 76 | } 77 | bufferedReader.close(); 78 | fileReader.close(); 79 | } catch (FileNotFoundException e) { 80 | e.printStackTrace(); 81 | } catch (IOException e) { 82 | e.printStackTrace(); 83 | } 84 | } 85 | 86 | return fileContentBuilder.toString(); 87 | } 88 | 89 | /** 90 | * Returns a boolean indicating whether this file can be found on the underlying file system. 91 | * 92 | * @param file The file to check existence. 93 | * @return true if this file exists, false otherwise. 94 | */ 95 | public boolean exists(File file) { 96 | return file.exists(); 97 | } 98 | 99 | /** 100 | * Warning: Deletes the content of a directory. 101 | * This is an I/O operation and this method executes in the main thread, so it is recommended to 102 | * perform the operation using another thread. 103 | * 104 | * @param directory The directory which its content will be deleted. 105 | */ 106 | public void clearDirectory(File directory) { 107 | if (directory.exists()) { 108 | for (File file : directory.listFiles()) { 109 | file.delete(); 110 | } 111 | } 112 | } 113 | 114 | /** 115 | * Write a value to a user preferences file. 116 | * 117 | * @param context {@link android.content.Context} to retrieve android user preferences. 118 | * @param preferenceFileName A file name reprensenting where data will be written to. 119 | * @param key A string for the key that will be used to retrieve the value in the future. 120 | * @param value A long representing the value to be inserted. 121 | */ 122 | public void writeToPreferences(Context context, String preferenceFileName, String key, 123 | long value) { 124 | 125 | SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceFileName, 126 | Context.MODE_PRIVATE); 127 | SharedPreferences.Editor editor = sharedPreferences.edit(); 128 | editor.putLong(key, value); 129 | editor.apply(); 130 | } 131 | 132 | /** 133 | * Get a value from a user preferences file. 134 | * 135 | * @param context {@link android.content.Context} to retrieve android user preferences. 136 | * @param preferenceFileName A file name representing where data will be get from. 137 | * @param key A key that will be used to retrieve the value from the preference file. 138 | * @return A long representing the value retrieved from the preferences file. 139 | */ 140 | public long getFromPreferences(Context context, String preferenceFileName, String key) { 141 | SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceFileName, 142 | Context.MODE_PRIVATE); 143 | return sharedPreferences.getLong(key, 0); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /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 rx.Observable; 20 | 21 | /** 22 | * An interface representing a user Cache. 23 | */ 24 | public interface UserCache { 25 | /** 26 | * Gets an {@link rx.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/UserCacheImpl.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 | 20 | import com.fernandocejas.android10.sample.data.cache.serializer.JsonSerializer; 21 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 22 | import com.fernandocejas.android10.sample.data.exception.UserNotFoundException; 23 | import com.fernandocejas.android10.sample.data.executor.JobExecutor; 24 | import com.fernandocejas.android10.sample.data.executor.ThreadExecutor; 25 | 26 | import java.io.File; 27 | 28 | import rx.Observable; 29 | import rx.Subscriber; 30 | 31 | /** 32 | * {@link UserCache} implementation. 33 | */ 34 | public class UserCacheImpl implements UserCache { 35 | 36 | private static final String SETTINGS_FILE_NAME = "com.fernandocejas.android10.SETTINGS"; 37 | private static final String SETTINGS_KEY_LAST_CACHE_UPDATE = "last_cache_update"; 38 | 39 | private static final String DEFAULT_FILE_NAME = "user_"; 40 | private static final long EXPIRATION_TIME = 60 * 10 * 1000; 41 | 42 | private final Context context; 43 | private final File cacheDir; 44 | private JsonSerializer serializer; 45 | private FileManager fileManager; 46 | private ThreadExecutor threadExecutor; 47 | 48 | /** 49 | * Constructor of the class {@link UserCacheImpl}. 50 | *

51 | *

52 | * UserCacheSerializer {@link JsonSerializer} for object serialization. 53 | * {@link FileManager} for saving serialized objects to the file system. 54 | */ 55 | public UserCacheImpl(Context applicationContext) { 56 | this(applicationContext, new JsonSerializer(), new FileManager(), new JobExecutor()); 57 | } 58 | 59 | /** 60 | * Constructor of the class {@link UserCacheImpl}. 61 | * 62 | * @param context A 63 | * @param userCacheSerializer {@link JsonSerializer} for object serialization. 64 | * @param fileManager {@link FileManager} for saving serialized objects to the file system. 65 | */ 66 | public UserCacheImpl(Context context, JsonSerializer userCacheSerializer, 67 | FileManager fileManager, ThreadExecutor executor) { 68 | if (context == null || userCacheSerializer == null || fileManager == null || executor == null) { 69 | throw new IllegalArgumentException("Invalid null parameter"); 70 | } 71 | this.context = context.getApplicationContext(); 72 | this.cacheDir = this.context.getCacheDir(); 73 | this.serializer = userCacheSerializer; 74 | this.fileManager = fileManager; 75 | this.threadExecutor = executor; 76 | } 77 | 78 | public void setSerializer(JsonSerializer serializer) { 79 | this.serializer = serializer; 80 | } 81 | 82 | public void setFileManager(FileManager fileManager) { 83 | this.fileManager = fileManager; 84 | } 85 | 86 | public void setThreadExecutor(ThreadExecutor threadExecutor) { 87 | this.threadExecutor = threadExecutor; 88 | } 89 | 90 | @Override 91 | public synchronized Observable get(final int userId) { 92 | return Observable.create(new Observable.OnSubscribe() { 93 | @Override 94 | public void call(Subscriber subscriber) { 95 | File userEntityFile = UserCacheImpl.this.buildFile(userId); 96 | String fileContent = UserCacheImpl.this.fileManager.readFileContent(userEntityFile); 97 | UserEntity userEntity = UserCacheImpl.this.serializer.deserialize(fileContent); 98 | 99 | if (userEntity != null) { 100 | subscriber.onNext(userEntity); 101 | subscriber.onCompleted(); 102 | } else { 103 | subscriber.onError(new UserNotFoundException()); 104 | } 105 | } 106 | }); 107 | } 108 | 109 | @Override 110 | public synchronized void put(UserEntity userEntity) { 111 | if (userEntity != null) { 112 | File userEntitiyFile = this.buildFile(userEntity.getUserId()); 113 | if (!isCached(userEntity.getUserId())) { 114 | String jsonString = this.serializer.serialize(userEntity); 115 | this.executeAsynchronously(new CacheWriter(this.fileManager, userEntitiyFile, 116 | jsonString)); 117 | setLastCacheUpdateTimeMillis(); 118 | } 119 | } 120 | } 121 | 122 | @Override 123 | public boolean isCached(int userId) { 124 | File userEntitiyFile = this.buildFile(userId); 125 | return this.fileManager.exists(userEntitiyFile); 126 | } 127 | 128 | @Override 129 | public boolean isExpired() { 130 | long currentTime = System.currentTimeMillis(); 131 | long lastUpdateTime = this.getLastCacheUpdateTimeMillis(); 132 | 133 | boolean expired = ((currentTime - lastUpdateTime) > EXPIRATION_TIME); 134 | 135 | if (expired) { 136 | this.evictAll(); 137 | } 138 | 139 | return expired; 140 | } 141 | 142 | @Override 143 | public synchronized void evictAll() { 144 | this.executeAsynchronously(new CacheEvictor(this.fileManager, this.cacheDir)); 145 | } 146 | 147 | /** 148 | * Build a file, used to be inserted in the disk cache. 149 | * 150 | * @param userId The id user to build the file. 151 | * @return A valid file. 152 | */ 153 | private File buildFile(int userId) { 154 | StringBuilder fileNameBuilder = new StringBuilder(); 155 | fileNameBuilder.append(this.cacheDir.getPath()); 156 | fileNameBuilder.append(File.separator); 157 | fileNameBuilder.append(DEFAULT_FILE_NAME); 158 | fileNameBuilder.append(userId); 159 | 160 | return new File(fileNameBuilder.toString()); 161 | } 162 | 163 | /** 164 | * Set in millis, the last time the cache was accessed. 165 | */ 166 | private void setLastCacheUpdateTimeMillis() { 167 | long currentMillis = System.currentTimeMillis(); 168 | this.fileManager.writeToPreferences(this.context, SETTINGS_FILE_NAME, 169 | SETTINGS_KEY_LAST_CACHE_UPDATE, currentMillis); 170 | } 171 | 172 | /** 173 | * Get in millis, the last time the cache was accessed. 174 | */ 175 | private long getLastCacheUpdateTimeMillis() { 176 | return this.fileManager.getFromPreferences(this.context, SETTINGS_FILE_NAME, 177 | SETTINGS_KEY_LAST_CACHE_UPDATE); 178 | } 179 | 180 | /** 181 | * Executes a {@link Runnable} in another Thread. 182 | * 183 | * @param runnable {@link Runnable} to execute 184 | */ 185 | private void executeAsynchronously(Runnable runnable) { 186 | this.threadExecutor.execute(runnable); 187 | } 188 | 189 | /** 190 | * {@link Runnable} class for writing to disk. 191 | */ 192 | private static class CacheWriter implements Runnable { 193 | private final FileManager fileManager; 194 | private final File fileToWrite; 195 | private final String fileContent; 196 | 197 | CacheWriter(FileManager fileManager, File fileToWrite, String fileContent) { 198 | this.fileManager = fileManager; 199 | this.fileToWrite = fileToWrite; 200 | this.fileContent = fileContent; 201 | } 202 | 203 | @Override 204 | public void run() { 205 | this.fileManager.writeToFile(fileToWrite, fileContent); 206 | } 207 | } 208 | 209 | /** 210 | * {@link Runnable} class for evicting all the cached files 211 | */ 212 | private static class CacheEvictor implements Runnable { 213 | private final FileManager fileManager; 214 | private final File cacheDir; 215 | 216 | CacheEvictor(FileManager fileManager, File cacheDir) { 217 | this.fileManager = fileManager; 218 | this.cacheDir = cacheDir; 219 | } 220 | 221 | @Override 222 | public void run() { 223 | this.fileManager.clearDirectory(this.cacheDir); 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/cache/serializer/JsonSerializer.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 com.google.gson.Gson; 20 | 21 | /** 22 | * Class user as Serializer/Deserializer for user entities. 23 | */ 24 | public class JsonSerializer { 25 | 26 | private final Gson gson = new Gson(); 27 | 28 | public JsonSerializer() {} 29 | 30 | /** 31 | * Serialize an object to Json. 32 | * 33 | * @param userEntity {@link UserEntity} to serialize. 34 | */ 35 | public String serialize(UserEntity userEntity) { 36 | String jsonString = gson.toJson(userEntity, UserEntity.class); 37 | return jsonString; 38 | } 39 | 40 | /** 41 | * Deserialize a json representation of an object. 42 | * 43 | * @param jsonString A json string to deserialize. 44 | * @return {@link UserEntity} 45 | */ 46 | public UserEntity deserialize(String jsonString) { 47 | UserEntity userEntity = gson.fromJson(jsonString, UserEntity.class); 48 | return userEntity; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/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.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 | 22 | import java.util.List; 23 | 24 | import rx.Observable; 25 | import rx.functions.Action1; 26 | 27 | /** 28 | * {@link UserDataStore} implementation based on connections to the api (Cloud). 29 | */ 30 | public class CloudUserDataStore implements UserDataStore { 31 | 32 | private final RestApi restApi; 33 | private final UserCache userCache; 34 | 35 | private final Action1 saveToCacheAction = new Action1() { 36 | @Override 37 | public void call(UserEntity userEntity) { 38 | if (userEntity != null) { 39 | CloudUserDataStore.this.userCache.put(userEntity); 40 | } 41 | } 42 | }; 43 | 44 | /** 45 | * Construct a {@link UserDataStore} based on connections to the api (Cloud). 46 | * 47 | * @param restApi The {@link RestApi} implementation to use. 48 | * @param userCache A {@link UserCache} to cache data retrieved from the api. 49 | */ 50 | public CloudUserDataStore(RestApi restApi, UserCache userCache) { 51 | this.restApi = restApi; 52 | this.userCache = userCache; 53 | } 54 | 55 | @Override 56 | public Observable> userEntityList() { 57 | return this.restApi.userEntityList(); 58 | } 59 | 60 | @Override 61 | public Observable userEntityDetails(final int userId) { 62 | return this.restApi.userEntityById(userId) 63 | .doOnNext(saveToCacheAction); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/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.datasource; 17 | 18 | import com.fernandocejas.android10.sample.data.cache.UserCache; 19 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 20 | import java.util.List; 21 | import rx.Observable; 22 | 23 | /** 24 | * {@link UserDataStore} implementation based on file system data store. 25 | */ 26 | public 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 | public 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/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.datasource; 17 | 18 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 19 | import java.util.List; 20 | import rx.Observable; 21 | 22 | /** 23 | * Interface that represents a data store from where data is retrieved. 24 | */ 25 | public interface UserDataStore { 26 | /** 27 | * Get an {@link rx.Observable} which will emit a List of {@link UserEntity}. 28 | */ 29 | Observable> userEntityList(); 30 | 31 | /** 32 | * Get an {@link rx.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/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.datasource; 17 | 18 | import android.content.Context; 19 | 20 | import com.fernandocejas.android10.sample.data.cache.UserCache; 21 | import com.fernandocejas.android10.sample.data.cache.UserCacheImpl; 22 | import com.fernandocejas.android10.sample.data.entity.mapper.UserEntityJsonMapper; 23 | import com.fernandocejas.android10.sample.data.net.RestApi; 24 | import com.fernandocejas.android10.sample.data.net.RestApiImpl; 25 | 26 | /** 27 | * Factory that creates different implementations of {@link UserDataStore}. 28 | */ 29 | public class UserDataStoreFactory { 30 | 31 | private final Context context ; 32 | private UserCache userCache; 33 | 34 | public UserDataStoreFactory(Context applicationContext) { 35 | this(applicationContext, new UserCacheImpl(applicationContext)); 36 | } 37 | 38 | public UserDataStoreFactory(Context context, UserCache userCache) { 39 | if (context == null || userCache == null) { 40 | throw new IllegalArgumentException("Constructor parameters cannot be null!!!"); 41 | } 42 | this.context = context.getApplicationContext(); 43 | this.userCache = userCache; 44 | } 45 | 46 | public void setUserCache(UserCache userCache) { 47 | this.userCache = userCache; 48 | } 49 | 50 | /** 51 | * Create {@link UserDataStore} from a user id. 52 | */ 53 | public UserDataStore create(int userId) { 54 | UserDataStore userDataStore; 55 | 56 | if (!this.userCache.isExpired() && this.userCache.isCached(userId)) { 57 | userDataStore = new DiskUserDataStore(this.userCache); 58 | } else { 59 | userDataStore = createCloudDataStore(); 60 | } 61 | 62 | return userDataStore; 63 | } 64 | 65 | /** 66 | * Create {@link UserDataStore} to retrieve data from the Cloud. 67 | */ 68 | public UserDataStore createCloudDataStore() { 69 | UserEntityJsonMapper userEntityJsonMapper = new UserEntityJsonMapper(); 70 | RestApi restApi = new RestApiImpl(this.context, userEntityJsonMapper); 71 | 72 | return new CloudUserDataStore(restApi, this.userCache); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/dto/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.data.dto; 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 | @Override public String toString() { 80 | StringBuilder stringBuilder = new StringBuilder(); 81 | 82 | stringBuilder.append("***** User Details *****\n"); 83 | stringBuilder.append("id=" + this.getUserId() + "\n"); 84 | stringBuilder.append("cover url=" + this.getCoverUrl() + "\n"); 85 | stringBuilder.append("fullname=" + this.getFullName() + "\n"); 86 | stringBuilder.append("email=" + this.getEmail() + "\n"); 87 | stringBuilder.append("description=" + this.getDescription() + "\n"); 88 | stringBuilder.append("followers=" + this.getFollowers() + "\n"); 89 | stringBuilder.append("*******************************"); 90 | 91 | return stringBuilder.toString(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /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 void setCoverUrl(String coverUrl) { 60 | this.coverUrl = coverUrl; 61 | } 62 | 63 | public String getFullname() { 64 | return fullname; 65 | } 66 | 67 | public void setFullname(String fullname) { 68 | this.fullname = fullname; 69 | } 70 | 71 | public String getDescription() { 72 | return description; 73 | } 74 | 75 | public void setDescription(String description) { 76 | this.description = description; 77 | } 78 | 79 | public int getFollowers() { 80 | return followers; 81 | } 82 | 83 | public void setFollowers(int followers) { 84 | this.followers = followers; 85 | } 86 | 87 | public String getEmail() { 88 | return email; 89 | } 90 | 91 | public void setEmail(String email) { 92 | this.email = email; 93 | } 94 | 95 | @Override public String toString() { 96 | StringBuilder stringBuilder = new StringBuilder(); 97 | 98 | stringBuilder.append("***** User Entity Details *****\n"); 99 | stringBuilder.append("id=" + this.getUserId() + "\n"); 100 | stringBuilder.append("cover url=" + this.getCoverUrl() + "\n"); 101 | stringBuilder.append("fullname=" + this.getFullname() + "\n"); 102 | stringBuilder.append("email=" + this.getEmail() + "\n"); 103 | stringBuilder.append("description=" + this.getDescription() + "\n"); 104 | stringBuilder.append("followers=" + this.getFollowers() + "\n"); 105 | stringBuilder.append("*******************************"); 106 | 107 | return stringBuilder.toString(); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /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.dto.User; 19 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Collection; 23 | import java.util.List; 24 | 25 | /** 26 | * Mapper class used to transform {@link UserEntity} (in the data layer) to {@link User} in the 27 | * domain layer. 28 | */ 29 | public class UserEntityDataMapper { 30 | 31 | public UserEntityDataMapper() { 32 | } 33 | 34 | /** 35 | * Transform a {@link UserEntity} into an {@link User}. 36 | * 37 | * @param userEntity Object to be transformed. 38 | * @return {@link User} if valid {@link UserEntity} otherwise null. 39 | */ 40 | public User transform(UserEntity userEntity) { 41 | User user = null; 42 | if (userEntity != null) { 43 | user = new User(userEntity.getUserId()); 44 | user.setCoverUrl(userEntity.getCoverUrl()); 45 | user.setFullName(userEntity.getFullname()); 46 | user.setDescription(userEntity.getDescription()); 47 | user.setFollowers(userEntity.getFollowers()); 48 | user.setEmail(userEntity.getEmail()); 49 | } 50 | 51 | return user; 52 | } 53 | 54 | /** 55 | * Transform a List of {@link UserEntity} into a Collection of {@link User}. 56 | * 57 | * @param userEntityCollection Object Collection to be transformed. 58 | * @return {@link User} if valid {@link UserEntity} otherwise null. 59 | */ 60 | public List transform(Collection userEntityCollection) { 61 | List userList = new ArrayList<>(20); 62 | User user; 63 | for (UserEntity userEntity : userEntityCollection) { 64 | user = transform(userEntity); 65 | if (user != null) { 66 | userList.add(user); 67 | } 68 | } 69 | 70 | return userList; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /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 | 25 | /** 26 | * Class used to transform from Strings representing json to valid objects. 27 | */ 28 | public class UserEntityJsonMapper { 29 | 30 | private final Gson gson; 31 | 32 | public UserEntityJsonMapper() { 33 | this.gson = new Gson(); 34 | } 35 | 36 | /** 37 | * Transform from valid json string to {@link UserEntity}. 38 | * 39 | * @param userJsonResponse A json representing a user profile. 40 | * @return {@link UserEntity}. 41 | * @throws com.google.gson.JsonSyntaxException if the json string is not a valid json structure. 42 | */ 43 | public UserEntity transformUserEntity(String userJsonResponse) throws JsonSyntaxException { 44 | try { 45 | Type userEntityType = new TypeToken() {}.getType(); 46 | UserEntity userEntity = this.gson.fromJson(userJsonResponse, userEntityType); 47 | 48 | return userEntity; 49 | } catch (JsonSyntaxException jsonException) { 50 | throw jsonException; 51 | } 52 | } 53 | 54 | /** 55 | * Transform from valid json string to List of {@link UserEntity}. 56 | * 57 | * @param userListJsonResponse A json representing a collection of users. 58 | * @return List of {@link UserEntity}. 59 | * @throws com.google.gson.JsonSyntaxException if the json string is not a valid json structure. 60 | */ 61 | public List transformUserEntityCollection(String userListJsonResponse) 62 | throws JsonSyntaxException { 63 | 64 | List userEntityCollection; 65 | try { 66 | Type listOfUserEntityType = new TypeToken>() {}.getType(); 67 | userEntityCollection = this.gson.fromJson(userListJsonResponse, listOfUserEntityType); 68 | 69 | return userEntityCollection; 70 | } catch (JsonSyntaxException jsonException) { 71 | throw jsonException; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/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.data.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 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/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.data.exception; 17 | 18 | /** 19 | * Interface to represent a wrapper around an {@link Exception} to manage errors. 20 | */ 21 | public interface ErrorBundle { 22 | Exception getException(); 23 | 24 | String getErrorMessage(); 25 | } 26 | -------------------------------------------------------------------------------- /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 String message) { 28 | super(message); 29 | } 30 | 31 | public NetworkConnectionException(final String message, final Throwable cause) { 32 | super(message, cause); 33 | } 34 | 35 | public NetworkConnectionException(final Throwable cause) { 36 | super(cause); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /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 | 19 | /** 20 | * Wrapper around Exceptions used to manage errors in the repository. 21 | */ 22 | public class RepositoryErrorBundle implements ErrorBundle { 23 | 24 | private final Exception exception; 25 | 26 | public RepositoryErrorBundle(Exception exception) { 27 | this.exception = exception; 28 | } 29 | 30 | @Override 31 | public Exception getException() { 32 | return exception; 33 | } 34 | 35 | @Override 36 | public String getErrorMessage() { 37 | String message = ""; 38 | if (this.exception != null) { 39 | this.exception.getMessage(); 40 | } 41 | return message; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /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 | 23 | public UserNotFoundException() { 24 | super(); 25 | } 26 | 27 | public UserNotFoundException(final String message) { 28 | super(message); 29 | } 30 | 31 | public UserNotFoundException(final String message, final Throwable cause) { 32 | super(message, cause); 33 | } 34 | 35 | public UserNotFoundException(final Throwable cause) { 36 | super(cause); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /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 | 19 | import java.util.concurrent.BlockingQueue; 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 | 25 | /** 26 | * Decorated {@link java.util.concurrent.ThreadPoolExecutor} 27 | */ 28 | public class JobExecutor implements ThreadExecutor { 29 | 30 | private static final int INITIAL_POOL_SIZE = 3; 31 | private static final int MAX_POOL_SIZE = 5; 32 | 33 | // Sets the amount of time an idle thread waits before terminating 34 | private static final int KEEP_ALIVE_TIME = 10; 35 | 36 | // Sets the Time Unit to seconds 37 | private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS; 38 | 39 | private final BlockingQueue workQueue; 40 | 41 | private final ThreadPoolExecutor threadPoolExecutor; 42 | 43 | private final ThreadFactory threadFactory; 44 | 45 | public JobExecutor() { 46 | this.workQueue = new LinkedBlockingQueue<>(); 47 | this.threadFactory = new JobThreadFactory(); 48 | this.threadPoolExecutor = new ThreadPoolExecutor(INITIAL_POOL_SIZE, MAX_POOL_SIZE, 49 | KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, this.workQueue, this.threadFactory); 50 | } 51 | 52 | @Override 53 | public void execute(Runnable runnable) { 54 | if (runnable == null) { 55 | throw new IllegalArgumentException("Runnable to execute cannot be null"); 56 | } 57 | this.threadPoolExecutor.execute(runnable); 58 | } 59 | 60 | private static class JobThreadFactory implements ThreadFactory { 61 | private static final String THREAD_NAME = "android_"; 62 | private int counter = 0; 63 | 64 | @Override 65 | public Thread newThread(Runnable runnable) { 66 | return new Thread(runnable, THREAD_NAME + counter); 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/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.data.executor; 17 | 18 | import rx.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 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/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.data.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 | -------------------------------------------------------------------------------- /data/src/main/java/com/fernandocejas/android10/sample/data/executor/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.data.executor; 17 | 18 | 19 | import rx.Scheduler; 20 | import rx.android.schedulers.AndroidSchedulers; 21 | 22 | /** 23 | * MainThread (UI Thread) implementation based on a {@link rx.Scheduler} 24 | * which will execute actions on the Android UI thread 25 | */ 26 | public class UIThread implements PostExecutionThread { 27 | 28 | public UIThread() { 29 | } 30 | 31 | @Override 32 | public Scheduler getScheduler() { 33 | return AndroidSchedulers.mainThread(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /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 | public 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 | public 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 | public 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 java.util.List; 20 | import rx.Observable; 21 | 22 | /** 23 | * RestApi for retrieving data from the network. 24 | */ 25 | public interface RestApi { 26 | String API_BASE_URL = "http://www.android10.org/myapi/"; 27 | 28 | /** Api url for getting all users */ 29 | String API_URL_GET_USER_LIST = API_BASE_URL + "users.json"; 30 | /** Api url for getting a user profile: Remember to concatenate id + 'json' */ 31 | String API_URL_GET_USER_DETAILS = API_BASE_URL + "user_"; 32 | 33 | /** 34 | * Retrieves an {@link rx.Observable} which will emit a List of {@link UserEntity}. 35 | */ 36 | Observable> userEntityList(); 37 | 38 | /** 39 | * Retrieves an {@link rx.Observable} which will emit a {@link UserEntity}. 40 | * 41 | * @param userId The user id used to get user data. 42 | */ 43 | Observable userEntityById(final int userId); 44 | } 45 | -------------------------------------------------------------------------------- /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 java.net.MalformedURLException; 25 | import java.util.List; 26 | import rx.Observable; 27 | import rx.Subscriber; 28 | 29 | /** 30 | * {@link RestApi} implementation for retrieving data from the network. 31 | */ 32 | public class RestApiImpl implements RestApi { 33 | 34 | private final Context context; 35 | private final UserEntityJsonMapper userEntityJsonMapper; 36 | 37 | /** 38 | * Constructor of the class 39 | * 40 | * @param context {@link android.content.Context}. 41 | * @param userEntityJsonMapper {@link UserEntityJsonMapper}. 42 | */ 43 | public RestApiImpl(Context context, UserEntityJsonMapper userEntityJsonMapper) { 44 | if (context == null || userEntityJsonMapper == null) { 45 | throw new IllegalArgumentException("The constructor parameters cannot be null!!!"); 46 | } 47 | this.context = context.getApplicationContext(); 48 | this.userEntityJsonMapper = userEntityJsonMapper; 49 | } 50 | 51 | @Override public Observable> userEntityList() { 52 | return Observable.create(new Observable.OnSubscribe>() { 53 | @Override public void call(Subscriber> subscriber) { 54 | 55 | if (isThereInternetConnection()) { 56 | try { 57 | String responseUserEntities = getUserEntitiesFromApi(); 58 | if (responseUserEntities != null) { 59 | subscriber.onNext(userEntityJsonMapper.transformUserEntityCollection( 60 | responseUserEntities)); 61 | subscriber.onCompleted(); 62 | } else { 63 | subscriber.onError(new NetworkConnectionException()); 64 | } 65 | } catch (Exception e) { 66 | subscriber.onError(new NetworkConnectionException(e.getCause())); 67 | } 68 | } else { 69 | subscriber.onError(new NetworkConnectionException()); 70 | } 71 | } 72 | }); 73 | } 74 | 75 | @Override public Observable userEntityById(final int userId) { 76 | return Observable.create(new Observable.OnSubscribe() { 77 | @Override public void call(Subscriber subscriber) { 78 | 79 | if (isThereInternetConnection()) { 80 | try { 81 | String responseUserDetails = getUserDetailsFromApi(userId); 82 | if (responseUserDetails != null) { 83 | subscriber.onNext(userEntityJsonMapper.transformUserEntity(responseUserDetails)); 84 | subscriber.onCompleted(); 85 | } else { 86 | subscriber.onError(new NetworkConnectionException()); 87 | } 88 | } catch (Exception e) { 89 | subscriber.onError(new NetworkConnectionException(e.getCause())); 90 | } 91 | } else { 92 | subscriber.onError(new NetworkConnectionException()); 93 | } 94 | } 95 | }); 96 | } 97 | 98 | private String getUserEntitiesFromApi() throws MalformedURLException { 99 | return ApiConnection.createGET(RestApi.API_URL_GET_USER_LIST).requestSyncCall(); 100 | } 101 | 102 | private String getUserDetailsFromApi(int userId) throws MalformedURLException { 103 | String apiUrl = RestApi.API_URL_GET_USER_DETAILS + userId + ".json"; 104 | return ApiConnection.createGET(apiUrl).requestSyncCall(); 105 | } 106 | 107 | /** 108 | * Checks if the device has any active internet connection. 109 | * 110 | * @return true device with internet connection, otherwise false. 111 | */ 112 | private boolean isThereInternetConnection() { 113 | boolean isConnected; 114 | 115 | ConnectivityManager connectivityManager = 116 | (ConnectivityManager) this.context.getSystemService(Context.CONNECTIVITY_SERVICE); 117 | NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); 118 | isConnected = (networkInfo != null && networkInfo.isConnectedOrConnecting()); 119 | 120 | return isConnected; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /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 org.junit.runner.RunWith; 19 | import org.robolectric.RobolectricGradleTestRunner; 20 | import org.robolectric.annotation.Config; 21 | 22 | /** 23 | * Base class for Robolectric data layer tests. 24 | * Inherit from this class to create a test. 25 | */ 26 | @RunWith(RobolectricGradleTestRunner.class) 27 | @Config(constants = BuildConfig.class, application = ApplicationStub.class, sdk = 21) 28 | public abstract class ApplicationTestCase {} 29 | -------------------------------------------------------------------------------- /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 | import org.robolectric.RuntimeEnvironment; 24 | 25 | import static org.hamcrest.CoreMatchers.equalTo; 26 | import static org.hamcrest.CoreMatchers.is; 27 | import static org.hamcrest.MatcherAssert.assertThat; 28 | 29 | public class FileManagerTest extends ApplicationTestCase { 30 | 31 | private FileManager fileManager; 32 | private File cacheDir; 33 | 34 | @Before 35 | public void setUp() { 36 | fileManager = new FileManager(); 37 | cacheDir = RuntimeEnvironment.application.getCacheDir(); 38 | } 39 | 40 | @After 41 | public void tearDown() { 42 | if (cacheDir != null) { 43 | fileManager.clearDirectory(cacheDir); 44 | } 45 | } 46 | 47 | @Test 48 | public void testWriteToFile() { 49 | File fileToWrite = createDummyFile(); 50 | String fileContent = "content"; 51 | 52 | fileManager.writeToFile(fileToWrite, fileContent); 53 | 54 | assertThat(fileToWrite.exists(), is(true)); 55 | } 56 | 57 | @Test 58 | public void testFileContent() { 59 | File fileToWrite = createDummyFile(); 60 | String fileContent = "content\n"; 61 | 62 | fileManager.writeToFile(fileToWrite, fileContent); 63 | String expectedContent = fileManager.readFileContent(fileToWrite); 64 | 65 | assertThat(expectedContent, is(equalTo(fileContent))); 66 | } 67 | 68 | private File createDummyFile() { 69 | String dummyFilePath = cacheDir.getPath() + File.separator + "dumyFile"; 70 | File dummyFile = new File(dummyFilePath); 71 | 72 | return dummyFile; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /data/src/test/java/com/fernandocejas/android10/sample/data/cache/serializer/JsonSerializerTest.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.ApplicationTestCase; 19 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 20 | import org.junit.Before; 21 | import org.junit.Test; 22 | 23 | import static org.hamcrest.CoreMatchers.equalTo; 24 | import static org.hamcrest.CoreMatchers.is; 25 | import static org.junit.Assert.assertThat; 26 | 27 | public class JsonSerializerTest extends ApplicationTestCase { 28 | 29 | private static final String JSON_RESPONSE = "{\n" 30 | + " \"id\": 1,\n" 31 | + " \"cover_url\": \"http://www.android10.org/myapi/cover_1.jpg\",\n" 32 | + " \"full_name\": \"Simon Hill\",\n" 33 | + " \"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" 34 | + " \"followers\": 7484,\n" 35 | + " \"email\": \"jcooper@babbleset.edu\"\n" 36 | + "}"; 37 | 38 | private JsonSerializer jsonSerializer; 39 | 40 | @Before 41 | public void setUp() { 42 | jsonSerializer = new JsonSerializer(); 43 | } 44 | 45 | @Test 46 | public void testSerializeHappyCase() { 47 | UserEntity userEntityOne = jsonSerializer.deserialize(JSON_RESPONSE); 48 | String jsonString = jsonSerializer.serialize(userEntityOne); 49 | UserEntity userEntityTwo = jsonSerializer.deserialize(jsonString); 50 | 51 | assertThat(userEntityOne.getUserId(), is(userEntityTwo.getUserId())); 52 | assertThat(userEntityOne.getFullname(), is(equalTo(userEntityTwo.getFullname()))); 53 | assertThat(userEntityOne.getFollowers(), is(userEntityTwo.getFollowers())); 54 | } 55 | 56 | @Test 57 | public void testDesearializeHappyCase() { 58 | UserEntity userEntity = jsonSerializer.deserialize(JSON_RESPONSE); 59 | 60 | assertThat(userEntity.getUserId(), is(1)); 61 | assertThat(userEntity.getFullname(), is("Simon Hill")); 62 | assertThat(userEntity.getFollowers(), is(7484)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /data/src/test/java/com/fernandocejas/android10/sample/data/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.datasource; 17 | 18 | import com.fernandocejas.android10.sample.data.ApplicationTestCase; 19 | import com.fernandocejas.android10.sample.data.cache.UserCache; 20 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 21 | import com.fernandocejas.android10.sample.data.net.RestApi; 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | import org.mockito.Mock; 25 | import org.mockito.MockitoAnnotations; 26 | import rx.Observable; 27 | 28 | import static org.mockito.BDDMockito.given; 29 | import static org.mockito.Mockito.verify; 30 | 31 | public class CloudUserDataStoreTest extends ApplicationTestCase { 32 | 33 | private static final int FAKE_USER_ID = 765; 34 | 35 | private CloudUserDataStore cloudUserDataStore; 36 | 37 | @Mock private RestApi mockRestApi; 38 | @Mock private UserCache mockUserCache; 39 | 40 | @Before 41 | public void setUp() { 42 | MockitoAnnotations.initMocks(this); 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/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.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.Rule; 22 | import org.junit.Test; 23 | import org.junit.rules.ExpectedException; 24 | import org.mockito.Mock; 25 | import org.mockito.MockitoAnnotations; 26 | 27 | import static org.mockito.Mockito.verify; 28 | 29 | public class DiskUserDataStoreTest extends ApplicationTestCase { 30 | 31 | private static final int FAKE_USER_ID = 11; 32 | 33 | private DiskUserDataStore diskUserDataStore; 34 | 35 | @Mock private UserCache mockUserCache; 36 | 37 | @Rule public ExpectedException expectedException = ExpectedException.none(); 38 | 39 | @Before 40 | public void setUp() { 41 | MockitoAnnotations.initMocks(this); 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/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.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.mockito.MockitoAnnotations; 24 | import org.robolectric.RuntimeEnvironment; 25 | 26 | import static org.hamcrest.CoreMatchers.instanceOf; 27 | import static org.hamcrest.CoreMatchers.is; 28 | import static org.hamcrest.CoreMatchers.notNullValue; 29 | import static org.hamcrest.MatcherAssert.assertThat; 30 | import static org.mockito.BDDMockito.given; 31 | import static org.mockito.Mockito.verify; 32 | 33 | public class UserDataStoreFactoryTest extends ApplicationTestCase { 34 | 35 | private static final int FAKE_USER_ID = 11; 36 | 37 | private UserDataStoreFactory userDataStoreFactory; 38 | 39 | @Mock 40 | private UserCache mockUserCache; 41 | 42 | @Before 43 | public void setUp() { 44 | MockitoAnnotations.initMocks(this); 45 | userDataStoreFactory = 46 | new UserDataStoreFactory(RuntimeEnvironment.application, mockUserCache); 47 | } 48 | 49 | @Test 50 | public void testCreateDiskDataStore() { 51 | given(mockUserCache.isCached(FAKE_USER_ID)).willReturn(true); 52 | given(mockUserCache.isExpired()).willReturn(false); 53 | 54 | UserDataStore userDataStore = userDataStoreFactory.create(FAKE_USER_ID); 55 | 56 | assertThat(userDataStore, is(notNullValue())); 57 | assertThat(userDataStore, is(instanceOf(DiskUserDataStore.class))); 58 | 59 | verify(mockUserCache).isCached(FAKE_USER_ID); 60 | verify(mockUserCache).isExpired(); 61 | } 62 | 63 | @Test 64 | public void testCreateCloudDataStore() { 65 | given(mockUserCache.isExpired()).willReturn(true); 66 | given(mockUserCache.isCached(FAKE_USER_ID)).willReturn(false); 67 | 68 | UserDataStore userDataStore = userDataStoreFactory.create(FAKE_USER_ID); 69 | 70 | assertThat(userDataStore, is(notNullValue())); 71 | assertThat(userDataStore, is(instanceOf(CloudUserDataStore.class))); 72 | 73 | verify(mockUserCache).isExpired(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /data/src/test/java/com/fernandocejas/android10/sample/data/dto/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.data.dto; 17 | 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | 21 | import static org.hamcrest.core.Is.is; 22 | import static org.junit.Assert.assertThat; 23 | 24 | public class UserTest { 25 | 26 | private static final int FAKE_USER_ID = 8; 27 | 28 | private User user; 29 | 30 | @Before 31 | public void setUp() { 32 | user = new User(FAKE_USER_ID); 33 | } 34 | 35 | @Test 36 | public void testUserConstructorHappyCase() { 37 | int userId = user.getUserId(); 38 | 39 | assertThat(userId, is(FAKE_USER_ID)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /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.ApplicationTestCase; 19 | import com.fernandocejas.android10.sample.data.dto.User; 20 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 21 | 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | 25 | import java.util.ArrayList; 26 | import java.util.Collection; 27 | import java.util.List; 28 | 29 | import static org.hamcrest.CoreMatchers.instanceOf; 30 | import static org.hamcrest.CoreMatchers.is; 31 | import static org.hamcrest.MatcherAssert.assertThat; 32 | import static org.mockito.Mockito.mock; 33 | 34 | public class UserEntityDataMapperTest extends ApplicationTestCase { 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.ApplicationTestCase; 19 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 20 | import com.google.gson.JsonSyntaxException; 21 | import java.util.Collection; 22 | import org.junit.Before; 23 | import org.junit.Rule; 24 | import org.junit.Test; 25 | import org.junit.rules.ExpectedException; 26 | 27 | import static org.hamcrest.CoreMatchers.equalTo; 28 | import static org.hamcrest.CoreMatchers.is; 29 | import static org.hamcrest.MatcherAssert.assertThat; 30 | 31 | public class UserEntityJsonMapperTest extends ApplicationTestCase { 32 | 33 | private static final String JSON_RESPONSE_USER_DETAILS = "{\n" 34 | + " \"id\": 1,\n" 35 | + " \"cover_url\": \"http://www.android10.org/myapi/cover_1.jpg\",\n" 36 | + " \"full_name\": \"Simon Hill\",\n" 37 | + " \"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" 38 | + " \"followers\": 7484,\n" 39 | + " \"email\": \"jcooper@babbleset.edu\"\n" 40 | + "}"; 41 | 42 | private static final String JSON_RESPONSE_USER_COLLECTION = "[{\n" 43 | + " \"id\": 1,\n" 44 | + " \"full_name\": \"Simon Hill\",\n" 45 | + " \"followers\": 7484\n" 46 | + "}, {\n" 47 | + " \"id\": 12,\n" 48 | + " \"full_name\": \"Pedro Garcia\",\n" 49 | + " \"followers\": 1381\n" 50 | + "}]"; 51 | 52 | private UserEntityJsonMapper userEntityJsonMapper; 53 | 54 | @Rule 55 | public ExpectedException expectedException = ExpectedException.none(); 56 | 57 | @Before 58 | public void setUp() { 59 | userEntityJsonMapper = new UserEntityJsonMapper(); 60 | } 61 | 62 | @Test 63 | public void testTransformUserEntityHappyCase() { 64 | UserEntity userEntity = userEntityJsonMapper.transformUserEntity(JSON_RESPONSE_USER_DETAILS); 65 | 66 | assertThat(userEntity.getUserId(), is(1)); 67 | assertThat(userEntity.getFullname(), is(equalTo("Simon Hill"))); 68 | assertThat(userEntity.getEmail(), is(equalTo("jcooper@babbleset.edu"))); 69 | } 70 | 71 | @Test 72 | public void testTransformUserEntityCollectionHappyCase() { 73 | Collection userEntityCollection = 74 | userEntityJsonMapper.transformUserEntityCollection( 75 | JSON_RESPONSE_USER_COLLECTION); 76 | 77 | assertThat(((UserEntity) userEntityCollection.toArray()[0]).getUserId(), is(1)); 78 | assertThat(((UserEntity) userEntityCollection.toArray()[1]).getUserId(), is(12)); 79 | assertThat(userEntityCollection.size(), is(2)); 80 | } 81 | 82 | @Test 83 | public void testTransformUserEntityNotValidResponse() { 84 | expectedException.expect(JsonSyntaxException.class); 85 | userEntityJsonMapper.transformUserEntity("ironman"); 86 | } 87 | 88 | @Test 89 | public void testTransformUserEntityCollectionNotValidResponse() { 90 | expectedException.expect(JsonSyntaxException.class); 91 | userEntityJsonMapper.transformUserEntityCollection("Tony Stark"); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /data/src/test/java/com/fernandocejas/android10/sample/data/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.data.exception; 17 | 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | import org.mockito.Mock; 21 | import org.mockito.MockitoAnnotations; 22 | 23 | import static org.mockito.Mockito.verify; 24 | 25 | public class DefaultErrorBundleTest { 26 | private DefaultErrorBundle defaultErrorBundle; 27 | 28 | @Mock 29 | private Exception mockException; 30 | 31 | @Before 32 | public void setUp() { 33 | MockitoAnnotations.initMocks(this); 34 | defaultErrorBundle = new DefaultErrorBundle(mockException); 35 | } 36 | 37 | @Test 38 | public void testGetErrorMessageInteraction() { 39 | defaultErrorBundle.getErrorMessage(); 40 | 41 | verify(mockException).getMessage(); 42 | } 43 | } -------------------------------------------------------------------------------- /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 com.fernandocejas.android10.sample.data.ApplicationTestCase; 19 | import org.junit.Before; 20 | import org.junit.Test; 21 | import org.mockito.Mock; 22 | import org.mockito.MockitoAnnotations; 23 | 24 | import static org.mockito.Mockito.verify; 25 | 26 | public class RepositoryErrorBundleTest extends ApplicationTestCase { 27 | 28 | private RepositoryErrorBundle repositoryErrorBundle; 29 | 30 | @Mock 31 | private Exception mockException; 32 | 33 | @Before 34 | public void setUp() { 35 | MockitoAnnotations.initMocks(this); 36 | repositoryErrorBundle = new RepositoryErrorBundle(mockException); 37 | } 38 | 39 | @Test 40 | public void testGetErrorMessageInteraction() { 41 | repositoryErrorBundle.getErrorMessage(); 42 | 43 | verify(mockException).getMessage(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /domain/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | def globalConfiguration = rootProject.extensions.getByName("ext") 5 | 6 | compileSdkVersion globalConfiguration.getAt("androidCompileSdkVersion") 7 | buildToolsVersion globalConfiguration.getAt("androidBuildToolsVersion") 8 | 9 | defaultConfig { 10 | minSdkVersion globalConfiguration.getAt("androidMinSdkVersion") 11 | targetSdkVersion globalConfiguration.getAt("androidTargetSdkVersion") 12 | versionCode globalConfiguration.getAt("androidVersionCode") 13 | } 14 | 15 | compileOptions { 16 | sourceCompatibility JavaVersion.VERSION_1_7 17 | targetCompatibility JavaVersion.VERSION_1_7 18 | } 19 | 20 | packagingOptions { 21 | exclude 'LICENSE.txt' 22 | exclude 'META-INF/DEPENDENCIES' 23 | exclude 'META-INF/ASL2.0' 24 | exclude 'META-INF/NOTICE' 25 | exclude 'META-INF/LICENSE' 26 | } 27 | 28 | lintOptions { 29 | quiet true 30 | abortOnError false 31 | ignoreWarnings true 32 | disable 'InvalidPackage' // Some libraries have issues with this 33 | disable 'OldTargetApi' // Due to Robolectric that modifies the manifest when running tests 34 | } 35 | } 36 | dependencies { 37 | def testDependencies = rootProject.ext.domainTestDependencies 38 | 39 | compile project(':data') 40 | 41 | testCompile testDependencies.junit 42 | testCompile testDependencies.mockito 43 | } -------------------------------------------------------------------------------- /domain/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/DefaultSubscriber.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 | /** 19 | * Default subscriber base class to be used whenever you want default error handling. 20 | */ 21 | public class DefaultSubscriber extends rx.Subscriber { 22 | @Override public void onCompleted() { 23 | // no-op by default. 24 | } 25 | 26 | @Override public void onError(Throwable e) { 27 | // no-op by default. 28 | } 29 | 30 | @Override public void onNext(T t) { 31 | // no-op by default. 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /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 | 19 | import android.content.Context; 20 | 21 | import com.fernandocejas.android10.sample.domain.interactor.repository.UserDataRepository; 22 | import com.fernandocejas.android10.sample.domain.interactor.repository.UserRepository; 23 | 24 | import rx.Observable; 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 com.fernandocejas.android10.sample.data.dto.User}. 29 | */ 30 | public class GetUserDetails extends UseCase { 31 | 32 | int userId; 33 | UserRepository userRepository; 34 | 35 | public GetUserDetails(Context appContext) { 36 | this(0, new UserDataRepository(appContext)); 37 | } 38 | 39 | public GetUserDetails(int userId, Context appContext) { 40 | this(userId, new UserDataRepository(appContext)); 41 | } 42 | 43 | public GetUserDetails(int userId, UserRepository userRepository) { 44 | this.userId = userId; 45 | this.userRepository = userRepository; 46 | } 47 | 48 | public void setUserRepository(UserRepository userRepository) { 49 | this.userRepository = userRepository; 50 | } 51 | 52 | public int getUserId() { 53 | return userId; 54 | } 55 | 56 | public void setUserId(int userId) { 57 | this.userId = userId; 58 | } 59 | 60 | @Override 61 | protected Observable buildUseCaseObservable() { 62 | return this.userRepository.user(this.userId); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /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 | 19 | import android.content.Context; 20 | 21 | import com.fernandocejas.android10.sample.data.dto.User; 22 | import com.fernandocejas.android10.sample.domain.interactor.repository.UserDataRepository; 23 | import com.fernandocejas.android10.sample.domain.interactor.repository.UserRepository; 24 | 25 | import rx.Observable; 26 | 27 | /** 28 | * This class is an implementation of {@link UseCase} that represents a use case for 29 | * retrieving a collection of all {@link User}. 30 | */ 31 | public class GetUserList extends UseCase { 32 | 33 | UserRepository userRepository; 34 | 35 | public GetUserList(Context appContext) { 36 | this(new UserDataRepository(appContext)); 37 | } 38 | 39 | public GetUserList(UserRepository userRepository) { 40 | this.userRepository = userRepository; 41 | } 42 | 43 | public void setUserRepository(UserRepository userRepository) { 44 | this.userRepository = userRepository; 45 | } 46 | 47 | @Override 48 | public Observable buildUseCaseObservable() { 49 | return this.userRepository.users(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /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.data.executor.JobExecutor; 19 | import com.fernandocejas.android10.sample.data.executor.PostExecutionThread; 20 | import com.fernandocejas.android10.sample.data.executor.ThreadExecutor; 21 | import com.fernandocejas.android10.sample.data.executor.UIThread; 22 | 23 | import rx.Observable; 24 | import rx.Subscriber; 25 | import rx.Subscription; 26 | import rx.schedulers.Schedulers; 27 | import rx.subscriptions.Subscriptions; 28 | 29 | /** 30 | * Abstract class for a Use Case (Interactor in terms of Clean Architecture). 31 | * This interface represents a execution unit for different use cases (this means any use case 32 | * in the application should implement this contract). 33 | * 34 | * By convention each UseCase implementation will return the result using a {@link rx.Subscriber} 35 | * that will execute its job in a background thread and will post the result in the UI thread. 36 | */ 37 | public abstract class UseCase { 38 | 39 | ThreadExecutor threadExecutor = new JobExecutor(); 40 | PostExecutionThread postExecutionThread = new UIThread(); 41 | 42 | private Subscription subscription = Subscriptions.empty(); 43 | 44 | 45 | public void setThreadExecutor(ThreadExecutor threadExecutor) { 46 | this.threadExecutor = threadExecutor; 47 | } 48 | 49 | public void setPostExecutionThread(PostExecutionThread postExecutionThread) { 50 | this.postExecutionThread = postExecutionThread; 51 | } 52 | 53 | /** 54 | * Builds an {@link rx.Observable} which will be used when executing the current {@link UseCase}. 55 | */ 56 | protected abstract Observable buildUseCaseObservable(); 57 | 58 | /** 59 | * Executes the current use case. 60 | * 61 | * @param UseCaseSubscriber The guy who will be listen to the observable build with {@link #buildUseCaseObservable()}. 62 | */ 63 | @SuppressWarnings("unchecked") 64 | public void execute(Subscriber UseCaseSubscriber) { 65 | this.subscription = this.buildUseCaseObservable() 66 | .subscribeOn(Schedulers.from(threadExecutor)) 67 | .observeOn(postExecutionThread.getScheduler()) 68 | .subscribe(UseCaseSubscriber); 69 | } 70 | 71 | /** 72 | * Unsubscribes from current {@link rx.Subscription}. 73 | */ 74 | public void unsubscribe() { 75 | if (!subscription.isUnsubscribed()) { 76 | subscription.unsubscribe(); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/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.domain.interactor.repository; 17 | 18 | import android.content.Context; 19 | 20 | import com.fernandocejas.android10.sample.data.dto.User; 21 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 22 | import com.fernandocejas.android10.sample.data.entity.mapper.UserEntityDataMapper; 23 | import com.fernandocejas.android10.sample.data.datasource.UserDataStore; 24 | import com.fernandocejas.android10.sample.data.datasource.UserDataStoreFactory; 25 | 26 | import java.util.List; 27 | 28 | import rx.Observable; 29 | import rx.functions.Func1; 30 | 31 | /** 32 | * {@link UserRepository} for retrieving user data. 33 | */ 34 | public class UserDataRepository implements UserRepository { 35 | 36 | private UserDataStoreFactory userDataStoreFactory; 37 | private UserEntityDataMapper userEntityDataMapper; 38 | 39 | public UserDataRepository(Context appContext) { 40 | this(new UserDataStoreFactory(appContext), new UserEntityDataMapper()); 41 | } 42 | 43 | /** 44 | * Constructs a {@link UserRepository}. 45 | * 46 | * @param dataStoreFactory A factory to construct different data source implementations. 47 | * @param userEntityDataMapper {@link UserEntityDataMapper}. 48 | */ 49 | public UserDataRepository(UserDataStoreFactory dataStoreFactory, 50 | UserEntityDataMapper userEntityDataMapper) { 51 | this.userDataStoreFactory = dataStoreFactory; 52 | this.userEntityDataMapper = userEntityDataMapper; 53 | } 54 | 55 | public void setUserDataStoreFactory(UserDataStoreFactory userDataStoreFactory) { 56 | this.userDataStoreFactory = userDataStoreFactory; 57 | } 58 | 59 | public void setUserEntityDataMapper(UserEntityDataMapper userEntityDataMapper) { 60 | this.userEntityDataMapper = userEntityDataMapper; 61 | } 62 | 63 | @SuppressWarnings("Convert2MethodRef") 64 | @Override 65 | public Observable> users() { 66 | //we always get all users from the cloud 67 | final UserDataStore userDataStore = this.userDataStoreFactory.createCloudDataStore(); 68 | 69 | return userDataStore.userEntityList() 70 | .map(new Func1, List>() { 71 | @Override 72 | public List call(List userEntities) { 73 | return userEntityDataMapper.transform(userEntities); 74 | } 75 | }); 76 | } 77 | 78 | @SuppressWarnings("Convert2MethodRef") 79 | @Override 80 | public Observable user(int userId) { 81 | final UserDataStore userDataStore = this.userDataStoreFactory.create(userId); 82 | return userDataStore.userEntityDetails(userId) 83 | .map(new Func1() { 84 | @Override 85 | public User call(UserEntity userEntity) { 86 | return userEntityDataMapper.transform(userEntity); 87 | } 88 | }); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/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.interactor.repository; 17 | 18 | 19 | import com.fernandocejas.android10.sample.data.dto.User; 20 | 21 | import java.util.List; 22 | 23 | import rx.Observable; 24 | 25 | /** 26 | * Interface that represents a Repository for getting {@link User} related data. 27 | */ 28 | public interface UserRepository { 29 | /** 30 | * Get an {@link Observable} which will emit a List of {@link User}. 31 | */ 32 | Observable> users(); 33 | 34 | /** 35 | * Get an {@link Observable} which will emit a {@link User}. 36 | * 37 | * @param userId The user id used to retrieve user data. 38 | */ 39 | Observable user(final int userId); 40 | } 41 | -------------------------------------------------------------------------------- /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.data.executor.PostExecutionThread; 19 | import com.fernandocejas.android10.sample.data.executor.ThreadExecutor; 20 | import com.fernandocejas.android10.sample.domain.interactor.repository.UserRepository; 21 | 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | import org.mockito.Mock; 25 | import org.mockito.MockitoAnnotations; 26 | 27 | import static org.mockito.Mockito.verify; 28 | import static org.mockito.Mockito.verifyNoMoreInteractions; 29 | import static org.mockito.Mockito.verifyZeroInteractions; 30 | 31 | public class GetUserDetailsTest { 32 | 33 | private static final int FAKE_USER_ID = 123; 34 | 35 | private GetUserDetails getUserDetails; 36 | 37 | @Mock 38 | private UserRepository mockUserRepository; 39 | @Mock 40 | private ThreadExecutor mockThreadExecutor; 41 | @Mock 42 | private PostExecutionThread mockPostExecutionThread; 43 | 44 | @Before 45 | public void setUp() { 46 | MockitoAnnotations.initMocks(this); 47 | getUserDetails = new GetUserDetails(FAKE_USER_ID, mockUserRepository); 48 | getUserDetails.setThreadExecutor(mockThreadExecutor); 49 | getUserDetails.setPostExecutionThread(mockPostExecutionThread); 50 | 51 | } 52 | 53 | @Test 54 | public void testGetUserDetailsUseCaseObservableHappyCase() { 55 | getUserDetails.buildUseCaseObservable(); 56 | 57 | verify(mockUserRepository).user(FAKE_USER_ID); 58 | verifyNoMoreInteractions(mockUserRepository); 59 | verifyZeroInteractions(mockPostExecutionThread); 60 | verifyZeroInteractions(mockThreadExecutor); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /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.data.executor.PostExecutionThread; 19 | import com.fernandocejas.android10.sample.data.executor.ThreadExecutor; 20 | import com.fernandocejas.android10.sample.domain.interactor.repository.UserRepository; 21 | 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | import org.mockito.Mock; 25 | import org.mockito.MockitoAnnotations; 26 | 27 | import static org.mockito.Mockito.verify; 28 | import static org.mockito.Mockito.verifyNoMoreInteractions; 29 | import static org.mockito.Mockito.verifyZeroInteractions; 30 | 31 | public class GetUserListTest { 32 | 33 | private GetUserList getUserList; 34 | 35 | @Mock private ThreadExecutor mockThreadExecutor; 36 | @Mock private PostExecutionThread mockPostExecutionThread; 37 | @Mock private UserRepository mockUserRepository; 38 | 39 | @Before 40 | public void setUp() { 41 | MockitoAnnotations.initMocks(this); 42 | getUserList = new GetUserList(mockUserRepository); 43 | } 44 | 45 | @Test 46 | public void testGetUserListUseCaseObservableHappyCase() { 47 | getUserList.buildUseCaseObservable(); 48 | 49 | verify(mockUserRepository).users(); 50 | verifyNoMoreInteractions(mockUserRepository); 51 | verifyZeroInteractions(mockThreadExecutor); 52 | verifyZeroInteractions(mockPostExecutionThread); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /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.data.executor.PostExecutionThread; 19 | import com.fernandocejas.android10.sample.data.executor.ThreadExecutor; 20 | 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | import org.mockito.Mock; 24 | import org.mockito.MockitoAnnotations; 25 | 26 | import rx.Observable; 27 | import rx.Subscriber; 28 | import rx.observers.TestSubscriber; 29 | import rx.schedulers.TestScheduler; 30 | 31 | import static org.hamcrest.MatcherAssert.assertThat; 32 | import static org.hamcrest.core.Is.is; 33 | import static org.mockito.BDDMockito.given; 34 | 35 | public class UseCaseTest { 36 | 37 | private UseCaseTestClass useCase; 38 | 39 | @Mock private ThreadExecutor mockThreadExecutor; 40 | @Mock private PostExecutionThread mockPostExecutionThread; 41 | 42 | @Before 43 | public void setUp() { 44 | MockitoAnnotations.initMocks(this); 45 | this.useCase = new UseCaseTestClass(); 46 | this.useCase.setThreadExecutor(mockThreadExecutor); 47 | this.useCase.setPostExecutionThread(mockPostExecutionThread); 48 | } 49 | 50 | @Test 51 | @SuppressWarnings("unchecked") 52 | public void testBuildUseCaseObservableReturnCorrectResult() { 53 | TestSubscriber testSubscriber = new TestSubscriber<>(); 54 | TestScheduler testScheduler = new TestScheduler(); 55 | given(mockPostExecutionThread.getScheduler()).willReturn(testScheduler); 56 | 57 | useCase.execute(testSubscriber); 58 | 59 | assertThat(testSubscriber.getOnNextEvents().size(), is(0)); 60 | } 61 | 62 | @Test 63 | public void testSubscriptionWhenExecutingUseCase() { 64 | TestSubscriber testSubscriber = new TestSubscriber<>(); 65 | 66 | useCase.execute(testSubscriber); 67 | useCase.unsubscribe(); 68 | 69 | assertThat(testSubscriber.isUnsubscribed(), is(true)); 70 | } 71 | 72 | private static class UseCaseTestClass extends UseCase { 73 | 74 | @Override protected Observable buildUseCaseObservable() { 75 | return Observable.empty(); 76 | } 77 | 78 | @Override public void execute(Subscriber UseCaseSubscriber) { 79 | super.execute(UseCaseSubscriber); 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /domain/src/test/java/com/fernandocejas/android10/sample/domain/interactor/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.domain.interactor.repository; 17 | 18 | import com.fernandocejas.android10.sample.data.dto.User; 19 | import com.fernandocejas.android10.sample.data.entity.UserEntity; 20 | import com.fernandocejas.android10.sample.data.entity.mapper.UserEntityDataMapper; 21 | import com.fernandocejas.android10.sample.data.datasource.UserDataStore; 22 | import com.fernandocejas.android10.sample.data.datasource.UserDataStoreFactory; 23 | 24 | import org.junit.Before; 25 | import org.junit.Rule; 26 | import org.junit.Test; 27 | import org.junit.rules.ExpectedException; 28 | import org.mockito.Mock; 29 | import org.mockito.MockitoAnnotations; 30 | 31 | import java.util.ArrayList; 32 | import java.util.List; 33 | 34 | import rx.Observable; 35 | 36 | import static org.mockito.BDDMockito.given; 37 | import static org.mockito.Matchers.anyInt; 38 | import static org.mockito.Mockito.verify; 39 | 40 | public class UserDataRepositoryTest{ 41 | 42 | private static final int FAKE_USER_ID = 123; 43 | 44 | private UserDataRepository userDataRepository; 45 | 46 | @Mock private UserDataStoreFactory mockUserDataStoreFactory; 47 | @Mock private UserEntityDataMapper mockUserEntityDataMapper; 48 | @Mock private UserDataStore mockUserDataStore; 49 | @Mock private UserEntity mockUserEntity; 50 | @Mock private User mockUser; 51 | 52 | @Rule 53 | public ExpectedException expectedException = ExpectedException.none(); 54 | 55 | @Before 56 | public void setUp() { 57 | MockitoAnnotations.initMocks(this); 58 | userDataRepository = new UserDataRepository(mockUserDataStoreFactory, 59 | mockUserEntityDataMapper); 60 | 61 | given(mockUserDataStoreFactory.create(anyInt())).willReturn(mockUserDataStore); 62 | given(mockUserDataStoreFactory.createCloudDataStore()).willReturn(mockUserDataStore); 63 | } 64 | 65 | @Test 66 | public void testGetUsersHappyCase() { 67 | List usersList = new ArrayList<>(); 68 | usersList.add(new UserEntity()); 69 | given(mockUserDataStore.userEntityList()).willReturn(Observable.just(usersList)); 70 | 71 | userDataRepository.users(); 72 | 73 | verify(mockUserDataStoreFactory).createCloudDataStore(); 74 | verify(mockUserDataStore).userEntityList(); 75 | } 76 | 77 | @Test 78 | public void testGetUserHappyCase() { 79 | UserEntity userEntity = new UserEntity(); 80 | given(mockUserDataStore.userEntityDetails(FAKE_USER_ID)).willReturn(Observable.just(userEntity)); 81 | userDataRepository.user(FAKE_USER_ID); 82 | 83 | verify(mockUserDataStoreFactory).create(FAKE_USER_ID); 84 | verify(mockUserDataStore).userEntityDetails(FAKE_USER_ID); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /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/zhengxiaopeng/MVVM_Android-CleanArchitecture/ab6b934fdb93bf47daa37c7c6d541497f6d4d951/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Aug 31 10:46:18 CEST 2015 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-2.9-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /presentation/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /presentation/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.android.databinding' 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 | 22 | compileOptions { 23 | sourceCompatibility JavaVersion.VERSION_1_7 24 | targetCompatibility JavaVersion.VERSION_1_7 25 | } 26 | 27 | packagingOptions { 28 | exclude 'LICENSE.txt' 29 | exclude 'META-INF/DEPENDENCIES' 30 | exclude 'META-INF/ASL2.0' 31 | exclude 'META-INF/NOTICE' 32 | exclude 'META-INF/LICENSE' 33 | } 34 | 35 | lintOptions { 36 | quiet true 37 | abortOnError false 38 | ignoreWarnings true 39 | disable 'InvalidPackage' //Some libraries have issues with this. 40 | disable 'OldTargetApi' //Lint gives this warning but SDK 20 would be Android L Beta. 41 | disable 'IconDensities' //For testing purpose. This is safe to remove. 42 | disable 'IconMissingDensityFolder' //For testing purpose. This is safe to remove. 43 | } 44 | 45 | signingConfigs { 46 | debug { 47 | storeFile file('../buildsystem/debug.keystore') 48 | storePassword 'android' 49 | keyAlias 'androiddebugkey' 50 | keyPassword 'android' 51 | } 52 | } 53 | 54 | buildTypes { 55 | debug { 56 | signingConfig signingConfigs.debug 57 | } 58 | } 59 | } 60 | 61 | dependencies { 62 | def presentationDependencies = rootProject.ext.presentationDependencies 63 | def presentationTestDependencies = rootProject.ext.presentationTestDependencies 64 | 65 | compile project(':domain') 66 | 67 | compile presentationDependencies.recyclerView 68 | compile presentationDependencies.rxJava 69 | compile presentationDependencies.rxAndroid 70 | 71 | androidTestCompile presentationTestDependencies.mockito 72 | androidTestCompile presentationTestDependencies.dexmaker 73 | androidTestCompile presentationTestDependencies.dexmakerMockito 74 | androidTestCompile presentationTestDependencies.espresso 75 | androidTestCompile presentationTestDependencies.espressoIntent 76 | androidTestCompile presentationTestDependencies.espressoContrib 77 | 78 | // androidTestCompile presentationTestDependencies.testingSupportLib 79 | androidTestCompile presentationTestDependencies.runner 80 | androidTestCompile presentationTestDependencies.rules 81 | androidTestCompile presentationTestDependencies.androidAnnotations 82 | } 83 | -------------------------------------------------------------------------------- /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.data.dto.User; 19 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper; 20 | import com.fernandocejas.android10.sample.presentation.model.UserModel; 21 | 22 | import junit.framework.TestCase; 23 | 24 | import java.util.ArrayList; 25 | import java.util.Collection; 26 | import java.util.List; 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 | public class UserModelDataMapperTest extends TestCase { 34 | 35 | private static final int FAKE_USER_ID = 123; 36 | private static final String FAKE_FULLNAME = "Tony Stark"; 37 | 38 | private UserModelDataMapper userModelDataMapper; 39 | 40 | @Override protected void setUp() throws Exception { 41 | super.setUp(); 42 | userModelDataMapper = new UserModelDataMapper(); 43 | } 44 | 45 | public void testTransformUser() { 46 | User user = createFakeUser(); 47 | UserModel userModel = userModelDataMapper.transformUser(user); 48 | 49 | assertThat(userModel, is(instanceOf(UserModel.class))); 50 | assertThat(userModel.getUserId(), is(FAKE_USER_ID)); 51 | assertThat(userModel.getFullName(), is(FAKE_FULLNAME)); 52 | } 53 | 54 | public void testTransformUserCollection() { 55 | User mockUserOne = mock(User.class); 56 | User mockUserTwo = mock(User.class); 57 | 58 | List userList = new ArrayList(5); 59 | userList.add(mockUserOne); 60 | userList.add(mockUserTwo); 61 | 62 | Collection userModelList = userModelDataMapper.transformUsers(userList); 63 | 64 | assertThat(userModelList.toArray()[0], is(instanceOf(UserModel.class))); 65 | assertThat(userModelList.toArray()[1], is(instanceOf(UserModel.class))); 66 | assertThat(userModelList.size(), is(2)); 67 | } 68 | 69 | private User createFakeUser() { 70 | User user = new User(FAKE_USER_ID); 71 | user.setFullName(FAKE_FULLNAME); 72 | 73 | return user; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /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.fl_fragment); 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.fragmentUserList); 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/androidTest/java/com/fernandocejas/android10/sample/test/viewmodel/UserDetailsViewModelTest.java: -------------------------------------------------------------------------------- 1 | package com.fernandocejas.android10.sample.test.viewmodel; 2 | 3 | import android.support.test.rule.ActivityTestRule; 4 | import android.support.test.runner.AndroidJUnit4; 5 | 6 | import com.fernandocejas.android10.sample.data.dto.User; 7 | import com.fernandocejas.android10.sample.presentation.R; 8 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper; 9 | import com.fernandocejas.android10.sample.presentation.view.activity.UserDetailsActivity; 10 | import com.fernandocejas.android10.sample.presentation.view.fragment.UserDetailsFragment; 11 | 12 | import org.junit.Before; 13 | import org.junit.Rule; 14 | import org.junit.Test; 15 | import org.junit.runner.RunWith; 16 | 17 | import static android.support.test.espresso.Espresso.onView; 18 | import static android.support.test.espresso.assertion.ViewAssertions.matches; 19 | import static android.support.test.espresso.matcher.ViewMatchers.withId; 20 | import static android.support.test.espresso.matcher.ViewMatchers.withText; 21 | 22 | /** 23 | * Created by rocko on 15-11-6. 24 | */ 25 | @RunWith(AndroidJUnit4.class) 26 | public class UserDetailsViewModelTest { 27 | 28 | private final static int FAKE_USER_ID = 1; 29 | private final static String FAKE_USER_NAME = "FAKE_USER_NAME"; 30 | private final static String FAKE_EMAIL = "FAKE_EMAIL@fake.com"; 31 | private final static String FAKE_DESCRIPTION = "FAKE_DESCRIPTION"; 32 | private final static int FAKE_FOLLOWERS = 300; 33 | 34 | private User fakeUser; 35 | private UserDetailsFragment detailsFragment; 36 | private UserModelDataMapper userModelDataMapper; 37 | 38 | 39 | @Rule 40 | public ActivityTestRule mActivityRule = new ActivityTestRule(UserDetailsActivity.class); 41 | 42 | @Before 43 | public void setUp() { 44 | fakeUser = makeFakeUser(); 45 | userModelDataMapper = new UserModelDataMapper(); 46 | detailsFragment = (UserDetailsFragment) mActivityRule.getActivity().getFragment(UserDetailsFragment.TAG); 47 | } 48 | 49 | @Test 50 | public void testShowUser() throws Exception { 51 | detailsFragment.getViewModel().showUserDetails(userModelDataMapper.transformUser(fakeUser)); 52 | // UserDetailsBinding userDetailsBinding = detailsFragment.getBinding(); 53 | onView(withId(R.id.tv_fullname)).check(matches(withText(FAKE_USER_NAME))); 54 | onView(withId(R.id.tv_email)).check(matches(withText(FAKE_EMAIL))); 55 | onView(withId(R.id.tv_followers)).check(matches(withText(String.valueOf(FAKE_FOLLOWERS)))); 56 | onView(withId(R.id.tv_description)).check(matches(withText(FAKE_DESCRIPTION))); 57 | } 58 | 59 | 60 | private User makeFakeUser() { 61 | User fakeUser = new User(FAKE_USER_ID); 62 | fakeUser.setFullName(FAKE_USER_NAME); 63 | fakeUser.setEmail(FAKE_EMAIL); 64 | fakeUser.setFollowers(FAKE_FOLLOWERS); 65 | fakeUser.setDescription(FAKE_DESCRIPTION); 66 | return fakeUser; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/viewmodel/UserListViewModelTest.java: -------------------------------------------------------------------------------- 1 | package com.fernandocejas.android10.sample.test.viewmodel; 2 | 3 | import android.support.test.espresso.contrib.RecyclerViewActions; 4 | import android.support.test.espresso.intent.rule.IntentsTestRule; 5 | import android.support.test.runner.AndroidJUnit4; 6 | import android.test.suitebuilder.annotation.LargeTest; 7 | 8 | import com.fernandocejas.android10.sample.data.dto.User; 9 | import com.fernandocejas.android10.sample.presentation.R; 10 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper; 11 | import com.fernandocejas.android10.sample.presentation.model.UserModel; 12 | import com.fernandocejas.android10.sample.presentation.view.activity.UserDetailsActivity; 13 | import com.fernandocejas.android10.sample.presentation.view.activity.UserListActivity; 14 | import com.fernandocejas.android10.sample.presentation.view.adapter.UsersAdapter; 15 | import com.fernandocejas.android10.sample.presentation.view.fragment.UserListFragment; 16 | import com.fernandocejas.android10.sample.presentation.viewmodel.UserListViewModel; 17 | 18 | import org.junit.Before; 19 | import org.junit.Rule; 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | 23 | import java.util.ArrayList; 24 | import java.util.Collection; 25 | import java.util.List; 26 | 27 | import static android.support.test.espresso.Espresso.onView; 28 | import static android.support.test.espresso.action.ViewActions.click; 29 | import static android.support.test.espresso.intent.Intents.intending; 30 | import static android.support.test.espresso.intent.matcher.IntentMatchers.hasExtra; 31 | import static android.support.test.espresso.matcher.ViewMatchers.withId; 32 | import static junit.framework.Assert.assertEquals; 33 | 34 | /** 35 | * Created by rocko on 15-11-6. 36 | */ 37 | @RunWith(AndroidJUnit4.class) 38 | @LargeTest 39 | public class UserListViewModelTest { 40 | 41 | private UserModelDataMapper userModelDataMapper; 42 | private List fakeUsersList; 43 | 44 | private UserListFragment userListFragment; 45 | private UserListViewModel userListViewModel; 46 | private UsersAdapter usersAdapter; 47 | 48 | @Rule 49 | public IntentsTestRule mActivityRule = new IntentsTestRule<>( 50 | UserListActivity.class); 51 | 52 | @Before 53 | public void setUp() { 54 | // intending(not(isInternal())).respondWith(new Instrumentation.ActivityResult(Activity.RESULT_OK, null)); 55 | 56 | userModelDataMapper = new UserModelDataMapper(); 57 | fakeUsersList = makeFakeUsers(); 58 | 59 | userListFragment = (UserListFragment) mActivityRule.getActivity().getFragmentManager().findFragmentById(R.id.fragmentUserList); 60 | userListViewModel = userListFragment.getViewModel(); 61 | 62 | Collection userModelsCollection = userModelDataMapper.transformUsers(fakeUsersList); 63 | usersAdapter = new UsersAdapter(mActivityRule.getActivity(), userModelsCollection); 64 | usersAdapter.setOnItemClickListener(userListViewModel.onUserItemClick()); 65 | userListViewModel.showContentList(usersAdapter); 66 | } 67 | 68 | @Test 69 | public void testShowContentList() throws Exception { 70 | assertEquals(fakeUsersList.size(), usersAdapter.getItemCount()); 71 | } 72 | 73 | @Test 74 | public void testOnUserItemClick() throws Exception { 75 | User user = fakeUsersList.get(1); 76 | 77 | onView(withId(R.id.rv_users)).perform(RecyclerViewActions.actionOnItemAtPosition(1, click())); 78 | 79 | intending(hasExtra(UserDetailsActivity.INTENT_EXTRA_PARAM_USER_ID, user.getUserId() + "")); 80 | } 81 | 82 | 83 | private List makeFakeUsers() { 84 | List fakeUsersList = new ArrayList<>(); 85 | for (int i = 0; i < 15; i++) { 86 | User user = new User(i); 87 | user.setFullName("NAME: " + i); 88 | user.setEmail(i + "@fake.com"); 89 | user.setFollowers(i); 90 | user.setDescription("Description: " + i); 91 | fakeUsersList.add(user); 92 | } 93 | 94 | return fakeUsersList; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /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.Activity; 19 | import android.app.Application; 20 | import android.support.annotation.NonNull; 21 | 22 | /** 23 | * Android Main Application 24 | */ 25 | public class AndroidApplication extends Application { 26 | 27 | private static AndroidApplication instance; 28 | 29 | private Activity mCurrentActivity; 30 | 31 | @Override 32 | public void onCreate() { 33 | super.onCreate(); 34 | instance = this; 35 | } 36 | 37 | public static AndroidApplication getContext() { 38 | return instance; 39 | } 40 | 41 | public static AndroidApplication getInstance() { 42 | return instance; 43 | } 44 | 45 | public Activity getCurrentActivity() { 46 | return mCurrentActivity; 47 | } 48 | 49 | public void setCurrentActivity(@NonNull Activity mCurrentActivity) { 50 | this.mCurrentActivity = mCurrentActivity; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /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/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.data.dto.User; 19 | import com.fernandocejas.android10.sample.presentation.model.UserModel; 20 | 21 | import java.util.ArrayList; 22 | import java.util.Collection; 23 | import java.util.Collections; 24 | 25 | /** 26 | * Mapper class used to transform {@link User} (in the domain layer) to {@link UserModel} in the 27 | * presentation layer. 28 | */ 29 | public class UserModelDataMapper { 30 | 31 | public UserModelDataMapper() {} 32 | 33 | /** 34 | * Transform a {@link User} into an {@link UserModel}. 35 | * 36 | * @param user Object to be transformed. 37 | * @return {@link UserModel}. 38 | */ 39 | public UserModel transformUser(User user) { 40 | if (user == null) { 41 | throw new IllegalArgumentException("Cannot transform a null value"); 42 | } 43 | UserModel userModel = new UserModel(user.getUserId()); 44 | userModel.setCoverUrl(user.getCoverUrl()); 45 | userModel.setFullName(user.getFullName()); 46 | userModel.setEmail(user.getEmail()); 47 | userModel.setDescription(user.getDescription()); 48 | userModel.setFollowers(user.getFollowers()); 49 | 50 | return userModel; 51 | } 52 | 53 | /** 54 | * Transform a Collection of {@link User} into a Collection of {@link UserModel}. 55 | * 56 | * @param usersCollection Objects to be transformed. 57 | * @return List of {@link UserModel}. 58 | */ 59 | public Collection transformUsers(Collection usersCollection) { 60 | Collection userModelsCollection; 61 | 62 | if (usersCollection != null && !usersCollection.isEmpty()) { 63 | userModelsCollection = new ArrayList<>(); 64 | for (User user : usersCollection) { 65 | userModelsCollection.add(transformUser(user)); 66 | } 67 | } else { 68 | userModelsCollection = Collections.emptyList(); 69 | } 70 | 71 | return userModelsCollection; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /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 | @Override public String toString() { 80 | StringBuilder stringBuilder = new StringBuilder(); 81 | 82 | stringBuilder.append("***** User Model Details *****\n"); 83 | stringBuilder.append("id=" + this.getUserId() + "\n"); 84 | stringBuilder.append("cover url=" + this.getCoverUrl() + "\n"); 85 | stringBuilder.append("fullname=" + this.getFullName() + "\n"); 86 | stringBuilder.append("email=" + this.getEmail() + "\n"); 87 | stringBuilder.append("description=" + this.getDescription() + "\n"); 88 | stringBuilder.append("followers=" + this.getFollowers() + "\n"); 89 | stringBuilder.append("*******************************"); 90 | 91 | return stringBuilder.toString(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/navigation/ActivityNavigator.java: -------------------------------------------------------------------------------- 1 | package com.fernandocejas.android10.sample.presentation.navigation; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.support.annotation.NonNull; 7 | 8 | import com.fernandocejas.android10.sample.presentation.AndroidApplication; 9 | 10 | /** 11 | * Created by rocko on 15-11-3. 12 | * Activity Navigator 13 | */ 14 | public class ActivityNavigator { 15 | 16 | /** 17 | * @param targetActivity 18 | */ 19 | public static void navigateTo(@NonNull Class targetActivity) { 20 | navigateTo(targetActivity, new Intent(AndroidApplication.getInstance().getCurrentActivity(), targetActivity)); 21 | } 22 | 23 | /** 24 | * @param targetActivity 25 | * @param intent 26 | */ 27 | public static void navigateTo(@NonNull Class targetActivity, @NonNull Intent intent) { 28 | Activity currentActivity = AndroidApplication.getInstance().getCurrentActivity(); 29 | navigateTo(currentActivity, targetActivity, intent); 30 | } 31 | 32 | /** 33 | * Used in onCreate(before onResume) method to ensure current activity is not null. 34 | * 35 | * @param context 36 | * @param targetActivity 37 | * @param intent 38 | */ 39 | public static void navigateTo(@NonNull Context context, @NonNull Class targetActivity, @NonNull Intent intent) { 40 | context.startActivity(intent); 41 | } 42 | 43 | public static void finish() { 44 | AndroidApplication.getInstance().getCurrentActivity().finish(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /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.databinding.ViewDataBinding; 7 | import android.os.Bundle; 8 | import android.support.annotation.NonNull; 9 | 10 | import com.fernandocejas.android10.sample.presentation.AndroidApplication; 11 | import com.fernandocejas.android10.sample.presentation.viewmodel.ViewModel; 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 | private VM viewModel; 19 | private B binding; 20 | 21 | @Override 22 | protected void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | } 25 | 26 | @Override 27 | protected void onStart() { 28 | super.onStart(); 29 | } 30 | 31 | @Override 32 | protected void onResume() { 33 | super.onResume(); 34 | 35 | AndroidApplication.getInstance().setCurrentActivity(this); 36 | } 37 | 38 | @Override 39 | protected void onPause() { 40 | super.onPause(); 41 | 42 | clearReferences(); 43 | } 44 | 45 | @Override 46 | protected void onStop() { 47 | super.onStop(); 48 | } 49 | 50 | @Override 51 | protected void onDestroy() { 52 | super.onDestroy(); 53 | 54 | clearReferences(); 55 | } 56 | 57 | @Override 58 | protected void onRestart() { 59 | super.onRestart(); 60 | } 61 | 62 | /** 63 | * Adds a {@link Fragment} to this activity's layout. 64 | * 65 | * @param containerViewId The container view to where add the fragment. 66 | * @param fragment The fragment to be added. 67 | */ 68 | protected void addFragment(int containerViewId, Fragment fragment, String tag) { 69 | FragmentTransaction fragmentTransaction = this.getFragmentManager().beginTransaction(); 70 | fragmentTransaction.add(containerViewId, fragment, tag); 71 | fragmentTransaction.commit(); 72 | } 73 | 74 | public T getFragment(String tag) { 75 | return (T) getFragmentManager().findFragmentByTag(tag); 76 | } 77 | 78 | public void setViewModel(@NonNull VM viewModel) { 79 | this.viewModel = viewModel; 80 | } 81 | 82 | public VM getViewModel() { 83 | if (viewModel == null) { 84 | throw new NullPointerException("You should setViewModel first!"); 85 | } 86 | return viewModel; 87 | } 88 | 89 | public void setBinding(@NonNull B binding) { 90 | this.binding = binding; 91 | } 92 | 93 | public B getBinding() { 94 | if (binding == null) { 95 | throw new NullPointerException("You should setBinding first!"); 96 | } 97 | return binding; 98 | } 99 | 100 | 101 | private void clearReferences(){ 102 | Activity currActivity = AndroidApplication.getInstance().getCurrentActivity(); 103 | if (this.equals(currActivity)) 104 | AndroidApplication.getInstance().setCurrentActivity(null); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /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.databinding.DataBindingUtil; 4 | import android.os.Bundle; 5 | import android.text.Html; 6 | import android.text.method.LinkMovementMethod; 7 | 8 | import com.fernandocejas.android10.sample.presentation.HomeBinding; 9 | import com.fernandocejas.android10.sample.presentation.R; 10 | import com.fernandocejas.android10.sample.presentation.viewmodel.HomeViewModel; 11 | 12 | /** 13 | * Main application screen. This is the app entry point. 14 | */ 15 | public class MainActivity extends BaseActivity { 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | 21 | setViewModel(new HomeViewModel()); 22 | setBinding(DataBindingUtil.setContentView(this, R.layout.home_activity)); 23 | getBinding().setViewModel(getViewModel()); 24 | 25 | initWidget(); 26 | } 27 | 28 | private void initWidget() { 29 | getBinding().linkTv.setText(Html.fromHtml(getResources().getString(R.string.url))); 30 | getBinding().linkTv.setMovementMethod(LinkMovementMethod.getInstance()); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /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.databinding.DataBindingUtil; 11 | import android.os.Bundle; 12 | import android.view.Window; 13 | 14 | import com.fernandocejas.android10.sample.presentation.R; 15 | import com.fernandocejas.android10.sample.presentation.view.fragment.UserDetailsFragment; 16 | 17 | /** 18 | * Activity that shows details of a certain user. 19 | */ 20 | public class UserDetailsActivity extends BaseActivity { 21 | 22 | public static final String INTENT_EXTRA_PARAM_USER_ID = "org.android10.INTENT_PARAM_USER_ID"; 23 | private static final String INSTANCE_STATE_PARAM_USER_ID = "org.android10.STATE_PARAM_USER_ID"; 24 | 25 | private int userId; 26 | 27 | public static Intent getCallingIntent(Context context, int userId) { 28 | Intent callingIntent = new Intent(context, UserDetailsActivity.class); 29 | callingIntent.putExtra(INTENT_EXTRA_PARAM_USER_ID, userId); 30 | 31 | return callingIntent; 32 | } 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 38 | DataBindingUtil.setContentView(this, R.layout.activity_user_details); 39 | 40 | this.initializeActivity(savedInstanceState); 41 | } 42 | 43 | @Override 44 | 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.fl_fragment, UserDetailsFragment.newInstance(this.userId), UserDetailsFragment.TAG); 58 | } else { 59 | this.userId = savedInstanceState.getInt(INSTANCE_STATE_PARAM_USER_ID); 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /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.databinding.DataBindingUtil; 11 | import android.os.Bundle; 12 | import android.view.Window; 13 | 14 | import com.fernandocejas.android10.sample.presentation.R; 15 | 16 | /** 17 | * Activity that shows a list of Users. 18 | */ 19 | public class UserListActivity extends BaseActivity{ 20 | 21 | public static Intent getCallingIntent(Context context) { 22 | return new Intent(context, UserListActivity.class); 23 | } 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 29 | 30 | DataBindingUtil.setContentView(this, R.layout.user_list_activity); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/adapter/UsersAdapter.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.adapter; 7 | 8 | import android.content.Context; 9 | import android.databinding.DataBindingUtil; 10 | import android.support.v7.widget.RecyclerView; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.TextView; 15 | 16 | import com.fernandocejas.android10.sample.presentation.R; 17 | import com.fernandocejas.android10.sample.presentation.RowUserBinding; 18 | import com.fernandocejas.android10.sample.presentation.model.UserModel; 19 | 20 | import java.util.Collection; 21 | import java.util.List; 22 | 23 | 24 | /** 25 | * Adaptar that manages a collection of {@link UserModel}. 26 | */ 27 | public class UsersAdapter extends RecyclerView.Adapter { 28 | 29 | private RowUserBinding rowUserBinding; 30 | 31 | public interface OnItemClickListener { 32 | void onUserItemClicked(UserModel userModel); 33 | } 34 | 35 | private List usersCollection; 36 | private final LayoutInflater layoutInflater; 37 | 38 | private OnItemClickListener onItemClickListener; 39 | 40 | public UsersAdapter(Context context, Collection usersCollection) { 41 | this.validateUsersCollection(usersCollection); 42 | this.layoutInflater = 43 | (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 44 | this.usersCollection = (List) usersCollection; 45 | } 46 | 47 | @Override 48 | public int getItemCount() { 49 | return (this.usersCollection != null) ? this.usersCollection.size() : 0; 50 | } 51 | 52 | @Override 53 | public UserViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 54 | rowUserBinding = DataBindingUtil.inflate(layoutInflater, R.layout.row_user, parent, false); 55 | UserViewHolder userViewHolder = new UserViewHolder(rowUserBinding); 56 | return userViewHolder; 57 | } 58 | 59 | @Override 60 | public void onBindViewHolder(UserViewHolder holder, final int position) { 61 | final UserModel userModel = this.usersCollection.get(position); 62 | holder.textViewTitle.setText(userModel.getFullName()); 63 | holder.itemView.setOnClickListener(new View.OnClickListener() { 64 | @Override 65 | public void onClick(View v) { 66 | if (UsersAdapter.this.onItemClickListener != null) { 67 | UsersAdapter.this.onItemClickListener.onUserItemClicked(userModel); 68 | } 69 | } 70 | }); 71 | } 72 | 73 | @Override 74 | public long getItemId(int position) { 75 | return position; 76 | } 77 | 78 | public void setUsersCollection(Collection usersCollection) { 79 | this.validateUsersCollection(usersCollection); 80 | this.usersCollection = (List) usersCollection; 81 | this.notifyDataSetChanged(); 82 | } 83 | 84 | public void setOnItemClickListener(OnItemClickListener onItemClickListener) { 85 | this.onItemClickListener = onItemClickListener; 86 | } 87 | 88 | private void validateUsersCollection(Collection usersCollection) { 89 | if (usersCollection == null) { 90 | throw new IllegalArgumentException("The list cannot be null"); 91 | } 92 | } 93 | 94 | static class UserViewHolder extends RecyclerView.ViewHolder { 95 | TextView textViewTitle; 96 | 97 | public UserViewHolder(RowUserBinding rowUserBinding) { 98 | super(rowUserBinding.getRoot()); 99 | textViewTitle = rowUserBinding.title; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /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.databinding.ViewDataBinding; 10 | import android.os.Bundle; 11 | import android.support.annotation.NonNull; 12 | import android.widget.Toast; 13 | 14 | import com.fernandocejas.android10.sample.presentation.viewmodel.ViewModel; 15 | 16 | /** 17 | * Base {@link android.app.Fragment} class for every fragment in this application. 18 | */ 19 | public abstract class BaseFragment extends Fragment { 20 | 21 | private VM viewModel; 22 | private B binding; 23 | 24 | @Override 25 | public void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | setRetainInstance(true); 28 | } 29 | 30 | /** 31 | * Shows a {@link android.widget.Toast} message. 32 | * 33 | * @param message An string representing a message to be shown. 34 | */ 35 | protected void showToastMessage(String message) { 36 | Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show(); 37 | } 38 | 39 | 40 | public void setViewModel(@NonNull VM viewModel) { 41 | this.viewModel = viewModel; 42 | } 43 | 44 | public VM getViewModel() { 45 | if (viewModel == null) { 46 | throw new NullPointerException("You should setViewModel first!"); 47 | } 48 | return viewModel; 49 | } 50 | 51 | public void setBinding(@NonNull B binding) { 52 | this.binding = binding; 53 | } 54 | 55 | public B getBinding() { 56 | if (binding == null) { 57 | throw new NullPointerException("You should setBinding first!"); 58 | } 59 | return binding; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/UserDetailsFragment.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.content.Context; 9 | import android.databinding.DataBindingUtil; 10 | import android.os.Bundle; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | 15 | import com.fernandocejas.android10.sample.presentation.R; 16 | import com.fernandocejas.android10.sample.presentation.UserDetailsBinding; 17 | import com.fernandocejas.android10.sample.presentation.viewmodel.UserDetailsViewModel; 18 | 19 | /** 20 | * Fragment that shows details of a certain user. 21 | */ 22 | public class UserDetailsFragment extends BaseFragment { 23 | 24 | public final static String TAG = UserDetailsFragment.class.getSimpleName(); 25 | 26 | private static final String ARGUMENT_KEY_USER_ID = "org.android10.ARGUMENT_USER_ID"; 27 | 28 | private int userId; 29 | 30 | public UserDetailsFragment() { 31 | super(); 32 | 33 | } 34 | 35 | public static UserDetailsFragment newInstance(int userId) { 36 | UserDetailsFragment userDetailsFragment = new UserDetailsFragment(); 37 | 38 | Bundle argumentsBundle = new Bundle(); 39 | argumentsBundle.putInt(ARGUMENT_KEY_USER_ID, userId); 40 | userDetailsFragment.setArguments(argumentsBundle); 41 | 42 | return userDetailsFragment; 43 | } 44 | 45 | @Override 46 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 47 | Bundle savedInstanceState) { 48 | 49 | setViewModel(new UserDetailsViewModel()); 50 | setBinding(DataBindingUtil.inflate(inflater, R.layout.fragment_user_details, container, false)); 51 | getBinding().setViewModel(getViewModel()); 52 | 53 | return getBinding().getRoot(); 54 | } 55 | 56 | @Override 57 | public void onActivityCreated(Bundle savedInstanceState) { 58 | super.onActivityCreated(savedInstanceState); 59 | this.initialize(); 60 | } 61 | 62 | @Override 63 | public void onResume() { 64 | super.onResume(); 65 | } 66 | 67 | @Override 68 | public void onPause() { 69 | super.onPause(); 70 | } 71 | 72 | @Override 73 | public void onDestroyView() { 74 | super.onDestroyView(); 75 | } 76 | 77 | @Override 78 | public void onDestroy() { 79 | super.onDestroy(); 80 | } 81 | 82 | private void initialize() { 83 | this.userId = getArguments().getInt(ARGUMENT_KEY_USER_ID); 84 | 85 | getViewModel().loadUserDetailsCommand(userId); 86 | } 87 | 88 | @Override 89 | public Context getContext() { 90 | return getActivity().getApplicationContext(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /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.content.Context; 9 | import android.databinding.DataBindingUtil; 10 | import android.os.Bundle; 11 | import android.view.LayoutInflater; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | 15 | import com.fernandocejas.android10.sample.presentation.R; 16 | import com.fernandocejas.android10.sample.presentation.UserListBinding; 17 | import com.fernandocejas.android10.sample.presentation.view.adapter.UsersLayoutManager; 18 | import com.fernandocejas.android10.sample.presentation.viewmodel.UserListViewModel; 19 | 20 | /** 21 | * Fragment that shows a list of Users. 22 | */ 23 | public class UserListFragment extends BaseFragment { 24 | 25 | public final static String TAG = UserListFragment.class.getSimpleName(); 26 | 27 | public UserListFragment() { 28 | super(); 29 | } 30 | 31 | @Override 32 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 33 | Bundle savedInstanceState) { 34 | 35 | setViewModel(new UserListViewModel()); 36 | setBinding(DataBindingUtil.inflate(inflater, R.layout.fragment_user_list, container, true)); 37 | getBinding().setViewModel(getViewModel()); 38 | 39 | setupUI(); 40 | 41 | return getBinding().getRoot(); 42 | } 43 | 44 | @Override 45 | public void onActivityCreated(Bundle savedInstanceState) { 46 | super.onActivityCreated(savedInstanceState); 47 | getViewModel().loadUsersCommand(); 48 | } 49 | 50 | private void setupUI() { 51 | getBinding().rvUsers.setLayoutManager(new UsersLayoutManager(getActivity())); 52 | } 53 | 54 | @Override 55 | public Context getContext() { 56 | return this.getActivity().getApplicationContext(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/viewmodel/HomeViewModel.java: -------------------------------------------------------------------------------- 1 | package com.fernandocejas.android10.sample.presentation.viewmodel; 2 | 3 | import android.view.View; 4 | 5 | import com.fernandocejas.android10.sample.presentation.navigation.ActivityNavigator; 6 | import com.fernandocejas.android10.sample.presentation.view.activity.UserListActivity; 7 | 8 | /** 9 | * Created by rocko on 15-11-5. 10 | */ 11 | public class HomeViewModel extends ViewModel { 12 | 13 | @Command 14 | public View.OnClickListener onClickLoadData() { 15 | return new View.OnClickListener(){ 16 | @Override 17 | public void onClick(View v) { 18 | ActivityNavigator.navigateTo(UserListActivity.class); 19 | } 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/viewmodel/LoadingViewModel.java: -------------------------------------------------------------------------------- 1 | package com.fernandocejas.android10.sample.presentation.viewmodel; 2 | 3 | import android.databinding.ObservableBoolean; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by rocko on 15-11-5. 8 | */ 9 | public abstract class LoadingViewModel extends ViewModel { 10 | public final ObservableBoolean showRetry = new ObservableBoolean(false); 11 | public final ObservableBoolean showLoading = new ObservableBoolean(false); 12 | 13 | @BindView 14 | public void showLoading() { 15 | showRetry.set(false); 16 | showLoading.set(true); 17 | } 18 | 19 | @BindView 20 | public void showRetry() { 21 | showLoading.set(false); 22 | showRetry.set(true); 23 | } 24 | 25 | public abstract View.OnClickListener onRetryClick(); 26 | } 27 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/viewmodel/UserDetailsViewModel.java: -------------------------------------------------------------------------------- 1 | package com.fernandocejas.android10.sample.presentation.viewmodel; 2 | 3 | import android.databinding.ObservableBoolean; 4 | import android.databinding.ObservableField; 5 | import android.view.View; 6 | 7 | import com.fernandocejas.android10.sample.data.dto.User; 8 | import com.fernandocejas.android10.sample.domain.interactor.DefaultSubscriber; 9 | import com.fernandocejas.android10.sample.domain.interactor.GetUserDetails; 10 | import com.fernandocejas.android10.sample.presentation.AndroidApplication; 11 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper; 12 | import com.fernandocejas.android10.sample.presentation.model.UserModel; 13 | 14 | /** 15 | * Created by rocko on 15-11-5. 16 | */ 17 | public class UserDetailsViewModel extends LoadingViewModel { 18 | 19 | public final ObservableBoolean showUserDetails = new ObservableBoolean(true); 20 | public final ObservableField userObs = new ObservableField<>(); 21 | 22 | GetUserDetails getUserDetailsUseCase = new GetUserDetails(AndroidApplication.getContext()); 23 | UserModelDataMapper userModelDataMapper = new UserModelDataMapper(); 24 | 25 | @BindView 26 | @Override 27 | public void showLoading() { 28 | // super.showLoading(); // show Details 29 | showRetry.set(false); 30 | showLoading.set(true); 31 | showUserDetails.set(true); 32 | } 33 | 34 | @BindView 35 | @Override 36 | public void showRetry() { 37 | super.showRetry(); 38 | showUserDetails.set(false); 39 | } 40 | 41 | @BindView 42 | public void showUserDetails(UserModel userModel) { 43 | showLoading.set(false); 44 | showRetry.set(false); 45 | showUserDetails.set(true); 46 | userObs.set(userModel); 47 | } 48 | 49 | 50 | @Command 51 | public void loadUserDetailsCommand(int userId) { 52 | showLoading(); 53 | getUserDetailsUseCase.setUserId(userId); 54 | getUserDetailsUseCase.execute(new DefaultSubscriber(){ 55 | @Override 56 | public void onNext(User user) { 57 | showUserDetails(userModelDataMapper.transformUser(user)); 58 | } 59 | 60 | @Override 61 | public void onError(Throwable e) { 62 | showRetry(); 63 | } 64 | }); 65 | } 66 | 67 | @Override 68 | public View.OnClickListener onRetryClick() { 69 | return new View.OnClickListener() { 70 | @Override 71 | public void onClick(View v) { 72 | loadUserDetailsCommand(userObs.get().getUserId()); 73 | } 74 | }; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/viewmodel/UserListViewModel.java: -------------------------------------------------------------------------------- 1 | package com.fernandocejas.android10.sample.presentation.viewmodel; 2 | 3 | 4 | import android.content.Intent; 5 | import android.databinding.ObservableBoolean; 6 | import android.databinding.ObservableField; 7 | import android.util.Log; 8 | import android.view.View; 9 | 10 | import com.fernandocejas.android10.sample.data.dto.User; 11 | import com.fernandocejas.android10.sample.domain.interactor.DefaultSubscriber; 12 | import com.fernandocejas.android10.sample.domain.interactor.GetUserList; 13 | import com.fernandocejas.android10.sample.domain.interactor.UseCase; 14 | import com.fernandocejas.android10.sample.presentation.AndroidApplication; 15 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper; 16 | import com.fernandocejas.android10.sample.presentation.model.UserModel; 17 | import com.fernandocejas.android10.sample.presentation.navigation.ActivityNavigator; 18 | import com.fernandocejas.android10.sample.presentation.view.activity.UserDetailsActivity; 19 | import com.fernandocejas.android10.sample.presentation.view.adapter.UsersAdapter; 20 | 21 | import java.util.Collection; 22 | import java.util.List; 23 | 24 | /** 25 | * Created by rocko on 15-11-5. 26 | */ 27 | public class UserListViewModel extends LoadingViewModel { 28 | private final static String TAG = UserListViewModel.class.getSimpleName(); 29 | 30 | public final ObservableBoolean showContentList = new ObservableBoolean(false); 31 | public final ObservableField usersListAdapter = new ObservableField<>(); 32 | 33 | UseCase getUserList = new GetUserList(AndroidApplication.getContext()); 34 | UserModelDataMapper userModelDataMapper = new UserModelDataMapper(); 35 | 36 | 37 | @BindView 38 | @Override 39 | public void showLoading() { 40 | super.showLoading(); 41 | showContentList.set(false); 42 | } 43 | 44 | @BindView 45 | @Override 46 | public void showRetry() { 47 | super.showRetry(); 48 | showContentList.set(false); 49 | } 50 | 51 | @BindView 52 | public void showContentList(UsersAdapter usersAdapter) { 53 | showLoading.set(false); 54 | showRetry.set(false); 55 | showContentList.set(true); 56 | usersListAdapter.set(usersAdapter); 57 | } 58 | 59 | @BindView 60 | public void showMoreContent() { 61 | // userAdapter 62 | } 63 | 64 | @Command 65 | public void loadUsersCommand() { 66 | if (showLoading.get()) { 67 | return; 68 | } 69 | showLoading(); 70 | getUserList.execute(new DefaultSubscriber>() { 71 | @Override 72 | public void onNext(List users) { 73 | Collection userModelsCollection = userModelDataMapper.transformUsers(users); 74 | UsersAdapter usersAdapter = new UsersAdapter(AndroidApplication.getContext(), userModelsCollection); 75 | usersAdapter.setOnItemClickListener(onUserItemClick()); 76 | showContentList(usersAdapter); 77 | } 78 | 79 | @Override 80 | public void onError(Throwable e) { 81 | showRetry(); 82 | } 83 | 84 | }); 85 | } 86 | 87 | @Override 88 | public View.OnClickListener onRetryClick() { 89 | return new View.OnClickListener() { 90 | @Override 91 | public void onClick(View v) { 92 | loadUsersCommand(); 93 | 94 | } 95 | }; 96 | } 97 | 98 | public UsersAdapter.OnItemClickListener onUserItemClick() { 99 | return new UsersAdapter.OnItemClickListener() { 100 | @Override 101 | public void onUserItemClicked(UserModel userModel) { 102 | Intent intent = UserDetailsActivity.getCallingIntent(AndroidApplication.getInstance().getCurrentActivity(), userModel.getUserId()); 103 | ActivityNavigator.navigateTo(UserDetailsActivity.class, intent); 104 | } 105 | }; 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /presentation/src/main/java/com/fernandocejas/android10/sample/presentation/viewmodel/ViewModel.java: -------------------------------------------------------------------------------- 1 | package com.fernandocejas.android10.sample.presentation.viewmodel; 2 | 3 | import android.databinding.BaseObservable; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * Created by rocko on 15-11-5. 12 | */ 13 | public abstract class ViewModel extends BaseObservable{ // TODO: Need Context(Activity)? 14 | 15 | /* Just mark a method in ViewModel */ 16 | @Target(ElementType.METHOD) 17 | @Retention(RetentionPolicy.SOURCE) 18 | protected @interface Command { 19 | } 20 | 21 | @Target(ElementType.METHOD) 22 | @Retention(RetentionPolicy.SOURCE) 23 | protected @interface BindView { 24 | } 25 | 26 | // ... InstanceState in ViewModel 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /presentation/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengxiaopeng/MVVM_Android-CleanArchitecture/ab6b934fdb93bf47daa37c7c6d541497f6d4d951/presentation/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /presentation/src/main/res/drawable-hdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengxiaopeng/MVVM_Android-CleanArchitecture/ab6b934fdb93bf47daa37c7c6d541497f6d4d951/presentation/src/main/res/drawable-hdpi/logo.png -------------------------------------------------------------------------------- /presentation/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengxiaopeng/MVVM_Android-CleanArchitecture/ab6b934fdb93bf47daa37c7c6d541497f6d4d951/presentation/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /presentation/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengxiaopeng/MVVM_Android-CleanArchitecture/ab6b934fdb93bf47daa37c7c6d541497f6d4d951/presentation/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /presentation/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhengxiaopeng/MVVM_Android-CleanArchitecture/ab6b934fdb93bf47daa37c7c6d541497f6d4d951/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_user_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/fragment_user_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 15 | 16 | 20 | 21 | 25 | 26 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/fragment_user_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 17 | 18 | 26 | 27 | 31 | 32 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /presentation/src/main/res/layout/home_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 20 | 21 | 28 | 29 | 38 | 39 |