├── .gitattributes
├── .gitignore
├── Data
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── .classpath
│ ├── .project
│ ├── .settings
│ └── org.eclipse.jdt.core.prefs
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── fernandocejas
│ │ └── android10
│ │ └── sample
│ │ └── data
│ │ ├── cache
│ │ ├── FileManager.java
│ │ ├── UserCache.java
│ │ ├── UserCacheImpl.java
│ │ └── serializer
│ │ │ └── JsonSerializer.java
│ │ ├── entity
│ │ ├── UserEntity.java
│ │ └── mapper
│ │ │ ├── UserEntityDataMapper.java
│ │ │ └── UserEntityJsonMapper.java
│ │ ├── exception
│ │ ├── NetworkConnectionException.java
│ │ ├── RepositoryErrorBundle.java
│ │ └── UserNotFoundException.java
│ │ ├── executor
│ │ └── JobExecutor.java
│ │ ├── net
│ │ ├── ApiConnection.java
│ │ ├── RestApi.java
│ │ └── RestApiImpl.java
│ │ └── repository
│ │ ├── UserDataRepository.java
│ │ └── datasource
│ │ ├── CloudUserDataStore.java
│ │ ├── DiskUserDataStore.java
│ │ ├── UserDataStore.java
│ │ └── UserDataStoreFactory.java
│ ├── libs
│ ├── dexmaker-1.0.jar
│ ├── dexmaker-mockito-1.0.jar
│ ├── espresso-1.1-bundled.jar
│ ├── gson-2.2.4.jar
│ └── mockito-all-1.9.5.jar
│ └── project.properties
├── Domain
├── .classpath
├── .project
├── AndroidManifest.xml
├── ic_launcher-web.png
├── libs
│ └── android-support-v4.jar
├── proguard-project.txt
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── drawable-xxhdpi
│ │ └── ic_launcher.png
│ ├── values-v11
│ │ └── styles.xml
│ ├── values-v14
│ │ └── styles.xml
│ └── values
│ │ ├── strings.xml
│ │ └── styles.xml
└── src
│ └── com
│ └── fernandocejas
│ └── android10
│ └── sample
│ └── domain
│ ├── User.java
│ ├── exception
│ └── ErrorBundle.java
│ ├── executor
│ ├── PostExecutionThread.java
│ └── ThreadExecutor.java
│ ├── interactor
│ ├── GetUserDetailsUseCase.java
│ ├── GetUserDetailsUseCaseImpl.java
│ ├── GetUserListUseCase.java
│ ├── GetUserListUseCaseImpl.java
│ └── Interactor.java
│ └── repository
│ └── UserRepository.java
├── Presentation
├── .gitignore
├── build.gradle
├── proguard-rules.pro
├── src
│ ├── androidTest
│ │ └── java
│ │ │ └── com
│ │ │ └── fernandocejas
│ │ │ └── android10
│ │ │ └── sample
│ │ │ └── test
│ │ │ ├── exception
│ │ │ └── ErrorMessageFactoryTest.java
│ │ │ ├── mapper
│ │ │ └── UserModelDataMapperTest.java
│ │ │ ├── presenter
│ │ │ ├── UserDetailsPresenterTest.java
│ │ │ └── UserListPresenterTest.java
│ │ │ └── view
│ │ │ └── activity
│ │ │ ├── UserDetailsActivityTest.java
│ │ │ └── UserListActivityTest.java
│ └── main
│ │ ├── .classpath
│ │ ├── .project
│ │ ├── .settings
│ │ └── org.eclipse.jdt.core.prefs
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── fernandocejas
│ │ │ └── android10
│ │ │ └── sample
│ │ │ └── presentation
│ │ │ ├── UIThread.java
│ │ │ ├── exception
│ │ │ └── ErrorMessageFactory.java
│ │ │ ├── mapper
│ │ │ └── UserModelDataMapper.java
│ │ │ ├── model
│ │ │ └── UserModel.java
│ │ │ ├── navigation
│ │ │ └── Navigator.java
│ │ │ ├── presenter
│ │ │ ├── Presenter.java
│ │ │ ├── UserDetailsPresenter.java
│ │ │ └── UserListPresenter.java
│ │ │ └── view
│ │ │ ├── LoadDataView.java
│ │ │ ├── UserDetailsView.java
│ │ │ ├── UserListView.java
│ │ │ ├── activity
│ │ │ ├── BaseActivity.java
│ │ │ ├── MainActivity.java
│ │ │ ├── UserDetailsActivity.java
│ │ │ └── UserListActivity.java
│ │ │ ├── adapter
│ │ │ └── UsersAdapter.java
│ │ │ ├── component
│ │ │ └── AutoLoadImageView.java
│ │ │ └── fragment
│ │ │ ├── BaseFragment.java
│ │ │ ├── UserDetailsFragment.java
│ │ │ └── UserListFragment.java
│ │ ├── project.properties
│ │ └── res
│ │ ├── drawable-hdpi
│ │ ├── ic_launcher.png
│ │ └── logo.png
│ │ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ │ ├── drawable-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── activity_user_details.xml
│ │ ├── activity_user_list.xml
│ │ ├── fragment_user_details.xml
│ │ ├── fragment_user_list.xml
│ │ ├── row_user.xml
│ │ ├── view_progress.xml
│ │ ├── view_retry.xml
│ │ └── view_user_details.xml
│ │ └── values
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
└── testLibs
│ ├── dexmaker-1.0.jar
│ ├── dexmaker-mockito-1.0.jar
│ ├── espresso-1.1-bundled.jar
│ └── mockito-all-1.9.5.jar
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Gradle files
16 | .gradle/
17 | build/
18 |
19 | # Local configuration file (sdk path, etc)
20 | local.properties
21 |
22 | # Proguard folder generated by Eclipse
23 | proguard/
24 |
25 | # Log Files
26 | *.log
27 |
28 | # =========================
29 | # Operating System Files
30 | # =========================
31 |
32 | # OSX
33 | # =========================
34 |
35 | .DS_Store
36 | .AppleDouble
37 | .LSOverride
38 |
39 | # Icon must end with two \r
40 | Icon
41 |
42 | # Thumbnails
43 | ._*
44 |
45 | # Files that might appear on external disk
46 | .Spotlight-V100
47 | .Trashes
48 |
49 | # Directories potentially created on remote AFP share
50 | .AppleDB
51 | .AppleDesktop
52 | Network Trash Folder
53 | Temporary Items
54 | .apdisk
55 |
56 | # Windows
57 | # =========================
58 |
59 | # Windows image file caches
60 | Thumbs.db
61 | ehthumbs.db
62 |
63 | # Folder config file
64 | Desktop.ini
65 |
66 | # Recycle Bin used on file shares
67 | $RECYCLE.BIN/
68 |
69 | # Windows Installer files
70 | *.cab
71 | *.msi
72 | *.msm
73 | *.msp
74 |
--------------------------------------------------------------------------------
/Data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/Data/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'android-library'
2 |
3 | android {
4 | compileSdkVersion 19
5 | buildToolsVersion '19.1.0'
6 |
7 | defaultConfig {
8 | applicationId "com.fernandocejas.android10.sample.data"
9 | minSdkVersion 15
10 | targetSdkVersion 19
11 | }
12 |
13 | packagingOptions {
14 | exclude 'META-INF/DEPENDENCIES'
15 | exclude 'META-INF/ASL2.0'
16 | exclude 'META-INF/NOTICE'
17 | exclude 'META-INF/LICENSE'
18 | }
19 |
20 | lintOptions {
21 | abortOnError false;
22 | disable 'InvalidPackage' // Some libraries have issues with this
23 | disable 'OldTargetApi' // Due to Robolectric that modifies the manifest when running tests
24 | }
25 | }
26 |
27 | dependencies {
28 | def domainLayer = project(':domain')
29 |
30 | //project dependencies
31 | compile domainLayer
32 |
33 | //library dependencies
34 | compile('com.google.code.gson:gson:2.2.4')
35 | }
36 |
--------------------------------------------------------------------------------
/Data/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/fcejas/Software/SDKs/android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/Data/src/main/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Data/src/main/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | Data
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Data/src/main/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
3 | org.eclipse.jdt.core.compiler.compliance=1.6
4 | org.eclipse.jdt.core.compiler.source=1.6
5 |
--------------------------------------------------------------------------------
/Data/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/cache/FileManager.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.cache;
6 |
7 | import android.content.Context;
8 | import android.content.SharedPreferences;
9 | import java.io.BufferedReader;
10 | import java.io.File;
11 | import java.io.FileNotFoundException;
12 | import java.io.FileReader;
13 | import java.io.FileWriter;
14 | import java.io.IOException;
15 |
16 | /**
17 | * Helper class to do operations on regular files/directories.
18 | */
19 | public class FileManager {
20 |
21 | private FileManager() {}
22 |
23 | private static class LazyHolder {
24 | private static final FileManager INSTANCE = new FileManager();
25 | }
26 |
27 | public static FileManager getInstance() {
28 | return LazyHolder.INSTANCE;
29 | }
30 |
31 | /**
32 | * Writes a file to Disk.
33 | * This is an I/O operation and this method executes in the main thread, so it is recommended to
34 | * perform this operation using another thread.
35 | *
36 | * @param file The file to write to Disk.
37 | */
38 | public void writeToFile(File file, String fileContent) {
39 | if (!file.exists()) {
40 | try {
41 | FileWriter writer = new FileWriter(file);
42 | writer.write(fileContent);
43 | writer.close();
44 | } catch (FileNotFoundException e) {
45 | e.printStackTrace();
46 | } catch (IOException e) {
47 | e.printStackTrace();
48 | } finally {
49 |
50 | }
51 | }
52 | }
53 |
54 | /**
55 | * Reads a content from a file.
56 | * This is an I/O operation and this method executes in the main thread, so it is recommended to
57 | * perform the operation using another thread.
58 | *
59 | * @param file The file to read from.
60 | * @return A string with the content of the file.
61 | */
62 | public String readFileContent(File file) {
63 | StringBuilder fileContentBuilder = new StringBuilder();
64 | if (file.exists()) {
65 | String stringLine;
66 | try {
67 | FileReader fileReader = new FileReader(file);
68 | BufferedReader bufferedReader = new BufferedReader(fileReader);
69 | while ((stringLine = bufferedReader.readLine()) != null) {
70 | fileContentBuilder.append(stringLine + "\n");
71 | }
72 | bufferedReader.close();
73 | fileReader.close();
74 | } catch (FileNotFoundException e) {
75 | e.printStackTrace();
76 | } catch (IOException e) {
77 | e.printStackTrace();
78 | }
79 | }
80 |
81 | return fileContentBuilder.toString();
82 | }
83 |
84 | /**
85 | * Returns a boolean indicating whether this file can be found on the underlying file system.
86 | *
87 | * @param file The file to check existence.
88 | * @return true if this file exists, false otherwise.
89 | */
90 | public boolean exists(File file) {
91 | return file.exists();
92 | }
93 |
94 | /**
95 | * Warning: Deletes the content of a directory.
96 | * This is an I/O operation and this method executes in the main thread, so it is recommended to
97 | * perform the operation using another thread.
98 | *
99 | * @param directory The directory which its content will be deleted.
100 | */
101 | public void clearDirectory(File directory) {
102 | if (directory.exists()) {
103 | for (File file : directory.listFiles()) {
104 | file.delete();
105 | }
106 | }
107 | }
108 |
109 | /**
110 | * Write a value to a user preferences file.
111 | *
112 | * @param context {@link android.content.Context} to retrieve android user preferences.
113 | * @param preferenceFileName A file name reprensenting where data will be written to.
114 | * @param key A string for the key that will be used to retrieve the value in the future.
115 | * @param value A long representing the value to be inserted.
116 | */
117 | public void writeToPreferences(Context context, String preferenceFileName, String key,
118 | long value) {
119 |
120 | SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceFileName,
121 | Context.MODE_PRIVATE);
122 | SharedPreferences.Editor editor = sharedPreferences.edit();
123 | editor.putLong(key, value);
124 | editor.apply();
125 | }
126 |
127 | /**
128 | * Get a value from a user preferences file.
129 | *
130 | * @param context {@link android.content.Context} to retrieve android user preferences.
131 | * @param preferenceFileName A file name representing where data will be get from.
132 | * @param key A key that will be used to retrieve the value from the preference file.
133 | * @return A long representing the value retrieved from the preferences file.
134 | */
135 | public long getFromPreferences(Context context, String preferenceFileName, String key) {
136 | SharedPreferences sharedPreferences = context.getSharedPreferences(preferenceFileName,
137 | Context.MODE_PRIVATE);
138 | return sharedPreferences.getLong(key, 0);
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/cache/UserCache.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.cache;
6 |
7 | import com.fernandocejas.android10.sample.data.entity.UserEntity;
8 |
9 | /**
10 | * An interface representing a user Cache.
11 | */
12 | public interface UserCache {
13 |
14 | /**
15 | * Callback used to be notified when a {@link UserEntity} has been loaded.
16 | */
17 | interface UserCacheCallback {
18 | void onUserEntityLoaded(UserEntity userEntity);
19 |
20 | void onError(Exception exception);
21 | }
22 |
23 | /**
24 | * Gets an element from the cache using a {@link UserCacheCallback}.
25 | *
26 | * @param userId The user id to retrieve data.
27 | * @param callback The {@link UserCacheCallback} to notify the client.
28 | */
29 | void get(final int userId, final UserCacheCallback callback);
30 |
31 | /**
32 | * Puts and element into the cache.
33 | *
34 | * @param userEntity Element to insert in the cache.
35 | */
36 | void put(UserEntity userEntity);
37 |
38 | /**
39 | * Checks if an element (User) exists in the cache.
40 | *
41 | * @param userId The id used to look for inside the cache.
42 | * @return true if the element is cached, otherwise false.
43 | */
44 | boolean isCached(final int userId);
45 |
46 | /**
47 | * Checks if the cache is expired.
48 | *
49 | * @return true, the cache is expired, otherwise false.
50 | */
51 | boolean isExpired();
52 |
53 | /**
54 | * Evict all elements of the cache.
55 | */
56 | void evictAll();
57 | }
58 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/cache/UserCacheImpl.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.cache;
6 |
7 | import android.content.Context;
8 | import com.fernandocejas.android10.sample.data.cache.serializer.JsonSerializer;
9 | import com.fernandocejas.android10.sample.data.entity.UserEntity;
10 | import com.fernandocejas.android10.sample.data.exception.UserNotFoundException;
11 | import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor;
12 | import java.io.File;
13 |
14 | /**
15 | * {@link UserCache} implementation.
16 | */
17 | public class UserCacheImpl implements UserCache {
18 |
19 | private static UserCacheImpl INSTANCE;
20 |
21 | public static synchronized UserCacheImpl getInstance(Context context,
22 | JsonSerializer userCacheSerializer, FileManager fileManager, ThreadExecutor threadExecutor) {
23 | if (INSTANCE == null) {
24 | INSTANCE = new UserCacheImpl(context, userCacheSerializer, fileManager, threadExecutor);
25 | }
26 | return INSTANCE;
27 | }
28 |
29 | private static final String SETTINGS_FILE_NAME = "com.fernandocejas.android10.SETTINGS";
30 | private static final String SETTINGS_KEY_LAST_CACHE_UPDATE = "last_cache_update";
31 |
32 | private static final String DEFAULT_FILE_NAME = "user_";
33 | private static final long EXPIRATION_TIME = 60 * 10 * 1000;
34 |
35 | private final Context context;
36 | private final File cacheDir;
37 | private final JsonSerializer serializer;
38 | private final FileManager fileManager;
39 | private final ThreadExecutor threadExecutor;
40 |
41 | /**
42 | * Constructor of the class {@link UserCacheImpl}.
43 | *
44 | * @param context A
45 | * @param userCacheSerializer {@link JsonSerializer} for object serialization.
46 | * @param fileManager {@link FileManager} for saving serialized objects to the file system.
47 | */
48 | private UserCacheImpl(Context context, JsonSerializer userCacheSerializer,
49 | FileManager fileManager, ThreadExecutor executor) {
50 | if (context == null || userCacheSerializer == null || fileManager == null || executor == null) {
51 | throw new IllegalArgumentException("Invalid null parameter");
52 | }
53 | this.context = context.getApplicationContext();
54 | this.cacheDir = this.context.getCacheDir();
55 | this.serializer = userCacheSerializer;
56 | this.fileManager = fileManager;
57 | this.threadExecutor = executor;
58 | }
59 |
60 | /**
61 | * {@inheritDoc}
62 | *
63 | * @param userId The user id to retrieve data.
64 | * @param callback The {@link UserCacheCallback} to notify the client.
65 | */
66 | @Override public synchronized void get(int userId, UserCacheCallback callback) {
67 | File userEntitiyFile = this.buildFile(userId);
68 | String fileContent = this.fileManager.readFileContent(userEntitiyFile);
69 | UserEntity userEntity = this.serializer.deserialize(fileContent);
70 |
71 | if (userEntity != null) {
72 | callback.onUserEntityLoaded(userEntity);
73 | } else {
74 | callback.onError(new UserNotFoundException());
75 | }
76 | }
77 |
78 | /**
79 | * {@inheritDoc}
80 | *
81 | * @param userEntity Element to insert in the cache.
82 | */
83 | @Override public synchronized void put(UserEntity userEntity) {
84 | if (userEntity != null) {
85 | File userEntitiyFile = this.buildFile(userEntity.getUserId());
86 | if (!isCached(userEntity.getUserId())) {
87 | String jsonString = this.serializer.serialize(userEntity);
88 | this.executeAsynchronously(new CacheWriter(this.fileManager, userEntitiyFile,
89 | jsonString));
90 | setLastCacheUpdateTimeMillis();
91 | }
92 | }
93 | }
94 |
95 | /**
96 | * {@inheritDoc}
97 | *
98 | * @param userId The id used to look for inside the cache.
99 | * @return true if the element is cached, otherwise false.
100 | */
101 | @Override public boolean isCached(int userId) {
102 | File userEntitiyFile = this.buildFile(userId);
103 | return this.fileManager.exists(userEntitiyFile);
104 | }
105 |
106 | /**
107 | * {@inheritDoc}
108 | *
109 | * @return true, the cache is expired, otherwise false.
110 | */
111 | @Override public boolean isExpired() {
112 | long currentTime = System.currentTimeMillis();
113 | long lastUpdateTime = this.getLastCacheUpdateTimeMillis();
114 |
115 | boolean expired = ((currentTime - lastUpdateTime) > EXPIRATION_TIME);
116 |
117 | if (expired) {
118 | this.evictAll();
119 | }
120 |
121 | return expired;
122 | }
123 |
124 | /**
125 | * {@inheritDoc}
126 | */
127 | @Override public synchronized void evictAll() {
128 | this.executeAsynchronously(new CacheEvictor(this.fileManager, this.cacheDir));
129 | }
130 |
131 | /**
132 | * Build a file, used to be inserted in the disk cache.
133 | *
134 | * @param userId The id user to build the file.
135 | * @return A valid file.
136 | */
137 | private File buildFile(int userId) {
138 | StringBuilder fileNameBuilder = new StringBuilder();
139 | fileNameBuilder.append(this.cacheDir.getPath());
140 | fileNameBuilder.append(File.separator);
141 | fileNameBuilder.append(DEFAULT_FILE_NAME);
142 | fileNameBuilder.append(userId);
143 |
144 | return new File(fileNameBuilder.toString());
145 | }
146 |
147 | /**
148 | * Set in millis, the last time the cache was accessed.
149 | */
150 | private void setLastCacheUpdateTimeMillis() {
151 | long currentMillis = System.currentTimeMillis();
152 | this.fileManager.writeToPreferences(this.context, SETTINGS_FILE_NAME,
153 | SETTINGS_KEY_LAST_CACHE_UPDATE, currentMillis);
154 | }
155 |
156 | /**
157 | * Get in millis, the last time the cache was accessed.
158 | */
159 | private long getLastCacheUpdateTimeMillis() {
160 | return this.fileManager.getFromPreferences(this.context, SETTINGS_FILE_NAME,
161 | SETTINGS_KEY_LAST_CACHE_UPDATE);
162 | }
163 |
164 | /**
165 | * Executes a {@link Runnable} in another Thread.
166 | *
167 | * @param runnable {@link Runnable} to execute
168 | */
169 | private void executeAsynchronously(Runnable runnable) {
170 | this.threadExecutor.execute(runnable);
171 | }
172 |
173 | /**
174 | * {@link Runnable} class for writing to disk.
175 | */
176 | private static class CacheWriter implements Runnable {
177 | private final FileManager fileManager;
178 | private final File fileToWrite;
179 | private final String fileContent;
180 |
181 | CacheWriter(FileManager fileManager, File fileToWrite, String fileContent) {
182 | this.fileManager = fileManager;
183 | this.fileToWrite = fileToWrite;
184 | this.fileContent = fileContent;
185 | }
186 |
187 | @Override public void run() {
188 | this.fileManager.writeToFile(fileToWrite, fileContent);
189 | }
190 | }
191 |
192 | /**
193 | * {@link Runnable} class for evicting all the cached files
194 | */
195 | private static class CacheEvictor implements Runnable {
196 | private final FileManager fileManager;
197 | private final File cacheDir;
198 |
199 | CacheEvictor(FileManager fileManager, File cacheDir) {
200 | this.fileManager = fileManager;
201 | this.cacheDir = cacheDir;
202 | }
203 |
204 | @Override public void run() {
205 | this.fileManager.clearDirectory(this.cacheDir);
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/cache/serializer/JsonSerializer.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.cache.serializer;
6 |
7 | import com.fernandocejas.android10.sample.data.entity.UserEntity;
8 | import com.google.gson.Gson;
9 |
10 | /**
11 | * Class user as Serializer/Deserializer for user entities.
12 | */
13 | public class JsonSerializer {
14 |
15 | private final Gson gson = new Gson();
16 |
17 | public JsonSerializer() {
18 | //empty
19 | }
20 |
21 | /**
22 | * Serialize an object to Json.
23 | *
24 | * @param userEntity {@link UserEntity} to serialize.
25 | */
26 | public String serialize(UserEntity userEntity) {
27 | String jsonString = gson.toJson(userEntity, UserEntity.class);
28 | return jsonString;
29 | }
30 |
31 | /**
32 | * Deserialize a json representation of an object.
33 | *
34 | * @param jsonString A json string to deserialize.
35 | * @return {@link UserEntity}
36 | */
37 | public UserEntity deserialize(String jsonString) {
38 | UserEntity userEntity = gson.fromJson(jsonString, UserEntity.class);
39 | return userEntity;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/entity/UserEntity.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.entity;
6 |
7 | import com.google.gson.annotations.SerializedName;
8 |
9 | /**
10 | * User Entity used in the data layer.
11 | */
12 | public class UserEntity {
13 |
14 | @SerializedName("id")
15 | private int userId;
16 |
17 | @SerializedName("cover_url")
18 | private String coverUrl;
19 |
20 | @SerializedName("full_name")
21 | private String fullname;
22 |
23 | @SerializedName("description")
24 | private String description;
25 |
26 | @SerializedName("followers")
27 | private int followers;
28 |
29 | @SerializedName("email")
30 | private String email;
31 |
32 | public UserEntity() {
33 | //empty
34 | }
35 |
36 | public int getUserId() {
37 | return userId;
38 | }
39 |
40 | public void setUserId(int userId) {
41 | this.userId = userId;
42 | }
43 |
44 | public String getCoverUrl() {
45 | return coverUrl;
46 | }
47 |
48 | public void setCoverUrl(String coverUrl) {
49 | this.coverUrl = coverUrl;
50 | }
51 |
52 | public String getFullname() {
53 | return fullname;
54 | }
55 |
56 | public void setFullname(String fullname) {
57 | this.fullname = fullname;
58 | }
59 |
60 | public String getDescription() {
61 | return description;
62 | }
63 |
64 | public void setDescription(String description) {
65 | this.description = description;
66 | }
67 |
68 | public int getFollowers() {
69 | return followers;
70 | }
71 |
72 | public void setFollowers(int followers) {
73 | this.followers = followers;
74 | }
75 |
76 | public String getEmail() {
77 | return email;
78 | }
79 |
80 | public void setEmail(String email) {
81 | this.email = email;
82 | }
83 |
84 | @Override public String toString() {
85 | StringBuilder stringBuilder = new StringBuilder();
86 |
87 | stringBuilder.append("***** User Entity Details *****\n");
88 | stringBuilder.append("id=" + this.getUserId() + "\n");
89 | stringBuilder.append("cover url=" + this.getCoverUrl() + "\n");
90 | stringBuilder.append("fullname=" + this.getFullname() + "\n");
91 | stringBuilder.append("email=" + this.getEmail() + "\n");
92 | stringBuilder.append("description=" + this.getDescription() + "\n");
93 | stringBuilder.append("followers=" + this.getFollowers() + "\n");
94 | stringBuilder.append("*******************************");
95 |
96 | return stringBuilder.toString();
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityDataMapper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.entity.mapper;
6 |
7 | import com.fernandocejas.android10.sample.data.entity.UserEntity;
8 | import com.fernandocejas.android10.sample.domain.User;
9 | import java.util.ArrayList;
10 | import java.util.Collection;
11 | import java.util.List;
12 |
13 | /**
14 | * Mapper class used to transform {@link UserEntity} (in the data layer) to {@link User} in the
15 | * domain layer.
16 | */
17 | public class UserEntityDataMapper {
18 |
19 | public UserEntityDataMapper() {
20 | //empty
21 | }
22 |
23 | /**
24 | * Transform a {@link UserEntity} into an {@link User}.
25 | *
26 | * @param userEntity Object to be transformed.
27 | * @return {@link User} if valid {@link UserEntity} otherwise null.
28 | */
29 | public User transform(UserEntity userEntity) {
30 | User user = null;
31 | if (userEntity != null) {
32 | user = new User(userEntity.getUserId());
33 | user.setCoverUrl(userEntity.getCoverUrl());
34 | user.setFullName(userEntity.getFullname());
35 | user.setDescription(userEntity.getDescription());
36 | user.setFollowers(userEntity.getFollowers());
37 | user.setEmail(userEntity.getEmail());
38 | }
39 |
40 | return user;
41 | }
42 |
43 | /**
44 | * Transform a Collection of {@link UserEntity} into a Collection of {@link User}.
45 | *
46 | * @param userEntityCollection Object Collection to be transformed.
47 | * @return {@link User} if valid {@link UserEntity} otherwise null.
48 | */
49 | public Collection transform(Collection userEntityCollection) {
50 | List userList = new ArrayList(20);
51 | User user;
52 | for (UserEntity userEntity : userEntityCollection) {
53 | user = transform(userEntity);
54 | if (user != null) {
55 | userList.add(user);
56 | }
57 | }
58 |
59 | return userList;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/entity/mapper/UserEntityJsonMapper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.entity.mapper;
6 |
7 | import com.fernandocejas.android10.sample.data.entity.UserEntity;
8 | import com.google.gson.Gson;
9 | import com.google.gson.JsonSyntaxException;
10 | import com.google.gson.reflect.TypeToken;
11 | import java.lang.reflect.Type;
12 | import java.util.Collection;
13 |
14 | /**
15 | * Class used to transform from Strings representing json to valid objects.
16 | */
17 | public class UserEntityJsonMapper {
18 |
19 | private final Gson gson;
20 |
21 | public UserEntityJsonMapper() {
22 | this.gson = new Gson();
23 | }
24 |
25 | /**
26 | * Transform from valid json string to {@link UserEntity}.
27 | *
28 | * @param userJsonResponse A json representing a user profile.
29 | * @return {@link UserEntity}.
30 | * @throws com.google.gson.JsonSyntaxException if the json string is not a valid json structure.
31 | */
32 | public UserEntity transformUserEntity(String userJsonResponse) throws JsonSyntaxException {
33 | try {
34 | Type userEntityType = new TypeToken() {}.getType();
35 | UserEntity userEntity = this.gson.fromJson(userJsonResponse, userEntityType);
36 |
37 | return userEntity;
38 | } catch (JsonSyntaxException jsonException) {
39 | throw jsonException;
40 | }
41 | }
42 |
43 | /**
44 | * Transform from valid json string to Collection of {@link UserEntity}.
45 | *
46 | * @param userListJsonResponse A json representing a collection of users.
47 | * @return Collection of {@link UserEntity}.
48 | * @throws com.google.gson.JsonSyntaxException if the json string is not a valid json structure.
49 | */
50 | public Collection transformUserEntityCollection(String userListJsonResponse)
51 | throws JsonSyntaxException {
52 |
53 | Collection userEntityCollection;
54 | try {
55 | Type listOfUserEntityType = new TypeToken>() {}.getType();
56 | userEntityCollection = this.gson.fromJson(userListJsonResponse, listOfUserEntityType);
57 |
58 | return userEntityCollection;
59 | } catch (JsonSyntaxException jsonException) {
60 | throw jsonException;
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/exception/NetworkConnectionException.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.exception;
6 |
7 | /**
8 | * Exception throw by the application when a there is a network connection exception.
9 | */
10 | public class NetworkConnectionException extends Exception {
11 |
12 | public NetworkConnectionException() {
13 | super();
14 | }
15 |
16 | public NetworkConnectionException(final String message) {
17 | super(message);
18 | }
19 |
20 | public NetworkConnectionException(final String message, final Throwable cause) {
21 | super(message, cause);
22 | }
23 |
24 | public NetworkConnectionException(final Throwable cause) {
25 | super(cause);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/exception/RepositoryErrorBundle.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.exception;
6 |
7 | import com.fernandocejas.android10.sample.domain.exception.ErrorBundle;
8 |
9 | /**
10 | * Wrapper around Exceptions used to manage errors in the repository.
11 | */
12 | public class RepositoryErrorBundle implements ErrorBundle {
13 |
14 | private final Exception exception;
15 |
16 | public RepositoryErrorBundle(Exception exception) {
17 | this.exception = exception;
18 | }
19 |
20 | public Exception getException() {
21 | return exception;
22 | }
23 |
24 | public String getErrorMessage() {
25 | String message = "";
26 | if (this.exception != null) {
27 | this.exception.getMessage();
28 | }
29 | return message;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/exception/UserNotFoundException.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.exception;
6 |
7 | /**
8 | * Exception throw by the application when a User search can't return a valid result.
9 | */
10 | public class UserNotFoundException extends Exception {
11 |
12 | public UserNotFoundException() {
13 | super();
14 | }
15 |
16 | public UserNotFoundException(final String message) {
17 | super(message);
18 | }
19 |
20 | public UserNotFoundException(final String message, final Throwable cause) {
21 | super(message, cause);
22 | }
23 |
24 | public UserNotFoundException(final Throwable cause) {
25 | super(cause);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/executor/JobExecutor.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.executor;
6 |
7 | import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor;
8 | import java.util.concurrent.BlockingQueue;
9 | import java.util.concurrent.LinkedBlockingQueue;
10 | import java.util.concurrent.ThreadPoolExecutor;
11 | import java.util.concurrent.TimeUnit;
12 |
13 | /**
14 | * Decorated {@link java.util.concurrent.ThreadPoolExecutor} Singleton class based on
15 | * 'Initialization on Demand Holder' pattern.
16 | */
17 | public class JobExecutor implements ThreadExecutor {
18 |
19 | private static class LazyHolder {
20 | private static final JobExecutor INSTANCE = new JobExecutor();
21 | }
22 |
23 | public static JobExecutor getInstance() {
24 | return LazyHolder.INSTANCE;
25 | }
26 |
27 | private static final int INITIAL_POOL_SIZE = 3;
28 | private static final int MAX_POOL_SIZE = 5;
29 |
30 | // Sets the amount of time an idle thread waits before terminating
31 | private static final int KEEP_ALIVE_TIME = 10;
32 |
33 | // Sets the Time Unit to seconds
34 | private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
35 |
36 | private final BlockingQueue workQueue;
37 |
38 | private final ThreadPoolExecutor threadPoolExecutor;
39 |
40 | private JobExecutor() {
41 | this.workQueue = new LinkedBlockingQueue();
42 | this.threadPoolExecutor = new ThreadPoolExecutor(INITIAL_POOL_SIZE, MAX_POOL_SIZE,
43 | KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, this.workQueue);
44 | }
45 |
46 | /**
47 | * {@inheritDoc}
48 | *
49 | * @param runnable The class that implements {@link Runnable} interface.
50 | */
51 | @Override public void execute(Runnable runnable) {
52 | if (runnable == null) {
53 | throw new IllegalArgumentException("Runnable to execute cannot be null");
54 | }
55 | this.threadPoolExecutor.execute(runnable);
56 | }
57 | }
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/net/ApiConnection.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.net;
6 |
7 | import java.io.BufferedReader;
8 | import java.io.IOException;
9 | import java.io.InputStream;
10 | import java.io.InputStreamReader;
11 | import java.net.HttpURLConnection;
12 | import java.net.MalformedURLException;
13 | import java.net.URL;
14 | import java.util.concurrent.Callable;
15 |
16 | /**
17 | * Api Connection class used to retrieve data from the cloud.
18 | * Implements {@link java.util.concurrent.Callable} so when executed asynchronously can
19 | * return a value.
20 | */
21 | public class ApiConnection implements Callable {
22 |
23 | private static final String CONTENT_TYPE_LABEL = "Content-Type";
24 | private static final String CONTENT_TYPE_VALUE_JSON = "application/json; charset=utf-8";
25 |
26 | public static final String REQUEST_METHOD_GET = "GET";
27 |
28 | private URL url;
29 | private String requestVerb;
30 | private int responseCode = 0;
31 | private String response = "";
32 |
33 | private ApiConnection(String url, String requestVerb) throws MalformedURLException {
34 | this.url = new URL(url);
35 | this.requestVerb = requestVerb;
36 | }
37 |
38 | public static ApiConnection createGET(String url) throws MalformedURLException {
39 | return new ApiConnection(url, REQUEST_METHOD_GET);
40 | }
41 |
42 | /**
43 | * Do a request to an api asynchronously.
44 | * It should not be executed in the main thread of the application.
45 | *
46 | * @return A string response
47 | */
48 | public String requestSyncCall() {
49 | connectToApi();
50 | return response;
51 | }
52 |
53 | private void connectToApi() {
54 | HttpURLConnection urlConnection = null;
55 |
56 | try {
57 | urlConnection = (HttpURLConnection) url.openConnection();
58 | setupConnection(urlConnection);
59 |
60 | responseCode = urlConnection.getResponseCode();
61 | if (responseCode == HttpURLConnection.HTTP_OK) {
62 | response = getStringFromInputStream(urlConnection.getInputStream());
63 | } else { response = getStringFromInputStream(urlConnection.getErrorStream()); }
64 | } catch (Exception e) {
65 | e.printStackTrace();
66 | } finally {
67 | if (urlConnection != null) { urlConnection.disconnect(); }
68 | }
69 | }
70 |
71 | private String getStringFromInputStream(InputStream inputStream) {
72 |
73 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
74 | StringBuilder stringBuilderResult = new StringBuilder();
75 |
76 | String line;
77 | try {
78 | while ((line = bufferedReader.readLine()) != null) {
79 | stringBuilderResult.append(line);
80 | }
81 | return stringBuilderResult.toString();
82 | } catch (IOException e) {
83 | e.printStackTrace();
84 | return "";
85 | }
86 | }
87 |
88 | private void setupConnection(HttpURLConnection connection) throws IOException {
89 | if (connection != null) {
90 | connection.setRequestMethod(requestVerb);
91 | connection.setReadTimeout(10000);
92 | connection.setConnectTimeout(15000);
93 | connection.setDoInput(true);
94 | connection.setRequestProperty(CONTENT_TYPE_LABEL, CONTENT_TYPE_VALUE_JSON);
95 | }
96 | }
97 |
98 | @Override public String call() throws Exception {
99 | return requestSyncCall();
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/net/RestApi.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.net;
6 |
7 | import com.fernandocejas.android10.sample.data.entity.UserEntity;
8 | import com.fernandocejas.android10.sample.domain.User;
9 | import java.util.Collection;
10 |
11 | /**
12 | * RestApi for retrieving data from the network.
13 | */
14 | public interface RestApi {
15 | /**
16 | * Callback used to be notified when either a user list has been loaded or an error happened.
17 | */
18 | interface UserListCallback {
19 | void onUserListLoaded(Collection usersCollection);
20 |
21 | void onError(Exception exception);
22 | }
23 |
24 | /**
25 | * Callback to be notified when getting a user from the network.
26 | */
27 | interface UserDetailsCallback {
28 | void onUserEntityLoaded(UserEntity userEntity);
29 |
30 | void onError(Exception exception);
31 | }
32 |
33 | static final String API_BASE_URL = "http://www.android10.org/myapi/";
34 |
35 | /** Api url for getting all users */
36 | static final String API_URL_GET_USER_LIST = API_BASE_URL + "users.json";
37 | /** Api url for getting a user profile: Remember to concatenate id + 'json' */
38 | static final String API_URL_GET_USER_DETAILS = API_BASE_URL + "user_";
39 |
40 | /**
41 | * Get a collection of {@link User}.
42 | *
43 | * @param userListCallback A {@link UserListCallback} used for notifying clients.
44 | */
45 | void getUserList(UserListCallback userListCallback);
46 |
47 | /**
48 | * Retrieves a user by id from the network.
49 | *
50 | * @param userId The user id used to get user data.
51 | * @param userDetailsCallback {@link UserDetailsCallback} to be notified when user data has been
52 | * retrieved.
53 | */
54 | void getUserById(final int userId, final UserDetailsCallback userDetailsCallback);
55 | }
56 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/net/RestApiImpl.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.net;
6 |
7 | import android.content.Context;
8 | import android.net.ConnectivityManager;
9 | import android.net.NetworkInfo;
10 | import com.fernandocejas.android10.sample.data.entity.UserEntity;
11 | import com.fernandocejas.android10.sample.data.entity.mapper.UserEntityJsonMapper;
12 | import com.fernandocejas.android10.sample.data.exception.NetworkConnectionException;
13 | import java.util.Collection;
14 |
15 | /**
16 | * {@link RestApi} implementation for retrieving data from the network.
17 | */
18 | public class RestApiImpl implements RestApi {
19 |
20 | private final Context context;
21 | private final UserEntityJsonMapper userEntityJsonMapper;
22 |
23 | /**
24 | * Constructor of the class
25 | *
26 | * @param context {@link android.content.Context}.
27 | * @param userEntityJsonMapper {@link UserEntityJsonMapper}.
28 | */
29 | public RestApiImpl(Context context, UserEntityJsonMapper userEntityJsonMapper) {
30 | if (context == null || userEntityJsonMapper == null) {
31 | throw new IllegalArgumentException("The constructor parameters cannot be null!!!");
32 | }
33 | this.context = context.getApplicationContext();
34 | this.userEntityJsonMapper = userEntityJsonMapper;
35 | }
36 |
37 | @Override public void getUserList(UserListCallback userListCallback) {
38 | if (userListCallback == null) {
39 | throw new IllegalArgumentException("Callback cannot be null!!!");
40 | }
41 |
42 | if (isThereInternetConnection()) {
43 | try {
44 | ApiConnection getUserListConnection =
45 | ApiConnection.createGET(RestApi.API_URL_GET_USER_LIST);
46 | String responseUserList = getUserListConnection.requestSyncCall();
47 | Collection userEntityList =
48 | this.userEntityJsonMapper.transformUserEntityCollection(responseUserList);
49 |
50 | userListCallback.onUserListLoaded(userEntityList);
51 | } catch (Exception e) {
52 | userListCallback.onError(new NetworkConnectionException(e.getCause()));
53 | }
54 | } else {
55 | userListCallback.onError(new NetworkConnectionException());
56 | }
57 | }
58 |
59 | /**
60 | * {@inheritDoc}
61 | */
62 | @Override public void getUserById(final int userId,
63 | final UserDetailsCallback userDetailsCallback) {
64 | if (userDetailsCallback == null) {
65 | throw new IllegalArgumentException("Callback cannot be null!!!");
66 | }
67 |
68 | if (isThereInternetConnection()) {
69 | try {
70 | String apiUrl = RestApi.API_URL_GET_USER_DETAILS + userId + ".json";
71 | ApiConnection getUserDetailsConnection = ApiConnection.createGET(apiUrl);
72 | String responseUserDetails = getUserDetailsConnection.requestSyncCall();
73 | UserEntity userEntity = this.userEntityJsonMapper.transformUserEntity(responseUserDetails);
74 |
75 | userDetailsCallback.onUserEntityLoaded(userEntity);
76 | } catch (Exception e) {
77 | userDetailsCallback.onError(new NetworkConnectionException(e.getCause()));
78 | }
79 | } else {
80 | userDetailsCallback.onError(new NetworkConnectionException());
81 | }
82 | }
83 |
84 | /**
85 | * Checks if the device has any active internet connection.
86 | *
87 | * @return true device with internet connection, otherwise false.
88 | */
89 | private boolean isThereInternetConnection() {
90 | boolean isConnected;
91 |
92 | ConnectivityManager connectivityManager =
93 | (ConnectivityManager) this.context.getSystemService(Context.CONNECTIVITY_SERVICE);
94 | NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
95 | isConnected = (networkInfo != null && networkInfo.isConnectedOrConnecting());
96 |
97 | return isConnected;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/repository/UserDataRepository.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.repository;
6 |
7 | import com.fernandocejas.android10.sample.data.entity.UserEntity;
8 | import com.fernandocejas.android10.sample.data.entity.mapper.UserEntityDataMapper;
9 | import com.fernandocejas.android10.sample.data.exception.RepositoryErrorBundle;
10 | import com.fernandocejas.android10.sample.data.exception.UserNotFoundException;
11 | import com.fernandocejas.android10.sample.data.repository.datasource.UserDataStore;
12 | import com.fernandocejas.android10.sample.data.repository.datasource.UserDataStoreFactory;
13 | import com.fernandocejas.android10.sample.domain.User;
14 | import com.fernandocejas.android10.sample.domain.repository.UserRepository;
15 | import java.util.Collection;
16 |
17 | /**
18 | * {@link UserRepository} for retrieving user data.
19 | */
20 | public class UserDataRepository implements UserRepository {
21 |
22 | private static UserDataRepository INSTANCE;
23 |
24 | public static synchronized UserDataRepository getInstance(UserDataStoreFactory dataStoreFactory,
25 | UserEntityDataMapper userEntityDataMapper) {
26 | if (INSTANCE == null) {
27 | INSTANCE = new UserDataRepository(dataStoreFactory, userEntityDataMapper);
28 | }
29 | return INSTANCE;
30 | }
31 |
32 | private final UserDataStoreFactory userDataStoreFactory;
33 | private final UserEntityDataMapper userEntityDataMapper;
34 |
35 | /**
36 | * Constructs a {@link UserRepository}.
37 | *
38 | * @param dataStoreFactory A factory to construct different data source implementations.
39 | * @param userEntityDataMapper {@link UserEntityDataMapper}.
40 | */
41 | protected UserDataRepository(UserDataStoreFactory dataStoreFactory,
42 | UserEntityDataMapper userEntityDataMapper) {
43 | if (dataStoreFactory == null || userEntityDataMapper == null) {
44 | throw new IllegalArgumentException("Invalid null parameters in constructor!!!");
45 | }
46 | this.userDataStoreFactory = dataStoreFactory;
47 | this.userEntityDataMapper = userEntityDataMapper;
48 | }
49 |
50 | /**
51 | * {@inheritDoc}
52 | *
53 | * @param userListCallback A {@link UserListCallback} used for notifying clients.
54 | */
55 | @Override public void getUserList(final UserListCallback userListCallback) {
56 | //we always get all users from the cloud
57 | final UserDataStore userDataStore = this.userDataStoreFactory.createCloudDataStore();
58 | userDataStore.getUsersEntityList(new UserDataStore.UserListCallback() {
59 | @Override public void onUserListLoaded(Collection usersCollection) {
60 | Collection users =
61 | UserDataRepository.this.userEntityDataMapper.transform(usersCollection);
62 | userListCallback.onUserListLoaded(users);
63 | }
64 |
65 | @Override public void onError(Exception exception) {
66 | userListCallback.onError(new RepositoryErrorBundle(exception));
67 | }
68 | });
69 | }
70 |
71 | /**
72 | * {@inheritDoc}
73 | *
74 | * @param userId The user id used to retrieve user data.
75 | * @param userCallback A {@link com.fernandocejas.android10.sample.domain.repository.UserRepository.UserDetailsCallback}
76 | * used for notifying clients.
77 | */
78 | @Override public void getUserById(final int userId, final UserDetailsCallback userCallback) {
79 | UserDataStore userDataStore = this.userDataStoreFactory.create(userId);
80 | userDataStore.getUserEntityDetails(userId, new UserDataStore.UserDetailsCallback() {
81 | @Override public void onUserEntityLoaded(UserEntity userEntity) {
82 | User user = UserDataRepository.this.userEntityDataMapper.transform(userEntity);
83 | if (user != null) {
84 | userCallback.onUserLoaded(user);
85 | } else {
86 | userCallback.onError(new RepositoryErrorBundle(new UserNotFoundException()));
87 | }
88 | }
89 |
90 | @Override public void onError(Exception exception) {
91 | userCallback.onError(new RepositoryErrorBundle(exception));
92 | }
93 | });
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/CloudUserDataStore.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.repository.datasource;
6 |
7 | import com.fernandocejas.android10.sample.data.cache.UserCache;
8 | import com.fernandocejas.android10.sample.data.entity.UserEntity;
9 | import com.fernandocejas.android10.sample.data.net.RestApi;
10 | import java.util.Collection;
11 |
12 | /**
13 | * {@link UserDataStore} implementation based on connections to the api (Cloud).
14 | */
15 | public class CloudUserDataStore implements UserDataStore {
16 |
17 | private final RestApi restApi;
18 | private final UserCache userCache;
19 |
20 | /**
21 | * Construct a {@link UserDataStore} based on connections to the api (Cloud).
22 | *
23 | * @param restApi The {@link RestApi} implementation to use.
24 | * @param userCache A {@link UserCache} to cache data retrieved from the api.
25 | */
26 | public CloudUserDataStore(RestApi restApi, UserCache userCache) {
27 | this.restApi = restApi;
28 | this.userCache = userCache;
29 | }
30 |
31 | /**
32 | * {@inheritDoc}
33 | *
34 | * @param userListCallback A {@link UserListCallback} used for notifying clients.
35 | */
36 | @Override public void getUsersEntityList(final UserListCallback userListCallback) {
37 | this.restApi.getUserList(new RestApi.UserListCallback() {
38 | @Override public void onUserListLoaded(Collection usersCollection) {
39 | userListCallback.onUserListLoaded(usersCollection);
40 | }
41 |
42 | @Override public void onError(Exception exception) {
43 | userListCallback.onError(exception);
44 | }
45 | });
46 | }
47 |
48 | /**
49 | * {@inheritDoc}
50 | *
51 | * @param id The user id used to retrieve user data.
52 | * @param userDetailsCallback A {@link UserDetailsCallback} used for notifying clients.
53 | */
54 | @Override public void getUserEntityDetails(int id,
55 | final UserDetailsCallback userDetailsCallback) {
56 | this.restApi.getUserById(id, new RestApi.UserDetailsCallback() {
57 | @Override public void onUserEntityLoaded(UserEntity userEntity) {
58 | userDetailsCallback.onUserEntityLoaded(userEntity);
59 | CloudUserDataStore.this.putUserEntityInCache(userEntity);
60 | }
61 |
62 | @Override public void onError(Exception exception) {
63 | userDetailsCallback.onError(exception);
64 | }
65 | });
66 | }
67 |
68 | /**
69 | * Saves a {@link UserEntity} into cache.
70 | *
71 | * @param userEntity The {@link UserEntity} to save.
72 | */
73 | private void putUserEntityInCache(UserEntity userEntity) {
74 | if (userEntity != null) {
75 | this.userCache.put(userEntity);
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/DiskUserDataStore.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.repository.datasource;
6 |
7 | import com.fernandocejas.android10.sample.data.cache.UserCache;
8 | import com.fernandocejas.android10.sample.data.entity.UserEntity;
9 |
10 | /**
11 | * {@link UserDataStore} implementation based on file system data store.
12 | */
13 | public class DiskUserDataStore implements UserDataStore {
14 |
15 | private final UserCache userCache;
16 |
17 | /**
18 | * Construct a {@link UserDataStore} based file system data store.
19 | *
20 | * @param userCache A {@link UserCache} to cache data retrieved from the api.
21 | */
22 | public DiskUserDataStore(UserCache userCache) {
23 | this.userCache = userCache;
24 | }
25 |
26 | /**
27 | * {@inheritDoc}
28 | *
29 | * @param userListCallback A {@link UserListCallback} used for notifying clients.
30 | */
31 | @Override public void getUsersEntityList(UserListCallback userListCallback) {
32 | //TODO: implement simple cache for storing/retrieving collections of users.
33 | throw new UnsupportedOperationException("Operation is not available!!!");
34 | }
35 |
36 | /**
37 | * {@inheritDoc}
38 | *
39 | * @param id The id to retrieve user data.
40 | * @param userDetailsCallback A {@link UserDataStore.UserDetailsCallback} to notify the client.
41 | */
42 | @Override public void getUserEntityDetails(int id,
43 | final UserDetailsCallback userDetailsCallback) {
44 | this.userCache.get(id, new UserCache.UserCacheCallback() {
45 | @Override public void onUserEntityLoaded(UserEntity userEntity) {
46 | userDetailsCallback.onUserEntityLoaded(userEntity);
47 | }
48 |
49 | @Override public void onError(Exception exception) {
50 | userDetailsCallback.onError(exception);
51 | }
52 | });
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStore.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.repository.datasource;
6 |
7 | import com.fernandocejas.android10.sample.data.entity.UserEntity;
8 | import java.util.Collection;
9 |
10 | /**
11 | * Interface that represents a data store from where data is retrieved.
12 | */
13 | public interface UserDataStore {
14 | /**
15 | * Callback used for clients to be notified when either a user list has been loaded or any error
16 | * occurred.
17 | */
18 | interface UserListCallback {
19 | void onUserListLoaded(Collection usersCollection);
20 |
21 | void onError(Exception exception);
22 | }
23 |
24 | /**
25 | * Callback used for clients to be notified when either user data has been retrieved successfully
26 | * or any error occurred.
27 | */
28 | interface UserDetailsCallback {
29 | void onUserEntityLoaded(UserEntity userEntity);
30 |
31 | void onError(Exception exception);
32 | }
33 |
34 | /**
35 | * Get a collection of {@link com.fernandocejas.android10.sample.domain.User}.
36 | *
37 | * @param userListCallback A {@link UserListCallback} used for notifying clients.
38 | */
39 | void getUsersEntityList(UserListCallback userListCallback);
40 |
41 | /**
42 | * Get a {@link UserEntity} by its id.
43 | *
44 | * @param id The id to retrieve user data.
45 | * @param userDetailsCallback A {@link UserDetailsCallback} for notifications.
46 | */
47 | void getUserEntityDetails(int id, UserDetailsCallback userDetailsCallback);
48 | }
49 |
--------------------------------------------------------------------------------
/Data/src/main/java/com/fernandocejas/android10/sample/data/repository/datasource/UserDataStoreFactory.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.data.repository.datasource;
6 |
7 | import android.content.Context;
8 | import com.fernandocejas.android10.sample.data.cache.UserCache;
9 | import com.fernandocejas.android10.sample.data.entity.mapper.UserEntityJsonMapper;
10 | import com.fernandocejas.android10.sample.data.net.RestApi;
11 | import com.fernandocejas.android10.sample.data.net.RestApiImpl;
12 |
13 | /**
14 | * Factory that creates different implementations of {@link UserDataStore}.
15 | */
16 | public class UserDataStoreFactory {
17 |
18 | private final Context context;
19 | private final UserCache userCache;
20 |
21 | public UserDataStoreFactory(Context context, UserCache userCache) {
22 | if (context == null || userCache == null) {
23 | throw new IllegalArgumentException("Constructor parameters cannot be null!!!");
24 | }
25 | this.context = context.getApplicationContext();
26 | this.userCache = userCache;
27 | }
28 |
29 | /**
30 | * Create {@link UserDataStore} from a user id.
31 | */
32 | public UserDataStore create(int userId) {
33 | UserDataStore userDataStore;
34 |
35 | if (!this.userCache.isExpired() && this.userCache.isCached(userId)) {
36 | userDataStore = new DiskUserDataStore(this.userCache);
37 | } else {
38 | userDataStore = createCloudDataStore();
39 | }
40 |
41 | return userDataStore;
42 | }
43 |
44 | /**
45 | * Create {@link UserDataStore} to retrieve data from the Cloud.
46 | */
47 | public UserDataStore createCloudDataStore() {
48 | UserEntityJsonMapper userEntityJsonMapper = new UserEntityJsonMapper();
49 | RestApi restApi = new RestApiImpl(this.context, userEntityJsonMapper);
50 |
51 | return new CloudUserDataStore(restApi, this.userCache);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Data/src/main/libs/dexmaker-1.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Data/src/main/libs/dexmaker-1.0.jar
--------------------------------------------------------------------------------
/Data/src/main/libs/dexmaker-mockito-1.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Data/src/main/libs/dexmaker-mockito-1.0.jar
--------------------------------------------------------------------------------
/Data/src/main/libs/espresso-1.1-bundled.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Data/src/main/libs/espresso-1.1-bundled.jar
--------------------------------------------------------------------------------
/Data/src/main/libs/gson-2.2.4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Data/src/main/libs/gson-2.2.4.jar
--------------------------------------------------------------------------------
/Data/src/main/libs/mockito-all-1.9.5.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Data/src/main/libs/mockito-all-1.9.5.jar
--------------------------------------------------------------------------------
/Data/src/main/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-10
15 | android.library=true
16 | android.library.reference.1=../../../Domain
17 |
--------------------------------------------------------------------------------
/Domain/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Domain/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | Domain
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Domain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
9 |
10 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Domain/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Domain/ic_launcher-web.png
--------------------------------------------------------------------------------
/Domain/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Domain/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/Domain/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/Domain/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-20
15 | android.library=true
16 |
--------------------------------------------------------------------------------
/Domain/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Domain/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Domain/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Domain/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Domain/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Domain/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Domain/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Domain/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Domain/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Domain/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Domain/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Domain
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Domain/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Domain/src/com/fernandocejas/android10/sample/domain/User.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.domain;
6 |
7 | /**
8 | * Class that represents a User in the domain layer.
9 | */
10 | public class User {
11 |
12 | private final int userId;
13 |
14 | public User(int userId) {
15 | this.userId = userId;
16 | }
17 |
18 | private String coverUrl;
19 | private String fullName;
20 | private String email;
21 | private String description;
22 | private int followers;
23 |
24 | public int getUserId() {
25 | return userId;
26 | }
27 |
28 | public String getCoverUrl() {
29 | return coverUrl;
30 | }
31 |
32 | public void setCoverUrl(String coverUrl) {
33 | this.coverUrl = coverUrl;
34 | }
35 |
36 | public String getFullName() {
37 | return fullName;
38 | }
39 |
40 | public void setFullName(String fullName) {
41 | this.fullName = fullName;
42 | }
43 |
44 | public String getEmail() {
45 | return email;
46 | }
47 |
48 | public void setEmail(String email) {
49 | this.email = email;
50 | }
51 |
52 | public String getDescription() {
53 | return description;
54 | }
55 |
56 | public void setDescription(String description) {
57 | this.description = description;
58 | }
59 |
60 | public int getFollowers() {
61 | return followers;
62 | }
63 |
64 | public void setFollowers(int followers) {
65 | this.followers = followers;
66 | }
67 |
68 | @Override public String toString() {
69 | StringBuilder stringBuilder = new StringBuilder();
70 |
71 | stringBuilder.append("***** User Details *****\n");
72 | stringBuilder.append("id=" + this.getUserId() + "\n");
73 | stringBuilder.append("cover url=" + this.getCoverUrl() + "\n");
74 | stringBuilder.append("fullname=" + this.getFullName() + "\n");
75 | stringBuilder.append("email=" + this.getEmail() + "\n");
76 | stringBuilder.append("description=" + this.getDescription() + "\n");
77 | stringBuilder.append("followers=" + this.getFollowers() + "\n");
78 | stringBuilder.append("*******************************");
79 |
80 | return stringBuilder.toString();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Domain/src/com/fernandocejas/android10/sample/domain/exception/ErrorBundle.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.domain.exception;
6 |
7 | /**
8 | * Interface to represent a wrapper around an {@link java.lang.Exception} to manage errors.
9 | */
10 | public interface ErrorBundle {
11 | Exception getException();
12 |
13 | String getErrorMessage();
14 | }
15 |
--------------------------------------------------------------------------------
/Domain/src/com/fernandocejas/android10/sample/domain/executor/PostExecutionThread.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.domain.executor;
6 |
7 | /**
8 | * Thread abstraction created to change the execution context from any thread to any other thread.
9 | * Useful to encapsulate a UI Thread for example, since some job will be done in background, an
10 | * implementation of this interface will change context and update the UI.
11 | */
12 | public interface PostExecutionThread {
13 | /**
14 | * Causes the {@link Runnable} to be added to the message queue of the Main UI Thread
15 | * of the application.
16 | *
17 | * @param runnable {@link Runnable} to be executed.
18 | */
19 | void post(Runnable runnable);
20 | }
21 |
--------------------------------------------------------------------------------
/Domain/src/com/fernandocejas/android10/sample/domain/executor/ThreadExecutor.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.domain.executor;
6 |
7 | import com.fernandocejas.android10.sample.domain.interactor.Interactor;
8 |
9 | /**
10 | * Executor implementation can be based on different frameworks or techniques of asynchronous
11 | * execution, but every implementation will execute the {@link Interactor} out of the UI thread.
12 | *
13 | * Use this class to execute an {@link Interactor}.
14 | */
15 | public interface ThreadExecutor {
16 | /**
17 | * Executes a {@link Runnable}.
18 | *
19 | * @param runnable The class that implements {@link Runnable} interface.
20 | */
21 | void execute(final Runnable runnable);
22 | }
23 |
--------------------------------------------------------------------------------
/Domain/src/com/fernandocejas/android10/sample/domain/interactor/GetUserDetailsUseCase.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.domain.interactor;
6 |
7 | import com.fernandocejas.android10.sample.domain.User;
8 | import com.fernandocejas.android10.sample.domain.exception.ErrorBundle;
9 |
10 | /**
11 | * This interface represents a execution unit for a use case to get data for an specific user.
12 | * By convention this use case ({@link Interactor}) implementation will return the result using a
13 | * callback that should be executed in the UI thread.
14 | */
15 | public interface GetUserDetailsUseCase extends Interactor {
16 | /**
17 | * Callback used to be notified when either a user has been loaded or an error happened.
18 | */
19 | interface Callback {
20 | void onUserDataLoaded(User user);
21 | void onError(ErrorBundle errorBundle);
22 | }
23 |
24 | /**
25 | * Executes this user case.
26 | *
27 | * @param userId The user id to retrieve.
28 | * @param callback A {@link GetUserDetailsUseCase.Callback} used for notify the client.
29 | */
30 | public void execute(int userId, Callback callback);
31 | }
32 |
--------------------------------------------------------------------------------
/Domain/src/com/fernandocejas/android10/sample/domain/interactor/GetUserDetailsUseCaseImpl.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.domain.interactor;
6 |
7 | import com.fernandocejas.android10.sample.domain.User;
8 | import com.fernandocejas.android10.sample.domain.exception.ErrorBundle;
9 | import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread;
10 | import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor;
11 | import com.fernandocejas.android10.sample.domain.repository.UserRepository;
12 |
13 | /**
14 | * This class is an implementation of {@link GetUserDetailsUseCase} that represents a use case for
15 | * retrieving data related to an specific {@link User}.
16 | */
17 | public class GetUserDetailsUseCaseImpl implements GetUserDetailsUseCase {
18 |
19 | private final UserRepository userRepository;
20 | private final ThreadExecutor threadExecutor;
21 | private final PostExecutionThread postExecutionThread;
22 |
23 | private int userId = -1;
24 | private GetUserDetailsUseCase.Callback callback;
25 |
26 | /**
27 | * Constructor of the class.
28 | *
29 | * @param userRepository A {@link UserRepository} as a source for retrieving data.
30 | * @param threadExecutor {@link ThreadExecutor} used to execute this use case in a background
31 | * thread.
32 | * @param postExecutionThread {@link PostExecutionThread} used to post updates when the use case
33 | * has been executed.
34 | */
35 | public GetUserDetailsUseCaseImpl(UserRepository userRepository, ThreadExecutor threadExecutor,
36 | PostExecutionThread postExecutionThread) {
37 | if (userRepository == null || threadExecutor == null || postExecutionThread == null) {
38 | throw new IllegalArgumentException("Constructor parameters cannot be null!!!");
39 | }
40 | this.userRepository = userRepository;
41 | this.threadExecutor = threadExecutor;
42 | this.postExecutionThread = postExecutionThread;
43 | }
44 |
45 | @Override public void execute(int userId, Callback callback) {
46 | if (userId < 0 || callback == null) {
47 | throw new IllegalArgumentException("Invalid parameter!!!");
48 | }
49 | this.userId = userId;
50 | this.callback = callback;
51 | this.threadExecutor.execute(this);
52 | }
53 |
54 | @Override public void run() {
55 | this.userRepository.getUserById(this.userId, this.repositoryCallback);
56 | }
57 |
58 | private final UserRepository.UserDetailsCallback repositoryCallback =
59 | new UserRepository.UserDetailsCallback() {
60 | @Override public void onUserLoaded(User user) {
61 | notifyGetUserDetailsSuccessfully(user);
62 | }
63 |
64 | @Override public void onError(ErrorBundle errorBundle) {
65 | notifyError(errorBundle);
66 | }
67 | };
68 |
69 | private void notifyGetUserDetailsSuccessfully(final User user) {
70 | this.postExecutionThread.post(new Runnable() {
71 | @Override public void run() {
72 | callback.onUserDataLoaded(user);
73 | }
74 | });
75 | }
76 |
77 | private void notifyError(final ErrorBundle errorBundle) {
78 | this.postExecutionThread.post(new Runnable() {
79 | @Override public void run() {
80 | callback.onError(errorBundle);
81 | }
82 | });
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Domain/src/com/fernandocejas/android10/sample/domain/interactor/GetUserListUseCase.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.domain.interactor;
6 |
7 | import com.fernandocejas.android10.sample.domain.User;
8 | import com.fernandocejas.android10.sample.domain.exception.ErrorBundle;
9 | import java.util.Collection;
10 |
11 | /**
12 | * This interface represents a execution unit for a use case to get a collection of {@link User}.
13 | * By convention this use case (Interactor) implementation will return the result using a Callback.
14 | * That callback should be executed in the UI thread.
15 | */
16 | public interface GetUserListUseCase extends Interactor {
17 | /**
18 | * Callback used to be notified when either a users collection has been loaded or an error
19 | * happened.
20 | */
21 | interface Callback {
22 | void onUserListLoaded(Collection usersCollection);
23 | void onError(ErrorBundle errorBundle);
24 | }
25 |
26 | /**
27 | * Executes this user case.
28 | *
29 | * @param callback A {@link GetUserListUseCase.Callback} used to notify the client.
30 | */
31 | void execute(Callback callback);
32 | }
33 |
--------------------------------------------------------------------------------
/Domain/src/com/fernandocejas/android10/sample/domain/interactor/GetUserListUseCaseImpl.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.domain.interactor;
6 |
7 | import com.fernandocejas.android10.sample.domain.User;
8 | import com.fernandocejas.android10.sample.domain.exception.ErrorBundle;
9 | import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread;
10 | import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor;
11 | import com.fernandocejas.android10.sample.domain.repository.UserRepository;
12 | import java.util.Collection;
13 |
14 | /**
15 | * This class is an implementation of {@link GetUserListUseCase} that represents a use case for
16 | * retrieving a collection of all {@link User}.
17 | */
18 | public class GetUserListUseCaseImpl implements GetUserListUseCase {
19 |
20 | private final UserRepository userRepository;
21 | private final ThreadExecutor threadExecutor;
22 | private final PostExecutionThread postExecutionThread;
23 |
24 | private Callback callback;
25 |
26 | /**
27 | * Constructor of the class.
28 | *
29 | * @param userRepository A {@link UserRepository} as a source for retrieving data.
30 | * @param threadExecutor {@link ThreadExecutor} used to execute this use case in a background
31 | * thread.
32 | * @param postExecutionThread {@link PostExecutionThread} used to post updates when the use case
33 | * has been executed.
34 | */
35 | public GetUserListUseCaseImpl(UserRepository userRepository, ThreadExecutor threadExecutor,
36 | PostExecutionThread postExecutionThread) {
37 | if (userRepository == null || threadExecutor == null || postExecutionThread == null) {
38 | throw new IllegalArgumentException("Constructor parameters cannot be null!!!");
39 | }
40 | this.userRepository = userRepository;
41 | this.threadExecutor = threadExecutor;
42 | this.postExecutionThread = postExecutionThread;
43 | }
44 |
45 | @Override public void execute(Callback callback) {
46 | if (callback == null) {
47 | throw new IllegalArgumentException("Interactor callback cannot be null!!!");
48 | }
49 | this.callback = callback;
50 | this.threadExecutor.execute(this);
51 | }
52 |
53 | @Override public void run() {
54 | this.userRepository.getUserList(this.repositoryCallback);
55 | }
56 |
57 | private final UserRepository.UserListCallback repositoryCallback =
58 | new UserRepository.UserListCallback() {
59 | @Override public void onUserListLoaded(Collection usersCollection) {
60 | notifyGetUserListSuccessfully(usersCollection);
61 | }
62 |
63 | @Override public void onError(ErrorBundle errorBundle) {
64 | notifyError(errorBundle);
65 | }
66 | };
67 |
68 | private void notifyGetUserListSuccessfully(final Collection usersCollection) {
69 | this.postExecutionThread.post(new Runnable() {
70 | @Override public void run() {
71 | callback.onUserListLoaded(usersCollection);
72 | }
73 | });
74 | }
75 |
76 | private void notifyError(final ErrorBundle errorBundle) {
77 | this.postExecutionThread.post(new Runnable() {
78 | @Override public void run() {
79 | callback.onError(errorBundle);
80 | }
81 | });
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Domain/src/com/fernandocejas/android10/sample/domain/interactor/Interactor.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.domain.interactor;
6 |
7 | /**
8 | * Common interface for an Interactor {@link java.lang.Runnable} declared in the application.
9 | * This interface represents a execution unit for different use cases (this means any use case
10 | * in the application should implement this contract).
11 | *
12 | * By convention each Interactor implementation will return the result using a Callback that should
13 | * be executed in the UI thread.
14 | */
15 | public interface Interactor extends Runnable {
16 | /**
17 | * Everything inside this method will be executed asynchronously.
18 | */
19 | void run();
20 | }
21 |
--------------------------------------------------------------------------------
/Domain/src/com/fernandocejas/android10/sample/domain/repository/UserRepository.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.domain.repository;
6 |
7 | import com.fernandocejas.android10.sample.domain.User;
8 | import com.fernandocejas.android10.sample.domain.exception.ErrorBundle;
9 | import java.util.Collection;
10 |
11 | /**
12 | * Interface that represents a Repository for getting {@link User} related data.
13 | */
14 | public interface UserRepository {
15 | /**
16 | * Callback used to be notified when either a user list has been loaded or an error happened.
17 | */
18 | interface UserListCallback {
19 | void onUserListLoaded(Collection usersCollection);
20 |
21 | void onError(ErrorBundle errorBundle);
22 | }
23 |
24 | /**
25 | * Callback used to be notified when either a user has been loaded or an error happened.
26 | */
27 | interface UserDetailsCallback {
28 | void onUserLoaded(User user);
29 |
30 | void onError(ErrorBundle errorBundle);
31 | }
32 |
33 | /**
34 | * Get a collection of {@link User}.
35 | *
36 | * @param userListCallback A {@link UserListCallback} used for notifying clients.
37 | */
38 | void getUserList(UserListCallback userListCallback);
39 |
40 | /**
41 | * Get an {@link User} by id.
42 | *
43 | * @param userId The user id used to retrieve user data.
44 | * @param userCallback A {@link UserDetailsCallback} used for notifying clients.
45 | */
46 | void getUserById(final int userId, UserDetailsCallback userCallback);
47 | }
48 |
--------------------------------------------------------------------------------
/Presentation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/Presentation/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'android'
2 |
3 | android {
4 | compileSdkVersion 19
5 | buildToolsVersion '19.1.0'
6 |
7 | defaultConfig {
8 | applicationId "com.fernandocejas.android10.sample.presentation"
9 | minSdkVersion 15
10 | targetSdkVersion 19
11 | versionCode 1
12 | versionName "1.0"
13 | testInstrumentationRunner "com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner"
14 | }
15 |
16 | packagingOptions {
17 | exclude 'LICENSE.txt'
18 | exclude 'META-INF/DEPENDENCIES'
19 | exclude 'META-INF/ASL2.0'
20 | exclude 'META-INF/NOTICE'
21 | exclude 'META-INF/LICENSE'
22 | }
23 |
24 | lintOptions {
25 | abortOnError false;
26 | disable 'InvalidPackage' // Some libraries have issues with this.
27 | disable 'OldTargetApi' // Lint gives this warning but SDK 20 would be Android L Beta.
28 | disable 'IconDensities' // For testing purpose. This is safe to remove.
29 | }
30 |
31 | buildTypes {
32 | release {
33 | runProguard false
34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
35 | }
36 | }
37 | }
38 |
39 | dependencies {
40 | def domainLayer = project(':domain')
41 | def dataLayer = project(':data')
42 |
43 | //project dependencies
44 | compile domainLayer
45 | compile dataLayer
46 |
47 | //compile this only for testing. I had to use a workaround for using Espresso (it is not in the
48 | //maven central repository): since both Mockito and Espresso use 'hamcrest' I had to remove them
49 | //on the mockito library: "zip -d mockito.jar org/hamcrest/*"
50 | androidTestCompile fileTree(dir: 'testLibs', include: '*.jar')
51 | }
52 |
--------------------------------------------------------------------------------
/Presentation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/fcejas/Software/SDKs/android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/Presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/exception/ErrorMessageFactoryTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.test.exception;
6 |
7 | import android.test.AndroidTestCase;
8 | import com.fernandocejas.android10.sample.data.exception.NetworkConnectionException;
9 | import com.fernandocejas.android10.sample.data.exception.UserNotFoundException;
10 | import com.fernandocejas.android10.sample.presentation.R;
11 | import com.fernandocejas.android10.sample.presentation.exception.ErrorMessageFactory;
12 |
13 | import static org.hamcrest.CoreMatchers.equalTo;
14 | import static org.hamcrest.CoreMatchers.is;
15 | import static org.hamcrest.MatcherAssert.assertThat;
16 |
17 | public class ErrorMessageFactoryTest extends AndroidTestCase {
18 |
19 | @Override protected void setUp() throws Exception {
20 | super.setUp();
21 | }
22 |
23 | public void testNetworkConnectionErrorMessage() {
24 | String expectedMessage = getContext().getString(R.string.exception_message_no_connection);
25 | String actualMessage = ErrorMessageFactory.create(getContext(),
26 | new NetworkConnectionException());
27 |
28 | assertThat(actualMessage, is(equalTo(expectedMessage)));
29 | }
30 |
31 | public void testUserNotFoundErrorMessage() {
32 | String expectedMessage = getContext().getString(R.string.exception_message_user_not_found);
33 | String actualMessage = ErrorMessageFactory.create(getContext(), new UserNotFoundException());
34 |
35 | assertThat(actualMessage, is(equalTo(expectedMessage)));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/mapper/UserModelDataMapperTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.test.mapper;
6 |
7 | import com.fernandocejas.android10.sample.domain.User;
8 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper;
9 | import com.fernandocejas.android10.sample.presentation.model.UserModel;
10 | import java.util.ArrayList;
11 | import java.util.Collection;
12 | import java.util.List;
13 | import junit.framework.TestCase;
14 |
15 | import static org.hamcrest.CoreMatchers.instanceOf;
16 | import static org.hamcrest.CoreMatchers.is;
17 | import static org.hamcrest.MatcherAssert.assertThat;
18 | import static org.mockito.Mockito.mock;
19 |
20 | public class UserModelDataMapperTest extends TestCase {
21 |
22 | private static final int FAKE_USER_ID = 123;
23 | private static final String FAKE_FULLNAME = "Tony Stark";
24 |
25 | private UserModelDataMapper userModelDataMapper;
26 |
27 | @Override protected void setUp() throws Exception {
28 | super.setUp();
29 | userModelDataMapper = new UserModelDataMapper();
30 | }
31 |
32 | public void testTransformUser() {
33 | User user = createFakeUser();
34 | UserModel userModel = userModelDataMapper.transform(user);
35 |
36 | assertThat(userModel, is(instanceOf(UserModel.class)));
37 | assertThat(userModel.getUserId(), is(FAKE_USER_ID));
38 | assertThat(userModel.getFullName(), is(FAKE_FULLNAME));
39 | }
40 |
41 | public void testTransformUserCollection() {
42 | User mockUserOne = mock(User.class);
43 | User mockUserTwo = mock(User.class);
44 |
45 | List userList = new ArrayList(5);
46 | userList.add(mockUserOne);
47 | userList.add(mockUserTwo);
48 |
49 | Collection userModelList = userModelDataMapper.transform(userList);
50 |
51 | assertThat(userModelList.toArray()[0], is(instanceOf(UserModel.class)));
52 | assertThat(userModelList.toArray()[1], is(instanceOf(UserModel.class)));
53 | assertThat(userModelList.size(), is(2));
54 | }
55 |
56 | private User createFakeUser() {
57 | User user = new User(FAKE_USER_ID);
58 | user.setFullName(FAKE_FULLNAME);
59 |
60 | return user;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/presenter/UserDetailsPresenterTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.test.presenter;
6 |
7 | import android.content.Context;
8 | import android.test.AndroidTestCase;
9 | import com.fernandocejas.android10.sample.domain.interactor.GetUserDetailsUseCase;
10 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper;
11 | import com.fernandocejas.android10.sample.presentation.presenter.UserDetailsPresenter;
12 | import com.fernandocejas.android10.sample.presentation.view.UserDetailsView;
13 | import org.mockito.Mock;
14 | import org.mockito.MockitoAnnotations;
15 |
16 | import static org.mockito.BDDMockito.given;
17 | import static org.mockito.Matchers.any;
18 | import static org.mockito.Matchers.anyInt;
19 | import static org.mockito.Mockito.doNothing;
20 | import static org.mockito.Mockito.verify;
21 |
22 | public class UserDetailsPresenterTest extends AndroidTestCase {
23 |
24 | private static final int FAKE_USER_ID = 123;
25 |
26 | private UserDetailsPresenter userDetailsPresenter;
27 |
28 | @Mock
29 | private Context mockContext;
30 | @Mock
31 | private UserDetailsView mockUserDetailsView;
32 | @Mock
33 | private GetUserDetailsUseCase mockGetUserDetailsUseCase;
34 | @Mock
35 | private UserModelDataMapper mockUserModelDataMapper;
36 |
37 | @Override protected void setUp() throws Exception {
38 | super.setUp();
39 | MockitoAnnotations.initMocks(this);
40 | userDetailsPresenter = new UserDetailsPresenter(mockUserDetailsView, mockGetUserDetailsUseCase,
41 | mockUserModelDataMapper);
42 | }
43 |
44 | public void testUserDetailsPresenterInitialize() {
45 | doNothing().when(mockGetUserDetailsUseCase)
46 | .execute(anyInt(), any(GetUserDetailsUseCase.Callback.class));
47 | given(mockUserDetailsView.getContext()).willReturn(mockContext);
48 |
49 | userDetailsPresenter.initialize(FAKE_USER_ID);
50 |
51 | verify(mockUserDetailsView).hideRetry();
52 | verify(mockUserDetailsView).showLoading();
53 | verify(mockGetUserDetailsUseCase).execute(anyInt(), any(GetUserDetailsUseCase.Callback.class));
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/presenter/UserListPresenterTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.test.presenter;
6 |
7 | import android.content.Context;
8 | import android.test.AndroidTestCase;
9 | import com.fernandocejas.android10.sample.domain.interactor.GetUserListUseCase;
10 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper;
11 | import com.fernandocejas.android10.sample.presentation.presenter.UserListPresenter;
12 | import com.fernandocejas.android10.sample.presentation.view.UserListView;
13 | import org.mockito.Mock;
14 | import org.mockito.MockitoAnnotations;
15 |
16 | import static org.mockito.BDDMockito.given;
17 | import static org.mockito.Matchers.any;
18 | import static org.mockito.Mockito.doNothing;
19 | import static org.mockito.Mockito.verify;
20 |
21 | public class UserListPresenterTest extends AndroidTestCase {
22 |
23 | private UserListPresenter userListPresenter;
24 |
25 | @Mock
26 | private Context mockContext;
27 | @Mock
28 | private UserListView mockUserListView;
29 | @Mock
30 | private GetUserListUseCase mockGetUserListUseCase;
31 | @Mock
32 | private UserModelDataMapper mockUserModelDataMapper;
33 |
34 | @Override protected void setUp() throws Exception {
35 | super.setUp();
36 | MockitoAnnotations.initMocks(this);
37 | userListPresenter = new UserListPresenter(mockUserListView, mockGetUserListUseCase,
38 | mockUserModelDataMapper);
39 | }
40 |
41 | public void testUserListPresenterInitialize() {
42 | doNothing().when(mockGetUserListUseCase).execute(any(GetUserListUseCase.Callback.class));
43 | given(mockUserListView.getContext()).willReturn(mockContext);
44 |
45 | userListPresenter.initialize();
46 |
47 | verify(mockUserListView).hideRetry();
48 | verify(mockUserListView).showLoading();
49 | verify(mockGetUserListUseCase).execute(any(GetUserListUseCase.Callback.class));
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/view/activity/UserDetailsActivityTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.test.view.activity;
6 |
7 | import android.app.Fragment;
8 | import android.content.Intent;
9 | import android.test.ActivityInstrumentationTestCase2;
10 | import com.fernandocejas.android10.sample.presentation.R;
11 | import com.fernandocejas.android10.sample.presentation.view.activity.UserDetailsActivity;
12 |
13 | import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;
14 | import static com.google.android.apps.common.testing.ui.espresso.assertion.ViewAssertions.matches;
15 | import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.isDisplayed;
16 | import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;
17 | import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withText;
18 | import static org.hamcrest.CoreMatchers.is;
19 | import static org.hamcrest.CoreMatchers.notNullValue;
20 | import static org.hamcrest.MatcherAssert.assertThat;
21 | import static org.hamcrest.Matchers.not;
22 |
23 | public class UserDetailsActivityTest extends ActivityInstrumentationTestCase2 {
24 |
25 | private static final int FAKE_USER_ID = 10;
26 |
27 | private UserDetailsActivity userDetailsActivity;
28 |
29 | public UserDetailsActivityTest() {
30 | super(UserDetailsActivity.class);
31 | }
32 |
33 | @Override protected void setUp() throws Exception {
34 | super.setUp();
35 | this.setActivityIntent(createTargetIntent());
36 | this.userDetailsActivity = getActivity();
37 | }
38 |
39 | @Override protected void tearDown() throws Exception {
40 | super.tearDown();
41 | }
42 |
43 | public void testContainsUserDetailsFragment() {
44 | Fragment userDetailsFragment =
45 | userDetailsActivity.getFragmentManager().findFragmentById(R.id.fl_fragment);
46 | assertThat(userDetailsFragment, is(notNullValue()));
47 | }
48 |
49 | public void testContainsProperTitle() {
50 | String actualTitle = this.userDetailsActivity.getTitle().toString().trim();
51 |
52 | assertThat(actualTitle, is("User Details"));
53 | }
54 |
55 | public void testLoadUserHappyCaseViews() {
56 | onView(withId(R.id.rl_retry)).check(matches(not(isDisplayed())));
57 | onView(withId(R.id.rl_progress)).check(matches(not(isDisplayed())));
58 |
59 | onView(withId(R.id.tv_fullname)).check(matches(isDisplayed()));
60 | onView(withId(R.id.tv_email)).check(matches(isDisplayed()));
61 | onView(withId(R.id.tv_description)).check(matches(isDisplayed()));
62 | }
63 |
64 | public void testLoadUserHappyCaseData() {
65 | onView(withId(R.id.tv_fullname)).check(matches(withText("John Sanchez")));
66 | onView(withId(R.id.tv_email)).check(matches(withText("dmedina@katz.edu")));
67 | onView(withId(R.id.tv_followers)).check(matches(withText("4523")));
68 | }
69 |
70 | private Intent createTargetIntent() {
71 | Intent intentLaunchActivity =
72 | UserDetailsActivity.getCallingIntent(getInstrumentation().getTargetContext(), FAKE_USER_ID);
73 |
74 | return intentLaunchActivity;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Presentation/src/androidTest/java/com/fernandocejas/android10/sample/test/view/activity/UserListActivityTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.test.view.activity;
6 |
7 | import android.app.Fragment;
8 | import android.content.Intent;
9 | import android.test.ActivityInstrumentationTestCase2;
10 | import com.fernandocejas.android10.sample.presentation.R;
11 | import com.fernandocejas.android10.sample.presentation.view.activity.UserListActivity;
12 |
13 | import static org.hamcrest.CoreMatchers.is;
14 | import static org.hamcrest.CoreMatchers.notNullValue;
15 | import static org.hamcrest.MatcherAssert.assertThat;
16 |
17 | public class UserListActivityTest extends ActivityInstrumentationTestCase2 {
18 |
19 | private UserListActivity userListActivity;
20 |
21 | public UserListActivityTest() {
22 | super(UserListActivity.class);
23 | }
24 |
25 | @Override protected void setUp() throws Exception {
26 | super.setUp();
27 | this.setActivityIntent(createTargetIntent());
28 | userListActivity = getActivity();
29 | }
30 |
31 | @Override protected void tearDown() throws Exception {
32 | super.tearDown();
33 | }
34 |
35 | public void testContainsUserListFragment() {
36 | Fragment userListFragment =
37 | userListActivity.getFragmentManager().findFragmentById(R.id.fragmentUserList);
38 | assertThat(userListFragment, is(notNullValue()));
39 | }
40 |
41 | public void testContainsProperTitle() {
42 | String actualTitle = this.userListActivity.getTitle().toString().trim();
43 |
44 | assertThat(actualTitle, is("Users List"));
45 | }
46 |
47 | private Intent createTargetIntent() {
48 | Intent intentLaunchActivity =
49 | UserListActivity.getCallingIntent(getInstrumentation().getTargetContext());
50 |
51 | return intentLaunchActivity;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Presentation/src/main/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Presentation/src/main/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | Presentation
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Presentation/src/main/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
3 | org.eclipse.jdt.core.compiler.compliance=1.6
4 | org.eclipse.jdt.core.compiler.source=1.6
5 |
--------------------------------------------------------------------------------
/Presentation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/UIThread.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation;
6 |
7 | import android.os.Handler;
8 | import android.os.Looper;
9 | import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread;
10 |
11 | /**
12 | * MainThread (UI Thread) implementation based on a Handler instantiated with the main
13 | * application Looper.
14 | */
15 | public class UIThread implements PostExecutionThread {
16 |
17 | private static class LazyHolder {
18 | private static final UIThread INSTANCE = new UIThread();
19 | }
20 |
21 | public static UIThread getInstance() {
22 | return LazyHolder.INSTANCE;
23 | }
24 |
25 | private final Handler handler;
26 |
27 | private UIThread() {
28 | this.handler = new Handler(Looper.getMainLooper());
29 | }
30 |
31 | /**
32 | * Causes the Runnable r to be added to the message queue.
33 | * The runnable will be run on the main thread.
34 | *
35 | * @param runnable {@link Runnable} to be executed.
36 | */
37 | @Override public void post(Runnable runnable) {
38 | handler.post(runnable);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/exception/ErrorMessageFactory.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.exception;
6 |
7 | import android.content.Context;
8 | import com.fernandocejas.android10.sample.data.exception.NetworkConnectionException;
9 | import com.fernandocejas.android10.sample.data.exception.UserNotFoundException;
10 | import com.fernandocejas.android10.sample.presentation.R;
11 |
12 | /**
13 | * Factory used to create error messages from an Exception as a condition.
14 | */
15 | public class ErrorMessageFactory {
16 |
17 | private ErrorMessageFactory() {
18 | //empty
19 | }
20 |
21 | /**
22 | * Creates a String representing an error message.
23 | *
24 | * @param context Context needed to retrieve string resources.
25 | * @param exception An exception used as a condition to retrieve the correct error message.
26 | * @return {@link String} an error message.
27 | */
28 | public static String create(Context context, Exception exception) {
29 | String message = context.getString(R.string.exception_message_generic);
30 |
31 | if (exception instanceof NetworkConnectionException) {
32 | message = context.getString(R.string.exception_message_no_connection);
33 | } else if (exception instanceof UserNotFoundException) {
34 | message = context.getString(R.string.exception_message_user_not_found);
35 | }
36 |
37 | return message;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/mapper/UserModelDataMapper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.mapper;
6 |
7 | import com.fernandocejas.android10.sample.domain.User;
8 | import com.fernandocejas.android10.sample.presentation.model.UserModel;
9 | import java.util.ArrayList;
10 | import java.util.Collection;
11 | import java.util.Collections;
12 |
13 | /**
14 | * Mapper class used to transform {@link User} (in the domain layer) to {@link UserModel} in the
15 | * presentation layer.
16 | */
17 | public class UserModelDataMapper {
18 |
19 | public UserModelDataMapper() {
20 | //empty
21 | }
22 |
23 | /**
24 | * Transform a {@link User} into an {@link UserModel}.
25 | *
26 | * @param user Object to be transformed.
27 | * @return {@link UserModel}.
28 | */
29 | public UserModel transform(User user) {
30 | if (user == null) {
31 | throw new IllegalArgumentException("Cannot transform a null value");
32 | }
33 | UserModel userModel = new UserModel(user.getUserId());
34 | userModel.setCoverUrl(user.getCoverUrl());
35 | userModel.setFullName(user.getFullName());
36 | userModel.setEmail(user.getEmail());
37 | userModel.setDescription(user.getDescription());
38 | userModel.setFollowers(user.getFollowers());
39 |
40 | return userModel;
41 | }
42 |
43 | /**
44 | * Transform a Collection of {@link User} into a Collection of {@link UserModel}.
45 | *
46 | * @param usersCollection Objects to be transformed.
47 | * @return List of {@link UserModel}.
48 | */
49 | public Collection transform(Collection usersCollection) {
50 | Collection userModelsCollection;
51 |
52 | if (usersCollection != null && !usersCollection.isEmpty()) {
53 | userModelsCollection = new ArrayList();
54 | for (User user : usersCollection) {
55 | userModelsCollection.add(transform(user));
56 | }
57 | } else {
58 | userModelsCollection = Collections.emptyList();
59 | }
60 |
61 | return userModelsCollection;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/model/UserModel.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.model;
6 |
7 | /**
8 | * Class that represents a user in the presentation layer.
9 | */
10 | public class UserModel {
11 |
12 | private final int userId;
13 |
14 | public UserModel(int userId) {
15 | this.userId = userId;
16 | }
17 |
18 | private String coverUrl;
19 | private String fullName;
20 | private String email;
21 | private String description;
22 | private int followers;
23 |
24 | public int getUserId() {
25 | return userId;
26 | }
27 |
28 | public String getCoverUrl() {
29 | return coverUrl;
30 | }
31 |
32 | public void setCoverUrl(String coverUrl) {
33 | this.coverUrl = coverUrl;
34 | }
35 |
36 | public String getFullName() {
37 | return fullName;
38 | }
39 |
40 | public void setFullName(String fullName) {
41 | this.fullName = fullName;
42 | }
43 |
44 | public String getEmail() {
45 | return email;
46 | }
47 |
48 | public void setEmail(String email) {
49 | this.email = email;
50 | }
51 |
52 | public String getDescription() {
53 | return description;
54 | }
55 |
56 | public void setDescription(String description) {
57 | this.description = description;
58 | }
59 |
60 | public int getFollowers() {
61 | return followers;
62 | }
63 |
64 | public void setFollowers(int followers) {
65 | this.followers = followers;
66 | }
67 |
68 | @Override public String toString() {
69 | StringBuilder stringBuilder = new StringBuilder();
70 |
71 | stringBuilder.append("***** User Model Details *****\n");
72 | stringBuilder.append("id=" + this.getUserId() + "\n");
73 | stringBuilder.append("cover url=" + this.getCoverUrl() + "\n");
74 | stringBuilder.append("fullname=" + this.getFullName() + "\n");
75 | stringBuilder.append("email=" + this.getEmail() + "\n");
76 | stringBuilder.append("description=" + this.getDescription() + "\n");
77 | stringBuilder.append("followers=" + this.getFollowers() + "\n");
78 | stringBuilder.append("*******************************");
79 |
80 | return stringBuilder.toString();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/navigation/Navigator.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.navigation;
6 |
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import com.fernandocejas.android10.sample.presentation.view.activity.UserDetailsActivity;
10 | import com.fernandocejas.android10.sample.presentation.view.activity.UserListActivity;
11 |
12 | /**
13 | * Class used to navigate through the application.
14 | */
15 | public class Navigator {
16 |
17 | public void Navigator() {
18 | //empty
19 | }
20 |
21 | /**
22 | * Goes to the user list screen.
23 | *
24 | * @param context A Context needed to open the destiny activity.
25 | */
26 | public void navigateToUserList(Context context) {
27 | if (context != null) {
28 | Intent intentToLaunch = UserListActivity.getCallingIntent(context);
29 | context.startActivity(intentToLaunch);
30 | }
31 | }
32 |
33 | /**
34 | * Goes to the user details screen.
35 | *
36 | * @param context A Context needed to open the destiny activity.
37 | */
38 | public void navigateToUserDetails(Context context, int userId) {
39 | if (context != null) {
40 | Intent intentToLaunch = UserDetailsActivity.getCallingIntent(context, userId);
41 | context.startActivity(intentToLaunch);
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/Presenter.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.presenter;
6 |
7 | /**
8 | * Interface representing a Presenter in a model view presenter (MVP) pattern.
9 | */
10 | public interface Presenter {
11 | /**
12 | * Method that control the lifecycle of the view. It should be called in the view's
13 | * (Activity or Fragment) onResume() method.
14 | */
15 | void resume();
16 |
17 | /**
18 | * Method that control the lifecycle of the view. It should be called in the view's
19 | * (Activity or Fragment) onPause() method.
20 | */
21 | void pause();
22 | }
23 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserDetailsPresenter.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.presenter;
6 |
7 | import com.fernandocejas.android10.sample.domain.User;
8 | import com.fernandocejas.android10.sample.domain.exception.ErrorBundle;
9 | import com.fernandocejas.android10.sample.domain.interactor.GetUserDetailsUseCase;
10 | import com.fernandocejas.android10.sample.presentation.exception.ErrorMessageFactory;
11 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper;
12 | import com.fernandocejas.android10.sample.presentation.model.UserModel;
13 | import com.fernandocejas.android10.sample.presentation.view.UserDetailsView;
14 |
15 | /**
16 | * {@link Presenter} that controls communication between views and models of the presentation
17 | * layer.
18 | */
19 | public class UserDetailsPresenter implements Presenter {
20 |
21 | /** id used to retrieve user details */
22 | private int userId;
23 |
24 | private final UserDetailsView viewDetailsView;
25 | private final GetUserDetailsUseCase getUserDetailsUseCase;
26 | private final UserModelDataMapper userModelDataMapper;
27 |
28 | public UserDetailsPresenter(UserDetailsView userDetailsView,
29 | GetUserDetailsUseCase getUserDetailsUseCase, UserModelDataMapper userModelDataMapper) {
30 | if (userDetailsView == null || getUserDetailsUseCase == null || userModelDataMapper == null) {
31 | throw new IllegalArgumentException("Constructor parameters cannot be null!!!");
32 | }
33 | this.viewDetailsView = userDetailsView;
34 | this.getUserDetailsUseCase = getUserDetailsUseCase;
35 | this.userModelDataMapper = userModelDataMapper;
36 | }
37 |
38 | @Override public void resume() {}
39 |
40 | @Override public void pause() {}
41 |
42 | /**
43 | * Initializes the presenter by start retrieving user details.
44 | */
45 | public void initialize(int userId) {
46 | this.userId = userId;
47 | this.loadUserDetails();
48 | }
49 |
50 | /**
51 | * Loads user details.
52 | */
53 | private void loadUserDetails() {
54 | this.hideViewRetry();
55 | this.showViewLoading();
56 | this.getUserDetails();
57 | }
58 |
59 | private void showViewLoading() {
60 | this.viewDetailsView.showLoading();
61 | }
62 |
63 | private void hideViewLoading() {
64 | this.viewDetailsView.hideLoading();
65 | }
66 |
67 | private void showViewRetry() {
68 | this.viewDetailsView.showRetry();
69 | }
70 |
71 | private void hideViewRetry() {
72 | this.viewDetailsView.hideRetry();
73 | }
74 |
75 | private void showErrorMessage(ErrorBundle errorBundle) {
76 | String errorMessage = ErrorMessageFactory.create(this.viewDetailsView.getContext(),
77 | errorBundle.getException());
78 | this.viewDetailsView.showError(errorMessage);
79 | }
80 |
81 | private void showUserDetailsInView(User user) {
82 | final UserModel userModel = this.userModelDataMapper.transform(user);
83 | this.viewDetailsView.renderUser(userModel);
84 | }
85 |
86 | private void getUserDetails() {
87 | this.getUserDetailsUseCase.execute(this.userId, this.userDetailsCallback);
88 | }
89 |
90 | private final GetUserDetailsUseCase.Callback userDetailsCallback = new GetUserDetailsUseCase.Callback() {
91 | @Override public void onUserDataLoaded(User user) {
92 | UserDetailsPresenter.this.showUserDetailsInView(user);
93 | UserDetailsPresenter.this.hideViewLoading();
94 | }
95 |
96 | @Override public void onError(ErrorBundle errorBundle) {
97 | UserDetailsPresenter.this.hideViewLoading();
98 | UserDetailsPresenter.this.showErrorMessage(errorBundle);
99 | UserDetailsPresenter.this.showViewRetry();
100 | }
101 | };
102 | }
103 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserListPresenter.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.presenter;
6 |
7 | import com.fernandocejas.android10.sample.domain.User;
8 | import com.fernandocejas.android10.sample.domain.exception.ErrorBundle;
9 | import com.fernandocejas.android10.sample.domain.interactor.GetUserListUseCase;
10 | import com.fernandocejas.android10.sample.presentation.exception.ErrorMessageFactory;
11 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper;
12 | import com.fernandocejas.android10.sample.presentation.model.UserModel;
13 | import com.fernandocejas.android10.sample.presentation.view.UserListView;
14 | import java.util.Collection;
15 |
16 | /**
17 | * {@link Presenter} that controls communication between views and models of the presentation
18 | * layer.
19 | */
20 | public class UserListPresenter implements Presenter {
21 |
22 | private final UserListView viewListView;
23 | private final GetUserListUseCase getUserListUseCase;
24 | private final UserModelDataMapper userModelDataMapper;
25 |
26 | public UserListPresenter(UserListView userListView, GetUserListUseCase getUserListUserCase,
27 | UserModelDataMapper userModelDataMapper) {
28 | if (userListView == null || getUserListUserCase == null || userModelDataMapper == null) {
29 | throw new IllegalArgumentException("Constructor parameters cannot be null!!!");
30 | }
31 | this.viewListView = userListView;
32 | this.getUserListUseCase = getUserListUserCase;
33 | this.userModelDataMapper = userModelDataMapper;
34 | }
35 |
36 | @Override public void resume() {}
37 |
38 | @Override public void pause() {}
39 |
40 | /**
41 | * Initializes the presenter by start retrieving the user list.
42 | */
43 | public void initialize() {
44 | this.loadUserList();
45 | }
46 |
47 | /**
48 | * Loads all users.
49 | */
50 | private void loadUserList() {
51 | this.hideViewRetry();
52 | this.showViewLoading();
53 | this.getUserList();
54 | }
55 |
56 | public void onUserClicked(UserModel userModel) {
57 | this.viewListView.viewUser(userModel);
58 | }
59 |
60 | private void showViewLoading() {
61 | this.viewListView.showLoading();
62 | }
63 |
64 | private void hideViewLoading() {
65 | this.viewListView.hideLoading();
66 | }
67 |
68 | private void showViewRetry() {
69 | this.viewListView.showRetry();
70 | }
71 |
72 | private void hideViewRetry() {
73 | this.viewListView.hideRetry();
74 | }
75 |
76 | private void showErrorMessage(ErrorBundle errorBundle) {
77 | String errorMessage = ErrorMessageFactory.create(this.viewListView.getContext(),
78 | errorBundle.getException());
79 | this.viewListView.showError(errorMessage);
80 | }
81 |
82 | private void showUsersCollectionInView(Collection usersCollection) {
83 | final Collection userModelsCollection =
84 | this.userModelDataMapper.transform(usersCollection);
85 | this.viewListView.renderUserList(userModelsCollection);
86 | }
87 |
88 | private void getUserList() {
89 | this.getUserListUseCase.execute(userListCallback);
90 | }
91 |
92 | private final GetUserListUseCase.Callback userListCallback = new GetUserListUseCase.Callback() {
93 | @Override public void onUserListLoaded(Collection usersCollection) {
94 | UserListPresenter.this.showUsersCollectionInView(usersCollection);
95 | UserListPresenter.this.hideViewLoading();
96 | }
97 |
98 | @Override public void onError(ErrorBundle errorBundle) {
99 | UserListPresenter.this.hideViewLoading();
100 | UserListPresenter.this.showErrorMessage(errorBundle);
101 | UserListPresenter.this.showViewRetry();
102 | }
103 | };
104 | }
105 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/LoadDataView.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.view;
6 |
7 | import android.content.Context;
8 |
9 | /**
10 | * Interface representing a View that will use to load data.
11 | */
12 | public interface LoadDataView {
13 | /**
14 | * Show a view with a progress bar indicating a loading process.
15 | */
16 | void showLoading();
17 |
18 | /**
19 | * Hide a loading view.
20 | */
21 | void hideLoading();
22 |
23 | /**
24 | * Show a retry view in case of an error when retrieving data.
25 | */
26 | void showRetry();
27 |
28 | /**
29 | * Hide a retry view shown if there was an error when retrieving data.
30 | */
31 | void hideRetry();
32 |
33 | /**
34 | * Show an error message
35 | *
36 | * @param message A string representing an error.
37 | */
38 | void showError(String message);
39 |
40 | /**
41 | * Get a {@link android.content.Context}.
42 | */
43 | Context getContext();
44 | }
45 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/UserDetailsView.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.view;
6 |
7 | import com.fernandocejas.android10.sample.presentation.model.UserModel;
8 |
9 | /**
10 | * Interface representing a View in a model view presenter (MVP) pattern.
11 | * In this case is used as a view representing a user profile.
12 | */
13 | public interface UserDetailsView extends LoadDataView {
14 | /**
15 | * Render a user in the UI.
16 | *
17 | * @param user The {@link UserModel} that will be shown.
18 | */
19 | void renderUser(UserModel user);
20 | }
21 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/UserListView.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.view;
6 |
7 | import com.fernandocejas.android10.sample.presentation.model.UserModel;
8 | import java.util.Collection;
9 |
10 | /**
11 | * Interface representing a View in a model view presenter (MVP) pattern.
12 | * In this case is used as a view representing a list of {@link UserModel}.
13 | */
14 | public interface UserListView extends LoadDataView {
15 | /**
16 | * Render a user list in the UI.
17 | *
18 | * @param userModelCollection The collection of {@link UserModel} that will be shown.
19 | */
20 | void renderUserList(Collection userModelCollection);
21 |
22 | /**
23 | * View a {@link UserModel} profile/details.
24 | *
25 | * @param userModel The user that will be shown.
26 | */
27 | void viewUser(UserModel userModel);
28 | }
29 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/activity/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.android10.sample.presentation.view.activity;
2 |
3 | import android.app.Activity;
4 | import android.app.Fragment;
5 | import android.app.FragmentTransaction;
6 |
7 | /**
8 | * Base {@link android.app.Activity} class for every Activity in this application.
9 | */
10 | public abstract class BaseActivity extends Activity {
11 |
12 | /**
13 | * Adds a {@link Fragment} to this activity's layout.
14 | *
15 | * @param containerViewId The container view to where add the fragment.
16 | * @param fragment The fragment to be added.
17 | */
18 | protected void addFragment(int containerViewId, Fragment fragment) {
19 | FragmentTransaction fragmentTransaction = this.getFragmentManager().beginTransaction();
20 | fragmentTransaction.add(containerViewId, fragment);
21 | fragmentTransaction.commit();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/activity/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.android10.sample.presentation.view.activity;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import android.widget.Button;
6 | import com.fernandocejas.android10.sample.presentation.R;
7 | import com.fernandocejas.android10.sample.presentation.navigation.Navigator;
8 |
9 | /**
10 | * Main application screen. This is the app entry point.
11 | */
12 | public class MainActivity extends BaseActivity {
13 |
14 | private Navigator navigator;
15 |
16 | private Button btn_LoadData;
17 |
18 | @Override
19 | protected void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | setContentView(R.layout.activity_main);
22 |
23 | this.mapGUI();
24 | this.initialize();
25 | }
26 |
27 | /**
28 | * Maps the graphical user interface controls.
29 | */
30 | private void mapGUI() {
31 | btn_LoadData = (Button) findViewById(R.id.btn_LoadData);
32 | btn_LoadData.setOnClickListener(loadDataOnClickListener);
33 | }
34 |
35 | /**
36 | * Initializes activity's private members.
37 | */
38 | private void initialize() {
39 | //This initialization should be avoided by using a dependency injection framework.
40 | //But this is an example and for testing purpose.
41 | this.navigator = new Navigator();
42 | }
43 |
44 | /**
45 | * Goes to the user list screen.
46 | */
47 | private void navigateToUserList() {
48 | this.navigator.navigateToUserList(this);
49 | }
50 |
51 | private final View.OnClickListener loadDataOnClickListener = new View.OnClickListener() {
52 | @Override public void onClick(View v) {
53 | navigateToUserList();
54 | }
55 | };
56 | }
57 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/activity/UserDetailsActivity.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.view.activity;
6 |
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.os.Bundle;
10 | import android.view.Window;
11 | import com.fernandocejas.android10.sample.presentation.R;
12 | import com.fernandocejas.android10.sample.presentation.view.fragment.UserDetailsFragment;
13 |
14 | /**
15 | * Activity that shows details of a certain user.
16 | */
17 | public class UserDetailsActivity extends BaseActivity {
18 |
19 | private static final String INTENT_EXTRA_PARAM_USER_ID = "org.android10.INTENT_PARAM_USER_ID";
20 | private static final String INSTANCE_STATE_PARAM_USER_ID = "org.android10.STATE_PARAM_USER_ID";
21 |
22 | private int userId;
23 |
24 | public static Intent getCallingIntent(Context context, int userId) {
25 | Intent callingIntent = new Intent(context, UserDetailsActivity.class);
26 | callingIntent.putExtra(INTENT_EXTRA_PARAM_USER_ID, userId);
27 |
28 | return callingIntent;
29 | }
30 |
31 | @Override protected void onCreate(Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 | requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
34 | setContentView(R.layout.activity_user_details);
35 |
36 | this.initializeActivity(savedInstanceState);
37 | }
38 |
39 | @Override protected void onSaveInstanceState(Bundle outState) {
40 | if (outState != null) {
41 | outState.putInt(INSTANCE_STATE_PARAM_USER_ID, this.userId);
42 | }
43 | super.onSaveInstanceState(outState);
44 | }
45 |
46 | /**
47 | * Initializes this activity.
48 | */
49 | private void initializeActivity(Bundle savedInstanceState) {
50 | if (savedInstanceState == null) {
51 | this.userId = getIntent().getIntExtra(INTENT_EXTRA_PARAM_USER_ID, -1);
52 | addFragment(R.id.fl_fragment, UserDetailsFragment.newInstance(this.userId));
53 | } else {
54 | this.userId = savedInstanceState.getInt(INSTANCE_STATE_PARAM_USER_ID);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/activity/UserListActivity.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.view.activity;
6 |
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.os.Bundle;
10 | import android.view.Window;
11 | import com.fernandocejas.android10.sample.presentation.R;
12 | import com.fernandocejas.android10.sample.presentation.model.UserModel;
13 | import com.fernandocejas.android10.sample.presentation.navigation.Navigator;
14 | import com.fernandocejas.android10.sample.presentation.view.fragment.UserListFragment;
15 |
16 | /**
17 | * Activity that shows a list of Users.
18 | */
19 | public class UserListActivity extends BaseActivity implements UserListFragment.UserListListener {
20 |
21 | private Navigator navigator;
22 |
23 | public static Intent getCallingIntent(Context context) {
24 | return new Intent(context, UserListActivity.class);
25 | }
26 |
27 | @Override protected void onCreate(Bundle savedInstanceState) {
28 | super.onCreate(savedInstanceState);
29 | requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
30 | setContentView(R.layout.activity_user_list);
31 |
32 | this.initialize();
33 | }
34 |
35 | @Override public void onUserClicked(UserModel userModel) {
36 | this.navigator.navigateToUserDetails(this, userModel.getUserId());
37 | }
38 |
39 | /**
40 | * Initializes activity's private members.
41 | */
42 | private void initialize() {
43 | //This initialization should be avoided by using a dependency injection framework.
44 | //But this is an example and for testing purpose.
45 | this.navigator = new Navigator();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/adapter/UsersAdapter.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.view.adapter;
6 |
7 | import android.content.Context;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.BaseAdapter;
12 | import android.widget.TextView;
13 | import com.fernandocejas.android10.sample.presentation.R;
14 | import com.fernandocejas.android10.sample.presentation.model.UserModel;
15 | import java.util.Collection;
16 | import java.util.List;
17 |
18 | /**
19 | * Adaptar that manages a collection of {@link UserModel}.
20 | */
21 | public class UsersAdapter extends BaseAdapter {
22 |
23 | private List usersCollection;
24 | private final LayoutInflater layoutInflater;
25 |
26 | public UsersAdapter(Context context, Collection usersCollection) {
27 | this.validateUsersCollection(usersCollection);
28 | this.layoutInflater =
29 | (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
30 | this.usersCollection = (List) usersCollection;
31 | }
32 |
33 | @Override public int getCount() {
34 | int count = 0;
35 | if (this.usersCollection != null && !this.usersCollection.isEmpty()) {
36 | count = this.usersCollection.size();
37 | }
38 | return count;
39 | }
40 |
41 | @Override
42 | public boolean isEmpty() {
43 | return (getCount() == 0);
44 | }
45 |
46 | @Override public Object getItem(int position) {
47 | return this.usersCollection.get(position);
48 | }
49 |
50 | @Override public long getItemId(int position) {
51 | return position;
52 | }
53 |
54 | @Override public View getView(int position, View convertView, ViewGroup parent) {
55 | UserViewHolder userViewHolder;
56 |
57 | if (convertView == null) {
58 | convertView = this.layoutInflater.inflate(R.layout.row_user, parent, false);
59 |
60 | userViewHolder = new UserViewHolder();
61 | userViewHolder.textViewTitle = (TextView) convertView.findViewById(R.id.title);
62 |
63 | convertView.setTag(userViewHolder);
64 | } else {
65 | userViewHolder = (UserViewHolder) convertView.getTag();
66 | }
67 |
68 | UserModel userModel = this.usersCollection.get(position);
69 | userViewHolder.textViewTitle.setText(userModel.getFullName());
70 |
71 | return convertView;
72 | }
73 |
74 | public void setUsersCollection(Collection usersCollection) {
75 | this.validateUsersCollection(usersCollection);
76 | this.usersCollection = (List) usersCollection;
77 | this.notifyDataSetChanged();
78 | }
79 |
80 | private void validateUsersCollection(Collection usersCollection) {
81 | if (usersCollection == null) {
82 | throw new IllegalArgumentException("The track list cannot be null");
83 | }
84 | }
85 |
86 | static class UserViewHolder {
87 | TextView textViewTitle;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/component/AutoLoadImageView.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.view.component;
6 |
7 | import android.app.Activity;
8 | import android.content.Context;
9 | import android.graphics.Bitmap;
10 | import android.graphics.BitmapFactory;
11 | import android.net.ConnectivityManager;
12 | import android.net.NetworkInfo;
13 | import android.util.AttributeSet;
14 | import android.util.Log;
15 | import android.widget.ImageView;
16 | import java.io.File;
17 | import java.io.FileNotFoundException;
18 | import java.io.FileOutputStream;
19 | import java.io.IOException;
20 | import java.net.MalformedURLException;
21 | import java.net.URL;
22 | import java.net.URLConnection;
23 |
24 | /**
25 | * Simple implementation of {@link android.widget.ImageView} with extended features like setting an
26 | * image from an url and an internal file cache using the application cache directory.
27 | */
28 | public class AutoLoadImageView extends ImageView {
29 |
30 | private static final String BASE_IMAGE_NAME_CACHED = "image_";
31 |
32 | private int imagePlaceHolderResourceId = -1;
33 | private DiskCache cache = new DiskCache(getContext().getCacheDir());
34 |
35 | public AutoLoadImageView(Context context) {
36 | super(context);
37 | }
38 |
39 | public AutoLoadImageView(Context context, AttributeSet attrs) {
40 | super(context, attrs);
41 | }
42 |
43 | public AutoLoadImageView(Context context, AttributeSet attrs, int defStyle) {
44 | super(context, attrs, defStyle);
45 | }
46 |
47 | /**
48 | * Set an image from a remote url.
49 | *
50 | * @param imageUrl The url of the resource to load.
51 | */
52 | public void setImageUrl(final String imageUrl) {
53 | AutoLoadImageView.this.loadImagePlaceHolder();
54 | if (imageUrl != null) {
55 | this.loadImageFromUrl(imageUrl);
56 | } else {
57 | this.loadImagePlaceHolder();
58 | }
59 | }
60 |
61 | /**
62 | * Set a place holder used for loading when an image is being downloaded from the internet.
63 | *
64 | * @param resourceId The resource id to use as a place holder.
65 | */
66 | public void setImagePlaceHolder(int resourceId) {
67 | this.imagePlaceHolderResourceId = resourceId;
68 | this.loadImagePlaceHolder();
69 | }
70 |
71 | /**
72 | * Invalidate the internal cache by evicting all cached elements.
73 | */
74 | public void invalidateImageCache() {
75 | if (this.cache != null) {
76 | this.cache.evictAll();
77 | }
78 | }
79 |
80 | /**
81 | * Loads and image from the internet (and cache it) or from the internal cache.
82 | *
83 | * @param imageUrl The remote image url to load.
84 | */
85 | private void loadImageFromUrl(final String imageUrl) {
86 | new Thread() {
87 | @Override public void run() {
88 | final Bitmap bitmap = AutoLoadImageView.this.getFromCache(getFileNameFromUrl(imageUrl));
89 | if (bitmap != null) {
90 | AutoLoadImageView.this.loadBitmap(bitmap);
91 | } else {
92 | if (isThereInternetConnection()) {
93 | final ImageDownloader imageDownloader = new ImageDownloader();
94 | imageDownloader.download(imageUrl, new ImageDownloader.Callback() {
95 | @Override public void onImageDownloaded(Bitmap bitmap) {
96 | AutoLoadImageView.this.cacheBitmap(bitmap, getFileNameFromUrl(imageUrl));
97 | AutoLoadImageView.this.loadBitmap(bitmap);
98 | }
99 |
100 | @Override public void onError() {
101 | AutoLoadImageView.this.loadImagePlaceHolder();
102 | }
103 | });
104 | } else {
105 | AutoLoadImageView.this.loadImagePlaceHolder();
106 | }
107 | }
108 | }
109 | }.start();
110 | }
111 |
112 | /**
113 | * Run the operation of loading a bitmap on the UI thread.
114 | *
115 | * @param bitmap The image to load.
116 | */
117 | private void loadBitmap(final Bitmap bitmap) {
118 | ((Activity) getContext()).runOnUiThread(new Runnable() {
119 | @Override public void run() {
120 | AutoLoadImageView.this.setImageBitmap(bitmap);
121 | }
122 | });
123 | }
124 |
125 | /**
126 | * Loads the image place holder if any has been assigned.
127 | */
128 | private void loadImagePlaceHolder() {
129 | if (this.imagePlaceHolderResourceId != -1) {
130 | ((Activity) getContext()).runOnUiThread(new Runnable() {
131 | @Override public void run() {
132 | AutoLoadImageView.this.setImageResource(
133 | AutoLoadImageView.this.imagePlaceHolderResourceId);
134 | }
135 | });
136 | }
137 | }
138 |
139 | /**
140 | * Get a {@link android.graphics.Bitmap} from the internal cache or null if it does not exist.
141 | *
142 | * @param fileName The name of the file to look for in the cache.
143 | * @return A valid cached bitmap, otherwise null.
144 | */
145 | private Bitmap getFromCache(String fileName) {
146 | Bitmap bitmap = null;
147 | if (this.cache != null) {
148 | bitmap = this.cache.get(fileName);
149 | }
150 | return bitmap;
151 | }
152 |
153 | /**
154 | * Cache an image using the internal cache.
155 | *
156 | * @param bitmap The bitmap to cache.
157 | * @param fileName The file name used for caching the bitmap.
158 | */
159 | private void cacheBitmap(Bitmap bitmap, String fileName) {
160 | if (this.cache != null) {
161 | this.cache.put(bitmap, fileName);
162 | }
163 | }
164 |
165 | /**
166 | * Checks if the device has any active internet connection.
167 | *
168 | * @return true device with internet connection, otherwise false.
169 | */
170 | private boolean isThereInternetConnection() {
171 | boolean isConnected;
172 |
173 | ConnectivityManager connectivityManager =
174 | (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
175 | NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
176 | isConnected = (networkInfo != null && networkInfo.isConnectedOrConnecting());
177 |
178 | return isConnected;
179 | }
180 |
181 | /**
182 | * Creates a file name from an image url
183 | *
184 | * @param imageUrl The image url used to build the file name.
185 | * @return An String representing a unique file name.
186 | */
187 | private String getFileNameFromUrl(String imageUrl) {
188 | //we could generate an unique MD5/SHA-1 here
189 | String hash = String.valueOf(imageUrl.hashCode());
190 | if (hash.startsWith("-")) {
191 | hash = hash.substring(1);
192 | }
193 | return BASE_IMAGE_NAME_CACHED + hash;
194 | }
195 |
196 | /**
197 | * Class used to download images from the internet
198 | */
199 | private static class ImageDownloader {
200 | interface Callback {
201 | void onImageDownloaded(Bitmap bitmap);
202 |
203 | void onError();
204 | }
205 |
206 | ImageDownloader() {}
207 |
208 | /**
209 | * Download an image from an url.
210 | *
211 | * @param imageUrl The url of the image to download.
212 | * @param callback A callback used to be reported when the task is finished.
213 | */
214 | void download(String imageUrl, Callback callback) {
215 | try {
216 | URLConnection conn = new URL(imageUrl).openConnection();
217 | conn.connect();
218 | Bitmap bitmap = BitmapFactory.decodeStream(conn.getInputStream());
219 | if (callback != null) {
220 | callback.onImageDownloaded(bitmap);
221 | }
222 | } catch (MalformedURLException e) {
223 | reportError(callback);
224 | } catch (IOException e) {
225 | reportError(callback);
226 | }
227 | }
228 |
229 | /**
230 | * Report an error to the caller
231 | *
232 | * @param callback Caller implementing {@link Callback}
233 | */
234 | private void reportError(Callback callback) {
235 | if (callback != null) {
236 | callback.onError();
237 | }
238 | }
239 | }
240 |
241 | /**
242 | * A simple disk cache implementation
243 | */
244 | private static class DiskCache {
245 |
246 | private static final String TAG = "DiskCache";
247 |
248 | private final File cacheDir;
249 |
250 | DiskCache(File cacheDir) {
251 | this.cacheDir = cacheDir;
252 | }
253 |
254 | /**
255 | * Get an element from the cache.
256 | *
257 | * @param fileName The name of the file to look for.
258 | * @return A valid element, otherwise false.
259 | */
260 | synchronized Bitmap get(String fileName) {
261 | Bitmap bitmap = null;
262 | File file = buildFileFromFilename(fileName);
263 | if (file.exists()) {
264 | bitmap = BitmapFactory.decodeFile(file.getPath());
265 | }
266 | return bitmap;
267 | }
268 |
269 | /**
270 | * Cache an element.
271 | *
272 | * @param bitmap The bitmap to be put in the cache.
273 | * @param fileName A string representing the name of the file to be cached.
274 | */
275 | synchronized void put(Bitmap bitmap, String fileName) {
276 | File file = buildFileFromFilename(fileName);
277 | if (!file.exists()) {
278 | try {
279 | FileOutputStream fileOutputStream = new FileOutputStream(file);
280 | bitmap.compress(Bitmap.CompressFormat.PNG, 90, fileOutputStream);
281 | fileOutputStream.flush();
282 | fileOutputStream.close();
283 | } catch (FileNotFoundException e) {
284 | Log.e(TAG, e.getMessage());
285 | } catch (IOException e) {
286 | Log.e(TAG, e.getMessage());
287 | }
288 | }
289 | }
290 |
291 | /**
292 | * Invalidate and expire the cache.
293 | */
294 | void evictAll() {
295 | if (cacheDir.exists()) {
296 | for (File file : cacheDir.listFiles()) {
297 | file.delete();
298 | }
299 | }
300 | }
301 |
302 | /**
303 | * Creates a file name from an image url
304 | *
305 | * @param fileName The image url used to build the file name.
306 | * @return A {@link java.io.File} representing a unique element.
307 | */
308 | private File buildFileFromFilename(String fileName) {
309 | String fullPath = this.cacheDir.getPath() + File.separator + fileName;
310 | return new File(fullPath);
311 | }
312 | }
313 | }
314 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/BaseFragment.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.view.fragment;
6 |
7 | import android.app.Fragment;
8 | import android.os.Bundle;
9 | import android.widget.Toast;
10 |
11 | /**
12 | * Base {@link android.app.Fragment} class for every fragment in this application.
13 | */
14 | public abstract class BaseFragment extends Fragment {
15 |
16 | @Override public void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setRetainInstance(true);
19 | initializePresenter();
20 | }
21 |
22 | /**
23 | * Initializes the {@link com.fernandocejas.android10.sample.presentation.presenter.Presenter}
24 | * for this fragment in a MVP pattern used to architect the application presentation layer.
25 | */
26 | abstract void initializePresenter();
27 |
28 | /**
29 | * Shows a {@link android.widget.Toast} message.
30 | *
31 | * @param message An string representing a message to be shown.
32 | */
33 | protected void showToastMessage(String message) {
34 | Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/UserDetailsFragment.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.view.fragment;
6 |
7 | import android.content.Context;
8 | import android.os.Bundle;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 | import android.widget.Button;
13 | import android.widget.RelativeLayout;
14 | import android.widget.TextView;
15 | import com.fernandocejas.android10.sample.data.cache.FileManager;
16 | import com.fernandocejas.android10.sample.data.cache.UserCache;
17 | import com.fernandocejas.android10.sample.data.cache.UserCacheImpl;
18 | import com.fernandocejas.android10.sample.data.cache.serializer.JsonSerializer;
19 | import com.fernandocejas.android10.sample.data.entity.mapper.UserEntityDataMapper;
20 | import com.fernandocejas.android10.sample.data.executor.JobExecutor;
21 | import com.fernandocejas.android10.sample.data.repository.UserDataRepository;
22 | import com.fernandocejas.android10.sample.data.repository.datasource.UserDataStoreFactory;
23 | import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread;
24 | import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor;
25 | import com.fernandocejas.android10.sample.domain.interactor.GetUserDetailsUseCase;
26 | import com.fernandocejas.android10.sample.domain.interactor.GetUserDetailsUseCaseImpl;
27 | import com.fernandocejas.android10.sample.domain.repository.UserRepository;
28 | import com.fernandocejas.android10.sample.presentation.R;
29 | import com.fernandocejas.android10.sample.presentation.UIThread;
30 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper;
31 | import com.fernandocejas.android10.sample.presentation.model.UserModel;
32 | import com.fernandocejas.android10.sample.presentation.presenter.UserDetailsPresenter;
33 | import com.fernandocejas.android10.sample.presentation.view.UserDetailsView;
34 | import com.fernandocejas.android10.sample.presentation.view.component.AutoLoadImageView;
35 |
36 | /**
37 | * Fragment that shows details of a certain user.
38 | */
39 | public class UserDetailsFragment extends BaseFragment implements UserDetailsView {
40 |
41 | private static final String ARGUMENT_KEY_USER_ID = "org.android10.ARGUMENT_USER_ID";
42 |
43 | private int userId;
44 | private UserDetailsPresenter userDetailsPresenter;
45 |
46 | private AutoLoadImageView iv_cover;
47 | private TextView tv_fullname;
48 | private TextView tv_email;
49 | private TextView tv_followers;
50 | private TextView tv_description;
51 | private RelativeLayout rl_progress;
52 | private RelativeLayout rl_retry;
53 | private Button bt_retry;
54 |
55 | public UserDetailsFragment() { super(); }
56 |
57 | public static UserDetailsFragment newInstance(int userId) {
58 | UserDetailsFragment userDetailsFragment = new UserDetailsFragment();
59 |
60 | Bundle argumentsBundle = new Bundle();
61 | argumentsBundle.putInt(ARGUMENT_KEY_USER_ID, userId);
62 | userDetailsFragment.setArguments(argumentsBundle);
63 |
64 | return userDetailsFragment;
65 | }
66 |
67 | @Override public void onCreate(Bundle savedInstanceState) {
68 | super.onCreate(savedInstanceState);
69 | this.initialize();
70 | }
71 |
72 | @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
73 | Bundle savedInstanceState) {
74 |
75 | View fragmentView = inflater.inflate(R.layout.fragment_user_details, container, false);
76 |
77 | this.iv_cover = (AutoLoadImageView) fragmentView.findViewById(R.id.iv_cover);
78 | this.tv_fullname = (TextView) fragmentView.findViewById(R.id.tv_fullname);
79 | this.tv_email = (TextView) fragmentView.findViewById(R.id.tv_email);
80 | this.tv_followers = (TextView) fragmentView.findViewById(R.id.tv_followers);
81 | this.tv_description = (TextView) fragmentView.findViewById(R.id.tv_description);
82 | this.rl_progress = (RelativeLayout) fragmentView.findViewById(R.id.rl_progress);
83 | this.rl_retry = (RelativeLayout) fragmentView.findViewById(R.id.rl_retry);
84 | this.bt_retry = (Button) fragmentView.findViewById(R.id.bt_retry);
85 | this.bt_retry.setOnClickListener(this.retryOnClickListener);
86 |
87 | return fragmentView;
88 | }
89 |
90 | @Override public void onActivityCreated(Bundle savedInstanceState) {
91 | super.onActivityCreated(savedInstanceState);
92 | this.userDetailsPresenter.initialize(this.userId);
93 | }
94 |
95 | @Override public void onResume() {
96 | super.onResume();
97 | this.userDetailsPresenter.resume();
98 | }
99 |
100 | @Override public void onPause() {
101 | super.onPause();
102 | this.userDetailsPresenter.pause();
103 | }
104 |
105 | @Override void initializePresenter() {
106 | // All these dependency initialization could have been avoided using a
107 | // dependency injection framework. But in this case are used this way for
108 | // LEARNING EXAMPLE PURPOSE.
109 | ThreadExecutor threadExecutor = JobExecutor.getInstance();
110 | PostExecutionThread postExecutionThread = UIThread.getInstance();
111 |
112 | JsonSerializer userCacheSerializer = new JsonSerializer();
113 | UserCache userCache = UserCacheImpl.getInstance(getActivity(), userCacheSerializer,
114 | FileManager.getInstance(), threadExecutor);
115 | UserDataStoreFactory userDataStoreFactory =
116 | new UserDataStoreFactory(this.getContext(), userCache);
117 | UserEntityDataMapper userEntityDataMapper = new UserEntityDataMapper();
118 | UserRepository userRepository = UserDataRepository.getInstance(userDataStoreFactory,
119 | userEntityDataMapper);
120 |
121 | GetUserDetailsUseCase getUserDetailsUseCase = new GetUserDetailsUseCaseImpl(userRepository,
122 | threadExecutor, postExecutionThread);
123 | UserModelDataMapper userModelDataMapper = new UserModelDataMapper();
124 |
125 | this.userDetailsPresenter =
126 | new UserDetailsPresenter(this, getUserDetailsUseCase, userModelDataMapper);
127 | }
128 |
129 | @Override public void renderUser(UserModel user) {
130 | if (user != null) {
131 | this.iv_cover.setImageUrl(user.getCoverUrl());
132 | this.tv_fullname.setText(user.getFullName());
133 | this.tv_email.setText(user.getEmail());
134 | this.tv_followers.setText(String.valueOf(user.getFollowers()));
135 | this.tv_description.setText(user.getDescription());
136 | }
137 | }
138 |
139 | @Override public void showLoading() {
140 | this.rl_progress.setVisibility(View.VISIBLE);
141 | this.getActivity().setProgressBarIndeterminateVisibility(true);
142 | }
143 |
144 | @Override public void hideLoading() {
145 | this.rl_progress.setVisibility(View.GONE);
146 | this.getActivity().setProgressBarIndeterminateVisibility(false);
147 | }
148 |
149 | @Override public void showRetry() {
150 | this.rl_retry.setVisibility(View.VISIBLE);
151 | }
152 |
153 | @Override public void hideRetry() {
154 | this.rl_retry.setVisibility(View.GONE);
155 | }
156 |
157 | @Override public void showError(String message) {
158 | this.showToastMessage(message);
159 | }
160 |
161 | @Override public Context getContext() {
162 | return getActivity().getApplicationContext();
163 | }
164 |
165 | /**
166 | * Initializes fragment's private members.
167 | */
168 | private void initialize() {
169 | this.userId = getArguments().getInt(ARGUMENT_KEY_USER_ID);
170 | }
171 |
172 | /**
173 | * Loads all users.
174 | */
175 | private void loadUserDetails() {
176 | if (this.userDetailsPresenter != null) {
177 | this.userDetailsPresenter.initialize(this.userId);
178 | }
179 | }
180 |
181 | private final View.OnClickListener retryOnClickListener = new View.OnClickListener() {
182 | @Override public void onClick(View view) {
183 | UserDetailsFragment.this.loadUserDetails();
184 | }
185 | };
186 | }
187 |
--------------------------------------------------------------------------------
/Presentation/src/main/java/com/fernandocejas/android10/sample/presentation/view/fragment/UserListFragment.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2014 android10.org. All rights reserved.
3 | * @author Fernando Cejas (the android10 coder)
4 | */
5 | package com.fernandocejas.android10.sample.presentation.view.fragment;
6 |
7 | import android.app.Activity;
8 | import android.content.Context;
9 | import android.os.Bundle;
10 | import android.view.LayoutInflater;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 | import android.widget.AdapterView;
14 | import android.widget.Button;
15 | import android.widget.ListView;
16 | import android.widget.RelativeLayout;
17 | import com.fernandocejas.android10.sample.data.cache.FileManager;
18 | import com.fernandocejas.android10.sample.data.cache.UserCache;
19 | import com.fernandocejas.android10.sample.data.cache.UserCacheImpl;
20 | import com.fernandocejas.android10.sample.data.cache.serializer.JsonSerializer;
21 | import com.fernandocejas.android10.sample.data.entity.mapper.UserEntityDataMapper;
22 | import com.fernandocejas.android10.sample.data.executor.JobExecutor;
23 | import com.fernandocejas.android10.sample.data.repository.UserDataRepository;
24 | import com.fernandocejas.android10.sample.data.repository.datasource.UserDataStoreFactory;
25 | import com.fernandocejas.android10.sample.domain.executor.PostExecutionThread;
26 | import com.fernandocejas.android10.sample.domain.executor.ThreadExecutor;
27 | import com.fernandocejas.android10.sample.domain.interactor.GetUserListUseCase;
28 | import com.fernandocejas.android10.sample.domain.interactor.GetUserListUseCaseImpl;
29 | import com.fernandocejas.android10.sample.domain.repository.UserRepository;
30 | import com.fernandocejas.android10.sample.presentation.R;
31 | import com.fernandocejas.android10.sample.presentation.UIThread;
32 | import com.fernandocejas.android10.sample.presentation.mapper.UserModelDataMapper;
33 | import com.fernandocejas.android10.sample.presentation.model.UserModel;
34 | import com.fernandocejas.android10.sample.presentation.presenter.UserListPresenter;
35 | import com.fernandocejas.android10.sample.presentation.view.UserListView;
36 | import com.fernandocejas.android10.sample.presentation.view.adapter.UsersAdapter;
37 | import java.util.Collection;
38 |
39 | /**
40 | * Fragment that shows a list of Users.
41 | */
42 | public class UserListFragment extends BaseFragment implements UserListView {
43 |
44 | /**
45 | * Interface for listening user list events.
46 | */
47 | public interface UserListListener {
48 | void onUserClicked(final UserModel userModel);
49 | }
50 |
51 | private UserListPresenter userListPresenter;
52 |
53 | private ListView lv_users;
54 | private RelativeLayout rl_progress;
55 | private RelativeLayout rl_retry;
56 | private Button bt_retry;
57 |
58 | private UsersAdapter usersAdapter;
59 |
60 | private UserListListener userListListener;
61 |
62 | public UserListFragment() { super(); }
63 |
64 | @Override public void onAttach(Activity activity) {
65 | super.onAttach(activity);
66 | if (activity instanceof UserListListener) {
67 | this.userListListener = (UserListListener) activity;
68 | }
69 | }
70 |
71 | @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
72 | Bundle savedInstanceState) {
73 |
74 | View fragmentView = inflater.inflate(R.layout.fragment_user_list, container, true);
75 |
76 | this.lv_users = (ListView) fragmentView.findViewById(R.id.lv_users);
77 | this.lv_users.setOnItemClickListener(this.userOnItemClickListener);
78 | this.rl_progress = (RelativeLayout) fragmentView.findViewById(R.id.rl_progress);
79 | this.rl_retry = (RelativeLayout) fragmentView.findViewById(R.id.rl_retry);
80 | this.bt_retry = (Button) fragmentView.findViewById(R.id.bt_retry);
81 | this.bt_retry.setOnClickListener(this.retryOnClickListener);
82 |
83 | return fragmentView;
84 | }
85 |
86 | @Override public void onActivityCreated(Bundle savedInstanceState) {
87 | super.onActivityCreated(savedInstanceState);
88 | this.userListPresenter.initialize();
89 | }
90 |
91 | @Override public void onResume() {
92 | super.onResume();
93 | this.userListPresenter.resume();
94 | }
95 |
96 | @Override public void onPause() {
97 | super.onPause();
98 | this.userListPresenter.pause();
99 | }
100 |
101 | @Override protected void initializePresenter() {
102 | // All these dependency initialization could have been avoided using a
103 | // dependency injection framework. But in this case are used this way for
104 | // LEARNING EXAMPLE PURPOSE.
105 | ThreadExecutor threadExecutor = JobExecutor.getInstance();
106 | PostExecutionThread postExecutionThread = UIThread.getInstance();
107 |
108 | JsonSerializer userCacheSerializer = new JsonSerializer();
109 | UserCache userCache = UserCacheImpl.getInstance(getActivity(), userCacheSerializer,
110 | FileManager.getInstance(), threadExecutor);
111 | UserDataStoreFactory userDataStoreFactory =
112 | new UserDataStoreFactory(this.getContext(), userCache);
113 | UserEntityDataMapper userEntityDataMapper = new UserEntityDataMapper();
114 | UserRepository userRepository = UserDataRepository.getInstance(userDataStoreFactory,
115 | userEntityDataMapper);
116 |
117 | GetUserListUseCase getUserListUseCase = new GetUserListUseCaseImpl(userRepository,
118 | threadExecutor, postExecutionThread);
119 | UserModelDataMapper userModelDataMapper = new UserModelDataMapper();
120 |
121 | this.userListPresenter = new UserListPresenter(this, getUserListUseCase, userModelDataMapper);
122 | }
123 |
124 | @Override public void showLoading() {
125 | this.rl_progress.setVisibility(View.VISIBLE);
126 | this.getActivity().setProgressBarIndeterminateVisibility(true);
127 | }
128 |
129 | @Override public void hideLoading() {
130 | this.rl_progress.setVisibility(View.GONE);
131 | this.getActivity().setProgressBarIndeterminateVisibility(false);
132 | }
133 |
134 | @Override public void showRetry() {
135 | this.rl_retry.setVisibility(View.VISIBLE);
136 | }
137 |
138 | @Override public void hideRetry() {
139 | this.rl_retry.setVisibility(View.GONE);
140 | }
141 |
142 | @Override public void renderUserList(Collection userModelCollection) {
143 | if (userModelCollection != null) {
144 | if (this.usersAdapter == null) {
145 | this.usersAdapter = new UsersAdapter(getActivity(), userModelCollection);
146 | } else {
147 | this.usersAdapter.setUsersCollection(userModelCollection);
148 | }
149 | this.lv_users.setAdapter(usersAdapter);
150 | }
151 | }
152 |
153 | @Override public void viewUser(UserModel userModel) {
154 | if (this.userListListener != null) {
155 | this.userListListener.onUserClicked(userModel);
156 | }
157 | }
158 |
159 | @Override public void showError(String message) {
160 | this.showToastMessage(message);
161 | }
162 |
163 | @Override public Context getContext() {
164 | return this.getActivity().getApplicationContext();
165 | }
166 |
167 | /**
168 | * Loads all users.
169 | */
170 | private void loadUserList() {
171 | if (this.userListPresenter != null) {
172 | this.userListPresenter.initialize();
173 | }
174 | }
175 |
176 | /**
177 | * Views a {@link UserModel} when is clicked.
178 | * Uses the presenter via composition to achieve this.
179 | *
180 | * @param userModel {@link UserModel} to show.
181 | */
182 | private void onUserClicked(UserModel userModel) {
183 | if (this.userListPresenter != null) {
184 | this.userListPresenter.onUserClicked(userModel);
185 | }
186 | }
187 |
188 | private final View.OnClickListener retryOnClickListener = new View.OnClickListener() {
189 | @Override public void onClick(View view) {
190 | UserListFragment.this.loadUserList();
191 | }
192 | };
193 |
194 | private final AdapterView.OnItemClickListener userOnItemClickListener =
195 | new AdapterView.OnItemClickListener() {
196 | @Override public void onItemClick(AdapterView> parent, View view, int position, long id) {
197 | UserModel userModel = (UserModel) UserListFragment.this.usersAdapter.getItem(position);
198 | UserListFragment.this.onUserClicked(userModel);
199 | }
200 | };
201 | }
202 |
--------------------------------------------------------------------------------
/Presentation/src/main/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-19
15 | android.library.reference.1=../../../data/src/main
16 |
--------------------------------------------------------------------------------
/Presentation/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Presentation/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Presentation/src/main/res/drawable-hdpi/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Presentation/src/main/res/drawable-hdpi/logo.png
--------------------------------------------------------------------------------
/Presentation/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Presentation/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Presentation/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Presentation/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Presentation/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Presentation/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Presentation/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
19 |
20 |
26 |
27 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Presentation/src/main/res/layout/activity_user_details.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/Presentation/src/main/res/layout/activity_user_list.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
--------------------------------------------------------------------------------
/Presentation/src/main/res/layout/fragment_user_details.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
14 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/Presentation/src/main/res/layout/fragment_user_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
16 |
17 |
20 |
21 |
--------------------------------------------------------------------------------
/Presentation/src/main/res/layout/row_user.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
20 |
21 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Presentation/src/main/res/layout/view_progress.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Presentation/src/main/res/layout/view_retry.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Presentation/src/main/res/layout/view_user_details.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
19 |
20 |
26 |
27 |
31 |
36 |
37 |
41 |
46 |
47 |
51 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/Presentation/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
7 | 26sp
8 | 5dp
9 | 140dp
10 |
11 |
12 | 5dp
13 | 20sp
14 | 50dp
15 | 5dp
16 |
17 |
--------------------------------------------------------------------------------
/Presentation/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Android Clean Architecture
4 | http://www.android10.org/
5 |
6 | Users List
7 | User Details
8 |
9 | Load Sample Data
10 | Retry
11 |
12 | Email:
13 | Followers:
14 | Description:
15 |
16 | Non accessible view
17 |
18 | There was an application error
19 | There is no internet connection
20 | Cannot retrieve user data. Check your internet connection
21 |
22 |
--------------------------------------------------------------------------------
/Presentation/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
14 |
15 |
22 |
23 |
32 |
33 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Presentation/testLibs/dexmaker-1.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Presentation/testLibs/dexmaker-1.0.jar
--------------------------------------------------------------------------------
/Presentation/testLibs/dexmaker-mockito-1.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Presentation/testLibs/dexmaker-mockito-1.0.jar
--------------------------------------------------------------------------------
/Presentation/testLibs/espresso-1.1-bundled.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Presentation/testLibs/espresso-1.1-bundled.jar
--------------------------------------------------------------------------------
/Presentation/testLibs/mockito-all-1.9.5.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mxy1228/MVP/ed2b4ee61abb5b2df57dc71e8d55e4349f9cfb5a/Presentation/testLibs/mockito-all-1.9.5.jar
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | MVP
2 | ===
3 | Android-MVP模式示例程序。
4 | 博客地址:http://blog.csdn.net/guxiao1201/article/details/40147209
5 |
--------------------------------------------------------------------------------