├── .gitignore
├── .idea
├── runConfigurations.xml
└── runConfigurations
│ ├── All_tests_at_once.xml
│ ├── App_and_modules_tests.xml
│ ├── Base_tests.xml
│ ├── Repositories_tests.xml
│ └── Repository_tests.xml
├── .travis.yml
├── README.md
├── app
├── build.gradle
├── proguard-rules.pro
├── proguard-test-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── frogermcs
│ │ └── multimodulegithubclient
│ │ └── endtoend
│ │ └── AppFlowTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── frogermcs
│ │ │ └── multimodulegithubclient
│ │ │ ├── App.java
│ │ │ ├── AppComponent.java
│ │ │ ├── AppComponentWrapper.java
│ │ │ ├── AppScope.java
│ │ │ ├── GithubClientModule.java
│ │ │ ├── SplashActivity.java
│ │ │ ├── SplashActivityComponent.java
│ │ │ ├── SplashActivityModule.java
│ │ │ └── SplashActivityPresenter.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── activity_splash.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── frogermcs
│ └── multimodulegithubclient
│ └── SplashActivityPresenterTest.java
├── build.gradle
├── buildsystem
├── android_commons.gradle
├── dependencies.gradle
└── jacoco.gradle
├── docs
└── img
│ ├── all_tests_sequential.png
│ ├── app_diagram.png
│ ├── as_run_configurations.png
│ ├── coverage_report.png
│ ├── dagger_diagram.png
│ ├── failing_all_tests.png
│ ├── instrumentation_report_example.png
│ ├── no-proguard.png
│ └── with-proguard.png
├── features
├── base
│ ├── build.gradle
│ ├── feature-base-proguard-rules.pro
│ └── src
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── frogermcs
│ │ │ │ └── multimodulegithubclient
│ │ │ │ └── base
│ │ │ │ ├── ActivityScope.java
│ │ │ │ ├── BaseActivity.java
│ │ │ │ ├── BaseComponent.java
│ │ │ │ ├── BaseComponentWrapper.java
│ │ │ │ ├── BaseModule.java
│ │ │ │ ├── data
│ │ │ │ ├── api
│ │ │ │ │ ├── GithubApiService.java
│ │ │ │ │ ├── NetworkingModule.java
│ │ │ │ │ ├── RepositoriesManager.java
│ │ │ │ │ ├── UserManager.java
│ │ │ │ │ └── response
│ │ │ │ │ │ ├── RepositoryResponse.java
│ │ │ │ │ │ └── UserResponse.java
│ │ │ │ └── model
│ │ │ │ │ ├── Repository.java
│ │ │ │ │ └── User.java
│ │ │ │ └── utils
│ │ │ │ ├── AnalyticsManager.java
│ │ │ │ └── Validator.java
│ │ └── res
│ │ │ ├── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── drawable
│ │ │ └── ic_launcher_background.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ └── values
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── frogermcs
│ │ └── multimodulegithubclient
│ │ └── base
│ │ └── ExampleUnitTest.java
├── repositories
│ ├── build.gradle
│ └── src
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── frogermcs
│ │ │ │ └── multimodulegithubclient
│ │ │ │ └── repositories
│ │ │ │ ├── RepositoriesFeatureComponent.java
│ │ │ │ ├── RepositoriesFeatureComponentWrapper.java
│ │ │ │ ├── RepositoriesFeatureScope.java
│ │ │ │ ├── RepositoriesListActivity.java
│ │ │ │ ├── RepositoriesListActivityComponent.java
│ │ │ │ ├── RepositoriesListActivityModule.java
│ │ │ │ ├── RepositoriesListActivityPresenter.java
│ │ │ │ ├── RepositoriesListAdapter.java
│ │ │ │ ├── RepositoriesListViewHolderFactory.java
│ │ │ │ ├── RepositoriesModule.java
│ │ │ │ ├── RepositoryViewHolder.java
│ │ │ │ ├── RepositoryViewHolderBig.java
│ │ │ │ ├── RepositoryViewHolderFeatured.java
│ │ │ │ └── RepositoryViewHolderNormal.java
│ │ └── res
│ │ │ ├── layout
│ │ │ ├── activity_repositories_list.xml
│ │ │ ├── list_item_big.xml
│ │ │ ├── list_item_featured.xml
│ │ │ └── list_item_normal.xml
│ │ │ └── values
│ │ │ └── strings.xml
│ │ └── test
│ │ └── java
│ │ └── com
│ │ └── frogermcs
│ │ └── multimodulegithubclient
│ │ └── repositories
│ │ └── RepositoriesListActivityPresenterTest.java
└── repository
│ ├── build.gradle
│ └── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── frogermcs
│ │ │ └── multimodulegithubclient
│ │ │ └── repository
│ │ │ ├── RepositoryDetailsActivity.java
│ │ │ ├── RepositoryDetailsActivityComponent.java
│ │ │ ├── RepositoryDetailsActivityModule.java
│ │ │ ├── RepositoryDetailsActivityPresenter.java
│ │ │ ├── RepositoryFeatureComponent.java
│ │ │ ├── RepositoryFeatureComponentWrapper.java
│ │ │ └── RepositoryFeatureScope.java
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ └── activity_repository_details.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── frogermcs
│ └── multimodulegithubclient
│ └── repository
│ └── RepositoryDetailsActivityPresenterTest.java
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/libraries
5 | /.idea/caches
6 | /.idea/codeStyles
7 | /.idea/markdown-navigator
8 | /.idea/modules.xml
9 | /.idea/workspace.xml
10 | /.idea/gradle.xml
11 | /.idea/markdown-navigator.xml
12 | /.idea/misc.xml
13 | /.idea/vcs.xml
14 | .DS_Store
15 | /build
16 | /captures
17 | .externalNativeBuild
18 | app/build
19 | features/base/build
20 | features/repositories/build
21 | features/repository/build
22 | jacoco.exec
23 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/All_tests_at_once.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/App_and_modules_tests.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Base_tests.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Repositories_tests.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/runConfigurations/Repository_tests.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 |
3 | android:
4 | components:
5 | - build-tools-28.0.3
6 | - android-29
7 |
8 | - extra-google-m2repository
9 | - extra-android-m2repository
10 | licenses:
11 | - 'android-sdk-preview-license-52d11cd2'
12 | - 'android-sdk-license-.+'
13 | - 'google-gdk-license-.+'
14 |
15 | jdk:
16 | - oraclejdk8
17 |
18 | before_install:
19 | - yes | sdkmanager "build-tools;28.0.3"
20 |
21 | notifications:
22 | email: true
23 |
24 | cache:
25 | directories:
26 | - $HOME/.m2
27 |
28 | script:
29 | ./gradlew testDebugUnitTest testDebugUnitTestCoverage assembleDebug
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.com/frogermcs/MultiModuleGithubClient)
2 |
3 | # MultiModuleGithubClient
4 |
5 | Breaking the monolith to microservices is a well-known concept to make backend solutions extendable and maintainable in a scale, by bigger teams. Since mobile apps have become more complex, very often developed by teams of tens of software engineers this concept also grows in mobile platforms. There are many benefits from having apps split into modules/features/libraries:
6 |
7 | * features can be developed independently
8 | * project structure is cleaner
9 | * building process can be way faster (e.g., running unit tests on a module can be a matter of seconds, instead of minutes for the entire project)
10 | * great starting point for [instant apps](https://developer.android.com/topic/google-play-instant/)
11 |
12 | This is the example project which resolves (or at least workarounds) the most common problems with the multi-module Android app.
13 |
14 | ## Project structure
15 |
16 | The project is a straightforward Github API client containing 3 screens (user search, repositories list, repository details). For the sake of simplicity each screen is a separate module:
17 |
18 | * app - application module containing main app screen (user search)
19 | * repositories - repositories list
20 | * repository - repository detail
21 | * base - module containing a code shared between all modules
22 |
23 | 
24 |
25 | ### Dependencies management
26 | It is easy to get lost with dependencies management across different project modules (especially with libs versions). To make it easier, take a look at `buildsystem/dependencies.gradle` where everything is configured. Each module has separate configuration, with additional two for testing and annotation processing. Like some other patterns, this was originally introduced in [Azimo](https://azimo.com) Android application by [@dbarwacz](https://github.com/dbarwacz).
27 |
28 | ## Dagger 2
29 |
30 | Recently there is no recommended Dagger 2 configuration for multi-module Android project. Some software engineers recommend exposing Dagger modules from Android feature module and use them in Components maintained only in the main App module. This project implements the second way - each feature module has its Component and dependencies tree. All of them depends on Base Component (created in Base module). All have their scope and subcomponents.
31 | This approach was proposed by my colleague [@dbarwacz](https://github.com/dbarwacz) and recently is heavily used in [Azimo](https://azimo.com) Android application.
32 |
33 | Here are some highlights from it:
34 |
35 | * `BaseComponent` is used as a dependency in feature components (e.g. `RepositoriesFeatureComponent`, `AppComponent`...). It means that all dependencies that are used in needs to be publicly exposed in `BaseComponent` interface.
36 | * Local components, like `SplashActivityComponent` are subcomponents of feature component (`SplashActivityComponent` is a subcomponent of `AppComponent`).
37 | * Each module has its own Scope (e.g. `RepositoryFeatureScope`, `AppScope`). Effectively they define singletons - they live as long as components, which are maintained by classes: `AppComponentWrapper`, `RepositoryFeatureComponentWrapper` which are singletons... . To have better control on Scopes lifecycle, a good idea would be to add `release()` method to ComponentWrappers.
38 |
39 | For the rest take a look at the code - should be self-explaining. As this is just self-invented setup (that works on production!), all kind of feedback is warmly welcomed.
40 |
41 | 
42 |
43 | ## Unit Testing
44 |
45 | Project contains some example unit tests for presenter classes.
46 |
47 | ### Gradle
48 |
49 | To run all unit tests from all modules at once execute:
50 |
51 | ```
52 | ./gradlew testDebugUnitTest
53 | ```
54 |
55 | In console you can see:
56 |
57 | ```
58 | ...
59 | > Task :app:testDebugUnitTest
60 | com.frogermcs.multimodulegithubclient.SplashActivityPresenterTest > testNavigation_whenUserLoaded_thenShouldNavigateToRepositoriesList PASSED
61 | com.frogermcs.multimodulegithubclient.SplashActivityPresenterTest > testErrorHandling_whenErrorOccuredWhileLoadingUser_thenShouldShowValidationError PASSED
62 | com.frogermcs.multimodulegithubclient.SplashActivityPresenterTest > testValidation_whenUserNameIsInvalid_thenShouldShowValidationError PASSED
63 | com.frogermcs.multimodulegithubclient.SplashActivityPresenterTest > testValidation_whenUserNameValid_thenShouldLoadUser PASSED
64 | com.frogermcs.multimodulegithubclient.SplashActivityPresenterTest > testInit_shouldLogLaunchedScreenIntoAnalytics PASSED
65 |
66 | > Task :features:base:testDebugUnitTest
67 | com.frogermcs.multimodulegithubclient.base.ExampleUnitTest > addition_isCorrect PASSED
68 |
69 | > Task :features:repositories:testDebugUnitTest
70 | com.frogermcs.multimodulegithubclient.repositories.RepositoriesListActivityPresenterTest > testRepositories_whenRepositoriesAreLoaded_thenShouldBePresented PASSED
71 | com.frogermcs.multimodulegithubclient.repositories.RepositoriesListActivityPresenterTest > testInit_shouldLoadRepositoriesForGivenUser PASSED
72 | com.frogermcs.multimodulegithubclient.repositories.RepositoriesListActivityPresenterTest > testNavigation_whenRepositoryClicked_thenShouldLaunchRepositoryDetails PASSED
73 | com.frogermcs.multimodulegithubclient.repositories.RepositoriesListActivityPresenterTest > testInit_shouldLogLaunchedScreenIntoAnalytics PASSED
74 |
75 | > Task :features:repository:testDebugUnitTest
76 | com.frogermcs.multimodulegithubclient.repository.RepositoryDetailsActivityPresenterTest > testUnit_shouldSetRepositoryDetails PASSED
77 | com.frogermcs.multimodulegithubclient.repository.RepositoryDetailsActivityPresenterTest > testInit_shouldSetUserName PASSED
78 | com.frogermcs.multimodulegithubclient.repository.RepositoryDetailsActivityPresenterTest > testInit_shouldLogLaunchedScreenIntoAnalytics PASSED
79 |
80 | BUILD SUCCESSFUL in 55s
81 | ...
82 | ```
83 | It can be useful especially when you run tests on your CI environment. To control logs take a look at file `buildsystem/android_commons.gradle` (it is included in all feature modules).
84 |
85 | ```
86 | testOptions {
87 | unitTests {
88 | all {
89 | testLogging {
90 | events 'passed', 'skipped', 'failed'
91 | }
92 | }
93 | }
94 | }
95 | ```
96 |
97 | In case you want to run unit test in one module, execute:
98 |
99 | ```
100 | ./gradlew clean app:testDebugUnitTest
101 | ```
102 | or
103 | ```
104 | /gradlew clean feature:repository:testDebugUnitTest
105 | ```
106 |
107 | ### Android Studio
108 |
109 | The repository contains shared configurations for running Unit Tests directly from Android Studio.
110 |
111 | 
112 |
113 | #### All tests at once
114 |
115 | Recently it is not always possible to run all tests at once (see troubleshooting below).
116 |
117 | #### All tests, module after module sequentially
118 |
119 | Run `App and modules tests`. This configuration will run unit tests from all modules in separate tabs, one after another. To modify list of tests, click on `Edit Configurations` -> `App and modules tests` -> `Before Launch`.
120 |
121 | 
122 |
123 | #### Troubleshooting
124 |
125 | * **Class not found: ... Empty test suite.**
126 | There is a bug in Android Studio which prevents from launching all unit tests at once, before their code is generated (what happens after the first run of unit tests for every single module independently). For more take a look at Android Studio bug tracker: https://issuetracker.google.com/issues/111154138.
127 |
128 | 
129 |
130 | ## Test coverage for unit tests
131 |
132 | The project contains additional configuration for Jacoco that enables coverage report for Unit Tests (initially Jacoco reports cover Android Instrumentation Tests).
133 |
134 | To run tests coverage, execute:
135 |
136 | ```
137 | ./gradlew testDebugUnitTestCoverage
138 | ```
139 |
140 | Coverage report can be found in `app/build/reports/jacoco/testDebugUnitTestCoverage/html/index.html` (there is also an .xml file in case you would like to integrate coverage report with CI/CD environment.
141 |
142 | ### Implementation details
143 |
144 | Setting up a coverage report for Android Project isn't still straightforward and can take a couple of hours/days of exploration. Example setup in this project could be a little bit easier and more elegant, but some solutions are coded explicitly for better clarity.
145 | Here are some highlights:
146 |
147 | * Each module should use Jacoco plugin `apply plugin: 'jacoco'` and config (defined in `android_commons.gradle`):
148 | ```
149 | buildTypes {
150 | debug {
151 | testCoverageEnabled true
152 | }
153 | }
154 | ```
155 |
156 | * App module defines custom Jacoco task called `testDebugUnitTestCoverage`. Entire configuration can be found in `buildsystem/jacoco.gradle`. The code should be self-explaining.
157 |
158 | * Task `testDebugUnitTestCoverage` depends on `testDebugUnitTest` tasks (each module separately). Thanks to it all sources required for coverage report are available before gradle starts generating it (in `/build/...`.
159 |
160 | 
161 |
162 |
163 | ## Instrumentation Testing
164 |
165 | Project contains example Instrumentation test.
166 |
167 | ### Gradle
168 |
169 | To run all Instrumentation tests from all modules at once launch emulator or plugin device and execute:
170 |
171 | ```
172 | ./gradlew connectedAndroidTest
173 | ```
174 |
175 | When all goes fine, you should see testing report in `app/build/reports/androidTests/connected/` directory.
176 | 
177 |
178 | ### Functional vs End-to-end testing
179 | From the high level, Android Instrumentation tests can be split into two types: functional and end-to-end. You can check my [article](https://medium.com/azimolabs/automated-testing-will-set-your-engineering-team-free-a89467c40731) about how we do QA at Azimo to see what is the difference between both of them.
180 |
181 | Having in mind multi-feature config, it's pretty likely building functional tests can be more difficult. It's because your modules won't always see each other. In our example `app` module doesn't have knowledge about `feature/repository` module, so it means that instead of code:
182 |
183 | ```java
184 | intended(hasComponent(RepositoryDetailsActivity.class.getName()));
185 | ```
186 |
187 | you need to use:
188 |
189 | ```java
190 | intended(hasComponent("com.frogermcs.multimodulegithubclient.repository.RepositoryDetailsActivity"));
191 | ```
192 |
193 | It is, because `app` module doesn't have access to `RepositoryDetailsActivity` class.
194 |
195 | What about end-to-end tests? They shouldn't be problematic, simply because tests shouldn't have knowledge about specific implementation, but rather how user interface is composed (so again, not: `withText("R.string.show_repos")` but `withText("Show repositories")`).
196 |
197 | More cases: TBD
198 |
199 |
200 | ## Proguard
201 |
202 | Proguard configuration isn't very different in standard and multi-feature project configuration. Minification process is enabled in `app/build.gradle` [file](app/build.gradle):
203 |
204 | ```groovy
205 | buildTypes {
206 | debug {
207 | minifyEnabled true
208 | proguardFiles getDefaultProguardFile('proguard-android.txt'),
209 | 'proguard-rules.pro'
210 | testProguardFile 'proguard-test-rules.pro'
211 | }
212 | //...
213 | }
214 | ```
215 |
216 | For proguard configuration and know-how we could create completely separate demo project and a bunch of articles. Instead, just take a look at screenshots that compare apk files built from this project, with and without minification enabled.
217 |
218 | #### Project without proguard
219 |
220 | 
221 |
222 | #### Project with proguard
223 |
224 | 
225 |
226 | ### Proguard config for project modules
227 |
228 | It is also possible to Provide proguard configuration for each module separately. Why would you like to do this? Usually Proguard configuration is set in app's module gradle file. Also all global flags `-dontoptimize` also should be set there.
229 | But sometimes there are module-specific configurations. So for example you would like to keep methods or classes, even if they aren't used in app's module. Also when you share .aar library file, you can provide it with Proguard configuration built in.
230 | In this situation you should use `consumerProguardFiles`. For example, see `features/base/build.gradle` (file)[features/base/feature-base-proguard-rules.pro]:
231 |
232 | ```groovy
233 | buildTypes {
234 | all {
235 | consumerProguardFiles 'feature-base-proguard-rules.pro'
236 | }
237 | }
238 | ```
239 |
240 | Configuration tells:
241 |
242 | ```
243 | -keep class com.frogermcs.multimodulegithubclient.base.BaseActivity {
244 | public void notUsedMethod();
245 | }
246 | ```
247 |
248 | It means that method `notUsedMethod()` from class (BaseActivity)[features/base/src/main/java/com/frogermcs/multimodulegithubclient/base/BaseActivity.java] will be kept, no matter what.
249 |
250 | For more details, take a look at [this blog post](https://proandroiddev.com/handling-proguard-as-library-developer-or-in-a-multi-module-android-application-2d738c37890) that describes how to setup Proguard for multi-module android app.
251 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'jacoco'
3 |
4 | apply from: '../buildsystem/jacoco.gradle'
5 | apply from: '../buildsystem/android_commons.gradle'
6 |
7 | android {
8 |
9 | defaultConfig {
10 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
11 | }
12 |
13 | buildTypes {
14 | debug {
15 | minifyEnabled true
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'),
17 | 'proguard-rules.pro'
18 | testProguardFile 'proguard-test-rules.pro'
19 | }
20 |
21 | release {
22 | minifyEnabled true
23 | proguardFiles getDefaultProguardFile('proguard-android.txt'),
24 | 'proguard-rules.pro'
25 | }
26 | }
27 |
28 | packagingOptions {
29 | exclude 'META-INF/androidx.legacy_legacy-support-core-utils.version'
30 | }
31 |
32 | dynamicFeatures = []
33 |
34 | }
35 |
36 | dependencies {
37 | implementation project(':features:base')
38 | implementation project(':features:repositories')
39 | implementation project(':features:repository')
40 |
41 | rootProject.app.each { item ->
42 | add(item.configuration, item.dependency, item.options)
43 | }
44 | rootProject.annotationProcessorsDependencies.each { item ->
45 | add(item.configuration, item.dependency, item.options)
46 | }
47 | rootProject.unitTestsDependencies.each { item ->
48 | add(item.configuration, item.dependency, item.options)
49 | }
50 | rootProject.instrumentationTestsDependencies.each { item ->
51 | add(item.configuration, item.dependency, item.options)
52 | }
53 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # === General rules ===
2 |
3 | -dontobfuscate # Only because of easy debugging
4 |
5 | -dontwarn org.conscrypt.**
6 | -dontwarn javax.lang.model.**
7 | -dontwarn javax.tools.**
8 | -dontwarn java.lang.instrument.**
9 | -dontwarn java.lang.ClassValue
10 |
11 | # === RxJava ===
12 |
13 | -dontwarn sun.misc.**
14 | -dontwarn sun.reflect.**
15 |
16 | -keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
17 | long producerIndex;
18 | long consumerIndex;
19 | }
20 |
21 | -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
22 | rx.internal.util.atomic.LinkedQueueNode producerNode;
23 | }
24 |
25 | -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
26 | rx.internal.util.atomic.LinkedQueueNode consumerNode;
27 | }
28 |
29 | -dontnote rx.internal.util.**
30 |
31 | # ===endof RxJava ===
32 |
33 |
34 | # === OkHttp ===
35 |
36 | # JSR 305 annotations are for embedding nullability information.
37 | -dontwarn javax.annotation.**
38 |
39 | # A resource is loaded with a relative path so the package of this class must be preserved.
40 | -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
41 |
42 | # Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
43 | -dontwarn org.codehaus.mojo.animal_sniffer.*
44 |
45 | # OkHttp platform used only on JVM and when Conscrypt dependency is available.
46 | -dontwarn okhttp3.internal.platform.ConscryptPlatform
47 |
48 | -dontnote okhttp3.**
49 |
50 | # ===endof OkHttp ===
51 |
52 |
53 | # === Retrofit ===
54 |
55 | # Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
56 | # EnclosingMethod is required to use InnerClasses.
57 | -keepattributes Signature, InnerClasses, EnclosingMethod
58 |
59 | # Retain service method parameters when optimizing.
60 | -keepclassmembers,allowshrinking,allowobfuscation interface * {
61 | @retrofit2.http.* ;
62 | }
63 |
64 | # Ignore annotation used for build tooling.
65 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
66 |
67 | # Ignore JSR 305 annotations for embedding nullability information.
68 | -dontwarn javax.annotation.**
69 |
70 | # Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
71 | -dontwarn kotlin.Unit
72 |
73 | # Top-level functions that can only be used by Kotlin.
74 | -dontwarn retrofit2.-KotlinExtensions
75 |
76 | # ===endof Retrofit ===
77 |
78 |
79 | # === Dagger ====
80 |
81 | -dontwarn com.google.errorprone.annotations.**
82 | -dontwarn com.squareup.javawriter.**
83 |
84 | # ===endof Dagger ===
85 |
86 |
87 | # === Google autofactory ===
88 |
89 | -dontwarn com.google.googlejavaformat.**
90 | -dontwarn com.google.common.**
91 | -dontwarn com.google.auto.**
92 |
93 | -dontnote com.google.common.**
94 | -dontnote com.google.auto.**
95 |
96 | # ===endof Google autofactory ===
97 |
98 | # === GSON ===
99 |
100 | # Gson uses generic type information stored in a class file when working with fields. Proguard
101 | # removes such information by default, so configure it to keep all of it.
102 | -keepattributes Signature
103 |
104 | # For using GSON @Expose annotation
105 | -keepattributes *Annotation*
106 |
107 | # Gson specific classes
108 | -dontwarn sun.misc.**
109 | #-keep class com.google.gson.stream.** { *; }
110 |
111 | # Application classes that will be serialized/deserialized over Gson
112 | -keep class com.google.gson.examples.android.model.** { *; }
113 |
114 | # Prevent proguard from stripping interface information from TypeAdapterFactory,
115 | # JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
116 | -keep class * implements com.google.gson.TypeAdapterFactory
117 | -keep class * implements com.google.gson.JsonSerializer
118 | -keep class * implements com.google.gson.JsonDeserializer
119 |
120 | -dontnote com.google.gson.internal.**
121 |
122 | # ===endof GSON ===
123 |
124 |
125 |
126 | # === App UI ===
127 |
128 | -keep class androidx.recyclerview.widget.RecyclerView {
129 | public ;
130 | }
131 | -keep class androidx.coordinatorlayout.widget.CoordinatorLayout$Behavior {*;}
132 |
133 | # ===endof App UI ===
--------------------------------------------------------------------------------
/app/proguard-test-rules.pro:
--------------------------------------------------------------------------------
1 | -dontwarn org.xmlpull.v1.**
2 |
3 | -dontwarn androidx.test.espresso.**
4 |
5 | -dontwarn android.support.test.services.**
6 | -dontwarn android.support.test.runner.**
7 | -dontwarn android.support.test.orchestrator.**
8 |
9 | -dontnote androidx.transition.**
10 | -dontnote androidx.test.runner.**
11 | -dontnote androidx.test.espresso.**
12 | -dontnote android.support.test.**
13 | -dontnote org.junit.**
14 | -dontnote junit.framework.**
15 | -dontnote junit.runner.**
16 | -dontnote org.xmlpull.**
17 | -dontnote java.lang.invoke.**
18 | -dontnote org.apache.**
19 | -dontnote android.net.**
20 |
21 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/frogermcs/multimodulegithubclient/endtoend/AppFlowTest.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.endtoend;
2 |
3 | import android.view.View;
4 | import android.widget.TextView;
5 |
6 | import com.frogermcs.multimodulegithubclient.SplashActivity;
7 | import com.frogermcs.multimodulegithubclient.repositories.RepositoriesListActivity;
8 |
9 | import org.hamcrest.Matcher;
10 | import org.junit.Rule;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 |
14 | import androidx.appcompat.widget.Toolbar;
15 | import androidx.recyclerview.widget.RecyclerView;
16 | import androidx.test.espresso.UiController;
17 | import androidx.test.espresso.ViewAction;
18 | import androidx.test.espresso.contrib.RecyclerViewActions;
19 | import androidx.test.espresso.intent.rule.IntentsTestRule;
20 | import androidx.test.filters.LargeTest;
21 | import androidx.test.runner.AndroidJUnit4;
22 |
23 | import static androidx.test.espresso.Espresso.onView;
24 | import static androidx.test.espresso.action.ViewActions.click;
25 | import static androidx.test.espresso.action.ViewActions.typeText;
26 | import static androidx.test.espresso.assertion.ViewAssertions.matches;
27 | import static androidx.test.espresso.intent.Intents.intended;
28 | import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent;
29 | import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
30 | import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
31 | import static androidx.test.espresso.matcher.ViewMatchers.withHint;
32 | import static androidx.test.espresso.matcher.ViewMatchers.withParent;
33 | import static androidx.test.espresso.matcher.ViewMatchers.withText;
34 | import static org.hamcrest.CoreMatchers.allOf;
35 | import static org.hamcrest.CoreMatchers.instanceOf;
36 |
37 | @RunWith(AndroidJUnit4.class)
38 | @LargeTest
39 | public class AppFlowTest {
40 | @Rule
41 | public IntentsTestRule splashActivityRule = new IntentsTestRule<>(SplashActivity.class);
42 |
43 | @Test
44 | public void goThroughAllScreens_HappyPath() {
45 |
46 | //
47 | // ===== Main Screen =====
48 | //
49 | onView(withHint("username")).perform(typeText("frogermcs"));
50 | onView(withText("Show repositories")).perform(click());
51 |
52 | // Dirty 'Tread.sleep()' replacement. The solution can be way more elegant here. :)
53 | onView(isRoot()).perform(waitFor(2000));
54 |
55 | //
56 | // ===== Repositories list screen =====
57 | //
58 |
59 | // Is Activity started ?
60 | //Don't use if this is end-to-end test. In theory you shouldn't have knowledge about classes and implementation
61 | intended(hasComponent(RepositoriesListActivity.class.getName()));
62 |
63 | //Assert screen title
64 | onView(
65 | allOf(
66 | isAssignableFrom(TextView.class),
67 | withParent(isAssignableFrom(Toolbar.class))
68 | ))
69 | .check(matches(withText("Repositories list")));
70 |
71 | onView(instanceOf(RecyclerView.class)).perform(RecyclerViewActions.actionOnItemAtPosition(2, click()));
72 |
73 | onView(isRoot()).perform(waitFor(500));
74 |
75 | //
76 | // ===== Repository details =====
77 | //
78 |
79 | // Is Activity started? This time Activity isn't a direct part of app module,
80 | // so class cannot be pointed directly.
81 | //Plus again, you shouldn't use this in end-to-end tests...
82 | intended(hasComponent("com.frogermcs.multimodulegithubclient.repository.RepositoryDetailsActivity"));
83 |
84 | //Assert screen title
85 | onView(
86 | allOf(
87 | isAssignableFrom(TextView.class),
88 | withParent(isAssignableFrom(Toolbar.class))
89 | ))
90 | .check(matches(withText("Repository details")));
91 | }
92 |
93 | public static ViewAction waitFor(final long millis) {
94 | return new ViewAction() {
95 | @Override
96 | public Matcher getConstraints() {
97 | return isRoot();
98 | }
99 |
100 | @Override
101 | public String getDescription() {
102 | return "Wait for " + millis + " milliseconds.";
103 | }
104 |
105 | @Override
106 | public void perform(UiController uiController, final View view) {
107 | uiController.loopMainThreadForAtLeast(millis);
108 | }
109 | };
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/frogermcs/multimodulegithubclient/App.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient;
2 |
3 | import android.app.Application;
4 |
5 | import com.frogermcs.multimodulegithubclient.base.BuildConfig;
6 |
7 | import timber.log.Timber;
8 |
9 | /**
10 | * Created by Miroslaw Stanek on 22.04.15.
11 | */
12 | public class App extends Application {
13 |
14 | @Override
15 | public void onCreate() {
16 | super.onCreate();
17 | if (BuildConfig.DEBUG) {
18 | Timber.plant(new Timber.DebugTree());
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/frogermcs/multimodulegithubclient/AppComponent.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.BaseComponent;
4 |
5 | import dagger.Component;
6 |
7 | @AppScope
8 | @Component(
9 | modules = GithubClientModule.class,
10 | dependencies = BaseComponent.class
11 | )
12 | public interface AppComponent {
13 | SplashActivityComponent plus(SplashActivityModule module);
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/frogermcs/multimodulegithubclient/AppComponentWrapper.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient;
2 |
3 | import android.app.Application;
4 |
5 | import com.frogermcs.multimodulegithubclient.base.BaseComponent;
6 | import com.frogermcs.multimodulegithubclient.base.BaseComponentWrapper;
7 |
8 | public class AppComponentWrapper {
9 |
10 | private static AppComponentWrapper appComponentWrapper;
11 |
12 | private AppComponentWrapper() {
13 |
14 | }
15 |
16 | public static AppComponentWrapper getInstance(Application application) {
17 | if (appComponentWrapper == null) {
18 | synchronized (AppComponentWrapper.class) {
19 | if (appComponentWrapper == null) {
20 | appComponentWrapper = new AppComponentWrapper();
21 | appComponentWrapper.initializeComponent(BaseComponentWrapper.getBaseComponent(application));
22 | }
23 | }
24 | }
25 | return appComponentWrapper;
26 | }
27 |
28 | private AppComponent appComponent;
29 |
30 | public static AppComponent getAppComponent(Application application) {
31 | AppComponentWrapper appComponentWrapper = getInstance(application);
32 | return appComponentWrapper.appComponent;
33 | }
34 |
35 | public AppComponent initializeComponent(BaseComponent baseComponent) {
36 | appComponent = DaggerAppComponent.builder()
37 | .baseComponent(baseComponent)
38 | .build();
39 | return appComponent;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/frogermcs/multimodulegithubclient/AppScope.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient;
2 |
3 | import javax.inject.Scope;
4 |
5 | @Scope
6 | public @interface AppScope {
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/frogermcs/multimodulegithubclient/GithubClientModule.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.data.api.GithubApiService;
4 | import com.frogermcs.multimodulegithubclient.base.data.api.UserManager;
5 |
6 | import dagger.Module;
7 | import dagger.Provides;
8 |
9 | @Module
10 | public class GithubClientModule {
11 |
12 | @Provides
13 | @AppScope
14 | public UserManager provideUserManager(GithubApiService githubApiService) {
15 | return new UserManager(githubApiService);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/frogermcs/multimodulegithubclient/SplashActivity.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import android.widget.Button;
6 | import android.widget.EditText;
7 | import android.widget.ProgressBar;
8 |
9 | import com.frogermcs.multimodulegithubclient.base.BaseActivity;
10 | import com.frogermcs.multimodulegithubclient.base.data.model.User;
11 | import com.frogermcs.multimodulegithubclient.base.utils.AnalyticsManager;
12 | import com.frogermcs.multimodulegithubclient.repositories.RepositoriesListActivity;
13 | import com.jakewharton.rxbinding.widget.RxTextView;
14 |
15 | import javax.inject.Inject;
16 |
17 | import butterknife.BindView;
18 | import butterknife.OnClick;
19 | import rx.Subscription;
20 |
21 |
22 | public class SplashActivity extends BaseActivity {
23 |
24 | @BindView(R.id.etUsername)
25 | EditText etUsername;
26 | @BindView(R.id.pbLoading)
27 | ProgressBar pbLoading;
28 | @BindView(R.id.btnShowRepositories)
29 | Button btnShowRepositories;
30 |
31 | @Inject
32 | SplashActivityPresenter presenter;
33 | @Inject
34 | AnalyticsManager analyticsManager;
35 |
36 | private Subscription textChangeSubscription;
37 |
38 | @Override
39 | protected void onCreate(Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | setContentView(R.layout.activity_splash);
42 | textChangeSubscription = RxTextView.textChangeEvents(etUsername)
43 | .subscribe(textViewTextChangeEvent -> {
44 | presenter.username = textViewTextChangeEvent.text().toString();
45 | etUsername.setError(null);
46 | });
47 | presenter.init();
48 | }
49 |
50 | @Override
51 | protected void onDestroy() {
52 | super.onDestroy();
53 | textChangeSubscription.unsubscribe();
54 | }
55 |
56 | @Override
57 | protected void setupActivityComponent() {
58 | AppComponentWrapper
59 | .getAppComponent(getApplication())
60 | .plus(new SplashActivityModule(this))
61 | .inject(this);
62 | }
63 |
64 | @Override
65 | public String getScreenName() {
66 | return "Splash";
67 | }
68 |
69 | @OnClick(R.id.btnShowRepositories)
70 | public void onShowRepositoriesClick() {
71 | presenter.onShowRepositoriesClick();
72 | }
73 |
74 | public void showRepositoriesListForUser(User user) {
75 | RepositoriesListActivity.startRepositoriesListActivity(user.login, this);
76 | }
77 |
78 | public void showValidationError() {
79 | etUsername.setError("Validation error");
80 | }
81 |
82 | public void showLoading(boolean loading) {
83 | btnShowRepositories.setVisibility(loading ? View.GONE : View.VISIBLE);
84 | pbLoading.setVisibility(loading ? View.VISIBLE : View.GONE);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/com/frogermcs/multimodulegithubclient/SplashActivityComponent.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.ActivityScope;
4 |
5 | import dagger.Subcomponent;
6 |
7 | /**
8 | * Created by Miroslaw Stanek on 23.04.15.
9 | */
10 | @ActivityScope
11 | @Subcomponent(
12 | modules = SplashActivityModule.class
13 | )
14 | public interface SplashActivityComponent {
15 |
16 | SplashActivity inject(SplashActivity splashActivity);
17 |
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/frogermcs/multimodulegithubclient/SplashActivityModule.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.ActivityScope;
4 | import com.frogermcs.multimodulegithubclient.base.data.api.UserManager;
5 | import com.frogermcs.multimodulegithubclient.base.utils.AnalyticsManager;
6 | import com.frogermcs.multimodulegithubclient.base.utils.Validator;
7 |
8 | import dagger.Module;
9 | import dagger.Provides;
10 |
11 | /**
12 | * Created by Miroslaw Stanek on 23.04.15.
13 | */
14 | @Module
15 | public class SplashActivityModule {
16 | private SplashActivity splashActivity;
17 |
18 | public SplashActivityModule(SplashActivity splashActivity) {
19 | this.splashActivity = splashActivity;
20 | }
21 |
22 | @Provides
23 | @ActivityScope
24 | SplashActivity provideSplashActivity() {
25 | return splashActivity;
26 | }
27 |
28 | @Provides
29 | @ActivityScope
30 | SplashActivityPresenter
31 | provideSplashActivityPresenter(Validator validator,
32 | UserManager userManager,
33 | AnalyticsManager analyticsManager) {
34 | return new SplashActivityPresenter(
35 | splashActivity,
36 | validator,
37 | userManager,
38 | analyticsManager
39 | );
40 | }
41 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/frogermcs/multimodulegithubclient/SplashActivityPresenter.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.data.api.UserManager;
4 | import com.frogermcs.multimodulegithubclient.base.utils.AnalyticsManager;
5 | import com.frogermcs.multimodulegithubclient.base.utils.Validator;
6 |
7 | /**
8 | * Created by Miroslaw Stanek on 23.04.15.
9 | */
10 | public class SplashActivityPresenter {
11 | public String username;
12 |
13 | private final SplashActivity splashActivity;
14 | private final Validator validator;
15 | private final UserManager userManager;
16 | private final AnalyticsManager analyticsManager;
17 |
18 | public SplashActivityPresenter(SplashActivity splashActivity,
19 | Validator validator,
20 | UserManager userManager,
21 | AnalyticsManager analyticsManager) {
22 | this.splashActivity = splashActivity;
23 | this.validator = validator;
24 | this.userManager = userManager;
25 | this.analyticsManager = analyticsManager;
26 | }
27 |
28 | public void init() {
29 | analyticsManager.logScreenView(splashActivity.getScreenName());
30 | }
31 |
32 | public void onShowRepositoriesClick() {
33 | if (validator.validUsername(username)) {
34 | splashActivity.showLoading(true);
35 | userManager.getUser(username)
36 | .doOnTerminate(() -> splashActivity.showLoading(false))
37 | .subscribe(
38 | splashActivity::showRepositoriesListForUser,
39 | throwable -> splashActivity.showValidationError());
40 | } else {
41 | splashActivity.showValidationError();
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_splash.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
14 |
15 |
20 |
21 |
28 |
29 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MultiModuleGithubClient
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/test/java/com/frogermcs/multimodulegithubclient/SplashActivityPresenterTest.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.data.api.UserManager;
4 | import com.frogermcs.multimodulegithubclient.base.data.model.User;
5 | import com.frogermcs.multimodulegithubclient.base.utils.AnalyticsManager;
6 | import com.frogermcs.multimodulegithubclient.base.utils.Validator;
7 |
8 | import org.junit.Before;
9 | import org.junit.Test;
10 | import org.mockito.Mock;
11 | import org.mockito.MockitoAnnotations;
12 |
13 | import rx.Observable;
14 |
15 | import static org.mockito.Mockito.mock;
16 | import static org.mockito.Mockito.verify;
17 | import static org.mockito.Mockito.when;
18 |
19 | public class SplashActivityPresenterTest {
20 | SplashActivityPresenter presenter;
21 |
22 | @Mock
23 | SplashActivity splashActivityMock;
24 | @Mock
25 | Validator validatorMock;
26 | @Mock
27 | UserManager userManagerMock;
28 | @Mock
29 | AnalyticsManager analyticsManagerMock;
30 |
31 | @Before
32 | public void setUp() throws Exception {
33 | MockitoAnnotations.initMocks(this);
34 | presenter = new SplashActivityPresenter(
35 | splashActivityMock,
36 | validatorMock,
37 | userManagerMock,
38 | analyticsManagerMock
39 | );
40 | }
41 |
42 | @Test
43 | public void testInit_shouldLogLaunchedScreenIntoAnalytics() {
44 | String expectedScreenName = "screenName";
45 | when(splashActivityMock.getScreenName()).thenReturn(expectedScreenName);
46 |
47 | presenter.init();
48 |
49 | verify(analyticsManagerMock).logScreenView(expectedScreenName);
50 | }
51 |
52 | @Test
53 | public void testValidation_whenUserNameValid_thenShouldLoadUser() {
54 | String expectedUsername = "user1";
55 | when(validatorMock.validUsername(expectedUsername)).thenReturn(true);
56 | User expepectedUser = mock(User.class);
57 | when(userManagerMock.getUser(expectedUsername)).thenReturn(Observable.just(expepectedUser));
58 |
59 | presenter.username = expectedUsername;
60 | presenter.onShowRepositoriesClick();
61 |
62 | verify(userManagerMock).getUser(expectedUsername);
63 | }
64 |
65 | @Test
66 | public void testValidation_whenUserNameIsInvalid_thenShouldShowValidationError() {
67 | String expectedUsername = "user1";
68 | when(validatorMock.validUsername(expectedUsername)).thenReturn(false);
69 |
70 | presenter.username = expectedUsername;
71 | presenter.onShowRepositoriesClick();
72 |
73 | verify(splashActivityMock).showValidationError();
74 | }
75 |
76 | @Test
77 | public void testErrorHandling_whenErrorOccuredWhileLoadingUser_thenShouldShowValidationError() {
78 | String expectedUsername = "user1";
79 | when(validatorMock.validUsername(expectedUsername)).thenReturn(true);
80 | when(userManagerMock.getUser(expectedUsername)).thenReturn(Observable.error(new RuntimeException()));
81 |
82 | presenter.username = expectedUsername;
83 | presenter.onShowRepositoriesClick();
84 |
85 | verify(splashActivityMock).showValidationError();
86 | }
87 |
88 | @Test
89 | public void testNavigation_whenUserLoaded_thenShouldNavigateToRepositoriesList() {
90 | String expectedUsername = "user1";
91 | when(validatorMock.validUsername(expectedUsername)).thenReturn(true);
92 | User expepectedUser = mock(User.class);
93 | when(userManagerMock.getUser(expectedUsername)).thenReturn(Observable.just(expepectedUser));
94 |
95 | presenter.username = expectedUsername;
96 | presenter.onShowRepositoriesClick();
97 |
98 | verify(splashActivityMock).showRepositoriesListForUser(expepectedUser);
99 | }
100 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | mavenCentral()
6 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.5.2'
10 | classpath 'org.jacoco:org.jacoco.core:0.8.5'
11 | classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.0'
12 | classpath "com.github.ben-manes:gradle-versions-plugin:0.27.0"
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | google()
19 | jcenter()
20 | mavenCentral()
21 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
22 | }
23 | }
24 |
25 | apply plugin: "com.github.ben-manes.versions"
26 | apply from: 'buildsystem/dependencies.gradle'
--------------------------------------------------------------------------------
/buildsystem/android_commons.gradle:
--------------------------------------------------------------------------------
1 | android {
2 | compileSdkVersion 29
3 | defaultConfig {
4 | minSdkVersion 23
5 | targetSdkVersion 29
6 | versionCode 1
7 | versionName "1.0"
8 | }
9 |
10 | buildTypes {
11 | debug {
12 | testCoverageEnabled true
13 | }
14 | }
15 |
16 | compileOptions {
17 | sourceCompatibility JavaVersion.VERSION_1_8
18 | targetCompatibility JavaVersion.VERSION_1_8
19 | }
20 |
21 | testOptions {
22 | unitTests {
23 | all {
24 | testLogging {
25 | events 'passed', 'skipped', 'failed'
26 | }
27 | }
28 | }
29 | }
30 |
31 | packagingOptions {
32 | //Bugfix for AutoValue 1.7rc1 and AutoFactory 1.0-beta7 in one project
33 | exclude 'META-INF/gradle/incremental.annotation.processors'
34 | }
35 | }
--------------------------------------------------------------------------------
/buildsystem/dependencies.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | //
3 | // ===== Versions =====
4 | //
5 |
6 | //Annotation processor dependencies
7 | daggerVersion = '2.25.2'
8 | autoFactoryVersion = '1.0-beta7'
9 | autoValueVersion = "1.7"
10 | butterknifeVersion = '10.2.0'
11 |
12 | //Standard dependencies
13 | supportLibraryVersion = '1.1.0'
14 | javaxAnnotationsVersion = '10.0-b28'
15 | autoFactoryVersion = '1.0-beta7'
16 | rxAndroidVersion = '1.2.1'
17 | rxJavaVersion = '1.3.8'
18 | rxBindingVersion = '1.0.1'
19 | timberVersion = '4.7.1'
20 | retrofitVersion = '2.6.2'
21 | gsonVersion = '2.6.2'
22 | okhttpVersion = '4.2.2'
23 | guavaVersion = '28.1-android'
24 |
25 | // Testing dependencies
26 | junitVersion = '4.13-rc-1'
27 | mockitoVersion = '3.1.0'
28 |
29 | //Functional/end-to-end testing dependencies
30 | espressoVersion = '3.3.0-alpha02'
31 | testRunnerVersion = '1.3.0-alpha02'
32 | testRulesVersion = '1.3.0-alpha02'
33 |
34 | //
35 | // ===== Libraries =====
36 | //
37 |
38 | //Annotation processor dependencies
39 | daggerCompiler = "com.google.dagger:dagger-compiler:${daggerVersion}"
40 | autoFactoryCompiler = "com.google.auto.factory:auto-factory:${autoFactoryVersion}"
41 | butterknifeCompiler = "com.jakewharton:butterknife-compiler:${butterknifeVersion}"
42 |
43 | //Standard dependencies
44 | appCompat = "androidx.appcompat:appcompat:${supportLibraryVersion}"
45 | recyclerView = "androidx.recyclerview:recyclerview:1.1.0-rc01"
46 |
47 | butterknife = "com.jakewharton:butterknife:${butterknifeVersion}"
48 | dagger = "com.google.dagger:dagger:${daggerVersion}"
49 | javaxAnnotations = "org.glassfish:javax.annotation:${javaxAnnotationsVersion}"
50 | autoFactory = "com.google.auto.factory:auto-factory:${autoFactoryVersion}"
51 | autoValue = "com.google.auto.value:auto-value:${autoValueVersion}"
52 | autoValueAnnotations = "com.google.auto.value:auto-value-annotations:${autoValueVersion}"
53 | rxAndroid = "io.reactivex:rxandroid:${rxAndroidVersion}"
54 | rxJava = "io.reactivex:rxjava:${rxJavaVersion}"
55 | rxBinding = "com.jakewharton.rxbinding:rxbinding:${rxBindingVersion}"
56 | timber = "com.jakewharton.timber:timber:${timberVersion}"
57 | retrofit = "com.squareup.retrofit2:retrofit:${retrofitVersion}"
58 | retrofitRxAdapter = "com.squareup.retrofit2:adapter-rxjava:${retrofitVersion}"
59 | retrofitGsonConverter = "com.squareup.retrofit2:converter-gson:${gsonVersion}"
60 | okhttp = "com.squareup.okhttp3:okhttp:${okhttpVersion}"
61 | okhttpLoggingInterceptor = "com.squareup.okhttp3:logging-interceptor:${okhttpVersion}"
62 | guavaAndroid = "com.google.guava:guava:${guavaVersion}"
63 |
64 | //Testing dependencies
65 | junit = "junit:junit:${junitVersion}"
66 | mockito = "org.mockito:mockito-core:${mockitoVersion}"
67 |
68 | //Functional/end-to-end testing dependencies
69 | espressoCore = "androidx.test.espresso:espresso-core:${espressoVersion}"
70 | espressoIntents = "androidx.test.espresso:espresso-intents:${espressoVersion}"
71 | espressoContrib = "androidx.test.espresso:espresso-contrib:${espressoVersion}"
72 | testRunner = "androidx.test:runner:${testRunnerVersion}"
73 | testRules = "androidx.test:rules:${testRulesVersion}"
74 |
75 | espressoExcludes = {
76 | exclude group: 'androidx.legacy', module: 'legacy-support-v4'
77 | exclude module: 'recyclerview'
78 | }
79 |
80 |
81 | //
82 | // ===== Modules =====
83 | //
84 | app = [
85 | [configuration: "implementation", dependency: appCompat],
86 | [configuration: "implementation", dependency: recyclerView],
87 | ]
88 |
89 | repository = [
90 | ]
91 |
92 | repos = [
93 | [configuration: "implementation", dependency: recyclerView],
94 | ]
95 |
96 | base = [
97 | [configuration: "api", dependency: butterknife],
98 | [configuration: "api", dependency: appCompat],
99 | [configuration: "api", dependency: dagger],
100 | [configuration: "api", dependency: javaxAnnotations],
101 | [configuration: "api", dependency: autoFactory],
102 | [configuration: "api", dependency: autoValue],
103 | [configuration: "api", dependency: autoValueAnnotations],
104 | [configuration: "api", dependency: rxAndroid],
105 | [configuration: "api", dependency: rxJava],
106 | [configuration: "api", dependency: rxBinding],
107 | [configuration: "api", dependency: timber],
108 | [configuration: "api", dependency: retrofit],
109 | [configuration: "api", dependency: retrofitRxAdapter],
110 | [configuration: "api", dependency: retrofitGsonConverter],
111 | [configuration: "api", dependency: okhttp],
112 | [configuration: "api", dependency: okhttpLoggingInterceptor],
113 | [configuration: "api", dependency: guavaAndroid],
114 | ]
115 |
116 | //Imported in all modules
117 | unitTestsDependencies = [
118 | [configuration: "testImplementation", dependency: junit],
119 | [configuration: "testImplementation", dependency: mockito]
120 | ]
121 |
122 | //Importend in app module only
123 | instrumentationTestsDependencies = [
124 | [configuration: "androidTestImplementation", dependency: espressoCore, options: espressoExcludes],
125 | [configuration: "androidTestImplementation", dependency: espressoIntents, options: espressoExcludes],
126 | [configuration: "androidTestImplementation", dependency: espressoContrib, options: espressoExcludes],
127 | [configuration: "androidTestImplementation", dependency: testRunner],
128 | [configuration: "androidTestImplementation", dependency: testRules],
129 | ]
130 |
131 | //Imported in all modules
132 | annotationProcessorsDependencies = [
133 | [configuration: "annotationProcessor", dependency: autoFactoryCompiler],
134 | [configuration: "annotationProcessor", dependency: daggerCompiler],
135 | [configuration: "annotationProcessor", dependency: butterknifeCompiler],
136 | [configuration: "annotationProcessor", dependency: autoValue],
137 | ]
138 | }
139 |
--------------------------------------------------------------------------------
/buildsystem/jacoco.gradle:
--------------------------------------------------------------------------------
1 | // Android Gradle Plugin out of the box only supports code coverage for instrumentation espresso) tests.
2 | // This add support for unit tests as well.
3 |
4 | def fileFilter = [
5 | '**/R.class',
6 | '**/R$*.class',
7 | '**/BuildConfig.*',
8 | '**/Manifest*.*',
9 | '**/*Test*.*',
10 | 'android/**/*.*',
11 |
12 | //Butterknife hack for multi-modules poroject
13 | '**/R2.class',
14 | '**/R2$*.class',
15 | '**/*_ViewBinding*.*',
16 |
17 | //Dagger 2
18 | '**/*Dagger*Component*.*',
19 | '**/*Module.*',
20 | '**/*Module$*.*',
21 | '**/*MembersInjector*.*',
22 | '**/*_Factory*.*',
23 | '**/*Provide*Factory*.*',
24 |
25 | //Autofactory
26 | '**/*Factory.*',
27 |
28 | //Classes I intentionally don't want to test
29 | '**/*Activity.*',
30 | '**/App.class',
31 | '**/*Adapter.*',
32 | '**/*ViewHolder*.*',
33 | '**/api/response/*.*',
34 | '**/data/model/*.*',
35 | ]
36 |
37 | task testDebugUnitTestCoverage(type: JacocoReport) {
38 | group = 'Reporting'
39 | description = "Generate Jacoco coverage reports for the debug build. Only unit tests."
40 |
41 | //Make sure that tests from all modules are run before coverage report
42 | dependsOn ":app:testDebugUnitTest"
43 | dependsOn ":features:base:testDebugUnitTest"
44 | dependsOn ":features:repository:testDebugUnitTest"
45 | dependsOn ":features:repositories:testDebugUnitTest"
46 |
47 | classDirectories = files([
48 | fileTree(dir: "$project.rootDir/app/build/intermediates/javac/debug", excludes: fileFilter),
49 | fileTree(dir: "$project.rootDir/features/base/build/intermediates/javac/debug", excludes: fileFilter),
50 | fileTree(dir: "$project.rootDir/features/repository/build/intermediates/javac/debug", excludes: fileFilter),
51 | fileTree(dir: "$project.rootDir/features/repositories/build/intermediates/javac/debug", excludes: fileFilter),
52 | ])
53 | def coverageSourceDirs = [
54 | "$project.rootDir/app/src/main/java",
55 | "$project.rootDir/features/base/src/main/java",
56 | "$project.rootDir/features/repository/src/main/java",
57 | "$project.rootDir/features/repositories/src/main/java",
58 | ]
59 | additionalSourceDirs = files(coverageSourceDirs)
60 | sourceDirectories = files(coverageSourceDirs)
61 | executionData = fileTree(dir: project.rootDir, includes: [
62 | 'app/build/jacoco/testDebugUnitTest.exec',
63 | 'features/base/build/jacoco/testDebugUnitTest.exec',
64 | 'features/repository/build/jacoco/testDebugUnitTest.exec',
65 | 'features/repositories/build/jacoco/testDebugUnitTest.exec',
66 | ])
67 |
68 | reports {
69 | xml.enabled = true
70 | html.enabled = true
71 | }
72 | }
--------------------------------------------------------------------------------
/docs/img/all_tests_sequential.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/docs/img/all_tests_sequential.png
--------------------------------------------------------------------------------
/docs/img/app_diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/docs/img/app_diagram.png
--------------------------------------------------------------------------------
/docs/img/as_run_configurations.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/docs/img/as_run_configurations.png
--------------------------------------------------------------------------------
/docs/img/coverage_report.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/docs/img/coverage_report.png
--------------------------------------------------------------------------------
/docs/img/dagger_diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/docs/img/dagger_diagram.png
--------------------------------------------------------------------------------
/docs/img/failing_all_tests.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/docs/img/failing_all_tests.png
--------------------------------------------------------------------------------
/docs/img/instrumentation_report_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/docs/img/instrumentation_report_example.png
--------------------------------------------------------------------------------
/docs/img/no-proguard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/docs/img/no-proguard.png
--------------------------------------------------------------------------------
/docs/img/with-proguard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/docs/img/with-proguard.png
--------------------------------------------------------------------------------
/features/base/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'jacoco'
3 |
4 | apply from: '../../buildsystem/android_commons.gradle'
5 |
6 | android {
7 | buildTypes {
8 | all {
9 | consumerProguardFiles 'feature-base-proguard-rules.pro'
10 | }
11 | }
12 | }
13 |
14 | dependencies {
15 | rootProject.base.each { item ->
16 | add(item.configuration, item.dependency, item.options)
17 | }
18 | rootProject.unitTestsDependencies.each { item ->
19 | add(item.configuration, item.dependency, item.options)
20 | }
21 | rootProject.annotationProcessorsDependencies.each { item ->
22 | add(item.configuration, item.dependency, item.options)
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/features/base/feature-base-proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # === Base module proguard configuration ===
2 |
3 | -keep class com.frogermcs.multimodulegithubclient.base.BaseActivity {
4 | public void notUsedMethod();
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/features/base/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
15 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/features/base/src/main/java/com/frogermcs/multimodulegithubclient/base/ActivityScope.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.base;
2 |
3 | import javax.inject.Scope;
4 |
5 | @Scope
6 | public @interface ActivityScope {
7 | }
--------------------------------------------------------------------------------
/features/base/src/main/java/com/frogermcs/multimodulegithubclient/base/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.base;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.appcompat.app.AppCompatActivity;
6 | import butterknife.ButterKnife;
7 |
8 | public abstract class BaseActivity extends AppCompatActivity {
9 |
10 | @Override
11 | protected void onCreate(Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 | setupActivityComponent();
14 | }
15 |
16 | @Override
17 | public void setContentView(int layoutResID) {
18 | super.setContentView(layoutResID);
19 | ButterKnife.bind(this);
20 | }
21 |
22 | protected abstract void setupActivityComponent();
23 |
24 | public abstract String getScreenName();
25 |
26 | /*
27 | This method isn't used anywhere. But it won't disappear during minification process. It's
28 | because we use added proguard configuration for module telling that this method should be
29 | kept. See features/base/build.gradle -> consumerProguardFiles for more details.
30 | */
31 | public void notUsedMethod() {
32 | int a = 1;
33 | int b = 3;
34 | int ab = a + b;
35 | }
36 | }
--------------------------------------------------------------------------------
/features/base/src/main/java/com/frogermcs/multimodulegithubclient/base/BaseComponent.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.base;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.data.api.GithubApiService;
4 | import com.frogermcs.multimodulegithubclient.base.data.api.NetworkingModule;
5 | import com.frogermcs.multimodulegithubclient.base.utils.AnalyticsManager;
6 | import com.frogermcs.multimodulegithubclient.base.utils.Validator;
7 |
8 | import javax.inject.Singleton;
9 |
10 | import dagger.Component;
11 |
12 | /**
13 | * Created by Miroslaw Stanek on 22.04.15.
14 | */
15 | @Singleton
16 | @Component(
17 | modules = {
18 | BaseModule.class,
19 | NetworkingModule.class
20 | }
21 | )
22 | public interface BaseComponent {
23 |
24 | AnalyticsManager analyticsManager();
25 |
26 | Validator validator();
27 |
28 | GithubApiService githubApiService();
29 | }
--------------------------------------------------------------------------------
/features/base/src/main/java/com/frogermcs/multimodulegithubclient/base/BaseComponentWrapper.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.base;
2 |
3 | import android.app.Application;
4 |
5 | public class BaseComponentWrapper {
6 |
7 | private static BaseComponentWrapper baseComponentWrapper;
8 |
9 | private BaseComponentWrapper() {
10 |
11 | }
12 |
13 | public static BaseComponentWrapper getInstance(Application application) {
14 | if (baseComponentWrapper == null) {
15 | synchronized (BaseComponentWrapper.class) {
16 | if (baseComponentWrapper == null) {
17 | baseComponentWrapper = new BaseComponentWrapper();
18 | baseComponentWrapper.initializeComponent(application);
19 | }
20 | }
21 | }
22 | return baseComponentWrapper;
23 | }
24 |
25 | private BaseComponent baseComponent;
26 |
27 | public static BaseComponent getBaseComponent(Application application) {
28 | BaseComponentWrapper appComponentWrapper = getInstance(application);
29 | return appComponentWrapper.baseComponent;
30 | }
31 |
32 | public BaseComponent initializeComponent(Application application) {
33 | baseComponent = DaggerBaseComponent.builder()
34 | .baseModule(new BaseModule(application))
35 | .build();
36 | return baseComponent;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/features/base/src/main/java/com/frogermcs/multimodulegithubclient/base/BaseModule.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.base;
2 |
3 | import android.app.Application;
4 |
5 | import com.frogermcs.multimodulegithubclient.base.utils.AnalyticsManager;
6 | import com.frogermcs.multimodulegithubclient.base.utils.Validator;
7 |
8 | import javax.inject.Singleton;
9 |
10 | import dagger.Module;
11 | import dagger.Provides;
12 |
13 | @Module
14 | public class BaseModule {
15 | private Application application;
16 |
17 | public BaseModule(Application application) {
18 | this.application = application;
19 | }
20 |
21 | @Provides
22 | @Singleton
23 | public Application provideApplication() {
24 | return application;
25 | }
26 |
27 | @Provides
28 | @Singleton
29 | AnalyticsManager provideAnalyticsManager() {
30 | return new AnalyticsManager(application);
31 | }
32 |
33 | @Provides
34 | @Singleton
35 | Validator provideValidator() {
36 | return new Validator();
37 | }
38 | }
--------------------------------------------------------------------------------
/features/base/src/main/java/com/frogermcs/multimodulegithubclient/base/data/api/GithubApiService.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.base.data.api;
2 |
3 |
4 | import com.frogermcs.multimodulegithubclient.base.data.api.response.RepositoryResponse;
5 | import com.frogermcs.multimodulegithubclient.base.data.api.response.UserResponse;
6 |
7 | import java.util.List;
8 |
9 | import retrofit2.http.GET;
10 | import retrofit2.http.Path;
11 | import rx.Observable;
12 |
13 | /**
14 | * Created by Miroslaw Stanek on 22.04.15.
15 | */
16 | public interface GithubApiService {
17 |
18 | @GET("/users/{username}")
19 | Observable getUser(
20 | @Path("username") String username
21 | );
22 |
23 | @GET("/users/{username}/repos")
24 | Observable> getUsersRepositories(
25 | @Path("username") String username
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/features/base/src/main/java/com/frogermcs/multimodulegithubclient/base/data/api/NetworkingModule.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.base.data.api;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.BuildConfig;
4 |
5 | import java.util.concurrent.TimeUnit;
6 |
7 | import javax.inject.Singleton;
8 |
9 | import dagger.Module;
10 | import dagger.Provides;
11 | import okhttp3.OkHttpClient;
12 | import okhttp3.logging.HttpLoggingInterceptor;
13 | import retrofit2.Retrofit;
14 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
15 | import retrofit2.converter.gson.GsonConverterFactory;
16 |
17 | /**
18 | * Created by Miroslaw Stanek on 22.04.15.
19 | */
20 | @Module
21 | public class NetworkingModule {
22 |
23 | @Provides
24 | @Singleton
25 | public GithubApiService provideGithubApiService(Retrofit restAdapter) {
26 | return restAdapter.create(GithubApiService.class);
27 | }
28 |
29 | @Provides
30 | @Singleton
31 | public OkHttpClient provideOkHttpClient(HttpLoggingInterceptor httpLoggingInterceptor) {
32 | final OkHttpClient.Builder builder = new OkHttpClient.Builder();
33 | builder.addInterceptor(httpLoggingInterceptor)
34 | .connectTimeout(60 * 1000, TimeUnit.MILLISECONDS)
35 | .readTimeout(60 * 1000, TimeUnit.MILLISECONDS);
36 |
37 | return builder.build();
38 | }
39 |
40 | @Provides
41 | @Singleton
42 | public HttpLoggingInterceptor provideHttpLoggingInterceptor() {
43 | HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
44 | logging.level(BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.NONE);
45 | return logging;
46 | }
47 |
48 | @Provides
49 | @Singleton
50 | public Retrofit provideRestAdapter(OkHttpClient okHttpClient) {
51 | Retrofit.Builder builder = new Retrofit.Builder();
52 | builder.client(okHttpClient)
53 | .baseUrl("https://api.github.com")
54 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
55 | .addConverterFactory(GsonConverterFactory.create());
56 | return builder.build();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/features/base/src/main/java/com/frogermcs/multimodulegithubclient/base/data/api/RepositoriesManager.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.base.data.api;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.data.api.response.RepositoryResponse;
4 | import com.frogermcs.multimodulegithubclient.base.data.model.Repository;
5 | import com.google.common.collect.ImmutableList;
6 |
7 | import rx.Observable;
8 | import rx.android.schedulers.AndroidSchedulers;
9 | import rx.schedulers.Schedulers;
10 |
11 | /**
12 | * Created by Miroslaw Stanek on 22.04.15.
13 | */
14 | public class RepositoriesManager {
15 | private GithubApiService githubApiService;
16 |
17 | public RepositoriesManager(GithubApiService githubApiService) {
18 | this.githubApiService = githubApiService;
19 | }
20 |
21 | public Observable> getUsersRepositories(String username) {
22 | return githubApiService.getUsersRepositories(username)
23 | .map(repositoriesListResponse -> {
24 | final ImmutableList.Builder listBuilder = ImmutableList.builder();
25 | for (RepositoryResponse repositoryResponse : repositoriesListResponse) {
26 | Repository repository = new Repository();
27 | repository.id = repositoryResponse.id;
28 | repository.name = repositoryResponse.name;
29 | repository.url = repositoryResponse.url;
30 | repository.stargazers_count = repositoryResponse.stargazers_count;
31 | repository.forks_count = repositoryResponse.forks_count;
32 | listBuilder.add(repository);
33 | }
34 | return listBuilder.build();
35 | })
36 | .subscribeOn(Schedulers.io())
37 | .observeOn(AndroidSchedulers.mainThread());
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/features/base/src/main/java/com/frogermcs/multimodulegithubclient/base/data/api/UserManager.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.base.data.api;
2 |
3 |
4 | import com.frogermcs.multimodulegithubclient.base.data.model.User;
5 |
6 | import rx.Observable;
7 | import rx.android.schedulers.AndroidSchedulers;
8 | import rx.schedulers.Schedulers;
9 |
10 | /**
11 | * Created by Miroslaw Stanek on 22.04.15.
12 | */
13 | public class UserManager {
14 |
15 | private GithubApiService githubApiService;
16 |
17 | public UserManager(GithubApiService githubApiService) {
18 | this.githubApiService = githubApiService;
19 | }
20 |
21 | public Observable getUser(String username) {
22 | return githubApiService.getUser(username)
23 | .map(userResponse -> {
24 | User user = new User();
25 | user.login = userResponse.login;
26 | user.id = userResponse.id;
27 | user.url = userResponse.url;
28 | user.email = userResponse.email;
29 | return user;
30 | })
31 | .subscribeOn(Schedulers.io())
32 | .observeOn(AndroidSchedulers.mainThread());
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/features/base/src/main/java/com/frogermcs/multimodulegithubclient/base/data/api/response/RepositoryResponse.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.base.data.api.response;
2 |
3 | import java.util.Date;
4 |
5 | /**
6 | * Created by Miroslaw Stanek on 22.04.15.
7 | */
8 | public class RepositoryResponse {
9 | public long id;
10 | public String name;
11 | public String full_name;
12 | public UserResponse owner;
13 | public String html_url;
14 | public String description;
15 | public String url;
16 | public Date created_at;
17 | public Date updated_at;
18 | public Date pushed_at;
19 | public String git_url;
20 | public String ssh_url;
21 | public String clone_url;
22 | public String svn_url;
23 | public String homepage;
24 | public int stargazers_count;
25 | public int watchers_count;
26 | public String language;
27 | public boolean has_issues;
28 | public boolean has_downloads;
29 | public boolean has_wiki;
30 | public boolean has_pages;
31 | public int forks_count;
32 | public int open_issues_count;
33 | public int forks;
34 | public int open_issues;
35 | public int watchers;
36 | public String default_branch;
37 | }
38 |
--------------------------------------------------------------------------------
/features/base/src/main/java/com/frogermcs/multimodulegithubclient/base/data/api/response/UserResponse.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.base.data.api.response;
2 |
3 | import java.util.Date;
4 |
5 | /**
6 | * Created by Miroslaw Stanek on 22.04.15.
7 | */
8 | public class UserResponse {
9 | public String login;
10 | public long id;
11 | public String avatar_url;
12 | public String gravatar_id;
13 | public String url;
14 | public String html_url;
15 | public String followers_url;
16 | public String following_url;
17 | public String gists_url;
18 | public String starred_url;
19 | public String subscriptions_url;
20 | public String organizations_url;
21 | public String repos_url;
22 | public String events_url;
23 | public String received_events_url;
24 | public String type;
25 | public String name;
26 | public String blog;
27 | public String location;
28 | public String email;
29 | public int public_repos;
30 | public int public_gists;
31 | public int followers;
32 | public int following;
33 | public Date created_at;
34 | public Date updated_at;
35 | }
36 |
--------------------------------------------------------------------------------
/features/base/src/main/java/com/frogermcs/multimodulegithubclient/base/data/model/Repository.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.base.data.model;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | /**
7 | * Created by Miroslaw Stanek on 22.04.15.
8 | */
9 | public class Repository implements Parcelable {
10 | public static final int TYPE_NORMAL = 0;
11 | public static final int TYPE_BIG = 1;
12 | public static final int TYPE_FEATURED = 2;
13 |
14 | public static final Creator CREATOR = new Creator() {
15 | public Repository createFromParcel(Parcel source) {
16 | return new Repository(source);
17 | }
18 |
19 | public Repository[] newArray(int size) {
20 | return new Repository[size];
21 | }
22 | };
23 | public long id;
24 | public String name;
25 | public String url;
26 | public int stargazers_count;
27 | public int forks_count;
28 |
29 | public Repository() {
30 | }
31 |
32 | private Repository(Parcel in) {
33 | this.id = in.readLong();
34 | this.name = in.readString();
35 | this.url = in.readString();
36 | this.stargazers_count = in.readInt();
37 | this.forks_count = in.readInt();
38 | }
39 |
40 | @Override
41 | public int describeContents() {
42 | return 0;
43 | }
44 |
45 | @Override
46 | public void writeToParcel(Parcel dest, int flags) {
47 | dest.writeLong(this.id);
48 | dest.writeString(this.name);
49 | dest.writeString(this.url);
50 | dest.writeInt(this.stargazers_count);
51 | dest.writeInt(this.forks_count);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/features/base/src/main/java/com/frogermcs/multimodulegithubclient/base/data/model/User.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.base.data.model;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | /**
7 | * Created by Miroslaw Stanek on 22.04.15.
8 | */
9 | public class User implements Parcelable {
10 | public static final Creator CREATOR = new Creator() {
11 | public User createFromParcel(Parcel source) {
12 | return new User(source);
13 | }
14 |
15 | public User[] newArray(int size) {
16 | return new User[size];
17 | }
18 | };
19 | public String login;
20 | public long id;
21 | public String url;
22 | public String email;
23 |
24 | public User() {
25 | }
26 |
27 | protected User(Parcel in) {
28 | this.login = in.readString();
29 | this.id = in.readLong();
30 | this.url = in.readString();
31 | this.email = in.readString();
32 | }
33 |
34 | @Override
35 | public int describeContents() {
36 | return 0;
37 | }
38 |
39 | @Override
40 | public void writeToParcel(Parcel dest, int flags) {
41 | dest.writeString(this.login);
42 | dest.writeLong(this.id);
43 | dest.writeString(this.url);
44 | dest.writeString(this.email);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/features/base/src/main/java/com/frogermcs/multimodulegithubclient/base/utils/AnalyticsManager.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.base.utils;
2 |
3 | import android.app.Application;
4 |
5 | import timber.log.Timber;
6 |
7 | /**
8 | * Created by Miroslaw Stanek on 23.04.15.
9 | */
10 | public class AnalyticsManager {
11 |
12 | private Application app;
13 |
14 | public AnalyticsManager(Application app) {
15 | this.app = app;
16 | }
17 |
18 | public void logScreenView(String screenName) {
19 | Timber.d("Logged screen name: " + screenName);
20 | }
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/features/base/src/main/java/com/frogermcs/multimodulegithubclient/base/utils/Validator.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.base.utils;
2 |
3 | import android.text.TextUtils;
4 |
5 | /**
6 | * Created by Miroslaw Stanek on 23.04.15.
7 | */
8 | public class Validator {
9 |
10 | public Validator() {
11 | }
12 |
13 | public boolean validUsername(String username) {
14 | return !TextUtils.isEmpty(username);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/features/base/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/features/base/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/features/base/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/features/base/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/features/base/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/base/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/features/base/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/base/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/features/base/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/base/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/features/base/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/base/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/features/base/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/base/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/features/base/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/base/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/features/base/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/base/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/features/base/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/base/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/features/base/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/base/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/features/base/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/base/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/features/base/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/features/base/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Base
3 |
4 |
--------------------------------------------------------------------------------
/features/base/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/features/base/src/test/java/com/frogermcs/multimodulegithubclient/base/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.base;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.assertEquals;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/features/repositories/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.jakewharton.butterknife'
3 | apply plugin: 'jacoco'
4 |
5 | apply from: '../../buildsystem/android_commons.gradle'
6 |
7 | android {
8 | }
9 |
10 | dependencies {
11 | implementation project(':features:repository')
12 |
13 | rootProject.repos.each { item ->
14 | add(item.configuration, item.dependency, item.options)
15 | }
16 | rootProject.unitTestsDependencies.each { item ->
17 | add(item.configuration, item.dependency, item.options)
18 | }
19 | rootProject.annotationProcessorsDependencies.each { item ->
20 | add(item.configuration, item.dependency, item.options)
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/features/repositories/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/features/repositories/src/main/java/com/frogermcs/multimodulegithubclient/repositories/RepositoriesFeatureComponent.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repositories;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.BaseComponent;
4 |
5 | import dagger.Component;
6 |
7 | @RepositoriesFeatureScope
8 | @Component(
9 | dependencies = BaseComponent.class,
10 | modules = RepositoriesModule.class
11 | )
12 | public interface RepositoriesFeatureComponent {
13 | RepositoriesListActivityComponent plus(RepositoriesListActivityModule module);
14 | }
15 |
--------------------------------------------------------------------------------
/features/repositories/src/main/java/com/frogermcs/multimodulegithubclient/repositories/RepositoriesFeatureComponentWrapper.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repositories;
2 |
3 | import android.app.Application;
4 |
5 | import com.frogermcs.multimodulegithubclient.base.BaseComponent;
6 | import com.frogermcs.multimodulegithubclient.base.BaseComponentWrapper;
7 |
8 | public class RepositoriesFeatureComponentWrapper {
9 |
10 | private static RepositoriesFeatureComponentWrapper repositoriesFeatureComponentWrapper;
11 |
12 | private RepositoriesFeatureComponentWrapper() {
13 |
14 | }
15 |
16 | public static RepositoriesFeatureComponentWrapper getInstance(Application application) {
17 | if (repositoriesFeatureComponentWrapper == null) {
18 | synchronized (RepositoriesFeatureComponentWrapper.class) {
19 | if (repositoriesFeatureComponentWrapper == null) {
20 | repositoriesFeatureComponentWrapper = new RepositoriesFeatureComponentWrapper();
21 | repositoriesFeatureComponentWrapper.initializeComponent(BaseComponentWrapper.getBaseComponent(application));
22 | }
23 | }
24 | }
25 | return repositoriesFeatureComponentWrapper;
26 | }
27 |
28 | private RepositoriesFeatureComponent repositoriesFeatureComponent;
29 |
30 | public static RepositoriesFeatureComponent getAppComponent(Application application) {
31 | RepositoriesFeatureComponentWrapper appComponentWrapper = getInstance(application);
32 | return appComponentWrapper.repositoriesFeatureComponent;
33 | }
34 |
35 | public RepositoriesFeatureComponent initializeComponent(BaseComponent baseComponent) {
36 | repositoriesFeatureComponent = DaggerRepositoriesFeatureComponent.builder()
37 | .baseComponent(baseComponent)
38 | .build();
39 | return repositoriesFeatureComponent;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/features/repositories/src/main/java/com/frogermcs/multimodulegithubclient/repositories/RepositoriesFeatureScope.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repositories;
2 |
3 | import javax.inject.Scope;
4 |
5 | @Scope
6 | public @interface RepositoriesFeatureScope {
7 | }
8 |
--------------------------------------------------------------------------------
/features/repositories/src/main/java/com/frogermcs/multimodulegithubclient/repositories/RepositoriesListActivity.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repositories;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.view.View;
7 | import android.widget.ProgressBar;
8 |
9 | import com.frogermcs.multimodulegithubclient.base.BaseActivity;
10 | import com.frogermcs.multimodulegithubclient.base.data.model.Repository;
11 | import com.frogermcs.multimodulegithubclient.feature.R;
12 | import com.frogermcs.multimodulegithubclient.feature.R2;
13 | import com.frogermcs.multimodulegithubclient.repository.RepositoryDetailsActivity;
14 | import com.google.common.collect.ImmutableList;
15 |
16 | import javax.inject.Inject;
17 |
18 | import androidx.recyclerview.widget.LinearLayoutManager;
19 | import androidx.recyclerview.widget.RecyclerView;
20 | import butterknife.BindView;
21 |
22 |
23 | public class RepositoriesListActivity extends BaseActivity {
24 | private final static String ARG_USERNAME = "arg_username";
25 |
26 | @BindView(R2.id.rvRepositories)
27 | RecyclerView rvRepositories;
28 | @BindView(R2.id.pbLoading)
29 | ProgressBar pbLoading;
30 |
31 | @Inject
32 | RepositoriesListActivityPresenter presenter;
33 | @Inject
34 | RepositoriesListAdapter repositoriesListAdapter;
35 | @Inject
36 | LinearLayoutManager linearLayoutManager;
37 |
38 | public static void startRepositoriesListActivity(String username, Context context) {
39 | Intent intent = new Intent(context, RepositoriesListActivity.class);
40 | intent.putExtra(ARG_USERNAME, username);
41 | context.startActivity(intent);
42 | }
43 |
44 | @Override
45 | protected void onCreate(Bundle savedInstanceState) {
46 | super.onCreate(savedInstanceState);
47 | setContentView(R.layout.activity_repositories_list);
48 | setTitle("Repositories list");
49 | setupRepositoriesListView();
50 | presenter.init();
51 | }
52 |
53 | private void setupRepositoriesListView() {
54 | rvRepositories.setAdapter(repositoriesListAdapter);
55 | rvRepositories.setLayoutManager(linearLayoutManager);
56 | }
57 |
58 | @Override
59 | protected void setupActivityComponent() {
60 | String username = getIntent().getStringExtra(ARG_USERNAME);
61 | RepositoriesFeatureComponentWrapper
62 | .getAppComponent(getApplication())
63 | .plus(new RepositoriesListActivityModule(this, username))
64 | .inject(this);
65 | }
66 |
67 | @Override
68 | public String getScreenName() {
69 | return "RepositoriesList";
70 | }
71 |
72 | public void showLoading(boolean loading) {
73 | rvRepositories.setVisibility(loading ? View.GONE : View.VISIBLE);
74 | pbLoading.setVisibility(loading ? View.VISIBLE : View.GONE);
75 | }
76 |
77 | public void setRepositories(ImmutableList repositories) {
78 | repositoriesListAdapter.updateRepositoriesList(repositories);
79 | }
80 |
81 | public void onRepositoryClick(Repository repository) {
82 | presenter.onRepositoryClick(repository);
83 | }
84 |
85 | public void openRepositoryDetailsScreen(Repository repository, String username) {
86 | RepositoryDetailsActivity.startWithRepository(repository, username, this);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/features/repositories/src/main/java/com/frogermcs/multimodulegithubclient/repositories/RepositoriesListActivityComponent.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repositories;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.ActivityScope;
4 |
5 | import dagger.Subcomponent;
6 |
7 | /**
8 | * Created by Miroslaw Stanek on 23.04.15.
9 | */
10 | @ActivityScope
11 | @Subcomponent(
12 | modules = RepositoriesListActivityModule.class
13 | )
14 | public interface RepositoriesListActivityComponent {
15 |
16 | RepositoriesListActivity inject(RepositoriesListActivity repositoriesListActivity);
17 |
18 | }
--------------------------------------------------------------------------------
/features/repositories/src/main/java/com/frogermcs/multimodulegithubclient/repositories/RepositoriesListActivityModule.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repositories;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.ActivityScope;
4 | import com.frogermcs.multimodulegithubclient.base.data.api.RepositoriesManager;
5 | import com.frogermcs.multimodulegithubclient.base.data.model.Repository;
6 | import com.frogermcs.multimodulegithubclient.base.utils.AnalyticsManager;
7 |
8 | import java.util.Map;
9 |
10 | import javax.inject.Named;
11 |
12 | import androidx.recyclerview.widget.LinearLayoutManager;
13 | import dagger.Module;
14 | import dagger.Provides;
15 | import dagger.multibindings.IntKey;
16 | import dagger.multibindings.IntoMap;
17 |
18 | /**
19 | * Created by Miroslaw Stanek on 23.04.15.
20 | */
21 | @Module
22 | public class RepositoriesListActivityModule {
23 | private RepositoriesListActivity repositoriesListActivity;
24 | private String username;
25 |
26 | public RepositoriesListActivityModule(RepositoriesListActivity repositoriesListActivity, String username) {
27 | this.repositoriesListActivity = repositoriesListActivity;
28 | this.username = username;
29 | }
30 |
31 | @Provides
32 | @ActivityScope
33 | @Named("github-username")
34 | public String provideUsername() {
35 | return this.username;
36 | }
37 |
38 |
39 | @Provides
40 | @ActivityScope
41 | RepositoriesListActivity provideRepositoriesListActivity() {
42 | return repositoriesListActivity;
43 | }
44 |
45 | @Provides
46 | @ActivityScope
47 | RepositoriesListActivityPresenter provideRepositoriesListActivityPresenter(RepositoriesManager repositoriesManager,
48 | RepositoriesListActivity repositoriesListActivity,
49 | @Named("github-username") String username,
50 | AnalyticsManager analyticsManager) {
51 | return new RepositoriesListActivityPresenter(
52 | repositoriesListActivity,
53 | repositoriesManager,
54 | username,
55 | analyticsManager
56 | );
57 | }
58 |
59 | @Provides
60 | @ActivityScope
61 | RepositoriesListAdapter provideRepositoriesListAdapter(RepositoriesListActivity repositoriesListActivity,
62 | Map viewHolderFactories) {
63 | return new RepositoriesListAdapter(repositoriesListActivity, viewHolderFactories);
64 | }
65 |
66 | @Provides
67 | @ActivityScope
68 | LinearLayoutManager provideLinearLayoutManager(RepositoriesListActivity repositoriesListActivity) {
69 | return new LinearLayoutManager(repositoriesListActivity);
70 | }
71 |
72 | @Provides
73 | @IntoMap
74 | @IntKey(Repository.TYPE_NORMAL)
75 | RepositoriesListViewHolderFactory provideViewHolderNormal() {
76 | return new RepositoryViewHolderNormalFactory();
77 | }
78 |
79 | @Provides
80 | @IntoMap
81 | @IntKey(Repository.TYPE_BIG)
82 | RepositoriesListViewHolderFactory provideViewHolderBig() {
83 | return new RepositoryViewHolderBigFactory();
84 | }
85 |
86 | @Provides
87 | @IntoMap
88 | @IntKey(Repository.TYPE_FEATURED)
89 | RepositoriesListViewHolderFactory provideViewHolderFeatured() {
90 | return new RepositoryViewHolderFeaturedFactory();
91 | }
92 | }
--------------------------------------------------------------------------------
/features/repositories/src/main/java/com/frogermcs/multimodulegithubclient/repositories/RepositoriesListActivityPresenter.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repositories;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.data.api.RepositoriesManager;
4 | import com.frogermcs.multimodulegithubclient.base.data.model.Repository;
5 | import com.frogermcs.multimodulegithubclient.base.utils.AnalyticsManager;
6 |
7 | /**
8 | * Created by Miroslaw Stanek on 23.04.15.
9 | */
10 | public class RepositoriesListActivityPresenter {
11 | private final RepositoriesListActivity repositoriesListActivity;
12 | private final RepositoriesManager repositoriesManager;
13 | private final String username;
14 | private final AnalyticsManager analyticsManager;
15 |
16 | public RepositoriesListActivityPresenter(RepositoriesListActivity repositoriesListActivity,
17 | RepositoriesManager repositoriesManager,
18 | String username,
19 | AnalyticsManager analyticsManager) {
20 | this.repositoriesListActivity = repositoriesListActivity;
21 | this.repositoriesManager = repositoriesManager;
22 | this.username = username;
23 | this.analyticsManager = analyticsManager;
24 | }
25 |
26 | public void init() {
27 | analyticsManager.logScreenView(repositoriesListActivity.getScreenName());
28 | loadRepositories();
29 | }
30 |
31 | public void loadRepositories() {
32 | repositoriesListActivity.showLoading(true);
33 | repositoriesManager.getUsersRepositories(username)
34 | .doOnTerminate(() -> repositoriesListActivity.showLoading(false))
35 | .subscribe(
36 | repositoriesListActivity::setRepositories,
37 | Throwable::printStackTrace
38 | );
39 | }
40 |
41 | public void onRepositoryClick(Repository repository) {
42 | repositoriesListActivity.openRepositoryDetailsScreen(repository, username);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/features/repositories/src/main/java/com/frogermcs/multimodulegithubclient/repositories/RepositoriesListAdapter.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repositories;
2 |
3 | import android.view.ViewGroup;
4 |
5 | import com.frogermcs.multimodulegithubclient.base.data.model.Repository;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | import androidx.recyclerview.widget.RecyclerView;
12 |
13 | /**
14 | * Created by Miroslaw Stanek on 24.04.15.
15 | */
16 | public class RepositoriesListAdapter extends RecyclerView.Adapter {
17 |
18 | private RepositoriesListActivity repositoriesListActivity;
19 | private Map viewHolderFactories;
20 |
21 | private final List repositories = new ArrayList<>();
22 |
23 | public RepositoriesListAdapter(RepositoriesListActivity repositoriesListActivity,
24 | Map viewHolderFactories) {
25 | this.repositoriesListActivity = repositoriesListActivity;
26 | this.viewHolderFactories = viewHolderFactories;
27 | }
28 |
29 | @Override
30 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
31 | final RecyclerView.ViewHolder viewHolder = viewHolderFactories.get(viewType).createViewHolder(parent);
32 | viewHolder.itemView.setOnClickListener(v -> onRepositoryItemClicked(viewHolder.getAdapterPosition()));
33 | return viewHolder;
34 | }
35 |
36 | private void onRepositoryItemClicked(int adapterPosition) {
37 | repositoriesListActivity.onRepositoryClick(repositories.get(adapterPosition));
38 | }
39 |
40 | @Override
41 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
42 | ((RepositoryViewHolder) holder).bind(repositories.get(position));
43 | }
44 |
45 | @Override
46 | public int getItemCount() {
47 | return repositories.size();
48 | }
49 |
50 | @Override
51 | public int getItemViewType(int position) {
52 | Repository repository = repositories.get(position);
53 | if (repository.stargazers_count > 500) {
54 | if (repository.forks_count > 100) {
55 | return Repository.TYPE_FEATURED;
56 | }
57 | return Repository.TYPE_BIG;
58 | }
59 | return Repository.TYPE_NORMAL;
60 | }
61 |
62 | public void updateRepositoriesList(List repositories) {
63 | this.repositories.clear();
64 | this.repositories.addAll(repositories);
65 | notifyDataSetChanged();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/features/repositories/src/main/java/com/frogermcs/multimodulegithubclient/repositories/RepositoriesListViewHolderFactory.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repositories;
2 |
3 | import android.view.ViewGroup;
4 |
5 | import androidx.recyclerview.widget.RecyclerView;
6 |
7 | /**
8 | * Created by Miroslaw Stanek on 11.06.2016.
9 | */
10 |
11 | public interface RepositoriesListViewHolderFactory {
12 | RecyclerView.ViewHolder createViewHolder(ViewGroup parent);
13 | }
14 |
--------------------------------------------------------------------------------
/features/repositories/src/main/java/com/frogermcs/multimodulegithubclient/repositories/RepositoriesModule.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repositories;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.data.api.GithubApiService;
4 | import com.frogermcs.multimodulegithubclient.base.data.api.RepositoriesManager;
5 |
6 | import dagger.Module;
7 | import dagger.Provides;
8 |
9 | @Module
10 | class RepositoriesModule {
11 | @Provides
12 | RepositoriesManager provideRepositoriesManager(GithubApiService githubApiService) {
13 | return new RepositoriesManager(githubApiService);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/features/repositories/src/main/java/com/frogermcs/multimodulegithubclient/repositories/RepositoryViewHolder.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repositories;
2 |
3 | import android.view.View;
4 |
5 | import com.frogermcs.multimodulegithubclient.base.data.model.Repository;
6 |
7 | import androidx.recyclerview.widget.RecyclerView;
8 |
9 | /**
10 | * Created by Miroslaw Stanek on 11.06.2016.
11 | */
12 |
13 | public abstract class RepositoryViewHolder extends RecyclerView.ViewHolder {
14 | public RepositoryViewHolder(View itemView) {
15 | super(itemView);
16 | }
17 |
18 | public abstract void bind(Repository repository);
19 | }
20 |
--------------------------------------------------------------------------------
/features/repositories/src/main/java/com/frogermcs/multimodulegithubclient/repositories/RepositoryViewHolderBig.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repositories;
2 |
3 | import android.view.LayoutInflater;
4 | import android.view.ViewGroup;
5 | import android.widget.TextView;
6 |
7 | import com.frogermcs.multimodulegithubclient.base.data.model.Repository;
8 | import com.frogermcs.multimodulegithubclient.feature.R;
9 | import com.frogermcs.multimodulegithubclient.feature.R2;
10 | import com.google.auto.factory.AutoFactory;
11 |
12 | import butterknife.BindView;
13 | import butterknife.ButterKnife;
14 |
15 | /**
16 | * Created by Miroslaw Stanek on 11.06.2016.
17 | */
18 | @AutoFactory(implementing = RepositoriesListViewHolderFactory.class)
19 | public class RepositoryViewHolderBig extends RepositoryViewHolder {
20 |
21 | @BindView(R2.id.tvName)
22 | TextView tvName;
23 | @BindView(R2.id.tvStars)
24 | TextView tvStars;
25 | @BindView(R2.id.tvForks)
26 | TextView tvForks;
27 |
28 | public RepositoryViewHolderBig(ViewGroup parent) {
29 | super(LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_big, parent, false));
30 | ButterKnife.bind(this, itemView);
31 | }
32 |
33 | @Override
34 | public void bind(Repository repository) {
35 | tvName.setText(repository.name);
36 | tvStars.setText("Stars: " + repository.stargazers_count);
37 | tvForks.setText("Forks: " + repository.forks_count);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/features/repositories/src/main/java/com/frogermcs/multimodulegithubclient/repositories/RepositoryViewHolderFeatured.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repositories;
2 |
3 | import android.view.LayoutInflater;
4 | import android.view.ViewGroup;
5 | import android.widget.TextView;
6 |
7 | import com.frogermcs.multimodulegithubclient.base.data.model.Repository;
8 | import com.frogermcs.multimodulegithubclient.feature.R;
9 | import com.frogermcs.multimodulegithubclient.feature.R2;
10 | import com.google.auto.factory.AutoFactory;
11 |
12 | import butterknife.BindView;
13 | import butterknife.ButterKnife;
14 |
15 | /**
16 | * Created by Miroslaw Stanek on 11.06.2016.
17 | */
18 | @AutoFactory(implementing = RepositoriesListViewHolderFactory.class)
19 | public class RepositoryViewHolderFeatured extends RepositoryViewHolder {
20 |
21 | @BindView(R2.id.tvName)
22 | TextView tvName;
23 | @BindView(R2.id.tvStars)
24 | TextView tvStars;
25 | @BindView(R2.id.tvForks)
26 | TextView tvForks;
27 |
28 | public RepositoryViewHolderFeatured(ViewGroup parent) {
29 | super(LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_featured, parent, false));
30 | ButterKnife.bind(this, itemView);
31 | }
32 |
33 | @Override
34 | public void bind(Repository repository) {
35 | tvName.setText(repository.name);
36 | tvStars.setText("Stars: " + repository.stargazers_count);
37 | tvForks.setText("Forks: " + repository.forks_count);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/features/repositories/src/main/java/com/frogermcs/multimodulegithubclient/repositories/RepositoryViewHolderNormal.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repositories;
2 |
3 | import android.view.LayoutInflater;
4 | import android.view.ViewGroup;
5 | import android.widget.TextView;
6 |
7 | import com.frogermcs.multimodulegithubclient.base.data.model.Repository;
8 | import com.frogermcs.multimodulegithubclient.feature.R;
9 | import com.frogermcs.multimodulegithubclient.feature.R2;
10 | import com.google.auto.factory.AutoFactory;
11 |
12 | import butterknife.BindView;
13 | import butterknife.ButterKnife;
14 |
15 | /**
16 | * Created by Miroslaw Stanek on 11.06.2016.
17 | */
18 | @AutoFactory(implementing = RepositoriesListViewHolderFactory.class)
19 | public class RepositoryViewHolderNormal extends RepositoryViewHolder {
20 |
21 | @BindView(R2.id.tvName)
22 | TextView tvName;
23 |
24 | public RepositoryViewHolderNormal(ViewGroup parent) {
25 | super(LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_normal, parent, false));
26 | ButterKnife.bind(this, itemView);
27 | }
28 |
29 | @Override
30 | public void bind(Repository repository) {
31 | tvName.setText(repository.name);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/features/repositories/src/main/res/layout/activity_repositories_list.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
11 |
12 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/features/repositories/src/main/res/layout/list_item_big.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/features/repositories/src/main/res/layout/list_item_featured.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
26 |
27 |
34 |
--------------------------------------------------------------------------------
/features/repositories/src/main/res/layout/list_item_normal.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
14 |
--------------------------------------------------------------------------------
/features/repositories/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/features/repositories/src/test/java/com/frogermcs/multimodulegithubclient/repositories/RepositoriesListActivityPresenterTest.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repositories;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.data.api.RepositoriesManager;
4 | import com.frogermcs.multimodulegithubclient.base.data.model.Repository;
5 | import com.frogermcs.multimodulegithubclient.base.utils.AnalyticsManager;
6 | import com.google.common.collect.ImmutableList;
7 |
8 | import org.junit.Before;
9 | import org.junit.Test;
10 | import org.mockito.Mock;
11 | import org.mockito.MockitoAnnotations;
12 |
13 | import rx.Observable;
14 |
15 | import static org.mockito.ArgumentMatchers.any;
16 | import static org.mockito.Mockito.verify;
17 | import static org.mockito.Mockito.when;
18 |
19 | public class RepositoriesListActivityPresenterTest {
20 | RepositoriesListActivityPresenter presenter;
21 |
22 | @Mock
23 | RepositoriesListActivity repositoriesListActivityMock;
24 | @Mock
25 | RepositoriesManager repositoriesManagerMock;
26 | @Mock
27 | AnalyticsManager analyticsManagerMock;
28 |
29 | String username = "User1";
30 |
31 |
32 | @Before
33 | public void setUp() throws Exception {
34 | MockitoAnnotations.initMocks(this);
35 | presenter = new RepositoriesListActivityPresenter(
36 | repositoriesListActivityMock,
37 | repositoriesManagerMock,
38 | username,
39 | analyticsManagerMock
40 | );
41 | }
42 |
43 | @Test
44 | public void testInit_shouldLogLaunchedScreenIntoAnalytics() {
45 | String expectedScreenName = "screenName";
46 | when(repositoriesListActivityMock.getScreenName()).thenReturn(expectedScreenName);
47 | when(repositoriesManagerMock.getUsersRepositories(any())).thenReturn(Observable.empty());
48 |
49 | presenter.init();
50 |
51 | verify(analyticsManagerMock).logScreenView(expectedScreenName);
52 | }
53 |
54 | @Test
55 | public void testInit_shouldLoadRepositoriesForGivenUser() {
56 | when(repositoriesManagerMock.getUsersRepositories(username)).thenReturn(Observable.empty());
57 | presenter.init();
58 | verify(repositoriesManagerMock).getUsersRepositories(username);
59 | }
60 |
61 | @Test
62 | public void testRepositories_whenRepositoriesAreLoaded_thenShouldBePresented() {
63 | Repository repository1 = new Repository();
64 | repository1.id = 1;
65 | Repository repository2 = new Repository();
66 | repository2.id = 2;
67 | ImmutableList expectedRepos = ImmutableList.of(repository1, repository2);
68 | when(repositoriesManagerMock.getUsersRepositories(username)).thenReturn(Observable.just(expectedRepos));
69 |
70 | presenter.init();
71 |
72 | verify(repositoriesListActivityMock).setRepositories(expectedRepos);
73 | }
74 |
75 | @Test
76 | public void testNavigation_whenRepositoryClicked_thenShouldLaunchRepositoryDetails() {
77 | Repository repository = new Repository();
78 | repository.id = 1;
79 |
80 | presenter.onRepositoryClick(repository);
81 |
82 | verify(repositoriesListActivityMock).openRepositoryDetailsScreen(repository, username);
83 | }
84 | }
--------------------------------------------------------------------------------
/features/repository/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.jakewharton.butterknife'
3 | apply plugin: 'jacoco'
4 |
5 | apply from: '../../buildsystem/android_commons.gradle'
6 |
7 | android {
8 | }
9 |
10 | dependencies {
11 | api project(':features:base')
12 |
13 | rootProject.repository.each { item ->
14 | add(item.configuration, item.dependency, item.options)
15 | }
16 | rootProject.unitTestsDependencies.each { item ->
17 | add(item.configuration, item.dependency, item.options)
18 | }
19 | rootProject.annotationProcessorsDependencies.each { item ->
20 | add(item.configuration, item.dependency, item.options)
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/features/repository/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/features/repository/src/main/java/com/frogermcs/multimodulegithubclient/repository/RepositoryDetailsActivity.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repository;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.widget.TextView;
7 |
8 | import com.frogermcs.multimodulegithubclient.base.BaseActivity;
9 | import com.frogermcs.multimodulegithubclient.base.data.model.Repository;
10 | import com.frogermcs.multimodulegithubclient.base.utils.AnalyticsManager;
11 |
12 | import javax.inject.Inject;
13 |
14 | import butterknife.BindView;
15 |
16 |
17 | public class RepositoryDetailsActivity extends BaseActivity {
18 | private static final String ARG_REPOSITORY = "arg_repository";
19 | private static final String ARG_USERNAME = "arg_username";
20 |
21 | @BindView(R2.id.tvRepoName)
22 | TextView tvRepoName;
23 | @BindView(R2.id.tvRepoDetails)
24 | TextView tvRepoDetails;
25 | @BindView(R2.id.tvUserName)
26 | TextView tvUserName;
27 |
28 | @Inject
29 | AnalyticsManager analyticsManager;
30 | @Inject
31 | RepositoryDetailsActivityPresenter presenter;
32 |
33 | public static void startWithRepository(Repository repository, String username, Activity startingActivity) {
34 | Intent intent = new Intent(startingActivity, RepositoryDetailsActivity.class);
35 | intent.putExtra(ARG_REPOSITORY, repository);
36 | intent.putExtra(ARG_USERNAME, username);
37 | startingActivity.startActivity(intent);
38 | }
39 |
40 | @Override
41 | protected void onCreate(Bundle savedInstanceState) {
42 | super.onCreate(savedInstanceState);
43 | setContentView(R.layout.activity_repository_details);
44 | setTitle("Repository details");
45 | presenter.init();
46 | }
47 |
48 | public void setRepositoryDetails(String name, String url) {
49 | tvRepoName.setText(name);
50 | tvRepoDetails.setText(url);
51 | }
52 |
53 | @Override
54 | protected void setupActivityComponent() {
55 | String username = getIntent().getStringExtra(ARG_USERNAME);
56 | Repository repository = getIntent().getParcelableExtra(ARG_REPOSITORY);
57 | RepositoryFeatureComponentWrapper
58 | .getRepositoryFeatureComponent(getApplication())
59 | .plus(new RepositoryDetailsActivityModule(this, username, repository))
60 | .inject(this);
61 |
62 | }
63 |
64 | @Override
65 | public String getScreenName() {
66 | return "RepositoryDetails";
67 | }
68 |
69 | public void setupUserName(String userName) {
70 | tvUserName.setText(userName);
71 | }
72 | }
--------------------------------------------------------------------------------
/features/repository/src/main/java/com/frogermcs/multimodulegithubclient/repository/RepositoryDetailsActivityComponent.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repository;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.ActivityScope;
4 |
5 | import dagger.Subcomponent;
6 |
7 | /**
8 | * Created by Miroslaw Stanek on 23.04.15.
9 | */
10 | @ActivityScope
11 | @Subcomponent(
12 | modules = RepositoryDetailsActivityModule.class
13 | )
14 | public interface RepositoryDetailsActivityComponent {
15 | RepositoryDetailsActivity inject(RepositoryDetailsActivity repositoriesListActivity);
16 | }
--------------------------------------------------------------------------------
/features/repository/src/main/java/com/frogermcs/multimodulegithubclient/repository/RepositoryDetailsActivityModule.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repository;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.ActivityScope;
4 | import com.frogermcs.multimodulegithubclient.base.data.model.Repository;
5 | import com.frogermcs.multimodulegithubclient.base.utils.AnalyticsManager;
6 |
7 | import javax.inject.Named;
8 |
9 | import dagger.Module;
10 | import dagger.Provides;
11 |
12 | /**
13 | * Created by Miroslaw Stanek on 23.04.15.
14 | */
15 | @Module
16 | public class RepositoryDetailsActivityModule {
17 | private final RepositoryDetailsActivity repositoryDetailsActivity;
18 | private final String username;
19 | private final Repository repository;
20 |
21 | public RepositoryDetailsActivityModule(RepositoryDetailsActivity repositoryDetailsActivity,
22 | String username,
23 | Repository repository) {
24 | this.repositoryDetailsActivity = repositoryDetailsActivity;
25 | this.username = username;
26 | this.repository = repository;
27 | }
28 |
29 | @Provides
30 | @ActivityScope
31 | RepositoryDetailsActivity provideRepositoryDetailsActivity() {
32 | return this.repositoryDetailsActivity;
33 | }
34 |
35 | @Provides
36 | @ActivityScope
37 | @Named("github-username")
38 | String provideUsername() {
39 | return this.username;
40 | }
41 |
42 | @Provides
43 | @ActivityScope
44 | Repository provideRepository() {
45 | return this.repository;
46 | }
47 |
48 | @Provides
49 | @ActivityScope
50 | RepositoryDetailsActivityPresenter provideRepositoryDetailsActivityPresenter(
51 | RepositoryDetailsActivity repositoryDetailsActivity,
52 | @Named("github-username") String username,
53 | AnalyticsManager analyticsManager,
54 | Repository repository
55 | ) {
56 | return new RepositoryDetailsActivityPresenter(
57 | repositoryDetailsActivity,
58 | username,
59 | analyticsManager,
60 | repository
61 | );
62 | }
63 | }
--------------------------------------------------------------------------------
/features/repository/src/main/java/com/frogermcs/multimodulegithubclient/repository/RepositoryDetailsActivityPresenter.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repository;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.data.model.Repository;
4 | import com.frogermcs.multimodulegithubclient.base.utils.AnalyticsManager;
5 |
6 | /**
7 | * Created by Miroslaw Stanek on 23.04.15.
8 | */
9 | public class RepositoryDetailsActivityPresenter {
10 | private final RepositoryDetailsActivity repositoryDetailsActivity;
11 | private final String username;
12 | private final AnalyticsManager analyticsManager;
13 | private final Repository repository;
14 |
15 | public RepositoryDetailsActivityPresenter(RepositoryDetailsActivity repositoryDetailsActivity,
16 | String username,
17 | AnalyticsManager analyticsManager,
18 | Repository repository) {
19 | this.repositoryDetailsActivity = repositoryDetailsActivity;
20 | this.username = username;
21 | this.analyticsManager = analyticsManager;
22 | this.repository = repository;
23 | }
24 |
25 | public void init() {
26 | analyticsManager.logScreenView(repositoryDetailsActivity.getScreenName());
27 | repositoryDetailsActivity.setupUserName(username);
28 | repositoryDetailsActivity.setRepositoryDetails(repository.name, repository.url);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/features/repository/src/main/java/com/frogermcs/multimodulegithubclient/repository/RepositoryFeatureComponent.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repository;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.BaseComponent;
4 |
5 | import dagger.Component;
6 |
7 | @RepositoryFeatureScope
8 | @Component(
9 | dependencies = BaseComponent.class
10 | )
11 | public interface RepositoryFeatureComponent {
12 | RepositoryDetailsActivityComponent plus(RepositoryDetailsActivityModule module);
13 | }
14 |
--------------------------------------------------------------------------------
/features/repository/src/main/java/com/frogermcs/multimodulegithubclient/repository/RepositoryFeatureComponentWrapper.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repository;
2 |
3 | import android.app.Application;
4 |
5 | import com.frogermcs.multimodulegithubclient.base.BaseComponent;
6 | import com.frogermcs.multimodulegithubclient.base.BaseComponentWrapper;
7 |
8 | public class RepositoryFeatureComponentWrapper {
9 |
10 | private static RepositoryFeatureComponentWrapper repositoryFeatureComponentWrapper;
11 |
12 | private RepositoryFeatureComponentWrapper() {
13 |
14 | }
15 |
16 | public static RepositoryFeatureComponentWrapper getInstance(Application application) {
17 | if (repositoryFeatureComponentWrapper == null) {
18 | synchronized (RepositoryFeatureComponentWrapper.class) {
19 | if (repositoryFeatureComponentWrapper == null) {
20 | repositoryFeatureComponentWrapper = new RepositoryFeatureComponentWrapper();
21 | repositoryFeatureComponentWrapper.initializeComponent(BaseComponentWrapper.getBaseComponent(application));
22 | }
23 | }
24 | }
25 | return repositoryFeatureComponentWrapper;
26 | }
27 |
28 | private RepositoryFeatureComponent repositoryFeatureComponent;
29 |
30 | public static RepositoryFeatureComponent getRepositoryFeatureComponent(Application application) {
31 | RepositoryFeatureComponentWrapper appComponentWrapper = getInstance(application);
32 | return appComponentWrapper.repositoryFeatureComponent;
33 | }
34 |
35 | public RepositoryFeatureComponent initializeComponent(BaseComponent baseComponent) {
36 | repositoryFeatureComponent = DaggerRepositoryFeatureComponent.builder()
37 | .baseComponent(baseComponent)
38 | .build();
39 | return repositoryFeatureComponent;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/features/repository/src/main/java/com/frogermcs/multimodulegithubclient/repository/RepositoryFeatureScope.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repository;
2 |
3 | import javax.inject.Scope;
4 |
5 | @Scope
6 | public @interface RepositoryFeatureScope {
7 | }
8 |
--------------------------------------------------------------------------------
/features/repository/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/features/repository/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/features/repository/src/main/res/layout/activity_repository_details.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
10 |
11 |
17 |
18 |
24 |
25 |
26 |
31 |
32 |
36 |
37 |
41 |
42 |
--------------------------------------------------------------------------------
/features/repository/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/features/repository/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/features/repository/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/repository/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/features/repository/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/repository/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/features/repository/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/repository/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/features/repository/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/repository/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/features/repository/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/repository/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/features/repository/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/repository/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/features/repository/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/repository/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/features/repository/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/repository/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/features/repository/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/repository/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/features/repository/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/features/repository/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/features/repository/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/features/repository/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Repository
3 |
4 |
--------------------------------------------------------------------------------
/features/repository/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/features/repository/src/test/java/com/frogermcs/multimodulegithubclient/repository/RepositoryDetailsActivityPresenterTest.java:
--------------------------------------------------------------------------------
1 | package com.frogermcs.multimodulegithubclient.repository;
2 |
3 | import com.frogermcs.multimodulegithubclient.base.data.model.Repository;
4 | import com.frogermcs.multimodulegithubclient.base.utils.AnalyticsManager;
5 |
6 | import org.junit.Before;
7 | import org.junit.Test;
8 | import org.mockito.Mock;
9 | import org.mockito.MockitoAnnotations;
10 |
11 | import static org.mockito.Mockito.verify;
12 | import static org.mockito.Mockito.when;
13 |
14 | public class RepositoryDetailsActivityPresenterTest {
15 | RepositoryDetailsActivityPresenter presenter;
16 |
17 | @Mock
18 | RepositoryDetailsActivity repositoryDetailsActivityMock;
19 | @Mock
20 | AnalyticsManager analyticsManagerMock;
21 |
22 | Repository repository = new Repository();
23 | String username = "User1";
24 |
25 | @Before
26 | public void setUp() throws Exception {
27 | MockitoAnnotations.initMocks(this);
28 | presenter = new RepositoryDetailsActivityPresenter(
29 | repositoryDetailsActivityMock,
30 | username,
31 | analyticsManagerMock,
32 | repository
33 | );
34 | }
35 |
36 | @Test
37 | public void testInit_shouldLogLaunchedScreenIntoAnalytics() {
38 | String expectedScreenName = "screenName";
39 | when(repositoryDetailsActivityMock.getScreenName()).thenReturn(expectedScreenName);
40 |
41 | presenter.init();
42 |
43 | verify(analyticsManagerMock).logScreenView(expectedScreenName);
44 | }
45 |
46 | @Test
47 | public void testInit_shouldSetUserName() {
48 | presenter.init();
49 | verify(repositoryDetailsActivityMock).setupUserName(username);
50 | }
51 |
52 | @Test
53 | public void testUnit_shouldSetRepositoryDetails() {
54 | repository.name = "repoName";
55 | repository.url = "url";
56 | presenter.init();
57 | verify(repositoryDetailsActivityMock).setRepositoryDetails(repository.name, repository.url);
58 | }
59 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | android.enableJetifier=true
10 | android.useAndroidX=true
11 | org.gradle.jvmargs=-Xmx1536m
12 | # When configured, Gradle will run in incubating parallel mode.
13 | # This option should only be used with decoupled projects. More details, visit
14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
15 | # org.gradle.parallel=true
16 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frogermcs/MultiModuleGithubClient/398d76a87b280ce766d0bbd4314b6481a36dd224/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Oct 18 11:44:19 CEST 2019
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-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
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 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app',
2 | ':features:base',
3 | ':features:repositories',
4 | ':features:repository'
--------------------------------------------------------------------------------