diff --git a/.travis.yml b/.travis.yml index 0f3726f4..3cae64da 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,18 @@ jdk: oraclejdk8 android: components: - - build-tools-23.0.1 - - android-21 + - tools + - platform-tools + - tools + - build-tools-27.0.1 + - android-26 + - extra-google-m2repository + - extra-android-m2repository + +licenses: + - 'android-sdk-preview-license-.+' + - 'android-sdk-license-.+' + - 'google-gdk-license-.+' script: ./gradlew build diff --git a/README.md b/README.md index 112b2e29..a060ce7e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ -Android-CleanArchitecture [![Build Status](https://travis-ci.org/android10/Android-CleanArchitecture.svg?branch=master)](https://travis-ci.org/android10/Android-CleanArchitecture) +Android-CleanArchitecture ========================= +## New version available written in Kotlin: +[Architecting Android… Reloaded](https://fernandocejas.com/2018/05/07/architecting-android-reloaded/) + +Introduction +----------------- This is a sample app that is part of a blog post I have written about how to architect android application using the Uncle Bob's clean architecture approach. [Architecting Android…The clean way?](http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/) @@ -9,19 +14,21 @@ This is a sample app that is part of a blog post I have written about how to arc [Tasting Dagger 2 on Android](http://fernandocejas.com/2015/04/11/tasting-dagger-2-on-android/) +[Clean Architecture…Dynamic Parameters in Use Cases](http://fernandocejas.com/2016/12/24/clean-architecture-dynamic-parameters-in-use-cases/) + [Demo video of this sample](http://youtu.be/XSjV4sG3ni0) Clean architecture ----------------- -![http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/](http://fernandocejas.com/wp-content/uploads/2014/09/clean_architecture1.png) +![http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/](https://github.com/android10/Sample-Data/blob/master/Android-CleanArchitecture/clean_architecture.png) Architectural approach ----------------- -![http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/](http://fernandocejas.com/wp-content/uploads/2014/09/clean_architecture_android.png) +![http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/](https://github.com/android10/Sample-Data/blob/master/Android-CleanArchitecture/clean_architecture_layers.png) Architectural reactive approach ----------------- -![http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/](http://fernandocejas.com/wp-content/uploads/2015/07/clean_architecture_evolution.png) +![http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/](https://github.com/android10/Sample-Data/blob/master/Android-CleanArchitecture/clean_architecture_layers_details.png) Local Development ----------------- @@ -49,7 +56,7 @@ https://github.com/android10/java-code-styles License -------- - Copyright 2014 Fernando Cejas + Copyright 2018 Fernando Cejas Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -64,6 +71,8 @@ License limitations under the License. -![http://www.fernandocejas.com](http://www.android10.org/myimages/android10_logo_big_github.png) +![http://www.fernandocejas.com](https://github.com/android10/Sample-Data/blob/master/android10/android10_logo_big.png) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Android--CleanArchitecture-brightgreen.svg?style=flat)](https://android-arsenal.com/details/3/909) + +Buy Me A Coffee diff --git a/build.gradle b/build.gradle index 54470a60..d951c72d 100644 --- a/build.gradle +++ b/build.gradle @@ -5,11 +5,11 @@ buildscript { repositories { jcenter() mavenCentral() + google() } dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' - classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4' - classpath "com.fernandocejas.frodo:frodo-plugin:0.8.1" + classpath 'com.android.tools.build:gradle:3.0.1' +// classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4' } } @@ -23,11 +23,6 @@ allprojects { } } -task wrapper(type: Wrapper) { - description 'Creates the gradle wrapper.' - gradleVersion '2.10' -} - task runDomainUnitTests(dependsOn: [':domain:test']) { description 'Run unit tests for the domain layer.' } diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index aedb3ab5..ba467503 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -6,29 +6,31 @@ allprojects { ext { //Android - androidBuildToolsVersion = "23.0.1" + androidBuildToolsVersion = "27.0.1" androidMinSdkVersion = 15 - androidTargetSdkVersion = 21 - androidCompileSdkVersion = 21 + androidTargetSdkVersion = 26 + androidCompileSdkVersion = 26 //Libraries - daggerVersion = '2.0.2' + daggerVersion = '2.8' butterKnifeVersion = '7.0.1' - recyclerViewVersion = '21.0.3' - rxJavaVersion = '1.0.14' - rxAndroidVersion = '1.0.1' + recyclerViewVersion = '25.4.0' + rxJavaVersion = '2.0.2' + rxAndroidVersion = '2.0.1' javaxAnnotationVersion = '1.0' + javaxInjectVersion = '1' gsonVersion = '2.3' okHttpVersion = '2.5.0' - androidAnnotationsVersion = '21.0.3' + androidAnnotationsVersion = '25.4.0' + arrowVersion = '1.0.0' //Testing - robolectricVersion = '3.0' + robolectricVersion = '3.1.1' jUnitVersion = '4.12' assertJVersion = '1.7.1' mockitoVersion = '1.9.5' dexmakerVersion = '1.0' - espressoVersion = '2.0' + espressoVersion = '3.0.1' testingSupportLibVersion = '0.1' //Development @@ -39,9 +41,9 @@ ext { dagger: "com.google.dagger:dagger:${daggerVersion}", butterKnife: "com.jakewharton:butterknife:${butterKnifeVersion}", recyclerView: "com.android.support:recyclerview-v7:${recyclerViewVersion}", - rxJava: "io.reactivex:rxjava:${rxJavaVersion}", - rxAndroid: "io.reactivex:rxandroid:${rxAndroidVersion}", - javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}", + rxJava: "io.reactivex.rxjava2:rxjava:${rxJavaVersion}", + rxAndroid: "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}", + javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}" ] presentationTestDependencies = [ @@ -53,15 +55,16 @@ ext { ] domainDependencies = [ - daggerCompiler: "com.google.dagger:dagger-compiler:${daggerVersion}", - dagger: "com.google.dagger:dagger:${daggerVersion}", javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}", - rxJava: "io.reactivex:rxjava:${rxJavaVersion}", + javaxInject: "javax.inject:javax.inject:${javaxInjectVersion}", + rxJava: "io.reactivex.rxjava2:rxjava:${rxJavaVersion}", + arrow: "com.fernandocejas:arrow:${arrowVersion}" ] domainTestDependencies = [ junit: "junit:junit:${jUnitVersion}", mockito: "org.mockito:mockito-core:${mockitoVersion}", + assertj: "org.assertj:assertj-core:${assertJVersion}" ] dataDependencies = [ @@ -69,9 +72,10 @@ ext { dagger: "com.google.dagger:dagger:${daggerVersion}", okHttp: "com.squareup.okhttp:okhttp:${okHttpVersion}", gson: "com.google.code.gson:gson:${gsonVersion}", - rxJava: "io.reactivex:rxjava:${rxJavaVersion}", - rxAndroid: "io.reactivex:rxandroid:${rxAndroidVersion}", + rxJava: "io.reactivex.rxjava2:rxjava:${rxJavaVersion}", + rxAndroid: "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}", javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}", + javaxInject: "javax.inject:javax.inject:${javaxInjectVersion}", androidAnnotations: "com.android.support:support-annotations:${androidAnnotationsVersion}" ] diff --git a/data/build.gradle b/data/build.gradle index 8d358887..e1f55a6a 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -3,13 +3,12 @@ buildscript { mavenCentral() } dependencies { - classpath 'me.tatarka:gradle-retrolambda:3.2.3' + classpath 'me.tatarka:gradle-retrolambda:3.7.0' } } apply plugin: 'com.android.library' -apply plugin: 'com.neenbedankt.android-apt' -apply plugin: 'com.fernandocejas.frodo' +//apply plugin: 'com.neenbedankt.android-apt' apply plugin: 'me.tatarka.retrolambda' android { @@ -52,18 +51,21 @@ dependencies { def dataDependencies = rootProject.ext.dataDependencies def testDependencies = rootProject.ext.dataTestDependencies - compile project(':domain') - apt dataDependencies.daggerCompiler - provided dataDependencies.javaxAnnotation - compile dataDependencies.dagger - compile dataDependencies.okHttp - compile dataDependencies.gson - compile dataDependencies.rxJava - compile dataDependencies.rxAndroid - compile dataDependencies.androidAnnotations + implementation project(':domain') + compileOnly dataDependencies.javaxAnnotation + implementation dataDependencies.javaxInject + implementation dataDependencies.okHttp + implementation dataDependencies.gson + implementation dataDependencies.rxJava + implementation dataDependencies.rxAndroid + implementation dataDependencies.androidAnnotations - testCompile testDependencies.junit - testCompile testDependencies.assertj - testCompile testDependencies.mockito - testCompile testDependencies.robolectric + testImplementation testDependencies.junit + testImplementation testDependencies.assertj + testImplementation testDependencies.mockito + testImplementation testDependencies.robolectric } + +repositories { + google() +} \ No newline at end of file diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/cache/FileManager.java b/data/src/main/java/com/fernandocejas/android10/sample/data/cache/FileManager.java index 670ee93f..62d99d76 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/cache/FileManager.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/cache/FileManager.java @@ -19,7 +19,6 @@ import android.content.SharedPreferences; import java.io.BufferedReader; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; @@ -33,7 +32,7 @@ public class FileManager { @Inject - public FileManager() {} + FileManager() {} /** * Writes a file to Disk. @@ -42,18 +41,14 @@ public FileManager() {} * * @param file The file to write to Disk. */ - public void writeToFile(File file, String fileContent) { + void writeToFile(File file, String fileContent) { if (!file.exists()) { try { - FileWriter writer = new FileWriter(file); + final FileWriter writer = new FileWriter(file); writer.write(fileContent); writer.close(); - } catch (FileNotFoundException e) { - e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); - } finally { - } } } @@ -66,25 +61,22 @@ public void writeToFile(File file, String fileContent) { * @param file The file to read from. * @return A string with the content of the file. */ - public String readFileContent(File file) { - StringBuilder fileContentBuilder = new StringBuilder(); + String readFileContent(File file) { + final StringBuilder fileContentBuilder = new StringBuilder(); if (file.exists()) { String stringLine; try { - FileReader fileReader = new FileReader(file); - BufferedReader bufferedReader = new BufferedReader(fileReader); + final FileReader fileReader = new FileReader(file); + final BufferedReader bufferedReader = new BufferedReader(fileReader); while ((stringLine = bufferedReader.readLine()) != null) { - fileContentBuilder.append(stringLine + "\n"); + fileContentBuilder.append(stringLine).append("\n"); } bufferedReader.close(); fileReader.close(); - } catch (FileNotFoundException e) { - e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } - return fileContentBuilder.toString(); } @@ -94,7 +86,7 @@ public String readFileContent(File file) { * @param file The file to check existence. * @return true if this file exists, false otherwise. */ - public boolean exists(File file) { + boolean exists(File file) { return file.exists(); } @@ -105,12 +97,14 @@ public boolean exists(File file) { * * @param directory The directory which its content will be deleted. */ - public void clearDirectory(File directory) { + boolean clearDirectory(File directory) { + boolean result = false; if (directory.exists()) { for (File file : directory.listFiles()) { - file.delete(); + result = file.delete(); } } + return result; } /** @@ -121,12 +115,12 @@ public void clearDirectory(File directory) { * @param key A string for the key that will be used to retrieve the value in the future. * @param value A long representing the value to be inserted. */ - public void writeToPreferences(Context context, String preferenceFileName, String key, + void writeToPreferences(Context context, String preferenceFileName, String key, long value) { - SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceFileName, + final SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceFileName, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = sharedPreferences.edit(); + final SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putLong(key, value); editor.apply(); } @@ -139,8 +133,8 @@ public void writeToPreferences(Context context, String preferenceFileName, Strin * @param key A key that will be used to retrieve the value from the preference file. * @return A long representing the value retrieved from the preferences file. */ - public long getFromPreferences(Context context, String preferenceFileName, String key) { - SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceFileName, + long getFromPreferences(Context context, String preferenceFileName, String key) { + final SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceFileName, Context.MODE_PRIVATE); return sharedPreferences.getLong(key, 0); } diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/cache/UserCache.java b/data/src/main/java/com/fernandocejas/android10/sample/data/cache/UserCache.java index 302cb760..1fe53a04 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/cache/UserCache.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/cache/UserCache.java @@ -16,14 +16,14 @@ package com.fernandocejas.android10.sample.data.cache; import com.fernandocejas.android10.sample.data.entity.UserEntity; -import rx.Observable; +import io.reactivex.Observable; /** * An interface representing a user Cache. */ public interface UserCache { /** - * Gets an {@link rx.Observable} which will emit a {@link UserEntity}. + * Gets an {@link Observable} which will emit a {@link UserEntity}. * * @param userId The user id to retrieve data. */ diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/cache/UserCacheImpl.java b/data/src/main/java/com/fernandocejas/android10/sample/data/cache/UserCacheImpl.java index a7252f48..cc4351e0 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/cache/UserCacheImpl.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/cache/UserCacheImpl.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,14 +16,14 @@ package com.fernandocejas.android10.sample.data.cache; import android.content.Context; -import com.fernandocejas.android10.sample.data.cache.serializer.JsonSerializer; +import com.fernandocejas.android10.sample.data.cache.serializer.Serializer; import com.fernandocejas.android10.sample.data.entity.UserEntity; import com.fernandocejas.android10.sample.data.exception.UserNotFoundException; import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; +import io.reactivex.Observable; import java.io.File; import javax.inject.Inject; import javax.inject.Singleton; -import rx.Observable; /** * {@link UserCache} implementation. @@ -39,7 +39,7 @@ public class UserCacheImpl implements UserCache { private final Context context; private final File cacheDir; - private final JsonSerializer serializer; + private final Serializer serializer; private final FileManager fileManager; private final ThreadExecutor threadExecutor; @@ -47,52 +47,51 @@ public class UserCacheImpl implements UserCache { * Constructor of the class {@link UserCacheImpl}. * * @param context A - * @param userCacheSerializer {@link JsonSerializer} for object serialization. + * @param serializer {@link Serializer} for object serialization. * @param fileManager {@link FileManager} for saving serialized objects to the file system. */ - @Inject - public UserCacheImpl(Context context, JsonSerializer userCacheSerializer, + @Inject UserCacheImpl(Context context, Serializer serializer, FileManager fileManager, ThreadExecutor executor) { - if (context == null || userCacheSerializer == null || fileManager == null || executor == null) { + if (context == null || serializer == null || fileManager == null || executor == null) { throw new IllegalArgumentException("Invalid null parameter"); } this.context = context.getApplicationContext(); this.cacheDir = this.context.getCacheDir(); - this.serializer = userCacheSerializer; + this.serializer = serializer; this.fileManager = fileManager; this.threadExecutor = executor; } @Override public Observable get(final int userId) { - return Observable.create(subscriber -> { - File userEntityFile = UserCacheImpl.this.buildFile(userId); - String fileContent = UserCacheImpl.this.fileManager.readFileContent(userEntityFile); - UserEntity userEntity = UserCacheImpl.this.serializer.deserialize(fileContent); + return Observable.create(emitter -> { + final File userEntityFile = UserCacheImpl.this.buildFile(userId); + final String fileContent = UserCacheImpl.this.fileManager.readFileContent(userEntityFile); + final UserEntity userEntity = + UserCacheImpl.this.serializer.deserialize(fileContent, UserEntity.class); if (userEntity != null) { - subscriber.onNext(userEntity); - subscriber.onCompleted(); + emitter.onNext(userEntity); + emitter.onComplete(); } else { - subscriber.onError(new UserNotFoundException()); + emitter.onError(new UserNotFoundException()); } }); } @Override public void put(UserEntity userEntity) { if (userEntity != null) { - File userEntitiyFile = this.buildFile(userEntity.getUserId()); + final File userEntityFile = this.buildFile(userEntity.getUserId()); if (!isCached(userEntity.getUserId())) { - String jsonString = this.serializer.serialize(userEntity); - this.executeAsynchronously(new CacheWriter(this.fileManager, userEntitiyFile, - jsonString)); + final String jsonString = this.serializer.serialize(userEntity, UserEntity.class); + this.executeAsynchronously(new CacheWriter(this.fileManager, userEntityFile, jsonString)); setLastCacheUpdateTimeMillis(); } } } @Override public boolean isCached(int userId) { - File userEntitiyFile = this.buildFile(userId); - return this.fileManager.exists(userEntitiyFile); + final File userEntityFile = this.buildFile(userId); + return this.fileManager.exists(userEntityFile); } @Override public boolean isExpired() { @@ -119,7 +118,7 @@ public UserCacheImpl(Context context, JsonSerializer userCacheSerializer, * @return A valid file. */ private File buildFile(int userId) { - StringBuilder fileNameBuilder = new StringBuilder(); + final StringBuilder fileNameBuilder = new StringBuilder(); fileNameBuilder.append(this.cacheDir.getPath()); fileNameBuilder.append(File.separator); fileNameBuilder.append(DEFAULT_FILE_NAME); @@ -132,7 +131,7 @@ private File buildFile(int userId) { * Set in millis, the last time the cache was accessed. */ private void setLastCacheUpdateTimeMillis() { - long currentMillis = System.currentTimeMillis(); + final long currentMillis = System.currentTimeMillis(); this.fileManager.writeToPreferences(this.context, SETTINGS_FILE_NAME, SETTINGS_KEY_LAST_CACHE_UPDATE, currentMillis); } diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/cache/serializer/JsonSerializer.java b/data/src/main/java/com/fernandocejas/android10/sample/data/cache/serializer/Serializer.java similarity index 60% rename from data/src/main/java/com/fernandocejas/android10/sample/data/cache/serializer/JsonSerializer.java rename to data/src/main/java/com/fernandocejas/android10/sample/data/cache/serializer/Serializer.java index 6e053187..f9a9c100 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/cache/serializer/JsonSerializer.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/cache/serializer/Serializer.java @@ -15,40 +15,35 @@ */ package com.fernandocejas.android10.sample.data.cache.serializer; -import com.fernandocejas.android10.sample.data.entity.UserEntity; import com.google.gson.Gson; import javax.inject.Inject; import javax.inject.Singleton; /** - * Class user as Serializer/Deserializer for user entities. + * Json Serializer/Deserializer. */ @Singleton -public class JsonSerializer { +public class Serializer { private final Gson gson = new Gson(); - @Inject - public JsonSerializer() {} + @Inject Serializer() {} /** * Serialize an object to Json. * - * @param userEntity {@link UserEntity} to serialize. + * @param object to serialize. */ - public String serialize(UserEntity userEntity) { - String jsonString = gson.toJson(userEntity, UserEntity.class); - return jsonString; + public String serialize(Object object, Class clazz) { + return gson.toJson(object, clazz); } /** * Deserialize a json representation of an object. * - * @param jsonString A json string to deserialize. - * @return {@link UserEntity} + * @param string A json string to deserialize. */ - public UserEntity deserialize(String jsonString) { - UserEntity userEntity = gson.fromJson(jsonString, UserEntity.class); - return userEntity; + public T deserialize(String string, Class clazz) { + return gson.fromJson(string, clazz); } } diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/entity/UserEntity.java b/data/src/main/java/com/fernandocejas/android10/sample/data/entity/UserEntity.java index 8d8fb1fc..550e9acf 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/entity/UserEntity.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/entity/UserEntity.java @@ -56,10 +56,6 @@ public String getCoverUrl() { return coverUrl; } - public void setCoverUrl(String coverUrl) { - this.coverUrl = coverUrl; - } - public String getFullname() { return fullname; } @@ -72,38 +68,11 @@ public String getDescription() { return description; } - public void setDescription(String description) { - this.description = description; - } - public int getFollowers() { return followers; } - public void setFollowers(int followers) { - this.followers = followers; - } - public String getEmail() { return email; } - - public void setEmail(String email) { - this.email = email; - } - - @Override public String toString() { - StringBuilder stringBuilder = new StringBuilder(); - - stringBuilder.append("***** User Entity Details *****\n"); - stringBuilder.append("id=" + this.getUserId() + "\n"); - stringBuilder.append("cover url=" + this.getCoverUrl() + "\n"); - stringBuilder.append("fullname=" + this.getFullname() + "\n"); - stringBuilder.append("email=" + this.getEmail() + "\n"); - stringBuilder.append("description=" + this.getDescription() + "\n"); - stringBuilder.append("followers=" + this.getFollowers() + "\n"); - stringBuilder.append("*******************************"); - - return stringBuilder.toString(); - } } diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityDataMapper.java b/data/src/main/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityDataMapper.java index 1ecbdcdc..e2bde175 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityDataMapper.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityDataMapper.java @@ -31,7 +31,7 @@ public class UserEntityDataMapper { @Inject - public UserEntityDataMapper() {} + UserEntityDataMapper() {} /** * Transform a {@link UserEntity} into an {@link User}. @@ -49,7 +49,6 @@ public User transform(UserEntity userEntity) { user.setFollowers(userEntity.getFollowers()); user.setEmail(userEntity.getEmail()); } - return user; } @@ -60,15 +59,13 @@ public User transform(UserEntity userEntity) { * @return {@link User} if valid {@link UserEntity} otherwise null. */ public List transform(Collection userEntityCollection) { - List userList = new ArrayList<>(20); - User user; + final List userList = new ArrayList<>(20); for (UserEntity userEntity : userEntityCollection) { - user = transform(userEntity); + final User user = transform(userEntity); if (user != null) { userList.add(user); } } - return userList; } } diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityJsonMapper.java b/data/src/main/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityJsonMapper.java index 8e8dca90..1e344a34 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityJsonMapper.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityJsonMapper.java @@ -43,14 +43,8 @@ public UserEntityJsonMapper() { * @throws com.google.gson.JsonSyntaxException if the json string is not a valid json structure. */ public UserEntity transformUserEntity(String userJsonResponse) throws JsonSyntaxException { - try { - Type userEntityType = new TypeToken() {}.getType(); - UserEntity userEntity = this.gson.fromJson(userJsonResponse, userEntityType); - - return userEntity; - } catch (JsonSyntaxException jsonException) { - throw jsonException; - } + final Type userEntityType = new TypeToken() {}.getType(); + return this.gson.fromJson(userJsonResponse, userEntityType); } /** @@ -62,15 +56,7 @@ public UserEntity transformUserEntity(String userJsonResponse) throws JsonSyntax */ public List transformUserEntityCollection(String userListJsonResponse) throws JsonSyntaxException { - - List userEntityCollection; - try { - Type listOfUserEntityType = new TypeToken>() {}.getType(); - userEntityCollection = this.gson.fromJson(userListJsonResponse, listOfUserEntityType); - - return userEntityCollection; - } catch (JsonSyntaxException jsonException) { - throw jsonException; - } + final Type listOfUserEntityType = new TypeToken>() {}.getType(); + return this.gson.fromJson(userListJsonResponse, listOfUserEntityType); } } diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/exception/NetworkConnectionException.java b/data/src/main/java/com/fernandocejas/android10/sample/data/exception/NetworkConnectionException.java index 987a106b..a02ea8f4 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/exception/NetworkConnectionException.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/exception/NetworkConnectionException.java @@ -24,14 +24,6 @@ public NetworkConnectionException() { super(); } - public NetworkConnectionException(final String message) { - super(message); - } - - public NetworkConnectionException(final String message, final Throwable cause) { - super(message, cause); - } - public NetworkConnectionException(final Throwable cause) { super(cause); } diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/exception/RepositoryErrorBundle.java b/data/src/main/java/com/fernandocejas/android10/sample/data/exception/RepositoryErrorBundle.java index c51144c4..a4ec28a2 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/exception/RepositoryErrorBundle.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/exception/RepositoryErrorBundle.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -20,11 +20,11 @@ /** * Wrapper around Exceptions used to manage errors in the repository. */ -public class RepositoryErrorBundle implements ErrorBundle { +class RepositoryErrorBundle implements ErrorBundle { private final Exception exception; - public RepositoryErrorBundle(Exception exception) { + RepositoryErrorBundle(Exception exception) { this.exception = exception; } @@ -37,7 +37,7 @@ public Exception getException() { public String getErrorMessage() { String message = ""; if (this.exception != null) { - this.exception.getMessage(); + message = this.exception.getMessage(); } return message; } diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/exception/UserNotFoundException.java b/data/src/main/java/com/fernandocejas/android10/sample/data/exception/UserNotFoundException.java index 8667eaa4..3865818d 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/exception/UserNotFoundException.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/exception/UserNotFoundException.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -19,20 +19,7 @@ * Exception throw by the application when a User search can't return a valid result. */ public class UserNotFoundException extends Exception { - public UserNotFoundException() { super(); } - - public UserNotFoundException(final String message) { - super(message); - } - - public UserNotFoundException(final String message, final Throwable cause) { - super(message, cause); - } - - public UserNotFoundException(final Throwable cause) { - super(cause); - } } diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/executor/JobExecutor.java b/data/src/main/java/com/fernandocejas/android10/sample/data/executor/JobExecutor.java index c82177d0..f0963cec 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/executor/JobExecutor.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/executor/JobExecutor.java @@ -15,8 +15,8 @@ */ package com.fernandocejas.android10.sample.data.executor; +import android.support.annotation.NonNull; import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; @@ -29,43 +29,23 @@ */ @Singleton public class JobExecutor implements ThreadExecutor { - - private static final int INITIAL_POOL_SIZE = 3; - private static final int MAX_POOL_SIZE = 5; - - // Sets the amount of time an idle thread waits before terminating - private static final int KEEP_ALIVE_TIME = 10; - - // Sets the Time Unit to seconds - private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS; - - private final BlockingQueue workQueue; - private final ThreadPoolExecutor threadPoolExecutor; - private final ThreadFactory threadFactory; - @Inject - public JobExecutor() { - this.workQueue = new LinkedBlockingQueue<>(); - this.threadFactory = new JobThreadFactory(); - this.threadPoolExecutor = new ThreadPoolExecutor(INITIAL_POOL_SIZE, MAX_POOL_SIZE, - KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, this.workQueue, this.threadFactory); + JobExecutor() { + this.threadPoolExecutor = new ThreadPoolExecutor(3, 5, 10, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), new JobThreadFactory()); } - @Override public void execute(Runnable runnable) { - if (runnable == null) { - throw new IllegalArgumentException("Runnable to execute cannot be null"); - } + @Override public void execute(@NonNull Runnable runnable) { this.threadPoolExecutor.execute(runnable); } private static class JobThreadFactory implements ThreadFactory { - private static final String THREAD_NAME = "android_"; private int counter = 0; - @Override public Thread newThread(Runnable runnable) { - return new Thread(runnable, THREAD_NAME + counter++); + @Override public Thread newThread(@NonNull Runnable runnable) { + return new Thread(runnable, "android_" + counter++); } } } diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/net/ApiConnection.java b/data/src/main/java/com/fernandocejas/android10/sample/data/net/ApiConnection.java index b5e153b7..7cf859d2 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/net/ApiConnection.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/net/ApiConnection.java @@ -29,7 +29,7 @@ * Implements {@link java.util.concurrent.Callable} so when executed asynchronously can * return a value. */ -public class ApiConnection implements Callable { +class ApiConnection implements Callable { private static final String CONTENT_TYPE_LABEL = "Content-Type"; private static final String CONTENT_TYPE_VALUE_JSON = "application/json; charset=utf-8"; @@ -41,7 +41,7 @@ private ApiConnection(String url) throws MalformedURLException { this.url = new URL(url); } - public static ApiConnection createGET(String url) throws MalformedURLException { + static ApiConnection createGET(String url) throws MalformedURLException { return new ApiConnection(url); } @@ -52,7 +52,7 @@ public static ApiConnection createGET(String url) throws MalformedURLException { * @return A string response */ @Nullable - public String requestSyncCall() { + String requestSyncCall() { connectToApi(); return response; } diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/net/RestApi.java b/data/src/main/java/com/fernandocejas/android10/sample/data/net/RestApi.java index f99d8f52..004acd6a 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/net/RestApi.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/net/RestApi.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,14 +16,15 @@ package com.fernandocejas.android10.sample.data.net; import com.fernandocejas.android10.sample.data.entity.UserEntity; +import io.reactivex.Observable; import java.util.List; -import rx.Observable; /** * RestApi for retrieving data from the network. */ public interface RestApi { - String API_BASE_URL = "http://www.android10.org/myapi/"; + String API_BASE_URL = + "https://raw.githubusercontent.com/android10/Sample-Data/master/Android-CleanArchitecture/"; /** Api url for getting all users */ String API_URL_GET_USER_LIST = API_BASE_URL + "users.json"; @@ -31,12 +32,12 @@ public interface RestApi { String API_URL_GET_USER_DETAILS = API_BASE_URL + "user_"; /** - * Retrieves an {@link rx.Observable} which will emit a List of {@link UserEntity}. + * Retrieves an {@link Observable} which will emit a List of {@link UserEntity}. */ Observable> userEntityList(); /** - * Retrieves an {@link rx.Observable} which will emit a {@link UserEntity}. + * Retrieves an {@link Observable} which will emit a {@link UserEntity}. * * @param userId The user id used to get user data. */ diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/net/RestApiImpl.java b/data/src/main/java/com/fernandocejas/android10/sample/data/net/RestApiImpl.java index 6b15518b..279fe6f4 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/net/RestApiImpl.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/net/RestApiImpl.java @@ -21,10 +21,9 @@ import com.fernandocejas.android10.sample.data.entity.UserEntity; import com.fernandocejas.android10.sample.data.entity.mapper.UserEntityJsonMapper; import com.fernandocejas.android10.sample.data.exception.NetworkConnectionException; -import com.fernandocejas.frodo.annotation.RxLogObservable; +import io.reactivex.Observable; import java.net.MalformedURLException; import java.util.List; -import rx.Observable; /** * {@link RestApi} implementation for retrieving data from the network. @@ -48,55 +47,53 @@ public RestApiImpl(Context context, UserEntityJsonMapper userEntityJsonMapper) { this.userEntityJsonMapper = userEntityJsonMapper; } - @RxLogObservable @Override public Observable> userEntityList() { - return Observable.create(subscriber -> { + return Observable.create(emitter -> { if (isThereInternetConnection()) { try { String responseUserEntities = getUserEntitiesFromApi(); if (responseUserEntities != null) { - subscriber.onNext(userEntityJsonMapper.transformUserEntityCollection( + emitter.onNext(userEntityJsonMapper.transformUserEntityCollection( responseUserEntities)); - subscriber.onCompleted(); + emitter.onComplete(); } else { - subscriber.onError(new NetworkConnectionException()); + emitter.onError(new NetworkConnectionException()); } } catch (Exception e) { - subscriber.onError(new NetworkConnectionException(e.getCause())); + emitter.onError(new NetworkConnectionException(e.getCause())); } } else { - subscriber.onError(new NetworkConnectionException()); + emitter.onError(new NetworkConnectionException()); } }); } - @RxLogObservable @Override public Observable userEntityById(final int userId) { - return Observable.create(subscriber -> { + return Observable.create(emitter -> { if (isThereInternetConnection()) { try { String responseUserDetails = getUserDetailsFromApi(userId); if (responseUserDetails != null) { - subscriber.onNext(userEntityJsonMapper.transformUserEntity(responseUserDetails)); - subscriber.onCompleted(); + emitter.onNext(userEntityJsonMapper.transformUserEntity(responseUserDetails)); + emitter.onComplete(); } else { - subscriber.onError(new NetworkConnectionException()); + emitter.onError(new NetworkConnectionException()); } } catch (Exception e) { - subscriber.onError(new NetworkConnectionException(e.getCause())); + emitter.onError(new NetworkConnectionException(e.getCause())); } } else { - subscriber.onError(new NetworkConnectionException()); + emitter.onError(new NetworkConnectionException()); } }); } private String getUserEntitiesFromApi() throws MalformedURLException { - return ApiConnection.createGET(RestApi.API_URL_GET_USER_LIST).requestSyncCall(); + return ApiConnection.createGET(API_URL_GET_USER_LIST).requestSyncCall(); } private String getUserDetailsFromApi(int userId) throws MalformedURLException { - String apiUrl = RestApi.API_URL_GET_USER_DETAILS + userId + ".json"; + String apiUrl = API_URL_GET_USER_DETAILS + userId + ".json"; return ApiConnection.createGET(apiUrl).requestSyncCall(); } diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/repository/UserDataRepository.java b/data/src/main/java/com/fernandocejas/android10/sample/data/repository/UserDataRepository.java index 24f125fd..edb0de0a 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/repository/UserDataRepository.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/repository/UserDataRepository.java @@ -20,10 +20,10 @@ import com.fernandocejas.android10.sample.data.repository.datasource.UserDataStoreFactory; import com.fernandocejas.android10.sample.domain.User; import com.fernandocejas.android10.sample.domain.repository.UserRepository; +import io.reactivex.Observable; import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; -import rx.Observable; /** * {@link UserRepository} for retrieving user data. @@ -41,24 +41,20 @@ public class UserDataRepository implements UserRepository { * @param userEntityDataMapper {@link UserEntityDataMapper}. */ @Inject - public UserDataRepository(UserDataStoreFactory dataStoreFactory, + UserDataRepository(UserDataStoreFactory dataStoreFactory, UserEntityDataMapper userEntityDataMapper) { this.userDataStoreFactory = dataStoreFactory; this.userEntityDataMapper = userEntityDataMapper; } - @SuppressWarnings("Convert2MethodRef") @Override public Observable> users() { //we always get all users from the cloud final UserDataStore userDataStore = this.userDataStoreFactory.createCloudDataStore(); - return userDataStore.userEntityList() - .map(userEntities -> this.userEntityDataMapper.transform(userEntities)); + return userDataStore.userEntityList().map(this.userEntityDataMapper::transform); } - @SuppressWarnings("Convert2MethodRef") @Override public Observable user(int userId) { final UserDataStore userDataStore = this.userDataStoreFactory.create(userId); - return userDataStore.userEntityDetails(userId) - .map(userEntity -> this.userEntityDataMapper.transform(userEntity)); + return userDataStore.userEntityDetails(userId).map(this.userEntityDataMapper::transform); } } diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/CloudUserDataStore.java b/data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/CloudUserDataStore.java index 579995bd..e681ecd7 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/CloudUserDataStore.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/CloudUserDataStore.java @@ -18,31 +18,24 @@ import com.fernandocejas.android10.sample.data.cache.UserCache; import com.fernandocejas.android10.sample.data.entity.UserEntity; import com.fernandocejas.android10.sample.data.net.RestApi; +import io.reactivex.Observable; import java.util.List; -import rx.Observable; -import rx.functions.Action1; /** * {@link UserDataStore} implementation based on connections to the api (Cloud). */ -public class CloudUserDataStore implements UserDataStore { +class CloudUserDataStore implements UserDataStore { private final RestApi restApi; private final UserCache userCache; - private final Action1 saveToCacheAction = userEntity -> { - if (userEntity != null) { - CloudUserDataStore.this.userCache.put(userEntity); - } - }; - /** * Construct a {@link UserDataStore} based on connections to the api (Cloud). * * @param restApi The {@link RestApi} implementation to use. * @param userCache A {@link UserCache} to cache data retrieved from the api. */ - public CloudUserDataStore(RestApi restApi, UserCache userCache) { + CloudUserDataStore(RestApi restApi, UserCache userCache) { this.restApi = restApi; this.userCache = userCache; } @@ -52,6 +45,6 @@ public CloudUserDataStore(RestApi restApi, UserCache userCache) { } @Override public Observable userEntityDetails(final int userId) { - return this.restApi.userEntityById(userId).doOnNext(saveToCacheAction); + return this.restApi.userEntityById(userId).doOnNext(CloudUserDataStore.this.userCache::put); } } diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/DiskUserDataStore.java b/data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/DiskUserDataStore.java index 5c5aaae2..d638032c 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/DiskUserDataStore.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/DiskUserDataStore.java @@ -17,13 +17,13 @@ import com.fernandocejas.android10.sample.data.cache.UserCache; import com.fernandocejas.android10.sample.data.entity.UserEntity; +import io.reactivex.Observable; import java.util.List; -import rx.Observable; /** * {@link UserDataStore} implementation based on file system data store. */ -public class DiskUserDataStore implements UserDataStore { +class DiskUserDataStore implements UserDataStore { private final UserCache userCache; @@ -32,7 +32,7 @@ public class DiskUserDataStore implements UserDataStore { * * @param userCache A {@link UserCache} to cache data retrieved from the api. */ - public DiskUserDataStore(UserCache userCache) { + DiskUserDataStore(UserCache userCache) { this.userCache = userCache; } diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStore.java b/data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStore.java index 744bae1e..ac4f9fbc 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStore.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStore.java @@ -16,20 +16,20 @@ package com.fernandocejas.android10.sample.data.repository.datasource; import com.fernandocejas.android10.sample.data.entity.UserEntity; +import io.reactivex.Observable; import java.util.List; -import rx.Observable; /** * Interface that represents a data store from where data is retrieved. */ public interface UserDataStore { /** - * Get an {@link rx.Observable} which will emit a List of {@link UserEntity}. + * Get an {@link Observable} which will emit a List of {@link UserEntity}. */ Observable> userEntityList(); /** - * Get an {@link rx.Observable} which will emit a {@link UserEntity} by its id. + * Get an {@link Observable} which will emit a {@link UserEntity} by its id. * * @param userId The id to retrieve user data. */ diff --git a/data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStoreFactory.java b/data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStoreFactory.java index 59e397e1..defc85df 100644 --- a/data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStoreFactory.java +++ b/data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStoreFactory.java @@ -16,6 +16,7 @@ package com.fernandocejas.android10.sample.data.repository.datasource; import android.content.Context; +import android.support.annotation.NonNull; import com.fernandocejas.android10.sample.data.cache.UserCache; import com.fernandocejas.android10.sample.data.entity.mapper.UserEntityJsonMapper; import com.fernandocejas.android10.sample.data.net.RestApi; @@ -33,10 +34,7 @@ public class UserDataStoreFactory { private final UserCache userCache; @Inject - public UserDataStoreFactory(Context context, UserCache userCache) { - if (context == null || userCache == null) { - throw new IllegalArgumentException("Constructor parameters cannot be null!!!"); - } + UserDataStoreFactory(@NonNull Context context, @NonNull UserCache userCache) { this.context = context.getApplicationContext(); this.userCache = userCache; } @@ -60,8 +58,8 @@ public UserDataStore create(int userId) { * Create {@link UserDataStore} to retrieve data from the Cloud. */ public UserDataStore createCloudDataStore() { - UserEntityJsonMapper userEntityJsonMapper = new UserEntityJsonMapper(); - RestApi restApi = new RestApiImpl(this.context, userEntityJsonMapper); + final UserEntityJsonMapper userEntityJsonMapper = new UserEntityJsonMapper(); + final RestApi restApi = new RestApiImpl(this.context, userEntityJsonMapper); return new CloudUserDataStore(restApi, this.userCache); } diff --git a/data/src/test/java/com/fernandocejas/android10/sample/data/ApplicationTestCase.java b/data/src/test/java/com/fernandocejas/android10/sample/data/ApplicationTestCase.java index 72c06075..d8426bcb 100644 --- a/data/src/test/java/com/fernandocejas/android10/sample/data/ApplicationTestCase.java +++ b/data/src/test/java/com/fernandocejas/android10/sample/data/ApplicationTestCase.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -15,14 +15,34 @@ */ package com.fernandocejas.android10.sample.data; +import android.content.Context; +import java.io.File; +import org.junit.Rule; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; -import org.robolectric.RobolectricGradleTestRunner; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; /** * Base class for Robolectric data layer tests. * Inherit from this class to create a test. */ -@RunWith(RobolectricGradleTestRunner.class) -@Config(constants = BuildConfig.class, application = ApplicationStub.class) -public abstract class ApplicationTestCase {} +@RunWith(RobolectricTestRunner.class) +@Config(constants = BuildConfig.class, application = ApplicationStub.class, sdk = 21) +public abstract class ApplicationTestCase { + + @Rule public TestRule injectMocksRule = (base, description) -> { + MockitoAnnotations.initMocks(ApplicationTestCase.this); + return base; + }; + + public static Context context() { + return RuntimeEnvironment.application; + } + + public static File cacheDir() { + return RuntimeEnvironment.application.getCacheDir(); + } +} diff --git a/data/src/test/java/com/fernandocejas/android10/sample/data/cache/FileManagerTest.java b/data/src/test/java/com/fernandocejas/android10/sample/data/cache/FileManagerTest.java index f71f8a8f..8f0cb488 100644 --- a/data/src/test/java/com/fernandocejas/android10/sample/data/cache/FileManagerTest.java +++ b/data/src/test/java/com/fernandocejas/android10/sample/data/cache/FileManagerTest.java @@ -20,7 +20,6 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.robolectric.RuntimeEnvironment; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; @@ -29,18 +28,16 @@ public class FileManagerTest extends ApplicationTestCase { private FileManager fileManager; - private File cacheDir; @Before public void setUp() { fileManager = new FileManager(); - cacheDir = RuntimeEnvironment.application.getCacheDir(); } @After public void tearDown() { - if (cacheDir != null) { - fileManager.clearDirectory(cacheDir); + if (cacheDir() != null) { + fileManager.clearDirectory(cacheDir()); } } @@ -66,9 +63,7 @@ public void testFileContent() { } private File createDummyFile() { - String dummyFilePath = cacheDir.getPath() + File.separator + "dumyFile"; - File dummyFile = new File(dummyFilePath); - - return dummyFile; + String dummyFilePath = cacheDir().getPath() + File.separator + "dummyFile"; + return new File(dummyFilePath); } } diff --git a/data/src/test/java/com/fernandocejas/android10/sample/data/cache/serializer/JsonSerializerTest.java b/data/src/test/java/com/fernandocejas/android10/sample/data/cache/serializer/SerializerTest.java similarity index 79% rename from data/src/test/java/com/fernandocejas/android10/sample/data/cache/serializer/JsonSerializerTest.java rename to data/src/test/java/com/fernandocejas/android10/sample/data/cache/serializer/SerializerTest.java index da15891a..d55bd3c3 100644 --- a/data/src/test/java/com/fernandocejas/android10/sample/data/cache/serializer/JsonSerializerTest.java +++ b/data/src/test/java/com/fernandocejas/android10/sample/data/cache/serializer/SerializerTest.java @@ -15,16 +15,18 @@ */ package com.fernandocejas.android10.sample.data.cache.serializer; -import com.fernandocejas.android10.sample.data.ApplicationTestCase; import com.fernandocejas.android10.sample.data.entity.UserEntity; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; -public class JsonSerializerTest extends ApplicationTestCase { +@RunWith(MockitoJUnitRunner.class) +public class SerializerTest { private static final String JSON_RESPONSE = "{\n" + " \"id\": 1,\n" @@ -35,18 +37,18 @@ public class JsonSerializerTest extends ApplicationTestCase { + " \"email\": \"jcooper@babbleset.edu\"\n" + "}"; - private JsonSerializer jsonSerializer; + private Serializer serializer; @Before public void setUp() { - jsonSerializer = new JsonSerializer(); + serializer = new Serializer(); } @Test public void testSerializeHappyCase() { - UserEntity userEntityOne = jsonSerializer.deserialize(JSON_RESPONSE); - String jsonString = jsonSerializer.serialize(userEntityOne); - UserEntity userEntityTwo = jsonSerializer.deserialize(jsonString); + final UserEntity userEntityOne = serializer.deserialize(JSON_RESPONSE, UserEntity.class); + final String jsonString = serializer.serialize(userEntityOne, UserEntity.class); + final UserEntity userEntityTwo = serializer.deserialize(jsonString, UserEntity.class); assertThat(userEntityOne.getUserId(), is(userEntityTwo.getUserId())); assertThat(userEntityOne.getFullname(), is(equalTo(userEntityTwo.getFullname()))); @@ -55,7 +57,7 @@ public void testSerializeHappyCase() { @Test public void testDesearializeHappyCase() { - UserEntity userEntity = jsonSerializer.deserialize(JSON_RESPONSE); + final UserEntity userEntity = serializer.deserialize(JSON_RESPONSE, UserEntity.class); assertThat(userEntity.getUserId(), is(1)); assertThat(userEntity.getFullname(), is("Simon Hill")); diff --git a/data/src/test/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityDataMapperTest.java b/data/src/test/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityDataMapperTest.java index 19a4db56..963d28e6 100644 --- a/data/src/test/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityDataMapperTest.java +++ b/data/src/test/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityDataMapperTest.java @@ -15,7 +15,6 @@ */ package com.fernandocejas.android10.sample.data.entity.mapper; -import com.fernandocejas.android10.sample.data.ApplicationTestCase; import com.fernandocejas.android10.sample.data.entity.UserEntity; import com.fernandocejas.android10.sample.domain.User; import java.util.ArrayList; @@ -23,13 +22,16 @@ import java.util.List; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.mock; -public class UserEntityDataMapperTest extends ApplicationTestCase { +@RunWith(MockitoJUnitRunner.class) +public class UserEntityDataMapperTest { private static final int FAKE_USER_ID = 123; private static final String FAKE_FULLNAME = "Tony Stark"; diff --git a/data/src/test/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityJsonMapperTest.java b/data/src/test/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityJsonMapperTest.java index f834f52d..a6eb3fcc 100644 --- a/data/src/test/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityJsonMapperTest.java +++ b/data/src/test/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityJsonMapperTest.java @@ -15,7 +15,6 @@ */ package com.fernandocejas.android10.sample.data.entity.mapper; -import com.fernandocejas.android10.sample.data.ApplicationTestCase; import com.fernandocejas.android10.sample.data.entity.UserEntity; import com.google.gson.JsonSyntaxException; import java.util.Collection; @@ -23,12 +22,15 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -public class UserEntityJsonMapperTest extends ApplicationTestCase { +@RunWith(MockitoJUnitRunner.class) +public class UserEntityJsonMapperTest { private static final String JSON_RESPONSE_USER_DETAILS = "{\n" + " \"id\": 1,\n" diff --git a/data/src/test/java/com/fernandocejas/android10/sample/data/exception/RepositoryErrorBundleTest.java b/data/src/test/java/com/fernandocejas/android10/sample/data/exception/RepositoryErrorBundleTest.java index 277f7ad7..d2f3d697 100644 --- a/data/src/test/java/com/fernandocejas/android10/sample/data/exception/RepositoryErrorBundleTest.java +++ b/data/src/test/java/com/fernandocejas/android10/sample/data/exception/RepositoryErrorBundleTest.java @@ -15,28 +15,28 @@ */ package com.fernandocejas.android10.sample.data.exception; -import com.fernandocejas.android10.sample.data.ApplicationTestCase; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.runners.MockitoJUnitRunner; import static org.mockito.Mockito.verify; -public class RepositoryErrorBundleTest extends ApplicationTestCase { +@RunWith(MockitoJUnitRunner.class) +public class RepositoryErrorBundleTest { private RepositoryErrorBundle repositoryErrorBundle; - @Mock - private Exception mockException; + @Mock private Exception mockException; @Before public void setUp() { - MockitoAnnotations.initMocks(this); repositoryErrorBundle = new RepositoryErrorBundle(mockException); } @Test + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") public void testGetErrorMessageInteraction() { repositoryErrorBundle.getErrorMessage(); diff --git a/data/src/test/java/com/fernandocejas/android10/sample/data/repository/UserDataRepositoryTest.java b/data/src/test/java/com/fernandocejas/android10/sample/data/repository/UserDataRepositoryTest.java index 70752055..1eac1d06 100644 --- a/data/src/test/java/com/fernandocejas/android10/sample/data/repository/UserDataRepositoryTest.java +++ b/data/src/test/java/com/fernandocejas/android10/sample/data/repository/UserDataRepositoryTest.java @@ -15,27 +15,26 @@ */ package com.fernandocejas.android10.sample.data.repository; -import com.fernandocejas.android10.sample.data.ApplicationTestCase; import com.fernandocejas.android10.sample.data.entity.UserEntity; import com.fernandocejas.android10.sample.data.entity.mapper.UserEntityDataMapper; import com.fernandocejas.android10.sample.data.repository.datasource.UserDataStore; import com.fernandocejas.android10.sample.data.repository.datasource.UserDataStoreFactory; import com.fernandocejas.android10.sample.domain.User; +import io.reactivex.Observable; import java.util.ArrayList; import java.util.List; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import rx.Observable; +import org.mockito.runners.MockitoJUnitRunner; import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.verify; -public class UserDataRepositoryTest extends ApplicationTestCase { +@RunWith(MockitoJUnitRunner.class) +public class UserDataRepositoryTest { private static final int FAKE_USER_ID = 123; @@ -47,15 +46,9 @@ public class UserDataRepositoryTest extends ApplicationTestCase { @Mock private UserEntity mockUserEntity; @Mock private User mockUser; - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Before public void setUp() { - MockitoAnnotations.initMocks(this); - userDataRepository = new UserDataRepository(mockUserDataStoreFactory, - mockUserEntityDataMapper); - + userDataRepository = new UserDataRepository(mockUserDataStoreFactory, mockUserEntityDataMapper); given(mockUserDataStoreFactory.create(anyInt())).willReturn(mockUserDataStore); given(mockUserDataStoreFactory.createCloudDataStore()).willReturn(mockUserDataStore); } diff --git a/data/src/test/java/com/fernandocejas/android10/sample/data/repository/datasource/CloudUserDataStoreTest.java b/data/src/test/java/com/fernandocejas/android10/sample/data/repository/datasource/CloudUserDataStoreTest.java index b54e58e1..a47a2e4e 100644 --- a/data/src/test/java/com/fernandocejas/android10/sample/data/repository/datasource/CloudUserDataStoreTest.java +++ b/data/src/test/java/com/fernandocejas/android10/sample/data/repository/datasource/CloudUserDataStoreTest.java @@ -15,20 +15,21 @@ */ package com.fernandocejas.android10.sample.data.repository.datasource; -import com.fernandocejas.android10.sample.data.ApplicationTestCase; import com.fernandocejas.android10.sample.data.cache.UserCache; import com.fernandocejas.android10.sample.data.entity.UserEntity; import com.fernandocejas.android10.sample.data.net.RestApi; +import io.reactivex.Observable; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import rx.Observable; +import org.mockito.runners.MockitoJUnitRunner; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; -public class CloudUserDataStoreTest extends ApplicationTestCase { +@RunWith(MockitoJUnitRunner.class) +public class CloudUserDataStoreTest { private static final int FAKE_USER_ID = 765; @@ -39,7 +40,6 @@ public class CloudUserDataStoreTest extends ApplicationTestCase { @Before public void setUp() { - MockitoAnnotations.initMocks(this); cloudUserDataStore = new CloudUserDataStore(mockRestApi, mockUserCache); } diff --git a/data/src/test/java/com/fernandocejas/android10/sample/data/repository/datasource/DiskUserDataStoreTest.java b/data/src/test/java/com/fernandocejas/android10/sample/data/repository/datasource/DiskUserDataStoreTest.java index 6c7bca5d..6a649142 100644 --- a/data/src/test/java/com/fernandocejas/android10/sample/data/repository/datasource/DiskUserDataStoreTest.java +++ b/data/src/test/java/com/fernandocejas/android10/sample/data/repository/datasource/DiskUserDataStoreTest.java @@ -15,18 +15,19 @@ */ package com.fernandocejas.android10.sample.data.repository.datasource; -import com.fernandocejas.android10.sample.data.ApplicationTestCase; import com.fernandocejas.android10.sample.data.cache.UserCache; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.runners.MockitoJUnitRunner; import static org.mockito.Mockito.verify; -public class DiskUserDataStoreTest extends ApplicationTestCase { +@RunWith(MockitoJUnitRunner.class) +public class DiskUserDataStoreTest { private static final int FAKE_USER_ID = 11; @@ -38,7 +39,6 @@ public class DiskUserDataStoreTest extends ApplicationTestCase { @Before public void setUp() { - MockitoAnnotations.initMocks(this); diskUserDataStore = new DiskUserDataStore(mockUserCache); } diff --git a/data/src/test/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStoreFactoryTest.java b/data/src/test/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStoreFactoryTest.java index b8c644a4..6c560a0a 100644 --- a/data/src/test/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStoreFactoryTest.java +++ b/data/src/test/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStoreFactoryTest.java @@ -20,7 +20,6 @@ import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import static org.hamcrest.CoreMatchers.instanceOf; @@ -36,14 +35,11 @@ public class UserDataStoreFactoryTest extends ApplicationTestCase { private UserDataStoreFactory userDataStoreFactory; - @Mock - private UserCache mockUserCache; + @Mock private UserCache mockUserCache; @Before public void setUp() { - MockitoAnnotations.initMocks(this); - userDataStoreFactory = - new UserDataStoreFactory(RuntimeEnvironment.application, mockUserCache); + userDataStoreFactory = new UserDataStoreFactory(RuntimeEnvironment.application, mockUserCache); } @Test diff --git a/domain/build.gradle b/domain/build.gradle index e9c9e8c6..f647f20c 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -19,12 +19,13 @@ dependencies { def domainDependencies = rootProject.ext.domainDependencies def domainTestDependencies = rootProject.ext.domainTestDependencies - provided domainDependencies.daggerCompiler - provided domainDependencies.javaxAnnotation + compileOnly domainDependencies.javaxAnnotation - compile domainDependencies.dagger - compile domainDependencies.rxJava + implementation domainDependencies.javaxInject + implementation domainDependencies.rxJava + compile domainDependencies.arrow - testCompile domainTestDependencies.junit - testCompile domainTestDependencies.mockito -} \ No newline at end of file + testImplementation domainTestDependencies.junit + testImplementation domainTestDependencies.mockito + testImplementation domainTestDependencies.assertj +} diff --git a/domain/src/main/java/com/fernandocejas/android10/sample/domain/User.java b/domain/src/main/java/com/fernandocejas/android10/sample/domain/User.java index d62a021d..21bf5378 100644 --- a/domain/src/main/java/com/fernandocejas/android10/sample/domain/User.java +++ b/domain/src/main/java/com/fernandocejas/android10/sample/domain/User.java @@ -75,19 +75,4 @@ public int getFollowers() { public void setFollowers(int followers) { this.followers = followers; } - - @Override public String toString() { - StringBuilder stringBuilder = new StringBuilder(); - - stringBuilder.append("***** User Details *****\n"); - stringBuilder.append("id=" + this.getUserId() + "\n"); - stringBuilder.append("cover url=" + this.getCoverUrl() + "\n"); - stringBuilder.append("fullname=" + this.getFullName() + "\n"); - stringBuilder.append("email=" + this.getEmail() + "\n"); - stringBuilder.append("description=" + this.getDescription() + "\n"); - stringBuilder.append("followers=" + this.getFollowers() + "\n"); - stringBuilder.append("*******************************"); - - return stringBuilder.toString(); - } } diff --git a/domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/PostExecutionThread.java b/domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/PostExecutionThread.java index a69f2859..6ed498cb 100644 --- a/domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/PostExecutionThread.java +++ b/domain/src/main/java/com/fernandocejas/android10/sample/domain/executor/PostExecutionThread.java @@ -15,7 +15,7 @@ */ package com.fernandocejas.android10.sample.domain.executor; -import rx.Scheduler; +import io.reactivex.Scheduler; /** * Thread abstraction created to change the execution context from any thread to any other thread. diff --git a/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/DefaultSubscriber.java b/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/DefaultObserver.java similarity index 72% rename from domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/DefaultSubscriber.java rename to domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/DefaultObserver.java index 81abea5c..4bcd3e49 100644 --- a/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/DefaultSubscriber.java +++ b/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/DefaultObserver.java @@ -15,19 +15,21 @@ */ package com.fernandocejas.android10.sample.domain.interactor; +import io.reactivex.observers.DisposableObserver; + /** - * Default subscriber base class to be used whenever you want default error handling. + * Default {@link DisposableObserver} base class to be used whenever you want default error handling. */ -public class DefaultSubscriber extends rx.Subscriber { - @Override public void onCompleted() { +public class DefaultObserver extends DisposableObserver { + @Override public void onNext(T t) { // no-op by default. } - @Override public void onError(Throwable e) { + @Override public void onComplete() { // no-op by default. } - @Override public void onNext(T t) { + @Override public void onError(Throwable exception) { // no-op by default. } } diff --git a/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserDetails.java b/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserDetails.java index 134fc4d0..377d0c1d 100644 --- a/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserDetails.java +++ b/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserDetails.java @@ -19,27 +19,40 @@ import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread; import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; import com.fernandocejas.android10.sample.domain.repository.UserRepository; +import com.fernandocejas.arrow.checks.Preconditions; +import io.reactivex.Observable; import javax.inject.Inject; -import rx.Observable; /** * This class is an implementation of {@link UseCase} that represents a use case for * retrieving data related to an specific {@link User}. */ -public class GetUserDetails extends UseCase { +public class GetUserDetails extends UseCase { - private final int userId; private final UserRepository userRepository; @Inject - public GetUserDetails(int userId, UserRepository userRepository, - ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) { + GetUserDetails(UserRepository userRepository, ThreadExecutor threadExecutor, + PostExecutionThread postExecutionThread) { super(threadExecutor, postExecutionThread); - this.userId = userId; this.userRepository = userRepository; } - @Override protected Observable buildUseCaseObservable() { - return this.userRepository.user(this.userId); + @Override Observable buildUseCaseObservable(Params params) { + Preconditions.checkNotNull(params); + return this.userRepository.user(params.userId); + } + + public static final class Params { + + private final int userId; + + private Params(int userId) { + this.userId = userId; + } + + public static Params forUser(int userId) { + return new Params(userId); + } } } diff --git a/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserList.java b/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserList.java index 0780ff3d..e43ce75e 100644 --- a/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserList.java +++ b/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/GetUserList.java @@ -19,25 +19,26 @@ import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread; import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; import com.fernandocejas.android10.sample.domain.repository.UserRepository; +import io.reactivex.Observable; +import java.util.List; import javax.inject.Inject; -import rx.Observable; /** * This class is an implementation of {@link UseCase} that represents a use case for * retrieving a collection of all {@link User}. */ -public class GetUserList extends UseCase { +public class GetUserList extends UseCase, Void> { private final UserRepository userRepository; @Inject - public GetUserList(UserRepository userRepository, ThreadExecutor threadExecutor, + GetUserList(UserRepository userRepository, ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) { super(threadExecutor, postExecutionThread); this.userRepository = userRepository; } - @Override public Observable buildUseCaseObservable() { + @Override Observable> buildUseCaseObservable(Void unused) { return this.userRepository.users(); } } diff --git a/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/UseCase.java b/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/UseCase.java index 24e9d010..e71f9145 100644 --- a/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/UseCase.java +++ b/domain/src/main/java/com/fernandocejas/android10/sample/domain/interactor/UseCase.java @@ -17,58 +17,68 @@ import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread; import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; -import rx.Subscriber; -import rx.Observable; -import rx.Subscription; -import rx.schedulers.Schedulers; -import rx.subscriptions.Subscriptions; +import com.fernandocejas.arrow.checks.Preconditions; +import io.reactivex.Observable; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; +import io.reactivex.observers.DisposableObserver; +import io.reactivex.schedulers.Schedulers; /** * Abstract class for a Use Case (Interactor in terms of Clean Architecture). * This interface represents a execution unit for different use cases (this means any use case * in the application should implement this contract). * - * By convention each UseCase implementation will return the result using a {@link rx.Subscriber} + * By convention each UseCase implementation will return the result using a {@link DisposableObserver} * that will execute its job in a background thread and will post the result in the UI thread. */ -public abstract class UseCase { +public abstract class UseCase { private final ThreadExecutor threadExecutor; private final PostExecutionThread postExecutionThread; + private final CompositeDisposable disposables; - private Subscription subscription = Subscriptions.empty(); - - protected UseCase(ThreadExecutor threadExecutor, - PostExecutionThread postExecutionThread) { + UseCase(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) { this.threadExecutor = threadExecutor; this.postExecutionThread = postExecutionThread; + this.disposables = new CompositeDisposable(); } /** - * Builds an {@link rx.Observable} which will be used when executing the current {@link UseCase}. + * Builds an {@link Observable} which will be used when executing the current {@link UseCase}. */ - protected abstract Observable buildUseCaseObservable(); + abstract Observable buildUseCaseObservable(Params params); /** * Executes the current use case. * - * @param UseCaseSubscriber The guy who will be listen to the observable build - * with {@link #buildUseCaseObservable()}. + * @param observer {@link DisposableObserver} which will be listening to the observable build + * by {@link #buildUseCaseObservable(Params)} ()} method. + * @param params Parameters (Optional) used to build/execute this use case. */ - @SuppressWarnings("unchecked") - public void execute(Subscriber UseCaseSubscriber) { - this.subscription = this.buildUseCaseObservable() + public void execute(DisposableObserver observer, Params params) { + Preconditions.checkNotNull(observer); + final Observable observable = this.buildUseCaseObservable(params) .subscribeOn(Schedulers.from(threadExecutor)) - .observeOn(postExecutionThread.getScheduler()) - .subscribe(UseCaseSubscriber); + .observeOn(postExecutionThread.getScheduler()); + addDisposable(observable.subscribeWith(observer)); } /** - * Unsubscribes from current {@link rx.Subscription}. + * Dispose from current {@link CompositeDisposable}. */ - public void unsubscribe() { - if (!subscription.isUnsubscribed()) { - subscription.unsubscribe(); + public void dispose() { + if (!disposables.isDisposed()) { + disposables.dispose(); } } + + /** + * Dispose from current {@link CompositeDisposable}. + */ + private void addDisposable(Disposable disposable) { + Preconditions.checkNotNull(disposable); + Preconditions.checkNotNull(disposables); + disposables.add(disposable); + } } diff --git a/domain/src/main/java/com/fernandocejas/android10/sample/domain/repository/UserRepository.java b/domain/src/main/java/com/fernandocejas/android10/sample/domain/repository/UserRepository.java index 69c40a03..29362a00 100644 --- a/domain/src/main/java/com/fernandocejas/android10/sample/domain/repository/UserRepository.java +++ b/domain/src/main/java/com/fernandocejas/android10/sample/domain/repository/UserRepository.java @@ -16,20 +16,20 @@ package com.fernandocejas.android10.sample.domain.repository; import com.fernandocejas.android10.sample.domain.User; +import io.reactivex.Observable; import java.util.List; -import rx.Observable; /** * Interface that represents a Repository for getting {@link User} related data. */ public interface UserRepository { /** - * Get an {@link rx.Observable} which will emit a List of {@link User}. + * Get an {@link Observable} which will emit a List of {@link User}. */ Observable> users(); /** - * Get an {@link rx.Observable} which will emit a {@link User}. + * Get an {@link Observable} which will emit a {@link User}. * * @param userId The user id used to retrieve user data. */ diff --git a/domain/src/test/java/com/fernandocejas/android10/sample/domain/UserTest.java b/domain/src/test/java/com/fernandocejas/android10/sample/domain/UserTest.java index ea45784a..e321d410 100644 --- a/domain/src/test/java/com/fernandocejas/android10/sample/domain/UserTest.java +++ b/domain/src/test/java/com/fernandocejas/android10/sample/domain/UserTest.java @@ -18,8 +18,7 @@ import org.junit.Before; import org.junit.Test; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; public class UserTest { @@ -34,8 +33,8 @@ public void setUp() { @Test public void testUserConstructorHappyCase() { - int userId = user.getUserId(); + final int userId = user.getUserId(); - assertThat(userId, is(FAKE_USER_ID)); + assertThat(userId).isEqualTo(FAKE_USER_ID); } } diff --git a/domain/src/test/java/com/fernandocejas/android10/sample/domain/exception/DefaultErrorBundleTest.java b/domain/src/test/java/com/fernandocejas/android10/sample/domain/exception/DefaultErrorBundleTest.java index 1ba00434..0e33d6e8 100644 --- a/domain/src/test/java/com/fernandocejas/android10/sample/domain/exception/DefaultErrorBundleTest.java +++ b/domain/src/test/java/com/fernandocejas/android10/sample/domain/exception/DefaultErrorBundleTest.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -17,20 +17,20 @@ import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.runners.MockitoJUnitRunner; import static org.mockito.Mockito.verify; +@RunWith(MockitoJUnitRunner.class) public class DefaultErrorBundleTest { private DefaultErrorBundle defaultErrorBundle; - @Mock - private Exception mockException; + @Mock private Exception mockException; @Before public void setUp() { - MockitoAnnotations.initMocks(this); defaultErrorBundle = new DefaultErrorBundle(mockException); } @@ -40,4 +40,4 @@ public void testGetErrorMessageInteraction() { verify(mockException).getMessage(); } -} \ No newline at end of file +} diff --git a/domain/src/test/java/com/fernandocejas/android10/sample/domain/interactor/GetUserDetailsTest.java b/domain/src/test/java/com/fernandocejas/android10/sample/domain/interactor/GetUserDetailsTest.java index 8e730cb2..01baab94 100644 --- a/domain/src/test/java/com/fernandocejas/android10/sample/domain/interactor/GetUserDetailsTest.java +++ b/domain/src/test/java/com/fernandocejas/android10/sample/domain/interactor/GetUserDetailsTest.java @@ -17,19 +17,24 @@ import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread; import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; +import com.fernandocejas.android10.sample.domain.interactor.GetUserDetails.Params; import com.fernandocejas.android10.sample.domain.repository.UserRepository; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.runners.MockitoJUnitRunner; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; +@RunWith(MockitoJUnitRunner.class) public class GetUserDetailsTest { - private static final int FAKE_USER_ID = 123; + private static final int USER_ID = 123; private GetUserDetails getUserDetails; @@ -37,20 +42,27 @@ public class GetUserDetailsTest { @Mock private ThreadExecutor mockThreadExecutor; @Mock private PostExecutionThread mockPostExecutionThread; + @Rule public ExpectedException expectedException = ExpectedException.none(); + @Before public void setUp() { - MockitoAnnotations.initMocks(this); - getUserDetails = new GetUserDetails(FAKE_USER_ID, mockUserRepository, - mockThreadExecutor, mockPostExecutionThread); + getUserDetails = new GetUserDetails(mockUserRepository, mockThreadExecutor, + mockPostExecutionThread); } @Test public void testGetUserDetailsUseCaseObservableHappyCase() { - getUserDetails.buildUseCaseObservable(); + getUserDetails.buildUseCaseObservable(Params.forUser(USER_ID)); - verify(mockUserRepository).user(FAKE_USER_ID); + verify(mockUserRepository).user(USER_ID); verifyNoMoreInteractions(mockUserRepository); verifyZeroInteractions(mockPostExecutionThread); verifyZeroInteractions(mockThreadExecutor); } + + @Test + public void testShouldFailWhenNoOrEmptyParameters() { + expectedException.expect(NullPointerException.class); + getUserDetails.buildUseCaseObservable(null); + } } diff --git a/domain/src/test/java/com/fernandocejas/android10/sample/domain/interactor/GetUserListTest.java b/domain/src/test/java/com/fernandocejas/android10/sample/domain/interactor/GetUserListTest.java index 0e1565ff..13694063 100644 --- a/domain/src/test/java/com/fernandocejas/android10/sample/domain/interactor/GetUserListTest.java +++ b/domain/src/test/java/com/fernandocejas/android10/sample/domain/interactor/GetUserListTest.java @@ -20,13 +20,15 @@ import com.fernandocejas.android10.sample.domain.repository.UserRepository; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.runners.MockitoJUnitRunner; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; +@RunWith(MockitoJUnitRunner.class) public class GetUserListTest { private GetUserList getUserList; @@ -37,14 +39,13 @@ public class GetUserListTest { @Before public void setUp() { - MockitoAnnotations.initMocks(this); getUserList = new GetUserList(mockUserRepository, mockThreadExecutor, mockPostExecutionThread); } @Test public void testGetUserListUseCaseObservableHappyCase() { - getUserList.buildUseCaseObservable(); + getUserList.buildUseCaseObservable(null); verify(mockUserRepository).users(); verifyNoMoreInteractions(mockUserRepository); diff --git a/domain/src/test/java/com/fernandocejas/android10/sample/domain/interactor/UseCaseTest.java b/domain/src/test/java/com/fernandocejas/android10/sample/domain/interactor/UseCaseTest.java index e8d20444..b8882001 100644 --- a/domain/src/test/java/com/fernandocejas/android10/sample/domain/interactor/UseCaseTest.java +++ b/domain/src/test/java/com/fernandocejas/android10/sample/domain/interactor/UseCaseTest.java @@ -17,68 +17,94 @@ import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread; import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; +import io.reactivex.Observable; +import io.reactivex.observers.DisposableObserver; +import io.reactivex.schedulers.TestScheduler; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import rx.Observable; -import rx.Subscriber; -import rx.observers.TestSubscriber; -import rx.schedulers.TestScheduler; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; +@RunWith(MockitoJUnitRunner.class) public class UseCaseTest { private UseCaseTestClass useCase; + private TestDisposableObserver testObserver; + @Mock private ThreadExecutor mockThreadExecutor; @Mock private PostExecutionThread mockPostExecutionThread; + @Rule public ExpectedException expectedException = ExpectedException.none(); + @Before public void setUp() { - MockitoAnnotations.initMocks(this); this.useCase = new UseCaseTestClass(mockThreadExecutor, mockPostExecutionThread); + this.testObserver = new TestDisposableObserver<>(); + given(mockPostExecutionThread.getScheduler()).willReturn(new TestScheduler()); } @Test - @SuppressWarnings("unchecked") public void testBuildUseCaseObservableReturnCorrectResult() { - TestSubscriber testSubscriber = new TestSubscriber<>(); - TestScheduler testScheduler = new TestScheduler(); - given(mockPostExecutionThread.getScheduler()).willReturn(testScheduler); - - useCase.execute(testSubscriber); + useCase.execute(testObserver, Params.EMPTY); - assertThat(testSubscriber.getOnNextEvents().size(), is(0)); + assertThat(testObserver.valuesCount).isZero(); } @Test public void testSubscriptionWhenExecutingUseCase() { - TestSubscriber testSubscriber = new TestSubscriber<>(); + useCase.execute(testObserver, Params.EMPTY); + useCase.dispose(); - useCase.execute(testSubscriber); - useCase.unsubscribe(); + assertThat(testObserver.isDisposed()).isTrue(); + } - assertThat(testSubscriber.isUnsubscribed(), is(true)); + @Test + public void testShouldFailWhenExecuteWithNullObserver() { + expectedException.expect(NullPointerException.class); + useCase.execute(null, Params.EMPTY); } - private static class UseCaseTestClass extends UseCase { + private static class UseCaseTestClass extends UseCase { - protected UseCaseTestClass( - ThreadExecutor threadExecutor, - PostExecutionThread postExecutionThread) { + UseCaseTestClass(ThreadExecutor threadExecutor, PostExecutionThread postExecutionThread) { super(threadExecutor, postExecutionThread); } - @Override protected Observable buildUseCaseObservable() { + @Override Observable buildUseCaseObservable(Params params) { return Observable.empty(); } - @Override public void execute(Subscriber UseCaseSubscriber) { - super.execute(UseCaseSubscriber); + @Override + public void execute(DisposableObserver observer, Params params) { + super.execute(observer, params); + } + } + + private static class TestDisposableObserver extends DisposableObserver { + private int valuesCount = 0; + + @Override public void onNext(T value) { + valuesCount++; + } + + @Override public void onError(Throwable e) { + // no-op by default. } + + @Override public void onComplete() { + // no-op by default. + } + } + + private static class Params { + private static Params EMPTY = new Params(); + private Params() {} } -} \ No newline at end of file +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 05ef575b..51288f9c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 32e96f4c..0ca07476 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Jan 19 23:59:31 CET 2016 +#Wed Dec 21 17:11:04 ART 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip diff --git a/gradlew b/gradlew index 9d82f789..4453ccea 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,12 +6,30 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,26 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -85,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -150,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index aec99730..e95643d6 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/presentation/build.gradle b/presentation/build.gradle index c6b32b06..08206665 100644 --- a/presentation/build.gradle +++ b/presentation/build.gradle @@ -1,6 +1,5 @@ apply plugin: 'com.android.application' -apply plugin: 'com.neenbedankt.android-apt' -apply plugin: 'com.fernandocejas.frodo' +//apply plugin: 'com.neenbedankt.android-apt' android { def globalConfiguration = rootProject.extensions.getByName("ext") @@ -63,23 +62,28 @@ dependencies { def presentationTestDependencies = rootProject.ext.presentationTestDependencies def developmentDependencies = rootProject.ext.developmentDependencies - compile project(':domain') - compile project(':data') + implementation project(':domain') + implementation project(':data') - apt presentationDependencies.daggerCompiler - compile presentationDependencies.dagger - compile presentationDependencies.butterKnife - compile presentationDependencies.recyclerView - compile presentationDependencies.rxJava - compile presentationDependencies.rxAndroid - provided presentationDependencies.javaxAnnotation + annotationProcessor presentationDependencies.daggerCompiler + implementation presentationDependencies.dagger + implementation presentationDependencies.butterKnife + annotationProcessor presentationDependencies.butterKnife + implementation presentationDependencies.recyclerView + implementation presentationDependencies.rxJava + implementation presentationDependencies.rxAndroid + compileOnly presentationDependencies.javaxAnnotation - androidTestCompile presentationTestDependencies.mockito - androidTestCompile presentationTestDependencies.dexmaker - androidTestCompile presentationTestDependencies.dexmakerMockito - androidTestCompile presentationTestDependencies.espresso - androidTestCompile presentationTestDependencies.testingSupportLib + androidTestImplementation presentationTestDependencies.mockito + androidTestImplementation presentationTestDependencies.dexmaker + androidTestImplementation presentationTestDependencies.dexmakerMockito + androidTestImplementation presentationTestDependencies.espresso + androidTestImplementation presentationTestDependencies.testingSupportLib //Development - compile developmentDependencies.leakCanary + implementation developmentDependencies.leakCanary } + +repositories { + google() +} \ No newline at end of file diff --git a/presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/mapper/UserModelDataMapperTest.java b/presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/mapper/UserModelDataMapperTest.java index 50a1e33b..bb48c6fc 100644 --- a/presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/mapper/UserModelDataMapperTest.java +++ b/presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/mapper/UserModelDataMapperTest.java @@ -31,7 +31,7 @@ public class UserModelDataMapperTest extends TestCase { private static final int FAKE_USER_ID = 123; - private static final String FAKE_FULLNAME = "Tony Stark"; + private static final String FAKE_FULL_NAME = "Tony Stark"; private UserModelDataMapper userModelDataMapper; @@ -46,7 +46,7 @@ public void testTransformUser() { assertThat(userModel, is(instanceOf(UserModel.class))); assertThat(userModel.getUserId(), is(FAKE_USER_ID)); - assertThat(userModel.getFullName(), is(FAKE_FULLNAME)); + assertThat(userModel.getFullName(), is(FAKE_FULL_NAME)); } public void testTransformUserCollection() { @@ -66,7 +66,7 @@ public void testTransformUserCollection() { private User createFakeUser() { User user = new User(FAKE_USER_ID); - user.setFullName(FAKE_FULLNAME); + user.setFullName(FAKE_FULL_NAME); return user; } diff --git a/presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/presenter/UserDetailsPresenterTest.java b/presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/presenter/UserDetailsPresenterTest.java index c4009696..e8360659 100644 --- a/presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/presenter/UserDetailsPresenterTest.java +++ b/presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/presenter/UserDetailsPresenterTest.java @@ -16,49 +16,49 @@ package com.fernandocejas.android10.sample.test.presenter; import android.content.Context; -import android.test.AndroidTestCase; import com.fernandocejas.android10.sample.domain.interactor.GetUserDetails; +import com.fernandocejas.android10.sample.domain.interactor.GetUserDetails.Params; import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper; import com.fernandocejas.android10.sample.presentation.presenter.UserDetailsPresenter; import com.fernandocejas.android10.sample.presentation.view.UserDetailsView; +import io.reactivex.observers.DisposableObserver; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import rx.Subscriber; +import org.mockito.runners.MockitoJUnitRunner; import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.any; import static org.mockito.Mockito.verify; -public class UserDetailsPresenterTest extends AndroidTestCase { +@RunWith(MockitoJUnitRunner.class) +public class UserDetailsPresenterTest { - private static final int FAKE_USER_ID = 123; + private static final int USER_ID = 1; private UserDetailsPresenter userDetailsPresenter; - @Mock - private Context mockContext; - @Mock - private UserDetailsView mockUserDetailsView; - @Mock - private GetUserDetails mockGetUserDetails; - @Mock - private UserModelDataMapper mockUserModelDataMapper; + @Mock private Context mockContext; + @Mock private UserDetailsView mockUserDetailsView; + @Mock private GetUserDetails mockGetUserDetails; + @Mock private UserModelDataMapper mockUserModelDataMapper; - @Override protected void setUp() throws Exception { - super.setUp(); - MockitoAnnotations.initMocks(this); - userDetailsPresenter = new UserDetailsPresenter(mockGetUserDetails, - mockUserModelDataMapper); + @Before + public void setUp() { + userDetailsPresenter = new UserDetailsPresenter(mockGetUserDetails, mockUserModelDataMapper); userDetailsPresenter.setView(mockUserDetailsView); } + @Test + @SuppressWarnings("unchecked") public void testUserDetailsPresenterInitialize() { given(mockUserDetailsView.context()).willReturn(mockContext); - userDetailsPresenter.initialize(); + userDetailsPresenter.initialize(USER_ID); verify(mockUserDetailsView).hideRetry(); verify(mockUserDetailsView).showLoading(); - verify(mockGetUserDetails).execute(any(Subscriber.class)); + verify(mockGetUserDetails).execute(any(DisposableObserver.class), any(Params.class)); } } diff --git a/presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/presenter/UserListPresenterTest.java b/presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/presenter/UserListPresenterTest.java index f8a98c8e..35c99855 100644 --- a/presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/presenter/UserListPresenterTest.java +++ b/presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/presenter/UserListPresenterTest.java @@ -16,39 +16,39 @@ package com.fernandocejas.android10.sample.test.presenter; import android.content.Context; -import android.test.AndroidTestCase; import com.fernandocejas.android10.sample.domain.interactor.GetUserList; import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper; import com.fernandocejas.android10.sample.presentation.presenter.UserListPresenter; import com.fernandocejas.android10.sample.presentation.view.UserListView; +import io.reactivex.observers.DisposableObserver; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import rx.Subscriber; +import org.mockito.runners.MockitoJUnitRunner; import static org.mockito.BDDMockito.given; import static org.mockito.Matchers.any; import static org.mockito.Mockito.verify; -public class UserListPresenterTest extends AndroidTestCase { +@RunWith(MockitoJUnitRunner.class) +public class UserListPresenterTest { private UserListPresenter userListPresenter; - @Mock - private Context mockContext; - @Mock - private UserListView mockUserListView; - @Mock - private GetUserList mockGetUserList; - @Mock - private UserModelDataMapper mockUserModelDataMapper; - - @Override protected void setUp() throws Exception { - super.setUp(); - MockitoAnnotations.initMocks(this); + @Mock private Context mockContext; + @Mock private UserListView mockUserListView; + @Mock private GetUserList mockGetUserList; + @Mock private UserModelDataMapper mockUserModelDataMapper; + + @Before + public void setUp() { userListPresenter = new UserListPresenter(mockGetUserList, mockUserModelDataMapper); userListPresenter.setView(mockUserListView); } + @Test + @SuppressWarnings("unchecked") public void testUserListPresenterInitialize() { given(mockUserListView.context()).willReturn(mockContext); @@ -56,6 +56,6 @@ public void testUserListPresenterInitialize() { verify(mockUserListView).hideRetry(); verify(mockUserListView).showLoading(); - verify(mockGetUserList).execute(any(Subscriber.class)); + verify(mockGetUserList).execute(any(DisposableObserver.class), any(Void.class)); } } diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/UIThread.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/UIThread.java index bd4ceca4..1a91a34e 100644 --- a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/UIThread.java +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/UIThread.java @@ -16,20 +16,20 @@ package com.fernandocejas.android10.sample.presentation; import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread; +import io.reactivex.Scheduler; +import io.reactivex.android.schedulers.AndroidSchedulers; import javax.inject.Inject; import javax.inject.Singleton; -import rx.Scheduler; -import rx.android.schedulers.AndroidSchedulers; /** - * MainThread (UI Thread) implementation based on a {@link rx.Scheduler} + * MainThread (UI Thread) implementation based on a {@link Scheduler} * which will execute actions on the Android UI thread */ @Singleton public class UIThread implements PostExecutionThread { @Inject - public UIThread() {} + UIThread() {} @Override public Scheduler getScheduler() { return AndroidSchedulers.mainThread(); diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/internal/di/components/ActivityComponent.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/internal/di/components/ActivityComponent.java index a7ffc0d9..f39e89eb 100644 --- a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/internal/di/components/ActivityComponent.java +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/internal/di/components/ActivityComponent.java @@ -29,7 +29,7 @@ */ @PerActivity @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) -public interface ActivityComponent { +interface ActivityComponent { //Exposed to sub-graphs. Activity activity(); } diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/internal/di/modules/UserModule.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/internal/di/modules/UserModule.java index 3f12d332..be255a0f 100644 --- a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/internal/di/modules/UserModule.java +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/internal/di/modules/UserModule.java @@ -15,16 +15,7 @@ */ package com.fernandocejas.android10.sample.presentation.internal.di.modules; -import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread; -import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor; -import com.fernandocejas.android10.sample.domain.interactor.GetUserDetails; -import com.fernandocejas.android10.sample.domain.interactor.GetUserList; -import com.fernandocejas.android10.sample.domain.interactor.UseCase; -import com.fernandocejas.android10.sample.domain.repository.UserRepository; -import com.fernandocejas.android10.sample.presentation.internal.di.PerActivity; import dagger.Module; -import dagger.Provides; -import javax.inject.Named; /** * Dagger module that provides user related collaborators. @@ -32,22 +23,5 @@ @Module public class UserModule { - private int userId = -1; - public UserModule() {} - - public UserModule(int userId) { - this.userId = userId; - } - - @Provides @PerActivity @Named("userList") UseCase provideGetUserListUseCase( - GetUserList getUserList) { - return getUserList; - } - - @Provides @PerActivity @Named("userDetails") UseCase provideGetUserDetailsUseCase( - UserRepository userRepository, ThreadExecutor threadExecutor, - PostExecutionThread postExecutionThread) { - return new GetUserDetails(userId, userRepository, threadExecutor, postExecutionThread); - } -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/mapper/UserModelDataMapper.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/mapper/UserModelDataMapper.java index 3c64bac3..489df6d9 100644 --- a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/mapper/UserModelDataMapper.java +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/mapper/UserModelDataMapper.java @@ -43,7 +43,7 @@ public UserModel transform(User user) { if (user == null) { throw new IllegalArgumentException("Cannot transform a null value"); } - UserModel userModel = new UserModel(user.getUserId()); + final UserModel userModel = new UserModel(user.getUserId()); userModel.setCoverUrl(user.getCoverUrl()); userModel.setFullName(user.getFullName()); userModel.setEmail(user.getEmail()); diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/model/UserModel.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/model/UserModel.java index d13ae2d4..fd6d25b0 100644 --- a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/model/UserModel.java +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/model/UserModel.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -75,19 +75,4 @@ public int getFollowers() { public void setFollowers(int followers) { this.followers = followers; } - - @Override public String toString() { - StringBuilder stringBuilder = new StringBuilder(); - - stringBuilder.append("***** User Model Details *****\n"); - stringBuilder.append("id=" + this.getUserId() + "\n"); - stringBuilder.append("cover url=" + this.getCoverUrl() + "\n"); - stringBuilder.append("fullname=" + this.getFullName() + "\n"); - stringBuilder.append("email=" + this.getEmail() + "\n"); - stringBuilder.append("description=" + this.getDescription() + "\n"); - stringBuilder.append("followers=" + this.getFollowers() + "\n"); - stringBuilder.append("*******************************"); - - return stringBuilder.toString(); - } } diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserDetailsPresenter.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserDetailsPresenter.java index e10967c0..4e3c3bf7 100644 --- a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserDetailsPresenter.java +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserDetailsPresenter.java @@ -19,16 +19,15 @@ import com.fernandocejas.android10.sample.domain.User; import com.fernandocejas.android10.sample.domain.exception.DefaultErrorBundle; import com.fernandocejas.android10.sample.domain.exception.ErrorBundle; -import com.fernandocejas.android10.sample.domain.interactor.DefaultSubscriber; -import com.fernandocejas.android10.sample.domain.interactor.UseCase; +import com.fernandocejas.android10.sample.domain.interactor.DefaultObserver; +import com.fernandocejas.android10.sample.domain.interactor.GetUserDetails; +import com.fernandocejas.android10.sample.domain.interactor.GetUserDetails.Params; import com.fernandocejas.android10.sample.presentation.exception.ErrorMessageFactory; import com.fernandocejas.android10.sample.presentation.internal.di.PerActivity; import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper; import com.fernandocejas.android10.sample.presentation.model.UserModel; import com.fernandocejas.android10.sample.presentation.view.UserDetailsView; -import com.fernandocejas.frodo.annotation.RxLogSubscriber; import javax.inject.Inject; -import javax.inject.Named; /** * {@link Presenter} that controls communication between views and models of the presentation @@ -39,11 +38,11 @@ public class UserDetailsPresenter implements Presenter { private UserDetailsView viewDetailsView; - private final UseCase getUserDetailsUseCase; + private final GetUserDetails getUserDetailsUseCase; private final UserModelDataMapper userModelDataMapper; @Inject - public UserDetailsPresenter(@Named("userDetails") UseCase getUserDetailsUseCase, + public UserDetailsPresenter(GetUserDetails getUserDetailsUseCase, UserModelDataMapper userModelDataMapper) { this.getUserDetailsUseCase = getUserDetailsUseCase; this.userModelDataMapper = userModelDataMapper; @@ -58,24 +57,22 @@ public void setView(@NonNull UserDetailsView view) { @Override public void pause() {} @Override public void destroy() { - this.getUserDetailsUseCase.unsubscribe(); + this.getUserDetailsUseCase.dispose(); this.viewDetailsView = null; } /** - * Initializes the presenter by start retrieving user details. + * Initializes the presenter by showing/hiding proper views + * and retrieving user details. */ - public void initialize() { - this.loadUserDetails(); - } - - /** - * Loads user details. - */ - private void loadUserDetails() { + public void initialize(int userId) { this.hideViewRetry(); this.showViewLoading(); - this.getUserDetails(); + this.getUserDetails(userId); + } + + private void getUserDetails(int userId) { + this.getUserDetailsUseCase.execute(new UserDetailsObserver(), Params.forUser(userId)); } private void showViewLoading() { @@ -105,14 +102,9 @@ private void showUserDetailsInView(User user) { this.viewDetailsView.renderUser(userModel); } - private void getUserDetails() { - this.getUserDetailsUseCase.execute(new UserDetailsSubscriber()); - } - - @RxLogSubscriber - private final class UserDetailsSubscriber extends DefaultSubscriber { + private final class UserDetailsObserver extends DefaultObserver { - @Override public void onCompleted() { + @Override public void onComplete() { UserDetailsPresenter.this.hideViewLoading(); } diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserListPresenter.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserListPresenter.java index 72cf4b51..f17a460d 100644 --- a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserListPresenter.java +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserListPresenter.java @@ -19,8 +19,8 @@ import com.fernandocejas.android10.sample.domain.User; import com.fernandocejas.android10.sample.domain.exception.DefaultErrorBundle; import com.fernandocejas.android10.sample.domain.exception.ErrorBundle; -import com.fernandocejas.android10.sample.domain.interactor.DefaultSubscriber; -import com.fernandocejas.android10.sample.domain.interactor.UseCase; +import com.fernandocejas.android10.sample.domain.interactor.DefaultObserver; +import com.fernandocejas.android10.sample.domain.interactor.GetUserList; import com.fernandocejas.android10.sample.presentation.exception.ErrorMessageFactory; import com.fernandocejas.android10.sample.presentation.internal.di.PerActivity; import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper; @@ -29,7 +29,6 @@ import java.util.Collection; import java.util.List; import javax.inject.Inject; -import javax.inject.Named; /** * {@link Presenter} that controls communication between views and models of the presentation @@ -40,11 +39,11 @@ public class UserListPresenter implements Presenter { private UserListView viewListView; - private final UseCase getUserListUseCase; + private final GetUserList getUserListUseCase; private final UserModelDataMapper userModelDataMapper; @Inject - public UserListPresenter(@Named("userList") UseCase getUserListUserCase, + public UserListPresenter(GetUserList getUserListUserCase, UserModelDataMapper userModelDataMapper) { this.getUserListUseCase = getUserListUserCase; this.userModelDataMapper = userModelDataMapper; @@ -59,7 +58,7 @@ public void setView(@NonNull UserListView view) { @Override public void pause() {} @Override public void destroy() { - this.getUserListUseCase.unsubscribe(); + this.getUserListUseCase.dispose(); this.viewListView = null; } @@ -112,12 +111,12 @@ private void showUsersCollectionInView(Collection usersCollection) { } private void getUserList() { - this.getUserListUseCase.execute(new UserListSubscriber()); + this.getUserListUseCase.execute(new UserListObserver(), null); } - private final class UserListSubscriber extends DefaultSubscriber> { + private final class UserListObserver extends DefaultObserver> { - @Override public void onCompleted() { + @Override public void onComplete() { UserListPresenter.this.hideViewLoading(); } diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/activity/BaseActivity.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/activity/BaseActivity.java index 716e2915..70ee8ca0 100644 --- a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/activity/BaseActivity.java +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/activity/BaseActivity.java @@ -30,7 +30,7 @@ protected void onCreate(Bundle savedInstanceState) { * @param fragment The fragment to be added. */ protected void addFragment(int containerViewId, Fragment fragment) { - FragmentTransaction fragmentTransaction = this.getFragmentManager().beginTransaction(); + final FragmentTransaction fragmentTransaction = this.getFragmentManager().beginTransaction(); fragmentTransaction.add(containerViewId, fragment); fragmentTransaction.commit(); } @@ -41,7 +41,7 @@ protected void addFragment(int containerViewId, Fragment fragment) { * @return {@link com.fernandocejas.android10.sample.presentation.internal.di.components.ApplicationComponent} */ protected ApplicationComponent getApplicationComponent() { - return ((AndroidApplication)getApplication()).getApplicationComponent(); + return ((AndroidApplication) getApplication()).getApplicationComponent(); } /** diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/activity/UserDetailsActivity.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/activity/UserDetailsActivity.java index fb2ae486..f13ecf22 100644 --- a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/activity/UserDetailsActivity.java +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/activity/UserDetailsActivity.java @@ -13,7 +13,6 @@ import com.fernandocejas.android10.sample.presentation.internal.di.HasComponent; import com.fernandocejas.android10.sample.presentation.internal.di.components.DaggerUserComponent; import com.fernandocejas.android10.sample.presentation.internal.di.components.UserComponent; -import com.fernandocejas.android10.sample.presentation.internal.di.modules.UserModule; import com.fernandocejas.android10.sample.presentation.view.fragment.UserDetailsFragment; /** @@ -55,7 +54,7 @@ public static Intent getCallingIntent(Context context, int userId) { private void initializeActivity(Bundle savedInstanceState) { if (savedInstanceState == null) { this.userId = getIntent().getIntExtra(INTENT_EXTRA_PARAM_USER_ID, -1); - addFragment(R.id.fragmentContainer, new UserDetailsFragment()); + addFragment(R.id.fragmentContainer, UserDetailsFragment.forUser(userId)); } else { this.userId = savedInstanceState.getInt(INSTANCE_STATE_PARAM_USER_ID); } @@ -65,7 +64,6 @@ private void initializeInjector() { this.userComponent = DaggerUserComponent.builder() .applicationComponent(getApplicationComponent()) .activityModule(getActivityModule()) - .userModule(new UserModule(this.userId)) .build(); } diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/adapter/UsersAdapter.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/adapter/UsersAdapter.java index 110a1002..3b13dc58 100644 --- a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/adapter/UsersAdapter.java +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/adapter/UsersAdapter.java @@ -34,7 +34,7 @@ public interface OnItemClickListener { private OnItemClickListener onItemClickListener; @Inject - public UsersAdapter(Context context) { + UsersAdapter(Context context) { this.layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); this.usersCollection = Collections.emptyList(); @@ -84,7 +84,7 @@ private void validateUsersCollection(Collection usersCollection) { static class UserViewHolder extends RecyclerView.ViewHolder { @Bind(R.id.title) TextView textViewTitle; - public UserViewHolder(View itemView) { + UserViewHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); } diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/component/AutoLoadImageView.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/component/AutoLoadImageView.java index c45e8d6c..45cecd3d 100644 --- a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/component/AutoLoadImageView.java +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/component/AutoLoadImageView.java @@ -17,10 +17,8 @@ import android.util.Log; import android.widget.ImageView; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; -import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; @@ -83,25 +81,6 @@ public void setImageUrl(final String imageUrl) { } } - /** - * Set a place holder used for loading when an image is being downloaded from the internet. - * - * @param resourceId The resource id to use as a place holder. - */ - public void setImagePlaceHolder(int resourceId) { - this.imagePlaceHolderResId = resourceId; - this.loadImagePlaceHolder(); - } - - /** - * Invalidate the internal cache by evicting all cached elements. - */ - public void invalidateImageCache() { - if (this.cache != null) { - this.cache.evictAll(); - } - } - /** * Loads and image from the internet (and cache it) or from the internal cache. * @@ -195,9 +174,9 @@ private void cacheBitmap(Bitmap bitmap, String fileName) { private boolean isThereInternetConnection() { boolean isConnected; - ConnectivityManager connectivityManager = + final ConnectivityManager connectivityManager = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); + final NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); isConnected = (networkInfo != null && networkInfo.isConnectedOrConnecting()); return isConnected; @@ -238,14 +217,12 @@ interface Callback { */ void download(String imageUrl, Callback callback) { try { - URLConnection conn = new URL(imageUrl).openConnection(); + final URLConnection conn = new URL(imageUrl).openConnection(); conn.connect(); - Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream()); + final Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream()); if (callback != null) { callback.onImageDownloaded(bitmap); } - } catch (MalformedURLException e) { - reportError(callback); } catch (IOException e) { reportError(callback); } @@ -298,32 +275,19 @@ synchronized Bitmap get(String fileName) { * @param fileName A string representing the name of the file to be cached. */ synchronized void put(Bitmap bitmap, String fileName) { - File file = buildFileFromFilename(fileName); + final File file = buildFileFromFilename(fileName); if (!file.exists()) { try { - FileOutputStream fileOutputStream = new FileOutputStream(file); + final FileOutputStream fileOutputStream = new FileOutputStream(file); bitmap.compress(Bitmap.CompressFormat.PNG, 90, fileOutputStream); fileOutputStream.flush(); fileOutputStream.close(); - } catch (FileNotFoundException e) { - Log.e(TAG, e.getMessage()); } catch (IOException e) { Log.e(TAG, e.getMessage()); } } } - /** - * Invalidate and expire the cache. - */ - void evictAll() { - if (cacheDir.exists()) { - for (File file : cacheDir.listFiles()) { - file.delete(); - } - } - } - /** * Creates a file name from an image url * diff --git a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/UserDetailsFragment.java b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/UserDetailsFragment.java index e5c51334..95d09a6d 100644 --- a/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/UserDetailsFragment.java +++ b/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/UserDetailsFragment.java @@ -21,12 +21,14 @@ import com.fernandocejas.android10.sample.presentation.presenter.UserDetailsPresenter; import com.fernandocejas.android10.sample.presentation.view.UserDetailsView; import com.fernandocejas.android10.sample.presentation.view.component.AutoLoadImageView; +import com.fernandocejas.arrow.checks.Preconditions; import javax.inject.Inject; /** * Fragment that shows details of a certain user. */ public class UserDetailsFragment extends BaseFragment implements UserDetailsView { + private static final String PARAM_USER_ID = "param_user_id"; @Inject UserDetailsPresenter userDetailsPresenter; @@ -39,6 +41,14 @@ public class UserDetailsFragment extends BaseFragment implements UserDetailsView @Bind(R.id.rl_retry) RelativeLayout rl_retry; @Bind(R.id.bt_retry) Button bt_retry; + public static UserDetailsFragment forUser(int userId) { + final UserDetailsFragment userDetailsFragment = new UserDetailsFragment(); + final Bundle arguments = new Bundle(); + arguments.putInt(PARAM_USER_ID, userId); + userDetailsFragment.setArguments(arguments); + return userDetailsFragment; + } + public UserDetailsFragment() { setRetainInstance(true); } @@ -120,14 +130,23 @@ public UserDetailsFragment() { } /** - * Loads all users. + * Load user details. */ private void loadUserDetails() { if (this.userDetailsPresenter != null) { - this.userDetailsPresenter.initialize(); + this.userDetailsPresenter.initialize(currentUserId()); } } + /** + * Get current user id from fragments arguments. + */ + private int currentUserId() { + final Bundle arguments = getArguments(); + Preconditions.checkNotNull(arguments, "Fragment arguments cannot be null"); + return arguments.getInt(PARAM_USER_ID); + } + @OnClick(R.id.bt_retry) void onButtonRetryClick() { UserDetailsFragment.this.loadUserDetails();