├── .gitignore
├── .travis.yml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── github
│ │ └── glomadrian
│ │ └── mvpcleanarchitecture
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── github
│ │ └── glomadrian
│ │ └── mvpcleanarchitecture
│ │ ├── app
│ │ ├── BaseActivity.java
│ │ ├── BaseFragment.java
│ │ ├── BasePresenter.java
│ │ ├── MVPCleanArchitectureApplication.java
│ │ └── dependencyinjection
│ │ │ ├── ExecutorModule.java
│ │ │ ├── InteractorModule.java
│ │ │ ├── PresenterModule.java
│ │ │ ├── ReactiveModule.java
│ │ │ ├── RepositoryModule.java
│ │ │ ├── RootModule.java
│ │ │ └── qualifier
│ │ │ ├── ActivityContext.java
│ │ │ └── ApplicationContext.java
│ │ ├── domain
│ │ ├── LogUtils.java
│ │ ├── interactor
│ │ │ ├── AbstractInteractor.java
│ │ │ ├── GetMarvelCharactersLimit.java
│ │ │ ├── GetMarvelCharactersLimitImp.java
│ │ │ ├── GetMarvelCharactersPaginated.java
│ │ │ └── GetMarvelCharactersPaginatedImp.java
│ │ ├── model
│ │ │ ├── MarvelCharacter.java
│ │ │ └── MarvelCharacterList.java
│ │ └── repository
│ │ │ ├── MarvelRepository.java
│ │ │ ├── ResponseMapper.java
│ │ │ ├── api
│ │ │ ├── ApiUtils.java
│ │ │ ├── mapper
│ │ │ │ └── MarvelApiResponseMapper.java
│ │ │ ├── model
│ │ │ │ ├── Character.java
│ │ │ │ ├── CharacterDataContainer.java
│ │ │ │ ├── CharacterDataWrapper.java
│ │ │ │ ├── Comic.java
│ │ │ │ ├── Image.java
│ │ │ │ ├── Serie.java
│ │ │ │ └── Story.java
│ │ │ └── retrofit
│ │ │ │ ├── RetrofitMarvelAPIRepository.java
│ │ │ │ ├── RetrofitMarvelService.java
│ │ │ │ └── interceptor
│ │ │ │ └── MarvelRequestInterceptor.java
│ │ │ ├── exception
│ │ │ └── GetCharactersException.java
│ │ │ └── mock
│ │ │ └── MarvelMockRepository.java
│ │ ├── executor
│ │ ├── Interactor.java
│ │ ├── InteractorExecutor.java
│ │ ├── MainThreadExecutor.java
│ │ ├── MainThreadExecutorImp.java
│ │ └── ThreadExecutor.java
│ │ └── ui
│ │ ├── activity
│ │ ├── MainActivity.java
│ │ └── ModelInfoActivity.java
│ │ ├── adapter
│ │ └── ModelAdapter.java
│ │ ├── custom
│ │ └── recycler
│ │ │ ├── ClickItemTouchListener.java
│ │ │ └── ClickRecyclerView.java
│ │ ├── fragment
│ │ ├── CharacterInfoFragment.java
│ │ └── CharacterListFragment.java
│ │ ├── presenter
│ │ ├── CharacterInfoPresenter.java
│ │ ├── CharacterInfoPresenterImp.java
│ │ ├── CharacterListPresenter.java
│ │ ├── CharacterListPresenterImp.java
│ │ └── Presenter.java
│ │ ├── reactive
│ │ ├── CharacterSelectedObservable.java
│ │ ├── CharacterSelectedObserver.java
│ │ └── Observable.java
│ │ ├── view
│ │ ├── CharacterListView.java
│ │ ├── ModelInfoView.java
│ │ ├── ModelListView.java
│ │ └── View.java
│ │ ├── viewholder
│ │ └── AbstractRecyclerViewHolder.java
│ │ └── viewmodel
│ │ ├── CharacterInfoViewModel.java
│ │ ├── CharacterViewModel.java
│ │ ├── Model.java
│ │ └── ModelInfo.java
│ └── res
│ ├── drawable-hdpi
│ ├── ic_launcher.png
│ └── spider.png
│ ├── drawable-mdpi
│ ├── green_circle.xml
│ ├── ic_launcher.png
│ └── yellow_circle.xml
│ ├── drawable-xhdpi
│ ├── ic_launcher.png
│ └── marvel.jpg
│ ├── drawable-xxhdpi
│ └── ic_launcher.png
│ ├── drawable-xxxhdpi
│ └── ic_launcher.png
│ ├── drawable
│ ├── blue_circle.xml
│ └── line.xml
│ ├── layout-land
│ ├── main.xml
│ └── model_info.xml
│ ├── layout
│ ├── character_list.xml
│ ├── main.xml
│ ├── model.xml
│ ├── model_info.xml
│ └── model_info_activity.xml
│ ├── values-v21
│ └── styles.xml
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── img
├── screenshot_1.png
├── screenshot_2.png
└── screenshot_3.png
├── settings.gradle
└── sonar-project.properties
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io
2 |
3 | ### Android ###
4 | # Built application files
5 | *.apk
6 | *.ap_
7 |
8 | # Files for the Dalvik VM
9 | *.dex
10 |
11 | # Java class files
12 | *.class
13 |
14 | # Generated files
15 | bin/
16 | gen/
17 |
18 | # Gradle files
19 | .gradle/
20 | build/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 |
25 | # Proguard folder generated by Eclipse
26 | proguard/
27 |
28 | # Log Files
29 | *.log
30 |
31 |
32 | ### Java ###
33 | *.class
34 |
35 | # Mobile Tools for Java (J2ME)
36 | .mtj.tmp/
37 |
38 | # Package Files #
39 | *.jar
40 | *.war
41 | *.ear
42 |
43 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
44 | hs_err_pid*
45 |
46 |
47 | ### Gradle ###
48 | .gradle
49 | build/
50 |
51 | # Ignore Gradle GUI config
52 | gradle-app.setting
53 |
54 |
55 | ### Intellij ###
56 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
57 |
58 | *.iml
59 |
60 | ## Directory-based project format:
61 | .idea/
62 | # if you remove the above rule, at least ignore the following:
63 |
64 | # User-specific stuff:
65 | # .idea/workspace.xml
66 | # .idea/tasks.xml
67 | # .idea/dictionaries
68 |
69 | # Sensitive or high-churn files:
70 | # .idea/dataSources.ids
71 | # .idea/dataSources.xml
72 | # .idea/sqlDataSources.xml
73 | # .idea/dynamic.xml
74 | # .idea/uiDesigner.xml
75 |
76 | # Gradle:
77 | # .idea/gradle.xml
78 | # .idea/libraries
79 |
80 | # Mongo Explorer plugin:
81 | # .idea/mongoSettings.xml
82 |
83 | ## File-based project format:
84 | *.ipr
85 | *.iws
86 |
87 | ## Plugin-specific files:
88 |
89 | # IntelliJ
90 | out/
91 |
92 | # mpeltonen/sbt-idea plugin
93 | .idea_modules/
94 |
95 | # JIRA plugin
96 | atlassian-ide-plugin.xml
97 |
98 | # Crashlytics plugin (for Android Studio and IntelliJ)
99 | com_crashlytics_export_strings.xml
100 | crashlytics.properties
101 | crashlytics-build.properties
102 |
103 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 | android:
3 | components:
4 | - build-tools-21.1.1
5 | - android-21
6 |
7 | # Additional components
8 | - extra-google-m2repository
9 | - extra-android-m2repository
10 | - extra-android-support
11 |
12 | script:
13 | ./gradlew build
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | MVP Clean Architecture
2 | ==
3 |
4 | [](https://travis-ci.org/glomadrian/MvpCleanArchitecture)
5 | [](https://android-arsenal.com/details/3/1210)
6 |
7 |
8 | A sample project using Clean architecture and MVP
9 |
10 |
11 | ## Motivation
12 |
13 | Auto learning and make an example of Clean Architecture with MVP are the principal motivation for this app
14 |
15 | Inspired by the project [EffectiveAndroidUI](https://github.com/pedrovgs/EffectiveAndroidUI) by @pedrovgs that I fully recommend
16 |
17 |
18 | Clean architecture
19 | ------------
20 |
21 | The Clean architecture search to make independent our code from data sources, UI, Frameworks, etc.
22 |
23 | Make more testable the code
24 |
25 | MVP
26 | ------------
27 |
28 | Model view presenter is one of the most used architectural pattern, in Android this pattern can be used with a few limitations, Activities and Fragments have more responsibility than a simple view, anywhere is a wonderfull pattern to be used in Android applications.
29 |
30 | Apllication structure
31 | ------------
32 |
33 |
34 | //TODO
35 |
36 |
37 | External Libs
38 | ------------
39 |
40 | This project use this external libs
41 |
42 | * [Retrofit ](http://square.github.io/dagger/)
43 | * [Dagger](http://square.github.io/retrofit/)
44 | * [ButterKnife](http://jakewharton.github.io/butterknife/)
45 | * [Parceler](https://github.com/johncarl81/parceler)
46 | * [Picasso](http://square.github.io/picasso/)
47 |
48 |
49 | Screenshots
50 | ------------
51 | 
52 |
53 | 
54 |
55 | 
56 |
57 |
58 | Developer By
59 | ------------
60 |
61 |
62 | Adrián García Lomas
63 |
64 | * [Twitter](https://twitter.com/glomadrian)
65 | * [LinkedIn](https://es.linkedin.com/in/glomadrian )
66 |
67 |
68 | License
69 | -------
70 |
71 | Copyright 2014 Adrián García Lomas
72 |
73 | Licensed under the Apache License, Version 2.0 (the "License");
74 | you may not use this file except in compliance with the License.
75 | You may obtain a copy of the License at
76 |
77 | http://www.apache.org/licenses/LICENSE-2.0
78 |
79 | Unless required by applicable law or agreed to in writing, software
80 | distributed under the License is distributed on an "AS IS" BASIS,
81 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
82 | See the License for the specific language governing permissions and
83 | limitations under the License.
84 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 21
5 | buildToolsVersion "21.1.1"
6 |
7 | defaultConfig {
8 | applicationId "com.github.glomadrian.mpvcleanarchitecture"
9 | minSdkVersion 21
10 | targetSdkVersion 21
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | packagingOptions {
21 | exclude 'META-INF/services/javax.annotation.processing.Processor'
22 | }
23 |
24 |
25 | }
26 | repositories {
27 | maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
28 | }
29 |
30 |
31 | dependencies {
32 | compile fileTree(dir: 'libs', include: ['*.jar'])
33 | compile 'com.squareup.retrofit:retrofit:1.7.1'
34 |
35 | compile 'com.squareup.dagger:dagger:1.2.2'
36 | compile 'com.squareup.dagger:dagger-compiler:1.2.2'
37 | compile 'com.squareup.picasso:picasso:2.4.0'
38 | compile 'com.android.support:cardview-v7:21.0.2'
39 | compile 'com.android.support:appcompat-v7:21+'
40 | compile 'com.android.support:support-v4:21+'
41 | compile 'com.android.support:recyclerview-v7:21.0.2'
42 | compile 'com.jakewharton:butterknife:6.0.0'
43 | compile "org.parceler:parceler-api:+"
44 | provided "org.parceler:parceler:+"
45 | }
46 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /media/Almacen/SDK/Android/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 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/github/glomadrian/mvpcleanarchitecture/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/app/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.app;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 |
6 | import butterknife.ButterKnife;
7 |
8 | /**
9 | * Base activity for doing the Dependency
10 | *
11 | * @author glomadrian
12 | */
13 | public abstract class BaseActivity extends Activity {
14 |
15 | @Override
16 | public void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | injectDependencies();
19 | injectViews();
20 | }
21 |
22 | private void injectDependencies() {
23 | MVPCleanArchitectureApplication mvpCleanArchitectureApplication = (MVPCleanArchitectureApplication) getApplication();
24 | mvpCleanArchitectureApplication.inject(this);
25 | }
26 |
27 |
28 | private void injectViews() {
29 | ButterKnife.inject(this);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/app/BaseFragment.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.app;
2 |
3 | import android.app.Fragment;
4 | import android.os.Bundle;
5 | import android.support.annotation.Nullable;
6 | import android.view.View;
7 |
8 | import butterknife.ButterKnife;
9 |
10 | /**
11 | * @author glomadrian
12 | */
13 | public abstract class BaseFragment extends Fragment {
14 |
15 | @Override
16 | public void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | injectDependencies();
19 | }
20 |
21 | @Override
22 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
23 | super.onViewCreated(view, savedInstanceState);
24 | injectViews(view);
25 | }
26 |
27 | private void injectDependencies() {
28 | MVPCleanArchitectureApplication mvpCleanArchitectureApplication = (MVPCleanArchitectureApplication) getActivity().getApplication();
29 | mvpCleanArchitectureApplication.inject(this);
30 | }
31 |
32 | private void injectViews(View view) {
33 | ButterKnife.inject(this, view);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/app/BasePresenter.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.app;
2 |
3 | import android.content.Context;
4 |
5 | /**
6 | * @author glomadrian
7 | */
8 | public class BasePresenter {
9 |
10 | public BasePresenter(Context context) {
11 | ((MVPCleanArchitectureApplication) context.getApplicationContext()).inject(this);
12 |
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/app/MVPCleanArchitectureApplication.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.app;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 |
6 | import com.github.glomadrian.mvpcleanarchitecture.app.dependencyinjection.RootModule;
7 |
8 | import dagger.ObjectGraph;
9 |
10 | /**
11 | * @author glomadrian
12 | */
13 | public class MVPCleanArchitectureApplication extends Application {
14 |
15 | private ObjectGraph objectGraph;
16 |
17 | /**
18 | * Static method for get application context
19 | *
20 | * @param context
21 | * @return
22 | */
23 | public static MVPCleanArchitectureApplication get(Context context) {
24 | return (MVPCleanArchitectureApplication) context.getApplicationContext();
25 | }
26 |
27 | @Override
28 | public void onCreate() {
29 | super.onCreate();
30 | objectGraph = ObjectGraph.create(new RootModule(this));
31 | objectGraph.inject(this);
32 | }
33 |
34 | public void inject(Object object) {
35 | objectGraph.inject(object);
36 | }
37 |
38 | /**
39 | * Add a new module to the dependency graph
40 | *
41 | * @param modules
42 | */
43 | public void addModules(Object... modules) {
44 | objectGraph.plus(modules);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/app/dependencyinjection/ExecutorModule.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.app.dependencyinjection;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.executor.InteractorExecutor;
4 | import com.github.glomadrian.mvpcleanarchitecture.executor.MainThreadExecutor;
5 | import com.github.glomadrian.mvpcleanarchitecture.executor.MainThreadExecutorImp;
6 | import com.github.glomadrian.mvpcleanarchitecture.executor.ThreadExecutor;
7 |
8 | import javax.inject.Singleton;
9 |
10 | import dagger.Module;
11 | import dagger.Provides;
12 |
13 | /**
14 | * @author glomadrian
15 | */
16 | @Module(
17 | complete = false,
18 | library = true
19 | )
20 | public class ExecutorModule {
21 |
22 |
23 | @Provides
24 | @Singleton
25 | public InteractorExecutor provideExecutor() {
26 | return new ThreadExecutor();
27 | }
28 |
29 | @Provides
30 | @Singleton
31 | public MainThreadExecutor provideMainThreadExecutor() {
32 | return new MainThreadExecutorImp();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/app/dependencyinjection/InteractorModule.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.app.dependencyinjection;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.domain.interactor.GetMarvelCharactersLimit;
4 | import com.github.glomadrian.mvpcleanarchitecture.domain.interactor.GetMarvelCharactersLimitImp;
5 | import com.github.glomadrian.mvpcleanarchitecture.domain.interactor.GetMarvelCharactersPaginated;
6 | import com.github.glomadrian.mvpcleanarchitecture.domain.interactor.GetMarvelCharactersPaginatedImp;
7 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.MarvelRepository;
8 | import com.github.glomadrian.mvpcleanarchitecture.executor.InteractorExecutor;
9 | import com.github.glomadrian.mvpcleanarchitecture.executor.MainThreadExecutor;
10 |
11 | import javax.inject.Named;
12 |
13 | import dagger.Module;
14 | import dagger.Provides;
15 |
16 | /**
17 | * @author glomadrian
18 | */
19 | @Module(
20 | complete = false,
21 | library = true
22 | )
23 | public class InteractorModule {
24 |
25 |
26 | @Provides
27 | public GetMarvelCharactersLimit provideGetMarvelCharactersLimit(InteractorExecutor interactorExecutor, MainThreadExecutor mainThreadExecutor, @Named("mock_api") MarvelRepository marvelRepository) {
28 | return new GetMarvelCharactersLimitImp(interactorExecutor, mainThreadExecutor, marvelRepository);
29 | }
30 |
31 | @Provides
32 | public GetMarvelCharactersPaginated provideGetMarvelCharactersPaginated(InteractorExecutor interactorExecutor, MainThreadExecutor mainThreadExecutor, @Named("mock_api") MarvelRepository marvelRepository) {
33 | return new GetMarvelCharactersPaginatedImp(interactorExecutor, mainThreadExecutor, marvelRepository);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/app/dependencyinjection/PresenterModule.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.app.dependencyinjection;
2 |
3 | import android.content.Context;
4 |
5 | import com.github.glomadrian.mvpcleanarchitecture.domain.interactor.GetMarvelCharactersLimit;
6 | import com.github.glomadrian.mvpcleanarchitecture.domain.interactor.GetMarvelCharactersPaginated;
7 | import com.github.glomadrian.mvpcleanarchitecture.ui.presenter.CharacterInfoPresenter;
8 | import com.github.glomadrian.mvpcleanarchitecture.ui.presenter.CharacterInfoPresenterImp;
9 | import com.github.glomadrian.mvpcleanarchitecture.ui.presenter.CharacterListPresenter;
10 | import com.github.glomadrian.mvpcleanarchitecture.ui.presenter.CharacterListPresenterImp;
11 | import com.github.glomadrian.mvpcleanarchitecture.ui.reactive.CharacterSelectedObservable;
12 |
13 | import dagger.Module;
14 | import dagger.Provides;
15 |
16 | /**
17 | * @author glomadrian
18 | */
19 | @Module(
20 | complete = false,
21 | library = true
22 | )
23 | public class PresenterModule {
24 |
25 | @Provides
26 | public CharacterListPresenter provideCharacterCollectionPresenter(Context context, GetMarvelCharactersLimit getMarvelCharactersLimit, GetMarvelCharactersPaginated getMarvelCharactersPaginated, CharacterSelectedObservable characterSelectedObservable) {
27 | return new CharacterListPresenterImp(context, getMarvelCharactersLimit, getMarvelCharactersPaginated, characterSelectedObservable);
28 | }
29 |
30 | @Provides
31 | public CharacterInfoPresenter provideCharacterInfoPresenter() {
32 | return new CharacterInfoPresenterImp();
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/app/dependencyinjection/ReactiveModule.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.app.dependencyinjection;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.ui.reactive.CharacterSelectedObservable;
4 |
5 | import javax.inject.Singleton;
6 |
7 | import dagger.Module;
8 | import dagger.Provides;
9 |
10 | /**
11 | * @author glomadrian
12 | */
13 | @Module(
14 | complete = false,
15 | library = true
16 | )
17 | public class ReactiveModule {
18 |
19 | @Singleton
20 | @Provides
21 | CharacterSelectedObservable proviCharacterSelectedObservable() {
22 | return new CharacterSelectedObservable();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/app/dependencyinjection/RepositoryModule.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.app.dependencyinjection;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.MarvelRepository;
4 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.ResponseMapper;
5 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.mapper.MarvelApiResponseMapper;
6 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.retrofit.RetrofitMarvelAPIRepository;
7 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.retrofit.interceptor.MarvelRequestInterceptor;
8 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.mock.MarvelMockRepository;
9 |
10 | import javax.inject.Named;
11 |
12 | import dagger.Module;
13 | import dagger.Provides;
14 | import retrofit.RequestInterceptor;
15 |
16 | /**
17 | * @author glomadrian
18 | */
19 | @Module(
20 | complete = false,
21 | library = true
22 | )
23 | public class RepositoryModule {
24 |
25 |
26 | @Provides
27 | @Named("api_public_key")
28 | public String provideApiKey() {
29 | return "fakePublicKey";
30 | }
31 |
32 | @Provides
33 | @Named("api_private_key")
34 | public String providePrivateKey() {
35 | return "fakePrivateKey";
36 | }
37 |
38 | @Provides
39 | @Named("api_base_url")
40 | public String provideApiBaseUrl() {
41 | return "http://gateway.marvel.com";
42 | }
43 |
44 |
45 | @Provides
46 | public RequestInterceptor provideRequestInterceptor(@Named("api_public_key") String publicKey,
47 | @Named("api_private_key") String privateKey) {
48 | return new MarvelRequestInterceptor(publicKey, privateKey);
49 |
50 | }
51 |
52 | @Provides
53 | public ResponseMapper provideResponseMapper() {
54 | return new MarvelApiResponseMapper();
55 | }
56 |
57 | @Provides
58 | @Named("production_api")
59 | public MarvelRepository provideMarvelRepository(RequestInterceptor requestInterceptor, ResponseMapper responseMapper, @Named("api_base_url") String endpoint) {
60 | return new RetrofitMarvelAPIRepository(endpoint, requestInterceptor, responseMapper);
61 | }
62 |
63 | @Provides
64 | @Named("mock_api")
65 | public MarvelRepository provideMarvelRepository() {
66 | return new MarvelMockRepository();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/app/dependencyinjection/RootModule.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.app.dependencyinjection;
2 |
3 | import android.content.Context;
4 | import android.view.LayoutInflater;
5 |
6 | import com.github.glomadrian.mvpcleanarchitecture.app.MVPCleanArchitectureApplication;
7 | import com.github.glomadrian.mvpcleanarchitecture.ui.activity.MainActivity;
8 | import com.github.glomadrian.mvpcleanarchitecture.ui.activity.ModelInfoActivity;
9 | import com.github.glomadrian.mvpcleanarchitecture.ui.fragment.CharacterInfoFragment;
10 | import com.github.glomadrian.mvpcleanarchitecture.ui.fragment.CharacterListFragment;
11 | import com.github.glomadrian.mvpcleanarchitecture.ui.presenter.CharacterListPresenterImp;
12 |
13 | import javax.inject.Singleton;
14 |
15 | import dagger.Module;
16 | import dagger.Provides;
17 |
18 | /**
19 | * @author glomadrian
20 | */
21 | @Module(
22 | includes = {
23 | ExecutorModule.class,
24 | InteractorModule.class,
25 | RepositoryModule.class,
26 | PresenterModule.class,
27 | ReactiveModule.class,
28 | },
29 | injects = {
30 | MVPCleanArchitectureApplication.class,
31 | CharacterListFragment.class,
32 | CharacterInfoFragment.class,
33 | CharacterListPresenterImp.class,
34 | MainActivity.class,
35 | CharacterInfoFragment.class,
36 | ModelInfoActivity.class,
37 | MainActivity.class
38 | },
39 | library = true
40 | )
41 | public class RootModule {
42 |
43 | private final Context context;
44 |
45 | public RootModule(Context context) {
46 | this.context = context;
47 | }
48 |
49 | @Provides
50 | @Singleton
51 | public Context provideApplicationContext() {
52 | return context;
53 | }
54 |
55 | @Provides
56 | public LayoutInflater provideLayoutInflater() {
57 | return LayoutInflater.from(context);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/app/dependencyinjection/qualifier/ActivityContext.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.app.dependencyinjection.qualifier;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.Retention;
5 |
6 | import javax.inject.Qualifier;
7 |
8 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
9 |
10 | /**
11 | * @author glomadrian
12 | */
13 | @Qualifier
14 | @Documented
15 | @Retention(RUNTIME)
16 | public @interface ActivityContext {
17 | String value() default "";
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/app/dependencyinjection/qualifier/ApplicationContext.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.app.dependencyinjection.qualifier;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.Retention;
5 |
6 | import javax.inject.Qualifier;
7 |
8 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
9 |
10 | /**
11 | * @author glomadrian
12 | */
13 | @Qualifier
14 | @Documented
15 | @Retention(RUNTIME)
16 | public @interface ApplicationContext {
17 | String value() default "";
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/LogUtils.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain;
2 |
3 | /**
4 | * @author glomadrian
5 | */
6 | public class LogUtils {
7 |
8 | private LogUtils() {
9 | //You shall not pass
10 | }
11 |
12 | public static String generateTag(Object object) {
13 | return object.getClass().getCanonicalName();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/interactor/AbstractInteractor.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.interactor;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.executor.Interactor;
4 | import com.github.glomadrian.mvpcleanarchitecture.executor.InteractorExecutor;
5 | import com.github.glomadrian.mvpcleanarchitecture.executor.MainThreadExecutor;
6 |
7 | /**
8 | * @author glomadrian
9 | */
10 | public abstract class AbstractInteractor implements Interactor {
11 |
12 | private InteractorExecutor interactorExecutor;
13 | private MainThreadExecutor mainThreadExecutor;
14 |
15 | public AbstractInteractor(InteractorExecutor interactorExecutor, MainThreadExecutor mainThreadExecutor) {
16 | this.interactorExecutor = interactorExecutor;
17 | this.mainThreadExecutor = mainThreadExecutor;
18 | }
19 |
20 | public InteractorExecutor getInteractorExecutor() {
21 | return interactorExecutor;
22 | }
23 |
24 | public void setInteractorExecutor(InteractorExecutor interactorExecutor) {
25 | this.interactorExecutor = interactorExecutor;
26 | }
27 |
28 | public MainThreadExecutor getMainThreadExecutor() {
29 | return mainThreadExecutor;
30 | }
31 |
32 | public void setMainThreadExecutor(MainThreadExecutor mainThreadExecutor) {
33 | this.mainThreadExecutor = mainThreadExecutor;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/interactor/GetMarvelCharactersLimit.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.interactor;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
4 |
5 | import java.util.List;
6 |
7 | /**
8 | * Interface for define this interactor
9 | *
10 | * @author glomadrian
11 | */
12 | public interface GetMarvelCharactersLimit {
13 |
14 | void execute(final int limit, final Callback callback);
15 |
16 | interface Callback {
17 |
18 | void onMarvelCharacterList(List marvelCharacters);
19 |
20 | void onError();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/interactor/GetMarvelCharactersLimitImp.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.interactor;
2 |
3 | import android.util.Log;
4 |
5 | import com.github.glomadrian.mvpcleanarchitecture.domain.LogUtils;
6 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
7 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.MarvelRepository;
8 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.exception.GetCharactersException;
9 | import com.github.glomadrian.mvpcleanarchitecture.executor.InteractorExecutor;
10 | import com.github.glomadrian.mvpcleanarchitecture.executor.MainThreadExecutor;
11 |
12 | import java.util.List;
13 |
14 | /**
15 | * This implementation of the interactor (case use) will use a repository (injected) to get a collection
16 | * of marvel characters
17 | *
18 | * @author glomadrian
19 | */
20 | public class GetMarvelCharactersLimitImp extends AbstractInteractor implements GetMarvelCharactersLimit {
21 |
22 | private MarvelRepository marvelRepository;
23 | private Callback callback;
24 | private int limit;
25 |
26 | public GetMarvelCharactersLimitImp(InteractorExecutor interactorExecutor, MainThreadExecutor mainThreadExecutor, MarvelRepository marvelRepository) {
27 | super(interactorExecutor, mainThreadExecutor);
28 | this.marvelRepository = marvelRepository;
29 | }
30 |
31 |
32 | @Override
33 | public void run() {
34 |
35 | try {
36 | final List marvelCharacters = marvelRepository.getCharacterCollection(limit);
37 |
38 | getMainThreadExecutor().execute(new Runnable() {
39 | @Override
40 | public void run() {
41 | callback.onMarvelCharacterList(marvelCharacters);
42 | }
43 | });
44 |
45 | } catch (GetCharactersException e) {
46 | Log.e(LogUtils.generateTag(this), "Error on GerMarvelCharacters interactor");
47 | getMainThreadExecutor().execute(new Runnable() {
48 | @Override
49 | public void run() {
50 | callback.onError();
51 | }
52 | });
53 | }
54 |
55 | }
56 |
57 | @Override
58 | public void execute(int limit, Callback callback) {
59 | this.callback = callback;
60 | this.limit = limit;
61 |
62 | getInteractorExecutor().run(this);
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/interactor/GetMarvelCharactersPaginated.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.interactor;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
4 |
5 | import java.util.List;
6 |
7 | /**
8 | * @author glomadrian
9 | */
10 | public interface GetMarvelCharactersPaginated {
11 |
12 | void execute(final int limit, final int offset, final Callback callback);
13 |
14 | interface Callback {
15 |
16 | void onMarvelCharacterList(List marvelCharacters);
17 |
18 | void onError();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/interactor/GetMarvelCharactersPaginatedImp.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.interactor;
2 |
3 |
4 | import android.util.Log;
5 |
6 | import com.github.glomadrian.mvpcleanarchitecture.domain.LogUtils;
7 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
8 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.MarvelRepository;
9 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.exception.GetCharactersException;
10 | import com.github.glomadrian.mvpcleanarchitecture.executor.InteractorExecutor;
11 | import com.github.glomadrian.mvpcleanarchitecture.executor.MainThreadExecutor;
12 |
13 | import java.util.List;
14 |
15 | /**
16 | * @author glomadrian
17 | */
18 | public class GetMarvelCharactersPaginatedImp extends AbstractInteractor implements GetMarvelCharactersPaginated {
19 |
20 | private MarvelRepository marvelRepository;
21 | private Callback callback;
22 | private int limit;
23 | private int offset;
24 |
25 | public GetMarvelCharactersPaginatedImp(InteractorExecutor interactorExecutor, MainThreadExecutor mainThreadExecutor, MarvelRepository marvelRepository) {
26 | super(interactorExecutor, mainThreadExecutor);
27 | this.marvelRepository = marvelRepository;
28 | }
29 |
30 |
31 | @Override
32 | public void run() {
33 | try {
34 | final List marvelCharacters = marvelRepository.getCharacterCollectionPaginated(limit, offset);
35 |
36 | getMainThreadExecutor().execute(new Runnable() {
37 | @Override
38 | public void run() {
39 | callback.onMarvelCharacterList(marvelCharacters);
40 | }
41 | });
42 | } catch (GetCharactersException e) {
43 | Log.e(LogUtils.generateTag(this), "Error on GetMarvelCharactersPaginatedImp interactor");
44 | getMainThreadExecutor().execute(new Runnable() {
45 | @Override
46 | public void run() {
47 | callback.onError();
48 | }
49 | });
50 | }
51 | }
52 |
53 | @Override
54 | public void execute(int limit, int offset, Callback callback) {
55 | this.limit = limit;
56 | this.offset = offset;
57 | this.callback = callback;
58 | getInteractorExecutor().run(this);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/model/MarvelCharacter.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.model;
2 |
3 | import org.parceler.Parcel;
4 |
5 | /**
6 | * Parcel is a library to manage parcelable in a easy way, for default serialization
7 | * is necessary to avoid private fields if you want to use proguard
8 | *
9 | * @author glomadrian
10 | */
11 | @Parcel
12 | public class MarvelCharacter {
13 |
14 | int id;
15 | String name;
16 | String description;
17 | String imageURL;
18 | int comics;
19 | int stories;
20 | int series;
21 |
22 |
23 | public MarvelCharacter() {
24 | }
25 |
26 | public MarvelCharacter(int id, String name, String description, String imageURL) {
27 | this.id = id;
28 | this.name = name;
29 | this.description = description;
30 | this.imageURL = imageURL;
31 | }
32 |
33 | public int getId() {
34 | return id;
35 | }
36 |
37 | public void setId(int id) {
38 | this.id = id;
39 | }
40 |
41 | public String getName() {
42 | return name;
43 | }
44 |
45 | public void setName(String name) {
46 | this.name = name;
47 | }
48 |
49 | public String getDescription() {
50 | return description;
51 | }
52 |
53 | public void setDescription(String description) {
54 | this.description = description;
55 | }
56 |
57 | public String getImageURL() {
58 | return imageURL;
59 | }
60 |
61 | public void setImageURL(String imageURL) {
62 | this.imageURL = imageURL;
63 | }
64 |
65 | public int getComics() {
66 | return comics;
67 | }
68 |
69 | public void setComics(int comics) {
70 | this.comics = comics;
71 | }
72 |
73 | public int getStories() {
74 | return stories;
75 | }
76 |
77 | public void setStories(int stories) {
78 | this.stories = stories;
79 | }
80 |
81 | public int getSeries() {
82 | return series;
83 | }
84 |
85 | public void setSeries(int series) {
86 | this.series = series;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/model/MarvelCharacterList.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.model;
2 |
3 | import org.parceler.Parcel;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | /**
9 | * @author glomadrian
10 | */
11 | @Parcel
12 | public class MarvelCharacterList {
13 |
14 | public List marvelCharacters;
15 |
16 | public MarvelCharacterList() {
17 | this.marvelCharacters = new ArrayList();
18 | }
19 |
20 | public List getMarvelCharacters() {
21 | return (List) ((ArrayList) marvelCharacters).clone();
22 | }
23 |
24 | public void add(MarvelCharacter marvelCharacter) {
25 | this.marvelCharacters.add(marvelCharacter);
26 | }
27 |
28 | public void addAll(List marvelCharacter) {
29 | this.marvelCharacters.addAll(marvelCharacter);
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/repository/MarvelRepository.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.repository;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
4 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.exception.GetCharactersException;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * Repository to get marvel information
10 | *
11 | * @author glomadrian
12 | */
13 | public interface MarvelRepository {
14 |
15 | List getCharacterCollection(int limit) throws GetCharactersException;
16 |
17 | List getCharacterCollectionPaginated(int limit, int offset) throws GetCharactersException;
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/repository/ResponseMapper.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.repository;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
4 |
5 | import java.util.List;
6 |
7 | /**
8 | * The domain model is different from others resources models, in each case the repository must map
9 | * the response to a basic model
10 | *
11 | * @author glomadrian
12 | */
13 | public interface ResponseMapper {
14 | List mapResponse(T response);
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/repository/api/ApiUtils.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.repository.api;
2 |
3 | /**
4 | * @author glomadrian
5 | */
6 | public class ApiUtils {
7 |
8 | public static final String PARAM_KEY = "apikey";
9 | public static final String PARAM_TIMESTAMP = "ts";
10 | public static final String PARAM_HASH = "hash";
11 |
12 |
13 | private ApiUtils() {
14 | //you shall not pass
15 | }
16 |
17 |
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/repository/api/mapper/MarvelApiResponseMapper.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.mapper;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
4 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.ResponseMapper;
5 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.model.Character;
6 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.model.CharacterDataWrapper;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Collections;
10 | import java.util.List;
11 |
12 | /**
13 | * This custom implementation of the mapper will map the response from the Marvel api to Domain model
14 | *
15 | * @author glomadrian
16 | */
17 | public class MarvelApiResponseMapper implements ResponseMapper {
18 |
19 | @Override
20 | public List mapResponse(CharacterDataWrapper characterDataWrapper) {
21 |
22 | List characters = characterDataWrapper.getCharacterDataContainer().getResults();
23 |
24 | List marvelCharacters = Collections.emptyList();
25 |
26 | if (characterDataWrapper.getCharacterDataContainer().getCount() > 0) {
27 | marvelCharacters = new ArrayList();
28 | }
29 |
30 | for (Character character : characters) {
31 | marvelCharacters.add(createMarvelCharacterFromResponseCharacter(character));
32 | }
33 |
34 | return marvelCharacters;
35 | }
36 |
37 |
38 | private MarvelCharacter createMarvelCharacterFromResponseCharacter(Character character) {
39 |
40 | MarvelCharacter marvelCharacter = new MarvelCharacter();
41 | marvelCharacter.setName(character.getName());
42 | marvelCharacter.setDescription(character.getDescription());
43 | marvelCharacter.setId(character.getId());
44 | marvelCharacter.setComics(character.getComics().getAvailable());
45 | marvelCharacter.setSeries(character.getSeries().getAvailable());
46 | marvelCharacter.setStories(character.getStories().getAvailable());
47 |
48 |
49 | if (character.getThumbnail() != null) {
50 | marvelCharacter.setImageURL(character.getThumbnail().getPath() + "." + character.getThumbnail().getExtension());
51 | }
52 |
53 | return marvelCharacter;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/repository/api/model/Character.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.model;
2 |
3 | /**
4 | * @author glomadrian
5 | */
6 | public class Character {
7 |
8 | private int id;
9 | private String name;
10 | private String description;
11 | private Image thumbnail;
12 | private Comic comics;
13 | private Serie series;
14 | private Story stories;
15 |
16 | public int getId() {
17 | return id;
18 | }
19 |
20 | public void setId(int id) {
21 | this.id = id;
22 | }
23 |
24 | public String getName() {
25 | return name;
26 | }
27 |
28 | public void setName(String name) {
29 | this.name = name;
30 | }
31 |
32 | public String getDescription() {
33 | return description;
34 | }
35 |
36 | public void setDescription(String description) {
37 | this.description = description;
38 | }
39 |
40 | public Image getThumbnail() {
41 | return thumbnail;
42 | }
43 |
44 | public void setThumbnail(Image thumbnail) {
45 | this.thumbnail = thumbnail;
46 | }
47 |
48 | public Comic getComics() {
49 | return comics;
50 | }
51 |
52 | public void setComics(Comic comics) {
53 | this.comics = comics;
54 | }
55 |
56 | public Serie getSeries() {
57 | return series;
58 | }
59 |
60 | public void setSeries(Serie series) {
61 | this.series = series;
62 | }
63 |
64 | public Story getStories() {
65 | return stories;
66 | }
67 |
68 | public void setStories(Story stories) {
69 | this.stories = stories;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/repository/api/model/CharacterDataContainer.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.model;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * @author glomadrian
7 | */
8 | public class CharacterDataContainer {
9 |
10 | private int limit;
11 | private int total;
12 | private int count;
13 | private List results;
14 |
15 | public int getLimit() {
16 | return limit;
17 | }
18 |
19 | public void setLimit(int limit) {
20 | this.limit = limit;
21 | }
22 |
23 | public int getTotal() {
24 | return total;
25 | }
26 |
27 | public void setTotal(int total) {
28 | this.total = total;
29 | }
30 |
31 | public int getCount() {
32 | return count;
33 | }
34 |
35 | public void setCount(int count) {
36 | this.count = count;
37 | }
38 |
39 | public List getResults() {
40 | return results;
41 | }
42 |
43 | public void setResults(List results) {
44 | this.results = results;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/repository/api/model/CharacterDataWrapper.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.model;
2 |
3 | /**
4 | * @author glomadrian
5 | */
6 | public class CharacterDataWrapper {
7 |
8 | private int code;
9 | private String status;
10 | private CharacterDataContainer data;
11 |
12 | public int getCode() {
13 | return code;
14 | }
15 |
16 | public void setCode(int code) {
17 | this.code = code;
18 | }
19 |
20 | public String getStatus() {
21 | return status;
22 | }
23 |
24 | public void setStatus(String status) {
25 | this.status = status;
26 | }
27 |
28 | public CharacterDataContainer getCharacterDataContainer() {
29 | return data;
30 | }
31 |
32 | public void setCharacterDataContainer(CharacterDataContainer data) {
33 | this.data = data;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/repository/api/model/Comic.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.model;
2 |
3 | /**
4 | * @author glomadrian
5 | */
6 | public class Comic {
7 |
8 | private int available;
9 |
10 |
11 | public int getAvailable() {
12 | return available;
13 | }
14 |
15 | public void setAvailable(int available) {
16 | this.available = available;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/repository/api/model/Image.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.model;
2 |
3 | /**
4 | * @author glomadrian
5 | */
6 | public class Image {
7 |
8 | private String path;
9 | private String extension;
10 |
11 | public String getPath() {
12 | return path;
13 | }
14 |
15 | public void setPath(String path) {
16 | this.path = path;
17 | }
18 |
19 | public String getExtension() {
20 | return extension;
21 | }
22 |
23 | public void setExtension(String extension) {
24 | this.extension = extension;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/repository/api/model/Serie.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.model;
2 |
3 | /**
4 | * @author glomadrian
5 | */
6 | public class Serie {
7 |
8 | private int available;
9 |
10 |
11 | public int getAvailable() {
12 | return available;
13 | }
14 |
15 | public void setAvailable(int available) {
16 | this.available = available;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/repository/api/model/Story.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.model;
2 |
3 | /**
4 | * @author glomadrian
5 | */
6 | public class Story {
7 |
8 | private int available;
9 |
10 |
11 | public int getAvailable() {
12 | return available;
13 | }
14 |
15 | public void setAvailable(int available) {
16 | this.available = available;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/repository/api/retrofit/RetrofitMarvelAPIRepository.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.retrofit;
2 |
3 | import android.util.Log;
4 |
5 | import com.github.glomadrian.mvpcleanarchitecture.domain.LogUtils;
6 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
7 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.MarvelRepository;
8 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.ResponseMapper;
9 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.model.CharacterDataWrapper;
10 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.exception.GetCharactersException;
11 |
12 | import java.util.List;
13 |
14 | import retrofit.RequestInterceptor;
15 | import retrofit.RestAdapter;
16 | import retrofit.RetrofitError;
17 |
18 | /**
19 | * Implementation of MarvelRepository using Retrofit as client, every call is synchronous
20 | * because the asynchronous is done by the concrete interactor (use case) for this repository
21 | *
22 | * @author glomadrian
23 | */
24 | public class RetrofitMarvelAPIRepository implements MarvelRepository {
25 |
26 | private String endpoint;
27 | private RequestInterceptor requestInterceptor;
28 | private RetrofitMarvelService marvelAPI;
29 | private ResponseMapper responseMapper;
30 |
31 | public RetrofitMarvelAPIRepository(String endpoint, RequestInterceptor requestInterceptor, ResponseMapper responseMapper) {
32 | this.endpoint = endpoint;
33 | this.requestInterceptor = requestInterceptor;
34 | this.responseMapper = responseMapper;
35 |
36 | init();
37 | }
38 |
39 | private void init() {
40 |
41 | RestAdapter restAdapter = new RestAdapter.Builder()
42 | .setEndpoint(endpoint)
43 | .setRequestInterceptor(requestInterceptor)
44 | .build();
45 |
46 | marvelAPI = restAdapter.create(RetrofitMarvelService.class);
47 |
48 | }
49 |
50 | @Override
51 | public List getCharacterCollection(int limit) throws GetCharactersException {
52 | //The request using retrofit
53 |
54 | try {
55 | CharacterDataWrapper characterDataWrapper = marvelAPI.getCharacters(limit);
56 | //Map response from api to domain model
57 | return responseMapper.mapResponse(characterDataWrapper);
58 | } catch (RetrofitError retrofitError) {
59 | Log.e(LogUtils.generateTag(this), "Error on marvel api repository");
60 | GetCharactersException getCharactersException = new GetCharactersException();
61 | getCharactersException.setStackTrace(retrofitError.getStackTrace());
62 | throw getCharactersException;
63 | }
64 |
65 |
66 | }
67 |
68 | @Override
69 | public List getCharacterCollectionPaginated(int limit, int offset) throws GetCharactersException {
70 | try {
71 | CharacterDataWrapper characterDataWrapper = marvelAPI.getCharacters(limit, offset);
72 | //Map response from api to domain model
73 | return responseMapper.mapResponse(characterDataWrapper);
74 | } catch (RetrofitError retrofitError) {
75 | Log.e(LogUtils.generateTag(this), "Error on marvel api repository");
76 | GetCharactersException getCharactersException = new GetCharactersException();
77 | getCharactersException.setStackTrace(retrofitError.getStackTrace());
78 | throw getCharactersException;
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/repository/api/retrofit/RetrofitMarvelService.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.retrofit;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.model.CharacterDataWrapper;
4 |
5 | import retrofit.http.GET;
6 | import retrofit.http.Query;
7 |
8 | /**
9 | * @author glomadrian
10 | */
11 | public interface RetrofitMarvelService {
12 |
13 | @GET("/v1/public/characters")
14 | CharacterDataWrapper getCharacters(@Query("limit") int limit);
15 |
16 | @GET("/v1/public/characters")
17 | CharacterDataWrapper getCharacters(@Query("limit") int limit, @Query("offset") int offset);
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/repository/api/retrofit/interceptor/MarvelRequestInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.retrofit.interceptor;
2 |
3 | import android.util.Log;
4 |
5 | import com.github.glomadrian.mvpcleanarchitecture.domain.LogUtils;
6 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.api.ApiUtils;
7 |
8 | import java.math.BigInteger;
9 | import java.security.MessageDigest;
10 | import java.security.NoSuchAlgorithmException;
11 | import java.sql.Timestamp;
12 | import java.util.Date;
13 |
14 | import retrofit.RequestInterceptor;
15 |
16 | /**
17 | * Interceptor to add the auth key, and generate the hash for every request
18 | * The data is exposed in the constructor
19 | *
20 | * @author glomadrian
21 | */
22 | public class MarvelRequestInterceptor implements RequestInterceptor {
23 |
24 | private static final int SIGNUM = 1;
25 | private static final int BYTES = 1;
26 |
27 | private String publicKey;
28 | private String privateKey;
29 |
30 | public MarvelRequestInterceptor(String publicKey, String privateKey) {
31 | this.publicKey = publicKey;
32 | this.privateKey = privateKey;
33 | }
34 |
35 | @Override
36 | public void intercept(RequestFacade request) {
37 | String timeStamp = generateTimestamp();
38 | request.addEncodedQueryParam(ApiUtils.PARAM_TIMESTAMP, timeStamp);
39 | request.addEncodedQueryParam(ApiUtils.PARAM_KEY, publicKey);
40 | request.addEncodedQueryParam(ApiUtils.PARAM_HASH, generateMarvelHash(timeStamp, privateKey, publicKey));
41 | }
42 |
43 | private String generateTimestamp() {
44 | Date date = new Date();
45 | Timestamp timestamp = new Timestamp(date.getTime());
46 | return String.valueOf(timestamp.getTime());
47 | }
48 |
49 |
50 | private String generateMarvelHash(String timeStamp, String privateKey, String publicKey) {
51 |
52 | try {
53 | MessageDigest messageDigest = MessageDigest.getInstance("MD5");
54 | String marvelHash = timeStamp + privateKey + publicKey;
55 | byte[] bytes = marvelHash.getBytes();
56 | return new BigInteger(SIGNUM, messageDigest.digest(bytes)).toString(BYTES);
57 |
58 | } catch (NoSuchAlgorithmException e) {
59 | Log.e(LogUtils.generateTag(this), " Hash type not found");
60 | return "invalid";
61 | }
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/repository/exception/GetCharactersException.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.repository.exception;
2 |
3 | /**
4 | * @author glomadrian
5 | */
6 | public class GetCharactersException extends RuntimeException {
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/domain/repository/mock/MarvelMockRepository.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.domain.repository.mock;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
4 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.MarvelRepository;
5 | import com.github.glomadrian.mvpcleanarchitecture.domain.repository.exception.GetCharactersException;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | /**
11 | * Mock repository for testing
12 | *
13 | * @author glomadrian
14 | */
15 | public class MarvelMockRepository implements MarvelRepository {
16 |
17 | @Override
18 | public List getCharacterCollection(int limit) throws GetCharactersException {
19 | return generateMockCharacters();
20 | }
21 |
22 | @Override
23 | public List getCharacterCollectionPaginated(int limit, int offset) throws GetCharactersException {
24 | return generateMockCharacters();
25 | }
26 |
27 |
28 | private List generateMockCharacters() {
29 |
30 | List marvelCharacters = new ArrayList<>();
31 |
32 | marvelCharacters.add(createMarvelCharacter("Thor",
33 | "Thor possesses a very high resistance to physical injury that approaches " +
34 | "invulnerability. Thor possesses keen senses that allow him to" +
35 | " track objects traveling faster than light and hear cries from the" +
36 | " other side of the planet.[203] Thor has the ability to travel through time.",
37 | "http://www.tuexpertojuegos.com/wp-content/uploads//2010/07/thor-marvel.jpg"
38 | ));
39 |
40 | marvelCharacters.add(createMarvelCharacter("Captain America",
41 | "Captain America was the first Marvel Comics to have appeared in media outside " +
42 | "comics with the release of the 1944 movie serial Captain America. Since " +
43 | "then, the character has been featured in such other films and television" +
44 | " series, more recently in the Marvel Cinematic Universe (MCU) portrayed " +
45 | "by Chris Evans in Captain America: The First Avenger, The Avengers, " +
46 | "Captain America: The Winter Soldier and the upcoming Avengers: " +
47 | "Age of Ultron.",
48 | "http://www.marvelnoise.com/wp-content/uploads/2009/06/CaptainAmerica_Reborn_01_QuesadaVariant.jpg"
49 | ));
50 |
51 | marvelCharacters.add(createMarvelCharacter("Hulk",
52 | "Lee said that the Hulk's creation was inspired by a combination of " +
53 | "Frankenstein and Dr. Jekyll and Mr. Hyde. Although the Hulk's" +
54 | " coloration has varied throughout the character's publication history," +
55 | " the most usual color is green. As a child, Banner's father Brian Banner" +
56 | " often got mad and physically abused his mother Rebecca, creating the " +
57 | "psychological complex of fear, anger, and the fear of anger and the " +
58 | "destruction it can cause that underlies the character.",
59 | "http://cdn.screenrant.com/wp-content/uploads/Incredible-Hulk-Marvel-Now-Comics.jpg"
60 | ));
61 | marvelCharacters.add(createMarvelCharacter("Iron Man",
62 | "Iron Man (Tony Stark) is a fictional character, a superhero that appears in " +
63 | "books published by Marvel Comics. The character was created by " +
64 | "writer-editor Stan Lee, developed by scripter Larry Lieber, and designed" +
65 | " by artists Don Heck and Jack Kirby. He made his first appearance in " +
66 | "Tales of Suspense #39 (March 1963).",
67 | "http://bigfanboy.com/wp/wp-content/uploads/2010/04/ironman-royalp-front.jpg"
68 | ));
69 | marvelCharacters.add(createMarvelCharacter("Black Widow",
70 | "Natalia \"Natasha\" Alianovna Romanova is the first character to take on the " +
71 | "Black Widow codename in the modern mainstream Marvel Comics. She was " +
72 | "created by editor and plotter Stan Lee, scripter Don Rico and artist" +
73 | " Don Heck, and first appeared in Tales of Suspense #52 (April 1964).",
74 | "http://static.comicvine.com/uploads/original/7/75210/1960320-black_widow_marvel_superheroines_8418333_796_545.jpg"
75 | ));
76 | marvelCharacters.add(createMarvelCharacter("Vision",
77 | "The first Vision was created by the writer-artist team of Joe Simon and Jack Kirby " +
78 | "in Marvel Mystery Comics #13 (Nov. 1940), published by Marvel predecessor" +
79 | " Timely Comics during the 1930s-1940s period which fans and historians " +
80 | "call the Golden Age of Comic Books.",
81 | "http://www.beyondhollywood.com/uploads/2012/05/Vision-Comic-Book-e1336494560758.jpg"
82 | ));
83 |
84 | return marvelCharacters;
85 | }
86 |
87 | private MarvelCharacter createMarvelCharacter(String name, String description, String imageUrl) {
88 |
89 | MarvelCharacter marvelCharacter = new MarvelCharacter();
90 | marvelCharacter.setId(getRandomNumber());
91 | marvelCharacter.setName(name);
92 | marvelCharacter.setComics(getRandomNumber());
93 | marvelCharacter.setDescription(description);
94 | marvelCharacter.setImageURL(imageUrl);
95 | marvelCharacter.setStories(getRandomNumber());
96 | marvelCharacter.setSeries(getRandomNumber());
97 |
98 | return marvelCharacter;
99 | }
100 |
101 | private int getRandomNumber() {
102 | return 1 + (int) (Math.random() * 999);
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/executor/Interactor.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.executor;
2 |
3 | /**
4 | * Each interactor is a Use Case implementation see Clean Architecture for more info
5 | *
6 | * @author glomadrian
7 | */
8 | public interface Interactor extends Runnable {
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/executor/InteractorExecutor.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.executor;
2 |
3 | /**
4 | * Interface for create executors, the executors will only execute Interactors
5 | *
6 | * @author glomadrian
7 | */
8 | public interface InteractorExecutor {
9 |
10 | void run(Interactor interactor);
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/executor/MainThreadExecutor.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.executor;
2 |
3 | /**
4 | * The interactors callbacks are executed in a different thread, in Android the ui thread to perform
5 | * view changes
6 | *
7 | * @author glomadrian
8 | */
9 | public interface MainThreadExecutor {
10 |
11 | void execute(Runnable runnable);
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/executor/MainThreadExecutorImp.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.executor;
2 |
3 |
4 | import android.os.Handler;
5 | import android.os.Looper;
6 |
7 | /**
8 | * Main Thread executor using classic Looper.getMainLooper() from Android
9 | *
10 | * @author glomadrian
11 | */
12 | public class MainThreadExecutorImp implements MainThreadExecutor {
13 |
14 | private Handler handler;
15 |
16 | public MainThreadExecutorImp() {
17 | this.handler = new Handler(Looper.getMainLooper());
18 | }
19 |
20 | @Override
21 | public void execute(Runnable runnable) {
22 | handler.post(runnable);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/executor/ThreadExecutor.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.executor;
2 |
3 | import java.util.concurrent.BlockingQueue;
4 | import java.util.concurrent.LinkedBlockingQueue;
5 | import java.util.concurrent.ThreadPoolExecutor;
6 | import java.util.concurrent.TimeUnit;
7 |
8 | /**
9 | * @author glomadrian
10 | */
11 | public class ThreadExecutor implements InteractorExecutor {
12 |
13 | private static final int CORE_POOL_SIZE = 3;
14 | private static final int MAX_POOL_SIZE = 5;
15 | private static final int KEEP_ALIVE_TIME = 120;
16 | private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;
17 | private static final BlockingQueue WORK_QUEUE = new LinkedBlockingQueue();
18 |
19 | private ThreadPoolExecutor threadPoolExecutor;
20 |
21 | public ThreadExecutor() {
22 | threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TIME_UNIT, WORK_QUEUE);
23 | }
24 |
25 | @Override
26 | public void run(Interactor interactor) {
27 | if (interactor != null) {
28 | threadPoolExecutor.submit(interactor);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/activity/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.activity;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.os.Parcelable;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 |
9 | import com.github.glomadrian.mvpcleanarchitecture.R;
10 | import com.github.glomadrian.mvpcleanarchitecture.app.BaseActivity;
11 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
12 | import com.github.glomadrian.mvpcleanarchitecture.ui.fragment.CharacterInfoFragment;
13 | import com.github.glomadrian.mvpcleanarchitecture.ui.reactive.CharacterSelectedObservable;
14 | import com.github.glomadrian.mvpcleanarchitecture.ui.reactive.CharacterSelectedObserver;
15 |
16 | import org.parceler.Parcels;
17 |
18 | import javax.inject.Inject;
19 |
20 |
21 | /**
22 | * Main activity of the Application, it decide if launch a new activity or update the fragment
23 | * depending of the view inflated
24 | *
25 | * This activity is the container of one fragment if is in portrait and 2 if is in landscape
26 | * The type of the layout inflated will be checked and use for navigation
27 | *
28 | * @author glomadrian
29 | */
30 | public class MainActivity extends BaseActivity implements CharacterSelectedObserver {
31 |
32 |
33 | public static final String TAG_PORTRAIT = "V11-portrait";
34 | public static final String TAG_LANDSCAPE = "V11-landscape";
35 |
36 | @Inject
37 | CharacterSelectedObservable characterSelectedObservable;
38 |
39 |
40 | //The viewTag is the key for the navigation with different sizes
41 | private String viewTag;
42 |
43 | @Override
44 | public void onCreate(Bundle savedInstanceState) {
45 | super.onCreate(savedInstanceState);
46 | View view = LayoutInflater.from(this).inflate(R.layout.main, null);
47 | viewTag = (String) view.getTag();
48 | setContentView(view);
49 | }
50 |
51 | @Override
52 | protected void onResume() {
53 | super.onResume();
54 | characterSelectedObservable.register(this);
55 | }
56 |
57 | @Override
58 | protected void onPause() {
59 | super.onPause();
60 | characterSelectedObservable.unregister(this);
61 | }
62 |
63 | /**
64 | * This method is called when a new marvel character is selected
65 | *
66 | * @param marvelCharacter
67 | */
68 | @Override
69 | public void characterSelected(MarvelCharacter marvelCharacter) {
70 | if (viewTag.equals(TAG_PORTRAIT)) {
71 | launchCharacterInfoActivity(marvelCharacter);
72 | }
73 |
74 | if (viewTag.equals(TAG_LANDSCAPE)) {
75 | replaceCharacterInfoFragment(marvelCharacter);
76 | }
77 | }
78 |
79 | //TODO Change to navigator class with activity context
80 | private void launchCharacterInfoActivity(MarvelCharacter marvelCharacter) {
81 | Intent intent = new Intent(this, ModelInfoActivity.class);
82 | Parcelable parcelable = Parcels.wrap(marvelCharacter);
83 | intent.putExtra(ModelInfoActivity.KEY_CHARACTER, parcelable);
84 | startActivity(intent);
85 | }
86 |
87 | private void replaceCharacterInfoFragment(MarvelCharacter marvelCharacter) {
88 | CharacterInfoFragment characterInfoFragment = CharacterInfoFragment.newInstance(marvelCharacter);
89 |
90 | getFragmentManager()
91 | .beginTransaction()
92 | .replace(R.id.character_info_fragment, characterInfoFragment)
93 | .disallowAddToBackStack()
94 | .commit();
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/activity/ModelInfoActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.activity;
2 |
3 | import android.os.Bundle;
4 | import android.os.Parcelable;
5 |
6 | import com.github.glomadrian.mvpcleanarchitecture.R;
7 | import com.github.glomadrian.mvpcleanarchitecture.app.BaseActivity;
8 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
9 | import com.github.glomadrian.mvpcleanarchitecture.ui.fragment.CharacterInfoFragment;
10 |
11 | import org.parceler.Parcels;
12 |
13 | /**
14 | * @author glomadrian
15 | */
16 | public class ModelInfoActivity extends BaseActivity {
17 |
18 | public static final String KEY_CHARACTER = "character";
19 |
20 | @Override
21 | public void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | setContentView(R.layout.model_info_activity);
24 |
25 | addInfoFragment();
26 | }
27 |
28 | public void addInfoFragment() {
29 |
30 | Parcelable marvelCharacterParcelable = getIntent().getExtras().getParcelable(KEY_CHARACTER);
31 | MarvelCharacter marvelCharacter = Parcels.unwrap(marvelCharacterParcelable);
32 |
33 | CharacterInfoFragment characterInfoFragment = CharacterInfoFragment.newInstance(marvelCharacter);
34 |
35 | getFragmentManager()
36 | .beginTransaction()
37 | .add(R.id.info_frame, characterInfoFragment)
38 | .commit();
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/adapter/ModelAdapter.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.adapter;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.ImageView;
8 | import android.widget.TextView;
9 |
10 | import com.github.glomadrian.mvpcleanarchitecture.R;
11 | import com.github.glomadrian.mvpcleanarchitecture.ui.viewholder.AbstractRecyclerViewHolder;
12 | import com.github.glomadrian.mvpcleanarchitecture.ui.viewmodel.Model;
13 | import com.squareup.picasso.Picasso;
14 |
15 | import java.util.ArrayList;
16 | import java.util.List;
17 |
18 | import butterknife.InjectView;
19 |
20 | /**
21 | * @author glomadrian
22 | */
23 | public class ModelAdapter extends RecyclerView.Adapter {
24 |
25 | private List models;
26 |
27 | public ModelAdapter() {
28 | models = new ArrayList();
29 | }
30 |
31 | public ModelAdapter(List models) {
32 | this.models = models;
33 | }
34 |
35 | @Override
36 |
37 | public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
38 | View modelView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.model, viewGroup, false);
39 | return new ViewHolder(modelView);
40 | }
41 |
42 | @Override
43 | public void onBindViewHolder(ViewHolder viewHolder, int position) {
44 | Model model = models.get(position);
45 |
46 | viewHolder.modelTitle.setText(model.getTitle());
47 | viewHolder.modelSubtitle.setText(model.getSubtitle());
48 |
49 | Picasso.with(viewHolder.view.getContext())
50 | .load(model.getImageUrl())
51 | .into(viewHolder.imageView);
52 | }
53 |
54 | @Override
55 | public int getItemCount() {
56 | return models.size();
57 | }
58 |
59 | public void add(Model model) {
60 | models.add(model);
61 | notifyDataSetChanged();
62 | }
63 |
64 | public void add(List models) {
65 | this.models.addAll(models);
66 | notifyDataSetChanged();
67 | }
68 |
69 | public class ViewHolder extends AbstractRecyclerViewHolder {
70 |
71 | View view;
72 |
73 | @InjectView(R.id.model_title)
74 | TextView modelTitle;
75 | @InjectView(R.id.model_image)
76 | ImageView imageView;
77 | @InjectView(R.id.model_subtitle)
78 | TextView modelSubtitle;
79 |
80 | public ViewHolder(View itemView) {
81 | super(itemView);
82 | this.view = itemView;
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/custom/recycler/ClickItemTouchListener.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.custom.recycler;
2 |
3 | import android.content.Context;
4 | import android.os.Build;
5 | import android.support.v4.view.GestureDetectorCompat;
6 | import android.support.v4.view.MotionEventCompat;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.view.GestureDetector;
9 | import android.view.MotionEvent;
10 | import android.view.View;
11 |
12 | /**
13 | * @author lucasr
14 | * from: https://github.com/lucasr/twoway-view
15 | */
16 | abstract class ClickItemTouchListener implements RecyclerView.OnItemTouchListener {
17 |
18 | private static final int ANDROID_SDK_19 = 19;
19 | private final GestureDetectorCompat mGestureDetector;
20 |
21 | ClickItemTouchListener(RecyclerView hostView) {
22 | mGestureDetector = new ItemClickGestureDetector(hostView.getContext(),
23 | new ItemClickGestureListener(hostView));
24 | }
25 |
26 | private boolean isAttachedToWindow(RecyclerView hostView) {
27 | if (Build.VERSION.SDK_INT >= ANDROID_SDK_19) {
28 | return hostView.isAttachedToWindow();
29 | } else {
30 | return hostView.getHandler() != null;
31 | }
32 | }
33 |
34 | private boolean hasAdapter(RecyclerView hostView) {
35 | return hostView.getAdapter() != null;
36 | }
37 |
38 | @Override
39 | public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) {
40 | if (!isAttachedToWindow(recyclerView) || !hasAdapter(recyclerView)) {
41 | return false;
42 | }
43 |
44 | mGestureDetector.onTouchEvent(event);
45 | return false;
46 | }
47 |
48 | @Override
49 | public void onTouchEvent(RecyclerView recyclerView, MotionEvent event) {
50 | // We can silently track tap and and long presses by silently
51 | // intercepting touch events in the host RecyclerView.
52 | }
53 |
54 | abstract boolean performItemClick(RecyclerView parent, View view, int position, long id);
55 |
56 | abstract boolean performItemLongClick(RecyclerView parent, View view, int position, long id);
57 |
58 | private class ItemClickGestureDetector extends GestureDetectorCompat {
59 | private final ItemClickGestureListener mGestureListener;
60 |
61 | public ItemClickGestureDetector(Context context, ItemClickGestureListener listener) {
62 | super(context, listener);
63 | mGestureListener = listener;
64 | }
65 |
66 | @Override
67 | public boolean onTouchEvent(MotionEvent event) {
68 | final boolean handled = super.onTouchEvent(event);
69 |
70 | final int action = event.getAction() & MotionEventCompat.ACTION_MASK;
71 | if (action == MotionEvent.ACTION_UP) {
72 | mGestureListener.dispatchSingleTapUpIfNeeded(event);
73 | }
74 |
75 | return handled;
76 | }
77 | }
78 |
79 | private class ItemClickGestureListener extends GestureDetector.SimpleOnGestureListener {
80 | private final RecyclerView mHostView;
81 | private View mTargetChild;
82 |
83 | public ItemClickGestureListener(RecyclerView hostView) {
84 | mHostView = hostView;
85 | }
86 |
87 | public void dispatchSingleTapUpIfNeeded(MotionEvent event) {
88 | // When the long press hook is called but the long press listener
89 | // returns false, the target child will be left around to be
90 | // handled later. In this case, we should still treat the gesture
91 | // as potential item click.
92 | if (mTargetChild != null) {
93 | onSingleTapUp(event);
94 | }
95 | }
96 |
97 | @Override
98 | public boolean onDown(MotionEvent event) {
99 | final int x = (int) event.getX();
100 | final int y = (int) event.getY();
101 |
102 | mTargetChild = mHostView.findChildViewUnder(x, y);
103 |
104 | return mTargetChild != null;
105 | }
106 |
107 |
108 | @Override
109 | public void onShowPress(MotionEvent event) {
110 | if (mTargetChild != null) {
111 | mTargetChild.setPressed(true);
112 | }
113 | }
114 |
115 | @Override
116 | public boolean onSingleTapUp(MotionEvent event) {
117 | boolean handled = false;
118 |
119 | if (mTargetChild != null) {
120 | mTargetChild.setPressed(false);
121 |
122 | final int position = mHostView.getChildPosition(mTargetChild);
123 | final long id = mHostView.getAdapter().getItemId(position);
124 | handled = performItemClick(mHostView, mTargetChild, position, id);
125 |
126 | mTargetChild = null;
127 | }
128 |
129 | return handled;
130 | }
131 |
132 | @Override
133 | public boolean onScroll(MotionEvent event, MotionEvent event2, float v, float v2) {
134 | if (mTargetChild != null) {
135 | mTargetChild.setPressed(false);
136 | mTargetChild = null;
137 |
138 | return true;
139 | }
140 |
141 | return false;
142 | }
143 |
144 | @Override
145 | public void onLongPress(MotionEvent event) {
146 | if (mTargetChild == null) {
147 | return;
148 | }
149 |
150 | final int position = mHostView.getChildPosition(mTargetChild);
151 | final long id = mHostView.getAdapter().getItemId(position);
152 | final boolean handled = performItemLongClick(mHostView, mTargetChild, position, id);
153 |
154 | if (handled) {
155 | mTargetChild.setPressed(false);
156 | mTargetChild = null;
157 | }
158 | }
159 | }
160 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/custom/recycler/ClickRecyclerView.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.custom.recycler;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.util.AttributeSet;
6 | import android.view.HapticFeedbackConstants;
7 | import android.view.SoundEffectConstants;
8 | import android.view.View;
9 |
10 | /**
11 | * From: From: https://github.com/lucasr/twoway-view/
12 | * Custom recycler view adding itemClickListener support
13 | *
14 | * @author glomadrian
15 | */
16 | public class ClickRecyclerView extends RecyclerView {
17 |
18 |
19 | private TouchListener mTouchListener;
20 |
21 | private OnItemClickListener mItemClickListener;
22 | private OnItemLongClickListener mItemLongClickListener;
23 |
24 |
25 | public ClickRecyclerView(Context context) {
26 | super(context);
27 | init();
28 | }
29 |
30 | public ClickRecyclerView(Context context, AttributeSet attrs) {
31 | super(context, attrs);
32 | init();
33 | }
34 |
35 | public ClickRecyclerView(Context context, AttributeSet attrs, int defStyle) {
36 | super(context, attrs, defStyle);
37 | init();
38 | }
39 |
40 | private void init() {
41 | mTouchListener = new TouchListener(this);
42 | addOnItemTouchListener(mTouchListener);
43 | }
44 |
45 | /**
46 | * Register a callback to be invoked when an item in the
47 | * RecyclerView has been clicked.
48 | *
49 | * @param listener The callback that will be invoked.
50 | */
51 | public void setOnItemClickListener(OnItemClickListener listener) {
52 | mItemClickListener = listener;
53 | }
54 |
55 | /**
56 | * Register a callback to be invoked when an item in the
57 | * RecyclerView has been clicked and held.
58 | *
59 | * @param listener The callback that will be invoked.
60 | */
61 | public void setOnItemLongClickListener(OnItemLongClickListener listener) {
62 | if (!isLongClickable()) {
63 | setLongClickable(true);
64 | }
65 |
66 | mItemLongClickListener = listener;
67 | }
68 |
69 | /**
70 | * Interface definition for a callback to be invoked when an item in the
71 | * RecyclerView has been clicked.
72 | */
73 | public interface OnItemClickListener {
74 | /**
75 | * Callback method to be invoked when an item in the RecyclerView
76 | * has been clicked.
77 | *
78 | * @param parent The RecyclerView where the click happened.
79 | * @param view The view within the RecyclerView that was clicked
80 | * @param position The position of the view in the adapter.
81 | * @param id The row id of the item that was clicked.
82 | */
83 | void onItemClick(RecyclerView parent, View view, int position, long id);
84 | }
85 |
86 | /**
87 | * Interface definition for a callback to be invoked when an item in the
88 | * RecyclerView has been clicked and held.
89 | */
90 | public interface OnItemLongClickListener {
91 | /**
92 | * Callback method to be invoked when an item in the RecyclerView
93 | * has been clicked and held.
94 | *
95 | * @param parent The RecyclerView where the click happened
96 | * @param view The view within the RecyclerView that was clicked
97 | * @param position The position of the view in the list
98 | * @param id The row id of the item that was clicked
99 | * @return true if the callback consumed the long click, false otherwise
100 | */
101 | boolean onItemLongClick(RecyclerView parent, View view, int position, long id);
102 | }
103 |
104 | private class TouchListener extends ClickItemTouchListener {
105 | TouchListener(RecyclerView recyclerView) {
106 | super(recyclerView);
107 | }
108 |
109 | @Override
110 | boolean performItemClick(RecyclerView parent, View view, int position, long id) {
111 | if (mItemClickListener != null) {
112 | view.playSoundEffect(SoundEffectConstants.CLICK);
113 | mItemClickListener.onItemClick(parent, view, position, id);
114 | return true;
115 | }
116 |
117 | return false;
118 | }
119 |
120 | @Override
121 | boolean performItemLongClick(RecyclerView parent, View view, int position, long id) {
122 | if (mItemLongClickListener != null) {
123 | view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
124 | return mItemLongClickListener.onItemLongClick(parent, view, position, id);
125 | }
126 |
127 | return false;
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/fragment/CharacterInfoFragment.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.fragment;
2 |
3 | import android.os.Bundle;
4 | import android.os.Parcelable;
5 | import android.support.annotation.Nullable;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ImageView;
10 | import android.widget.TextView;
11 |
12 | import com.github.glomadrian.mvpcleanarchitecture.R;
13 | import com.github.glomadrian.mvpcleanarchitecture.app.BaseFragment;
14 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
15 | import com.github.glomadrian.mvpcleanarchitecture.ui.presenter.CharacterInfoPresenter;
16 | import com.github.glomadrian.mvpcleanarchitecture.ui.view.ModelInfoView;
17 | import com.github.glomadrian.mvpcleanarchitecture.ui.viewmodel.CharacterInfoViewModel;
18 | import com.squareup.picasso.Picasso;
19 |
20 | import org.parceler.Parcels;
21 |
22 | import javax.inject.Inject;
23 |
24 | import butterknife.InjectView;
25 | import butterknife.Optional;
26 |
27 | /**
28 | * @author glomadrian
29 | */
30 | public class CharacterInfoFragment extends BaseFragment implements ModelInfoView {
31 |
32 | public static final String KEY_CHARACTER = "character";
33 |
34 | @Inject
35 | CharacterInfoPresenter characterInfoPresenter;
36 | @Optional
37 | @InjectView(R.id.info_image)
38 | ImageView infoImage;
39 | @InjectView(R.id.info_description)
40 | TextView infoDescription;
41 | @InjectView(R.id.info_title)
42 | TextView infoTitle;
43 | @InjectView(R.id.info_number_1)
44 | TextView infoNumber1;
45 | @InjectView(R.id.info_number_2)
46 | TextView infoNumber2;
47 | @InjectView(R.id.info_number_3)
48 | TextView infoNumber3;
49 |
50 | private String tag;
51 |
52 | public static CharacterInfoFragment newInstance(MarvelCharacter marvelCharacter) {
53 |
54 | Bundle args = new Bundle();
55 | Parcelable marvelCharacterParcel = Parcels.wrap(marvelCharacter);
56 | args.putParcelable(KEY_CHARACTER, marvelCharacterParcel);
57 |
58 | CharacterInfoFragment characterInfoFragment = new CharacterInfoFragment();
59 | characterInfoFragment.setArguments(args);
60 | return characterInfoFragment;
61 | }
62 |
63 | @Override
64 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
65 | View view = inflater.inflate(R.layout.model_info, container, false);
66 | this.tag = String.valueOf(view.getTag());
67 | return view;
68 | }
69 |
70 | @Override
71 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
72 | super.onViewCreated(view, savedInstanceState);
73 | characterInfoPresenter.onViewCreate();
74 | characterInfoPresenter.setView(this);
75 |
76 | //Get character info
77 | if (getArguments() != null) {
78 | MarvelCharacter marvelCharacter = getMarvelCharacterFromArgs();
79 | characterInfoPresenter.onCharacter(marvelCharacter);
80 | }
81 | }
82 |
83 | @Override
84 | public void showCharacterInfo(CharacterInfoViewModel characterInfoViewModel) {
85 |
86 | if (tag.equals("character_info_portrait")) {
87 | Picasso.with(getActivity()).load(characterInfoViewModel.getInfoImageUrl()).into(infoImage);
88 | }
89 |
90 | infoNumber1.setText(characterInfoViewModel.getInfoNumber1());
91 | infoNumber2.setText(characterInfoViewModel.getInfoNumber2());
92 | infoNumber3.setText(characterInfoViewModel.getInfoNumber3());
93 | infoDescription.setText(characterInfoViewModel.getInfoDescription());
94 | infoTitle.setText(characterInfoViewModel.getInfoTitle());
95 | }
96 |
97 | private MarvelCharacter getMarvelCharacterFromArgs() {
98 | Parcelable marvelCharacterParcelable = getArguments().getParcelable(KEY_CHARACTER);
99 | return Parcels.unwrap(marvelCharacterParcelable);
100 | }
101 |
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/fragment/CharacterListFragment.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.fragment;
2 |
3 | import android.os.Bundle;
4 | import android.os.Parcelable;
5 | import android.support.annotation.Nullable;
6 | import android.support.v7.widget.DefaultItemAnimator;
7 | import android.support.v7.widget.LinearLayoutManager;
8 | import android.support.v7.widget.RecyclerView;
9 | import android.util.Log;
10 | import android.view.LayoutInflater;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 | import android.widget.ProgressBar;
14 | import android.widget.Toast;
15 |
16 | import com.github.glomadrian.mvpcleanarchitecture.R;
17 | import com.github.glomadrian.mvpcleanarchitecture.app.BaseFragment;
18 | import com.github.glomadrian.mvpcleanarchitecture.domain.LogUtils;
19 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacterList;
20 | import com.github.glomadrian.mvpcleanarchitecture.ui.adapter.ModelAdapter;
21 | import com.github.glomadrian.mvpcleanarchitecture.ui.custom.recycler.ClickRecyclerView;
22 | import com.github.glomadrian.mvpcleanarchitecture.ui.presenter.CharacterListPresenter;
23 | import com.github.glomadrian.mvpcleanarchitecture.ui.view.CharacterListView;
24 | import com.github.glomadrian.mvpcleanarchitecture.ui.viewmodel.Model;
25 |
26 | import org.parceler.Parcels;
27 |
28 | import java.util.List;
29 |
30 | import javax.inject.Inject;
31 |
32 | import butterknife.InjectView;
33 |
34 | /**
35 | * @author glomadrian
36 | */
37 | public class CharacterListFragment extends BaseFragment implements CharacterListView {
38 |
39 | private static final String EXTRA_CHARACTER_COLLECTION = "extraCharacterCollection";
40 |
41 | @Inject
42 | CharacterListPresenter characterCollectionPresenter;
43 |
44 | @InjectView(R.id.collection_view)
45 | ClickRecyclerView collectionView;
46 | @InjectView(R.id.loading)
47 | ProgressBar loading;
48 |
49 | private ModelAdapter modelAdapter;
50 | private LinearLayoutManager mLayoutManager;
51 |
52 | @Override
53 | public void onCreate(Bundle savedInstanceState) {
54 | Log.i(LogUtils.generateTag(this), "on crate()");
55 | super.onCreate(savedInstanceState);
56 | modelAdapter = new ModelAdapter();
57 | mLayoutManager = new LinearLayoutManager(getActivity());
58 | }
59 |
60 | @Override
61 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
62 | return inflater.inflate(R.layout.character_list, container, false);
63 | }
64 |
65 | @Override
66 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
67 | super.onViewCreated(view, savedInstanceState);
68 | initializeCollectionView();
69 | characterCollectionPresenter.setView(this);
70 | characterCollectionPresenter.onViewCreate();
71 |
72 | if (savedInstanceState == null) {
73 | Log.i(LogUtils.generateTag(this), "First time running");
74 | characterCollectionPresenter.initialize();
75 | }
76 |
77 | addClickListenerToCharacterList();
78 |
79 | }
80 |
81 |
82 | /**
83 | * In android the view is not a simple view, there is some cases when the functionality of the
84 | * view is more than the excepted, in this case for example the view save the state
85 | *
86 | * @param outState
87 | */
88 | @Override
89 | public void onSaveInstanceState(Bundle outState) {
90 | super.onSaveInstanceState(outState);
91 |
92 | //Get the actual state of the characters
93 | MarvelCharacterList marvelCharacters = characterCollectionPresenter.getParcelableCollection();
94 |
95 | //Parcel the object to be saved in the bundle
96 | Parcelable marvelCharactersWrapped = Parcels.wrap(marvelCharacters);
97 |
98 | //Save the parcelable
99 | outState.putParcelable(EXTRA_CHARACTER_COLLECTION, marvelCharactersWrapped);
100 | }
101 |
102 | @Override
103 | public void onViewStateRestored(Bundle savedInstanceState) {
104 | super.onViewStateRestored(savedInstanceState);
105 |
106 | if (savedInstanceState != null) {
107 | Log.i(LogUtils.generateTag(this), "onViewStateRestored");
108 | //Get parcelable from bundle
109 | Parcelable marvelCharactersWrapped = savedInstanceState.getParcelable(EXTRA_CHARACTER_COLLECTION);
110 | MarvelCharacterList marvelCharacters = Parcels.unwrap(marvelCharactersWrapped);
111 | characterCollectionPresenter.restoreParcelableCollection(marvelCharacters);
112 | }
113 | }
114 |
115 | private void initializeCollectionView() {
116 | collectionView.setAdapter(modelAdapter);
117 | collectionView.setLayoutManager(mLayoutManager);
118 | collectionView.setItemAnimator(new DefaultItemAnimator());
119 | }
120 |
121 | @Override
122 | public void add(Model model) {
123 | modelAdapter.add(model);
124 | }
125 |
126 | @Override
127 | public void add(List models) {
128 | modelAdapter.add(models);
129 | }
130 |
131 | @Override
132 | public void remove(Model model) {
133 | //TODO implement
134 | }
135 |
136 | @Override
137 | public int getModelsRenderer() {
138 | return modelAdapter.getItemCount();
139 | }
140 |
141 | @Override
142 | public void showLoading() {
143 | loading.setVisibility(View.VISIBLE);
144 | }
145 |
146 | @Override
147 | public void hideLoading() {
148 | loading.setVisibility(View.GONE);
149 | }
150 |
151 |
152 | @Override
153 | public void activateLastCharacterViewListener() {
154 | enableSearchOnFinish();
155 | }
156 |
157 | @Override
158 | public void disableLastCharacterViewListener() {
159 | disableSearchOnFinish();
160 | }
161 |
162 | @Override
163 | public void onError() {
164 | Toast.makeText(this.getActivity(), getString(R.string.genericError), Toast.LENGTH_LONG).show();
165 | }
166 |
167 | private void addClickListenerToCharacterList() {
168 | collectionView.setOnItemClickListener(new CharacterClickListener());
169 | }
170 |
171 | private void enableSearchOnFinish() {
172 | collectionView.setOnScrollListener(new FinishScrollListener());
173 | }
174 |
175 | private void disableSearchOnFinish() {
176 | collectionView.setOnScrollListener(null);
177 | }
178 |
179 | private class FinishScrollListener extends RecyclerView.OnScrollListener {
180 | @Override
181 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
182 | int lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition() + 1;
183 | int modelsCount = modelAdapter.getItemCount();
184 |
185 | if (lastVisibleItemPosition == modelsCount) {
186 | Log.i(LogUtils.generateTag(this), "finish scroll!");
187 | characterCollectionPresenter.onLastCharacterViewed();
188 | }
189 | }
190 | }
191 |
192 | private class CharacterClickListener implements ClickRecyclerView.OnItemClickListener {
193 |
194 | @Override
195 | public void onItemClick(RecyclerView parent, View view, int position, long id) {
196 | characterCollectionPresenter.onCharacterSelected(position);
197 | }
198 | }
199 |
200 | }
201 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/presenter/CharacterInfoPresenter.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.presenter;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
4 | import com.github.glomadrian.mvpcleanarchitecture.ui.view.ModelInfoView;
5 |
6 | /**
7 | * @author glomadrian
8 | */
9 | public interface CharacterInfoPresenter extends Presenter {
10 |
11 | /**
12 | * When the view is created it will recibe a marvel character into the bundle, then
13 | * call the presenter
14 | *
15 | * @param marvelCharacter
16 | */
17 | void onCharacter(MarvelCharacter marvelCharacter);
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/presenter/CharacterInfoPresenterImp.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.presenter;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
4 | import com.github.glomadrian.mvpcleanarchitecture.ui.view.ModelInfoView;
5 | import com.github.glomadrian.mvpcleanarchitecture.ui.viewmodel.CharacterInfoViewModel;
6 |
7 | /**
8 | * @author glomadrian
9 | */
10 | public class CharacterInfoPresenterImp implements CharacterInfoPresenter {
11 |
12 | ModelInfoView modelInfoView;
13 |
14 |
15 | @Override
16 | public void initialize() {
17 | //Do nothing
18 | }
19 |
20 | @Override
21 | public void onViewCreate() {
22 | //Do nothing
23 | }
24 |
25 | @Override
26 | public void onViewResume() {
27 | //Do nothing
28 | }
29 |
30 | @Override
31 | public void onViewDestroy() {
32 | //Do nothing
33 | }
34 |
35 | @Override
36 | public void setView(ModelInfoView view) {
37 | this.modelInfoView = view;
38 | }
39 |
40 | @Override
41 | public void onCharacter(MarvelCharacter marvelCharacter) {
42 | //Parse the character to characterInfoModelView and call the view to show
43 | CharacterInfoViewModel characterInfoViewModel = new CharacterInfoViewModel(marvelCharacter);
44 | modelInfoView.showCharacterInfo(characterInfoViewModel);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/presenter/CharacterListPresenter.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.presenter;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacterList;
4 | import com.github.glomadrian.mvpcleanarchitecture.ui.view.CharacterListView;
5 |
6 |
7 | /**
8 | * @author glomadrian
9 | */
10 | public interface CharacterListPresenter extends Presenter {
11 |
12 |
13 | void onLastCharacterViewed();
14 |
15 | MarvelCharacterList getParcelableCollection();
16 |
17 | void restoreParcelableCollection(MarvelCharacterList marvelCharacters);
18 |
19 | void onCharacterSelected(int position);
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/presenter/CharacterListPresenterImp.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.presenter;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 |
6 | import com.github.glomadrian.mvpcleanarchitecture.app.BasePresenter;
7 | import com.github.glomadrian.mvpcleanarchitecture.domain.LogUtils;
8 | import com.github.glomadrian.mvpcleanarchitecture.domain.interactor.GetMarvelCharactersLimit;
9 | import com.github.glomadrian.mvpcleanarchitecture.domain.interactor.GetMarvelCharactersPaginated;
10 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
11 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacterList;
12 | import com.github.glomadrian.mvpcleanarchitecture.ui.reactive.CharacterSelectedObservable;
13 | import com.github.glomadrian.mvpcleanarchitecture.ui.view.CharacterListView;
14 | import com.github.glomadrian.mvpcleanarchitecture.ui.viewmodel.CharacterViewModel;
15 | import com.github.glomadrian.mvpcleanarchitecture.ui.viewmodel.Model;
16 |
17 | import java.util.ArrayList;
18 | import java.util.Collection;
19 | import java.util.List;
20 |
21 | /**
22 | * @author glomadrian
23 | */
24 | public class CharacterListPresenterImp extends BasePresenter implements CharacterListPresenter {
25 |
26 | private static final int LIMIT_GET_CHARACTERS = 10;
27 | private static final int LIMIT_MORE_CHARACTERS = 5;
28 |
29 | private CharacterListView modelCollectionView;
30 | private GetMarvelCharactersLimit getMarvelCharactersLimit;
31 | private GetMarvelCharactersPaginated getMarvelCharactersPaginated;
32 | private MarvelCharacterList marvelCharacterCollection;
33 | private CharacterSelectedObservable characterSelectedObservable;
34 | private Context context;
35 |
36 | public CharacterListPresenterImp(Context context, GetMarvelCharactersLimit getMarvelCharactersLimit, GetMarvelCharactersPaginated getMarvelCharactersPaginated, CharacterSelectedObservable characterSelectedObservable) {
37 | super(context);
38 | this.context = context;
39 | this.getMarvelCharactersLimit = getMarvelCharactersLimit;
40 | this.getMarvelCharactersPaginated = getMarvelCharactersPaginated;
41 | this.characterSelectedObservable = characterSelectedObservable;
42 | }
43 |
44 | @Override
45 | public void initialize() {
46 | marvelCharacterCollection = new MarvelCharacterList();
47 | searchForCharacters();
48 | }
49 |
50 | @Override
51 | public void onViewCreate() {
52 | modelCollectionView.activateLastCharacterViewListener();
53 | }
54 |
55 | @Override
56 | public void onViewResume() {
57 | //Do nothing
58 | }
59 |
60 | @Override
61 | public void onViewDestroy() {
62 | //Do nothing
63 | }
64 |
65 | @Override
66 | public void setView(CharacterListView view) {
67 | this.modelCollectionView = view;
68 | }
69 |
70 | @Override
71 | public void onLastCharacterViewed() {
72 | searchForMoreCharacters();
73 | }
74 |
75 | @Override
76 | public MarvelCharacterList getParcelableCollection() {
77 | return marvelCharacterCollection;
78 | }
79 |
80 | @Override
81 | public void restoreParcelableCollection(MarvelCharacterList marvelCharacters) {
82 | this.marvelCharacterCollection = marvelCharacters;
83 | modelCollectionView.add(convertToModelViewList(marvelCharacters.getMarvelCharacters()));
84 | }
85 |
86 | @Override
87 | public void onCharacterSelected(int position) {
88 | Collection marvelCharacters = marvelCharacterCollection.getMarvelCharacters();
89 | MarvelCharacter marvelCharacter = (MarvelCharacter) marvelCharacters.toArray()[position];
90 | characterSelectedObservable.notifyObservers(marvelCharacter);
91 | }
92 |
93 | private void searchForCharacters() {
94 |
95 | getMarvelCharactersLimit.execute(LIMIT_GET_CHARACTERS, new GetMarvelCharactersLimit.Callback() {
96 | @Override
97 | public void onMarvelCharacterList(List marvelCharacters) {
98 | marvelCharacterCollection.addAll(marvelCharacters);
99 | modelCollectionView.add(convertToModelViewList(marvelCharacters));
100 | }
101 |
102 | @Override
103 | public void onError() {
104 | Log.e(LogUtils.generateTag(this), "Error on interactor getMarvelCharactersLimit");
105 | modelCollectionView.onError();
106 | }
107 | });
108 | }
109 |
110 | private void searchForMoreCharacters() {
111 |
112 | modelCollectionView.disableLastCharacterViewListener();
113 | modelCollectionView.showLoading();
114 |
115 | getMarvelCharactersPaginated.execute(LIMIT_MORE_CHARACTERS, modelCollectionView.getModelsRenderer(), new GetMarvelCharactersPaginated.Callback() {
116 | @Override
117 | public void onMarvelCharacterList(List marvelCharacters) {
118 | marvelCharacterCollection.addAll(marvelCharacters);
119 | modelCollectionView.add(convertToModelViewList(marvelCharacters));
120 | modelCollectionView.hideLoading();
121 | modelCollectionView.activateLastCharacterViewListener();
122 | }
123 |
124 | @Override
125 | public void onError() {
126 | Log.e(LogUtils.generateTag(this), "Error on interactor getMarvelCharactersPaginated");
127 | modelCollectionView.hideLoading();
128 | modelCollectionView.activateLastCharacterViewListener();
129 | modelCollectionView.onError();
130 | }
131 | });
132 | }
133 |
134 | private List convertToModelViewList(List marvelCharacters) {
135 |
136 |
137 | List modelList = new ArrayList();
138 |
139 | for (MarvelCharacter marvelCharacter : marvelCharacters) {
140 | modelList.add(new CharacterViewModel(marvelCharacter));
141 | }
142 |
143 | return modelList;
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/presenter/Presenter.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.presenter;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.ui.view.View;
4 |
5 | /**
6 | * Presenter with lifecycle
7 | *
8 | * @author glomadrian
9 | */
10 | public interface Presenter {
11 |
12 | void initialize();
13 |
14 | void onViewCreate();
15 |
16 | void onViewResume();
17 |
18 | void onViewDestroy();
19 |
20 | void setView(T view);
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/reactive/CharacterSelectedObservable.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.reactive;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | /**
9 | * Observable to character selected, this observable call all observer subscribed when a
10 | * character is selected
11 | *
12 | * @author glomadrian
13 | */
14 | public class CharacterSelectedObservable implements Observable {
15 |
16 | List characterSelectedObservers;
17 |
18 | public CharacterSelectedObservable() {
19 | characterSelectedObservers = new ArrayList();
20 | }
21 |
22 |
23 | @Override
24 | public void register(CharacterSelectedObserver observer) {
25 | //To avoid duplicated register
26 | if (!characterSelectedObservers.contains(observer)) {
27 | characterSelectedObservers.add(observer);
28 | }
29 | }
30 |
31 | @Override
32 | public void unregister(CharacterSelectedObserver observer) {
33 | characterSelectedObservers.remove(observer);
34 | }
35 |
36 |
37 | public void notifyObservers(MarvelCharacter marvelCharacter) {
38 | for (CharacterSelectedObserver characterSelectedObserver : characterSelectedObservers) {
39 | characterSelectedObserver.characterSelected(marvelCharacter);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/reactive/CharacterSelectedObserver.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.reactive;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
4 |
5 | /**
6 | * @author glomadrian
7 | */
8 | public interface CharacterSelectedObserver {
9 |
10 | void characterSelected(MarvelCharacter marvelCharacter);
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/reactive/Observable.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.reactive;
2 |
3 | /**
4 | * @author glomadrian
5 | */
6 | public interface Observable {
7 |
8 | void register(T observer);
9 |
10 | void unregister(T observer);
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/view/CharacterListView.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.view;
2 |
3 | /**
4 | * @author glomadrian
5 | */
6 | public interface CharacterListView extends ModelListView {
7 |
8 | int getModelsRenderer();
9 |
10 | void showLoading();
11 |
12 | void hideLoading();
13 |
14 | void activateLastCharacterViewListener();
15 |
16 | void disableLastCharacterViewListener();
17 |
18 | void onError();
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/view/ModelInfoView.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.view;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.ui.viewmodel.CharacterInfoViewModel;
4 |
5 | /**
6 | * @author glomadrian
7 | */
8 | public interface ModelInfoView extends View {
9 |
10 | void showCharacterInfo(CharacterInfoViewModel characterInfoViewModel);
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/view/ModelListView.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.view;
2 |
3 |
4 | import com.github.glomadrian.mvpcleanarchitecture.ui.viewmodel.Model;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * Model collection view is designed to be used in different scenarios, in this concrete case
10 | * the collection is of characters but can be of Users or Animals, but the view can be the same
11 | *
12 | * @author glomadrian
13 | */
14 | public interface ModelListView extends View {
15 |
16 |
17 | void add(Model model);
18 |
19 | void add(List models);
20 |
21 | void remove(Model model);
22 |
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/view/View.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.view;
2 |
3 | /**
4 | * @author glomadrian
5 | */
6 | public interface View {
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/viewholder/AbstractRecyclerViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.viewholder;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.view.View;
5 |
6 | import butterknife.ButterKnife;
7 |
8 | /**
9 | * ViewHolder for inject views using ButterKnife
10 | *
11 | * @author glomadrian
12 | */
13 | public abstract class AbstractRecyclerViewHolder extends RecyclerView.ViewHolder {
14 |
15 |
16 | public AbstractRecyclerViewHolder(View itemView) {
17 | super(itemView);
18 | ButterKnife.inject(this, itemView);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/viewmodel/CharacterInfoViewModel.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.viewmodel;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
4 |
5 | /**
6 | * Custom implementation for use with the view ModelInfoView, in this case the model is a Character
7 | *
8 | * @author glomadrian
9 | */
10 | public class CharacterInfoViewModel extends ModelInfo {
11 |
12 | private MarvelCharacter marvelCharacter;
13 |
14 | public CharacterInfoViewModel(MarvelCharacter model) {
15 | marvelCharacter = model;
16 | }
17 |
18 | @Override
19 | public String getInfoImageUrl() {
20 | return marvelCharacter.getImageURL();
21 | }
22 |
23 | @Override
24 | public String getInfoNumber1() {
25 | return String.valueOf(marvelCharacter.getComics());
26 | }
27 |
28 | @Override
29 | public String getInfoNumber2() {
30 | return String.valueOf(marvelCharacter.getComics());
31 | }
32 |
33 | @Override
34 | public String getInfoNumber3() {
35 | return String.valueOf(marvelCharacter.getStories());
36 | }
37 |
38 | @Override
39 | public String getInfoDescription() {
40 | return String.valueOf(marvelCharacter.getDescription());
41 | }
42 |
43 | @Override
44 | public String getInfoTitle() {
45 | return String.valueOf(marvelCharacter.getName());
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/viewmodel/CharacterViewModel.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.viewmodel;
2 |
3 | import com.github.glomadrian.mvpcleanarchitecture.domain.model.MarvelCharacter;
4 |
5 | /**
6 | * Custom implementation of a model to use with ModelCollectionView
7 | *
8 | * @author glomadrian
9 | */
10 | public class CharacterViewModel extends Model {
11 |
12 | MarvelCharacter model;
13 |
14 | public CharacterViewModel(MarvelCharacter model) {
15 | this.model = model;
16 | }
17 |
18 | @Override
19 | public String getImageUrl() {
20 | return model.getImageURL();
21 | }
22 |
23 | @Override
24 | public String getTitle() {
25 | return model.getName();
26 | }
27 |
28 | @Override
29 | public String getSubtitle() {
30 | return String.valueOf(model.getId());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/viewmodel/Model.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.viewmodel;
2 |
3 | /**
4 | * This view model is a adapter from other model, usually a domain model, if in any moment the
5 | * application need to show other model we only need to create a new adapter for a viewmodel and the
6 | * view continue working
7 | *
8 | * This abstract model is for use with model.xml in layouts (or other with the same information)
9 | *
10 | * @author glomadrian
11 | */
12 | public abstract class Model {
13 |
14 | public abstract String getImageUrl();
15 |
16 | public abstract String getTitle();
17 |
18 | public abstract String getSubtitle();
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/glomadrian/mvpcleanarchitecture/ui/viewmodel/ModelInfo.java:
--------------------------------------------------------------------------------
1 | package com.github.glomadrian.mvpcleanarchitecture.ui.viewmodel;
2 |
3 | /**
4 | * Custom model for the view CharacterInfoView
5 | *
6 | * @author glomadrian
7 | */
8 | public abstract class ModelInfo {
9 |
10 | public abstract String getInfoImageUrl();
11 |
12 | public abstract String getInfoNumber1();
13 |
14 | public abstract String getInfoNumber2();
15 |
16 | public abstract String getInfoNumber3();
17 |
18 | public abstract String getInfoDescription();
19 |
20 | public abstract String getInfoTitle();
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glomadrian/MvpCleanArchitecture/24009eaf8310e907cf5f61b169dabe1c28f362a3/app/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/spider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glomadrian/MvpCleanArchitecture/24009eaf8310e907cf5f61b169dabe1c28f362a3/app/src/main/res/drawable-hdpi/spider.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/green_circle.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glomadrian/MvpCleanArchitecture/24009eaf8310e907cf5f61b169dabe1c28f362a3/app/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/yellow_circle.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glomadrian/MvpCleanArchitecture/24009eaf8310e907cf5f61b169dabe1c28f362a3/app/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/marvel.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glomadrian/MvpCleanArchitecture/24009eaf8310e907cf5f61b169dabe1c28f362a3/app/src/main/res/drawable-xhdpi/marvel.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glomadrian/MvpCleanArchitecture/24009eaf8310e907cf5f61b169dabe1c28f362a3/app/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glomadrian/MvpCleanArchitecture/24009eaf8310e907cf5f61b169dabe1c28f362a3/app/src/main/res/drawable-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/blue_circle.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/line.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 | -
13 |
14 |
15 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
18 |
19 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/model_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
17 |
23 |
24 |
31 |
32 |
39 |
40 |
46 |
47 |
48 |
49 |
50 |
57 |
58 |
65 |
66 |
72 |
73 |
74 |
75 |
82 |
83 |
90 |
91 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
107 |
108 |
112 |
113 |
127 |
128 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/character_list.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
13 |
14 |
15 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/model.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
16 |
17 |
20 |
21 |
26 |
27 |
32 |
33 |
42 |
43 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/model_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
31 |
32 |
38 |
39 |
44 |
45 |
52 |
53 |
60 |
61 |
67 |
68 |
69 |
70 |
71 |
78 |
79 |
86 |
87 |
93 |
94 |
95 |
96 |
103 |
104 |
111 |
112 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
128 |
129 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/model_info_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | - #2196F3
6 | - #1565C0
7 | - #F50057
8 | - #FFFF00
9 |
10 |
11 | - #4CAF50
12 | - #F4FF81
13 | - #FFEB3B
14 |
15 |
16 |
17 | - @color/material_blue_500
18 | - @color/material_blue_800
19 |
20 | - #58000000
21 | - #FFF
22 |
23 | @color/material_blue_500
24 | @color/material_green_500
25 | @color/material_yellow_500
26 | @color/material_blue_800
27 | #FFF
28 | @color/material_blue_500
29 | #fff
30 | #333333
31 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MVP Clean Architecture
3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc sagittis faucibus rhoncus. Donec dignissim finibus diam nec sollicitudin. Nullam sed leo nec felis imperdiet maximus in mattis lacus. Phasellus eget quam ut metus commodo fermentum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec condimentum ultricies risus, et ultricies leo sollicitudin sed. In hac habitasse platea dictumst. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse in lacinia risus, sed mattis justo. Sed a quam laoreet nisl efficitur eleifend nec quis nisi.
4 | Comics
5 | Series
6 | Histories
7 | No description avaiable
8 | Error getting api information
9 | Info image
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.0.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glomadrian/MvpCleanArchitecture/24009eaf8310e907cf5f61b169dabe1c28f362a3/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Dec 13 16:53:24 CET 2014
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/img/screenshot_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glomadrian/MvpCleanArchitecture/24009eaf8310e907cf5f61b169dabe1c28f362a3/img/screenshot_1.png
--------------------------------------------------------------------------------
/img/screenshot_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glomadrian/MvpCleanArchitecture/24009eaf8310e907cf5f61b169dabe1c28f362a3/img/screenshot_2.png
--------------------------------------------------------------------------------
/img/screenshot_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glomadrian/MvpCleanArchitecture/24009eaf8310e907cf5f61b169dabe1c28f362a3/img/screenshot_3.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 | equired metadata
2 | sonar.projectKey=MvpCleanArchitecture
3 | sonar.projectName=MvpCleanArchitecture
4 | sonar.projectVersion=1.0
5 |
6 | # Path to the parent source code directory.
7 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
8 | # Since SonarQube 4.2, this property is optional if sonar.modules is set.
9 | # If not set, SonarQube starts looking for source code from the directory containing
10 | # the sonar-project.properties file.
11 | # Language
12 | sonar.language=java
13 | sonar.sources=app/src
14 |
--------------------------------------------------------------------------------